From ef4b0bc3714b8d43b2f59419e3afa7850744a74c Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 11 Jun 2025 12:38:51 +0800 Subject: [PATCH 01/61] =?UTF-8?q?=F0=9F=A7=B6chore:=20remove=20redundant?= =?UTF-8?q?=20semantic-related=20dependencies=20and=20configurations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/package.json | 2 -- web/vite.config.js | 1 - 2 files changed, 3 deletions(-) diff --git a/web/package.json b/web/package.json index df98db8e..a313e0f5 100644 --- a/web/package.json +++ b/web/package.json @@ -37,8 +37,6 @@ "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", - "semantic-ui-offline": "^2.5.0", - "semantic-ui-react": "^2.1.3", "sse.js": "^2.6.0", "unist-util-visit": "^5.0.0", "use-debounce": "^10.0.4" diff --git a/web/vite.config.js b/web/vite.config.js index aaa9c1b7..5681d30e 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -41,7 +41,6 @@ export default defineConfig({ manualChunks: { 'react-core': ['react', 'react-dom', 'react-router-dom'], 'semi-ui': ['@douyinfe/semi-icons', '@douyinfe/semi-ui'], - semantic: ['semantic-ui-offline', 'semantic-ui-react'], visactor: ['@visactor/react-vchart', '@visactor/vchart'], tools: ['axios', 'history', 'marked'], 'react-components': [ From dd211832613dad501048430d74a5285ffeb9604b Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 11 Jun 2025 14:45:12 +0800 Subject: [PATCH 02/61] =?UTF-8?q?=F0=9F=A7=B6chore:=20remove=20useless=20w?= =?UTF-8?q?eb=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/README.md | 21 - web/pnpm-lock.yaml | 5584 -------------------------------------------- 2 files changed, 5605 deletions(-) delete mode 100644 web/README.md delete mode 100644 web/pnpm-lock.yaml diff --git a/web/README.md b/web/README.md deleted file mode 100644 index 07a1fd2a..00000000 --- a/web/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# React Template - -## Basic Usages - -```shell -# Runs the app in the development mode -npm start - -# Builds the app for production to the `build` folder -npm run build -``` - -If you want to change the default server, please set `REACT_APP_SERVER` environment variables before build, -for example: `REACT_APP_SERVER=http://your.domain.com`. - -Before you start editing, make sure your `Actions on Save` options have `Optimize imports` & `Run Prettier` enabled. - -## Reference - -1. https://github.com/OIerDb-ng/OIerDb -2. https://github.com/cornflourblue/react-hooks-redux-registration-login-example diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml deleted file mode 100644 index c503b5bb..00000000 --- a/web/pnpm-lock.yaml +++ /dev/null @@ -1,5584 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - .: - dependencies: - '@douyinfe/semi-icons': - specifier: ^2.63.1 - version: 2.77.1(react@18.3.1) - '@douyinfe/semi-ui': - specifier: ^2.69.1 - version: 2.77.1(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@visactor/react-vchart': - specifier: ~1.8.8 - version: 1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@visactor/vchart': - specifier: ~1.8.8 - version: 1.8.11 - '@visactor/vchart-semi-theme': - specifier: ~1.8.8 - version: 1.8.8(@visactor/vchart@1.8.11) - axios: - specifier: ^0.27.2 - version: 0.27.2 - dayjs: - specifier: ^1.11.11 - version: 1.11.13 - history: - specifier: ^5.3.0 - version: 5.3.0 - i18next: - specifier: ^23.16.8 - version: 23.16.8 - i18next-browser-languagedetector: - specifier: ^7.2.0 - version: 7.2.2 - marked: - specifier: ^4.1.1 - version: 4.3.0 - react: - specifier: ^18.2.0 - version: 18.3.1 - react-dom: - specifier: ^18.2.0 - version: 18.3.1(react@18.3.1) - react-dropzone: - specifier: ^14.2.3 - version: 14.3.8(react@18.3.1) - react-fireworks: - specifier: ^1.0.4 - version: 1.0.4 - react-i18next: - specifier: ^13.0.0 - version: 13.5.0(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router-dom: - specifier: ^6.3.0 - version: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-telegram-login: - specifier: ^1.1.2 - version: 1.1.2(react@18.3.1) - react-toastify: - specifier: ^9.0.8 - version: 9.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-turnstile: - specifier: ^1.0.5 - version: 1.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - semantic-ui-offline: - specifier: ^2.5.0 - version: 2.5.0 - semantic-ui-react: - specifier: ^2.1.3 - version: 2.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - sse: - specifier: github:mpetazzoni/sse.js - version: sse.js@https://codeload.github.com/mpetazzoni/sse.js/tar.gz/39b9b82aae95fd58d9d08b487845fe230f4b14e6 - devDependencies: - '@so1ve/prettier-config': - specifier: ^3.1.0 - version: 3.1.0(prettier@3.5.3) - '@vitejs/plugin-react': - specifier: ^4.2.1 - version: 4.3.4(vite@5.4.16) - prettier: - specifier: ^3.0.0 - version: 3.5.3 - typescript: - specifier: 4.4.2 - version: 4.4.2 - vite: - specifier: ^5.2.0 - version: 5.4.16 - -packages: - '@ampproject/remapping@2.3.0': - resolution: - { - integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, - } - engines: { node: '>=6.0.0' } - - '@astrojs/compiler@2.11.0': - resolution: - { - integrity: sha512-zZOO7i+JhojO8qmlyR/URui6LyfHJY6m+L9nwyX5GiKD78YoRaZ5tzz6X0fkl+5bD3uwlDHayf6Oe8Fu36RKNg==, - } - - '@babel/code-frame@7.26.2': - resolution: - { - integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==, - } - engines: { node: '>=6.9.0' } - - '@babel/compat-data@7.26.8': - resolution: - { - integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==, - } - engines: { node: '>=6.9.0' } - - '@babel/core@7.26.10': - resolution: - { - integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==, - } - engines: { node: '>=6.9.0' } - - '@babel/generator@7.27.0': - resolution: - { - integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==, - } - engines: { node: '>=6.9.0' } - - '@babel/helper-compilation-targets@7.27.0': - resolution: - { - integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==, - } - engines: { node: '>=6.9.0' } - - '@babel/helper-module-imports@7.25.9': - resolution: - { - integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==, - } - engines: { node: '>=6.9.0' } - - '@babel/helper-module-transforms@7.26.0': - resolution: - { - integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==, - } - engines: { node: '>=6.9.0' } - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.26.5': - resolution: - { - integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==, - } - engines: { node: '>=6.9.0' } - - '@babel/helper-string-parser@7.25.9': - resolution: - { - integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==, - } - engines: { node: '>=6.9.0' } - - '@babel/helper-validator-identifier@7.25.9': - resolution: - { - integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==, - } - engines: { node: '>=6.9.0' } - - '@babel/helper-validator-option@7.25.9': - resolution: - { - integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==, - } - engines: { node: '>=6.9.0' } - - '@babel/helpers@7.27.0': - resolution: - { - integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==, - } - engines: { node: '>=6.9.0' } - - '@babel/parser@7.27.0': - resolution: - { - integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==, - } - engines: { node: '>=6.0.0' } - hasBin: true - - '@babel/plugin-transform-react-jsx-self@7.25.9': - resolution: - { - integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==, - } - engines: { node: '>=6.9.0' } - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.25.9': - resolution: - { - integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==, - } - engines: { node: '>=6.9.0' } - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.27.0': - resolution: - { - integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==, - } - engines: { node: '>=6.9.0' } - - '@babel/template@7.27.0': - resolution: - { - integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==, - } - engines: { node: '>=6.9.0' } - - '@babel/traverse@7.27.0': - resolution: - { - integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==, - } - engines: { node: '>=6.9.0' } - - '@babel/types@7.27.0': - resolution: - { - integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==, - } - engines: { node: '>=6.9.0' } - - '@dnd-kit/accessibility@3.1.1': - resolution: - { - integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==, - } - peerDependencies: - react: '>=16.8.0' - - '@dnd-kit/core@6.3.1': - resolution: - { - integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==, - } - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@dnd-kit/sortable@7.0.2': - resolution: - { - integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==, - } - peerDependencies: - '@dnd-kit/core': ^6.0.7 - react: '>=16.8.0' - - '@dnd-kit/utilities@3.2.2': - resolution: - { - integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==, - } - peerDependencies: - react: '>=16.8.0' - - '@douyinfe/semi-animation-react@2.77.1': - resolution: - { - integrity: sha512-imELR02pufgGFkZURfTd9oBUtZPYhHvXv9WsYoRvEoBM9U7yzxrR6Fb/Lc3TH+WHVJ2oZHH2S0APS5t1MceEOw==, - } - - '@douyinfe/semi-animation-styled@2.77.1': - resolution: - { - integrity: sha512-FBRroqVJroel1CXmBgV58ulZHG2xUVInJF7k0FAag54noKKaToEobSxRjiTJ6JHne3ZDU1M6sBqpbzYJElFnPQ==, - } - - '@douyinfe/semi-animation@2.77.1': - resolution: - { - integrity: sha512-Q1D7whvQe0D+mPov8hXeH/e1uR/iBhpGGcW1LCTL2pSVMEZEYGJLf2KeXTTiCIgRVWm0PRH3Sux7auJ64zg7vw==, - } - - '@douyinfe/semi-foundation@2.77.1': - resolution: - { - integrity: sha512-DAXRy8ryLNzbKAiTAv+RrivGCoMU0asv2cO7PNV5aBq0ICB8XXn97FHyZo6Wb5NpqpyMhOaOr8Ro1bfpd0FeaA==, - } - - '@douyinfe/semi-icons@2.77.1': - resolution: - { - integrity: sha512-IbGqYzbjzCoSd+//HlO/Gn1c3XmbulQwGys+JgDfQhYIbPeGyhQfLk56Q7ku3vJGC8BGy7dUmR9MbeTf1UQGtw==, - } - peerDependencies: - react: '>=16.0.0' - - '@douyinfe/semi-illustrations@2.77.1': - resolution: - { - integrity: sha512-FlESLOPaY0SadiSIFcP4gqJUk+CYkd4rHK6YP9bfjmU26v7h1S02H7pGLLV1lS0WnY4j0ad4zqRV9tbXFvba9g==, - } - peerDependencies: - react: '>=16.0.0' - - '@douyinfe/semi-json-viewer-core@2.77.1': - resolution: - { - integrity: sha512-LOW+7ga2OzFIL9pGKftwHfl1kKLTV3x6Cs857iyvq9GIF/GHbAboiHcKUy2OZIHfy66zvP+Focs+yhfZG7IcZw==, - } - - '@douyinfe/semi-theme-default@2.77.1': - resolution: - { - integrity: sha512-Rug75C7jjSqmCP2L2tBI0K4dnXuo4GardzwSzdSjxDkiaIXwOwR5KE0K1FRbKWkQ7xmxbyRu4S6Pff+CDEJ/lA==, - } - - '@douyinfe/semi-ui@2.77.1': - resolution: - { - integrity: sha512-eIy7kr9OleCwlNRby3VICSGScHM23Zt2u7TJpID68qN3WrfQowGaB4wQ/0k5bvpLzv463HQnVWFk5aak+v46yw==, - } - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.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] - - '@fluentui/react-component-event-listener@0.63.1': - resolution: - { - integrity: sha512-gSMdOh6tI3IJKZFqxfQwbTpskpME0CvxdxGM2tdglmf6ZPVDi0L4+KKIm+2dN8nzb8Ya1A8ZT+Ddq0KmZtwVQg==, - } - peerDependencies: - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - - '@fluentui/react-component-ref@0.63.1': - resolution: - { - integrity: sha512-8MkXX4+R3i80msdbD4rFpEB4WWq2UDvGwG386g3ckIWbekdvN9z2kWAd9OXhRGqB7QeOsoAGWocp6gAMCivRlw==, - } - peerDependencies: - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - - '@jridgewell/gen-mapping@0.3.8': - resolution: - { - integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==, - } - engines: { node: '>=6.0.0' } - - '@jridgewell/resolve-uri@3.1.2': - resolution: - { - integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, - } - engines: { node: '>=6.0.0' } - - '@jridgewell/set-array@1.2.1': - resolution: - { - integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==, - } - engines: { node: '>=6.0.0' } - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: - { - integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==, - } - - '@jridgewell/trace-mapping@0.3.25': - resolution: - { - integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==, - } - - '@mdx-js/mdx@3.1.0': - resolution: - { - integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==, - } - - '@popperjs/core@2.11.8': - resolution: - { - integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==, - } - - '@remix-run/router@1.23.0': - resolution: - { - integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==, - } - engines: { node: '>=14.0.0' } - - '@resvg/resvg-js-android-arm-eabi@2.4.1': - resolution: - { - integrity: sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==, - } - engines: { node: '>= 10' } - cpu: [arm] - os: [android] - - '@resvg/resvg-js-android-arm64@2.4.1': - resolution: - { - integrity: sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [android] - - '@resvg/resvg-js-darwin-arm64@2.4.1': - resolution: - { - integrity: sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [darwin] - - '@resvg/resvg-js-darwin-x64@2.4.1': - resolution: - { - integrity: sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [darwin] - - '@resvg/resvg-js-linux-arm-gnueabihf@2.4.1': - resolution: - { - integrity: sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==, - } - engines: { node: '>= 10' } - cpu: [arm] - os: [linux] - - '@resvg/resvg-js-linux-arm64-gnu@2.4.1': - resolution: - { - integrity: sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [linux] - - '@resvg/resvg-js-linux-arm64-musl@2.4.1': - resolution: - { - integrity: sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [linux] - - '@resvg/resvg-js-linux-x64-gnu@2.4.1': - resolution: - { - integrity: sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [linux] - - '@resvg/resvg-js-linux-x64-musl@2.4.1': - resolution: - { - integrity: sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [linux] - - '@resvg/resvg-js-win32-arm64-msvc@2.4.1': - resolution: - { - integrity: sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==, - } - engines: { node: '>= 10' } - cpu: [arm64] - os: [win32] - - '@resvg/resvg-js-win32-ia32-msvc@2.4.1': - resolution: - { - integrity: sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==, - } - engines: { node: '>= 10' } - cpu: [ia32] - os: [win32] - - '@resvg/resvg-js-win32-x64-msvc@2.4.1': - resolution: - { - integrity: sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==, - } - engines: { node: '>= 10' } - cpu: [x64] - os: [win32] - - '@resvg/resvg-js@2.4.1': - resolution: - { - integrity: sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==, - } - engines: { node: '>= 10' } - - '@rollup/rollup-android-arm-eabi@4.39.0': - resolution: - { - integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==, - } - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.39.0': - resolution: - { - integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==, - } - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.39.0': - resolution: - { - integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==, - } - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.39.0': - resolution: - { - integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==, - } - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.39.0': - resolution: - { - integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==, - } - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.39.0': - resolution: - { - integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==, - } - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.39.0': - resolution: - { - integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==, - } - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.39.0': - resolution: - { - integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==, - } - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.39.0': - resolution: - { - integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==, - } - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.39.0': - resolution: - { - integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==, - } - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.39.0': - resolution: - { - integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==, - } - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': - resolution: - { - integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==, - } - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.39.0': - resolution: - { - integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==, - } - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.39.0': - resolution: - { - integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==, - } - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.39.0': - resolution: - { - integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==, - } - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.39.0': - resolution: - { - integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==, - } - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.39.0': - resolution: - { - integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==, - } - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.39.0': - resolution: - { - integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==, - } - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.39.0': - resolution: - { - integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==, - } - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.39.0': - resolution: - { - integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==, - } - cpu: [x64] - os: [win32] - - '@semantic-ui-react/event-stack@3.1.3': - resolution: - { - integrity: sha512-FdTmJyWvJaYinHrKRsMLDrz4tTMGdFfds299Qory53hBugiDvGC0tEJf+cHsi5igDwWb/CLOgOiChInHwq8URQ==, - } - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 - - '@so1ve/prettier-config@3.1.0': - resolution: - { - integrity: sha512-9GJ1yXKBC4DzqCTTaZoBf8zw7WWkVuXcccZt1Aqk4lj6ab/GiNUnjPGajUVYLjaqAEOKqM7jUSUfTjk2JTjCAg==, - } - peerDependencies: - prettier: ^3.0.0 - - '@so1ve/prettier-plugin-toml@3.1.0': - resolution: - { - integrity: sha512-8WZAGjAVNIJlkfWL6wHKxlUuEBY45fdd5qY5bR/Z6r/txgzKXk/r9qi1DTwc17gi/WcNuRrcRugecRT+mWbIYg==, - } - peerDependencies: - prettier: ^3.0.0 - - '@turf/boolean-clockwise@6.5.0': - resolution: - { - integrity: sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==, - } - - '@turf/clone@6.5.0': - resolution: - { - integrity: sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==, - } - - '@turf/flatten@6.5.0': - resolution: - { - integrity: sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==, - } - - '@turf/helpers@6.5.0': - resolution: - { - integrity: sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==, - } - - '@turf/invariant@6.5.0': - resolution: - { - integrity: sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==, - } - - '@turf/meta@3.14.0': - resolution: - { - integrity: sha512-OtXqLQuR9hlQ/HkAF/OdzRea7E0eZK1ay8y8CBXkoO2R6v34CsDrWYLMSo0ZzMsaQDpKo76NPP2GGo+PyG1cSg==, - } - - '@turf/meta@6.5.0': - resolution: - { - integrity: sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==, - } - - '@turf/rewind@6.5.0': - resolution: - { - integrity: sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==, - } - - '@types/babel__core@7.20.5': - resolution: - { - integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, - } - - '@types/babel__generator@7.6.8': - resolution: - { - integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==, - } - - '@types/babel__template@7.4.4': - resolution: - { - integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, - } - - '@types/babel__traverse@7.20.7': - resolution: - { - integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==, - } - - '@types/debug@4.1.12': - resolution: - { - integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==, - } - - '@types/estree-jsx@1.0.5': - resolution: - { - integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==, - } - - '@types/estree@1.0.7': - resolution: - { - integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==, - } - - '@types/hast@3.0.4': - resolution: - { - integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==, - } - - '@types/mdast@4.0.4': - resolution: - { - integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==, - } - - '@types/mdx@2.0.13': - resolution: - { - integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==, - } - - '@types/ms@2.1.0': - resolution: - { - integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==, - } - - '@types/parse-author@2.0.3': - resolution: - { - integrity: sha512-pgRW2K/GVQoogylrGJXDl7PBLW9A6T4OOc9Hy9MLT5f7vgufK2GQ8FcfAbjFHR5HjcN9ByzuCczAORk49REqoA==, - } - - '@types/parse-json@4.0.2': - resolution: - { - integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==, - } - - '@types/unist@2.0.11': - resolution: - { - integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==, - } - - '@types/unist@3.0.3': - resolution: - { - integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, - } - - '@ungap/structured-clone@1.3.0': - resolution: - { - integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==, - } - - '@visactor/react-vchart@1.8.11': - resolution: - { - integrity: sha512-wHnCex9gOpnttTtSu04ozKJhTveUk8Ln2KX/7PZyCJxqlXq+eWvW4zvM6Ja8T8kGXfXtFYVVNh9zBMQ7y2T/Sw==, - } - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@visactor/vchart-semi-theme@1.8.8': - resolution: - { - integrity: sha512-lm57CX3r6Bm7iGBYYyWhDY+1BvkyhNVLEckKx2PnlPKpJHikKSIK2ACyI5SmHuSOOdYzhY2QK6ZfYa2NShJ83w==, - } - peerDependencies: - '@visactor/vchart': ~1.8.8 - - '@visactor/vchart-theme-utils@1.8.8': - resolution: - { - integrity: sha512-RdCey3/t0+82EYyFZvx210rgJJWti9rsgcL3ROZS7o9CtRW1CMj9u9LKLDNIcPLNcLNACFC0aoT03jpdD1BCpA==, - } - peerDependencies: - '@visactor/vchart': ~1.8.8 - - '@visactor/vchart@1.8.11': - resolution: - { - integrity: sha512-RdQ822J02GgAQNXvO1LiT0T3O6FjdgPdcm9hVBFyrpBBmuI8MH02IE7Y1kGe9NiFTH4tDwP0ixRgBmqNSGSLZQ==, - } - - '@visactor/vdataset@0.17.5': - resolution: - { - integrity: sha512-zVBdLWHWrhldGc8JDjSYF9lvpFT4ZEFQDB0b6yvfSiHzHKHiSco+rWmUFvA7r4ObT6j2QWF1vZAV9To8Ml4vHw==, - } - - '@visactor/vgrammar-coordinate@0.10.11': - resolution: - { - integrity: sha512-XSUvEkaf/NQHFafmTwqoIMZicp9fF3o6NB2FDpuWrK4DI1lTuip/0RkqrC+kBAjc5erjt0em0TiITyqXpp4G6w==, - } - - '@visactor/vgrammar-core@0.10.11': - resolution: - { - integrity: sha512-VL9vcLPDg1LrHl7EOx0Ga9ATsoaChKIaCGzxjrPEjWiIS5VPU9Rs0jBKP+ch8BjamAoSuqL5mKd0L/RaUBqlaA==, - } - - '@visactor/vgrammar-hierarchy@0.10.11': - resolution: - { - integrity: sha512-0r3k51pPlJHu63BduG3htsV/ul62aVcKJxFftRfvKkwGjm1KeHoOZEEAwIf78U2puio0BkLqVn2Ek2L4FYZaIg==, - } - - '@visactor/vgrammar-projection@0.10.11': - resolution: - { - integrity: sha512-yEiKsxdfs5+g60wv5xZ1kyS/EDrAsUzAxCMpFFASVUYbQObHvW+elm+UPq2TBX6KZqAM0gsd1inzaLvfsCrLSg==, - } - - '@visactor/vgrammar-sankey@0.10.11': - resolution: - { - integrity: sha512-BbJTPuyydsL/L5XtQv59Q82GgJeePY7Wleac798usx3GnDK0GAOrPsI3bubSsOESJ4pNk3V4HPGEQDG1vCPb4w==, - } - - '@visactor/vgrammar-util@0.10.11': - resolution: - { - integrity: sha512-cJZLmKZvN95Y+yGhX+28+UpZu3bhYYlXDlHJNvXHyonI76ZYgtceyon2b3lI6XIsUsBGcD4Uo777s949X5os3g==, - } - - '@visactor/vgrammar-wordcloud-shape@0.10.11': - resolution: - { - integrity: sha512-NsQOYJp+9WHnIApMvkcUOaajxIg5U/r6rD8LKnoXW/HqAN2TFYXcRR3Daqmk9rrpM5VztQimKOsA1yZWyzozrA==, - } - - '@visactor/vgrammar-wordcloud@0.10.11': - resolution: - { - integrity: sha512-JWDqjGhr9JlYkKVBeEkiOqLQk7C1x1BtnsZ+E8oN541gzUqHwfS9qZyhwI3OyoSLewJlsSSPu1vXLKSQzLzKPA==, - } - - '@visactor/vrender-components@0.17.17': - resolution: - { - integrity: sha512-7gYFQrozvBkyGF7s/JHXdWDZnATzymxzug63CZd4EB7A0OXKatVDImXRePqwzlPD3QamF7QMVWn0CuIx3gQ2gA==, - } - - '@visactor/vrender-core@0.17.17': - resolution: - { - integrity: sha512-pAZGaimunDAWOBdFhzPh0auH5ryxAHr+MVoz+QdASG+6RZXy8D02l8v2QYu4+e4uorxe/s2ZkdNDm81SlNkoHQ==, - } - - '@visactor/vrender-kits@0.17.17': - resolution: - { - integrity: sha512-noRP1hAHvPCv36nf2P6sZ930Tk+dJ8jpPWIUm1cFYmUNdcumgIS8Cug0RyeZ+saSqVt5FDTwIwifhOqupw5Zaw==, - } - - '@visactor/vscale@0.17.5': - resolution: - { - integrity: sha512-2dkS1IlAJ/IdTp8JElbctOOv6lkHKBKPDm8KvwBo0NuGWQeYAebSeyN3QCdwKbj76gMlCub4zc+xWrS5YiA2zA==, - } - - '@visactor/vutils-extension@1.8.11': - resolution: - { - integrity: sha512-Hknzpy3+xh4sdL0iSn5N93BHiMJF4FdwSwhHYEibRpriZmWKG6wBxsJ0Bll4d7oS4f+svxt8Sg2vRYKzQEcIxQ==, - } - - '@visactor/vutils@0.17.5': - resolution: - { - integrity: sha512-HFN6Pk1Wc1RK842g02MeKOlvdri5L7/nqxMVTqxIvi0XMhHXpmoqN4+/9H+h8LmJpVohyrI/MT85TRBV/rManw==, - } - - '@vitejs/plugin-react@4.3.4': - resolution: - { - integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==, - } - engines: { node: ^14.18.0 || >=16.0.0 } - peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 - - abs-svg-path@0.1.1: - resolution: - { - integrity: sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==, - } - - acorn-jsx@5.3.2: - resolution: - { - integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, - } - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.14.1: - resolution: - { - integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==, - } - engines: { node: '>=0.4.0' } - hasBin: true - - array-source@0.0.4: - resolution: - { - integrity: sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw==, - } - - astring@1.9.0: - resolution: - { - integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==, - } - hasBin: true - - async-validator@3.5.2: - resolution: - { - integrity: sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==, - } - - asynckit@0.4.0: - resolution: - { - integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, - } - - attr-accept@2.2.5: - resolution: - { - integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==, - } - engines: { node: '>=4' } - - author-regex@1.0.0: - resolution: - { - integrity: sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==, - } - engines: { node: '>=0.8' } - - axios@0.27.2: - resolution: - { - integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==, - } - - bail@2.0.2: - resolution: - { - integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==, - } - - balanced-match@1.0.2: - resolution: - { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, - } - - bezier-easing@2.1.0: - resolution: - { - integrity: sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==, - } - - brace-expansion@1.1.11: - resolution: - { - integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==, - } - - browserslist@4.24.4: - resolution: - { - integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==, - } - 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==, - } - - call-bind-apply-helpers@1.0.2: - resolution: - { - integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, - } - engines: { node: '>= 0.4' } - - callsites@3.1.0: - resolution: - { - integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, - } - engines: { node: '>=6' } - - caniuse-lite@1.0.30001709: - resolution: - { - integrity: sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==, - } - - ccount@2.0.1: - resolution: - { - integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, - } - - character-entities-html4@2.1.0: - resolution: - { - integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==, - } - - character-entities-legacy@3.0.0: - resolution: - { - integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==, - } - - character-entities@2.0.2: - resolution: - { - integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==, - } - - character-reference-invalid@2.0.1: - resolution: - { - integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==, - } - - classnames@2.5.1: - resolution: - { - integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==, - } - - clsx@1.2.1: - resolution: - { - integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==, - } - engines: { node: '>=6' } - - collapse-white-space@2.1.0: - resolution: - { - integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==, - } - - 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' } - - comma-separated-tokens@2.0.3: - resolution: - { - integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==, - } - - commander@2.20.3: - resolution: - { - integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, - } - - commander@4.1.1: - resolution: - { - integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==, - } - engines: { node: '>= 6' } - - compute-scroll-into-view@1.0.20: - resolution: - { - integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==, - } - - concat-map@0.0.1: - resolution: - { - integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, - } - - concat-stream@1.4.11: - resolution: - { - integrity: sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==, - } - engines: { '0': node >= 0.8 } - - concat-stream@2.0.0: - resolution: - { - integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==, - } - engines: { '0': node >= 6.0 } - - convert-source-map@2.0.0: - resolution: - { - integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, - } - - copy-text-to-clipboard@2.2.0: - resolution: - { - integrity: sha512-WRvoIdnTs1rgPMkgA2pUOa/M4Enh2uzCwdKsOMYNAJiz/4ZvEJgmbF4OmninPmlFdAWisfeh0tH+Cpf7ni3RqQ==, - } - engines: { node: '>=6' } - - core-util-is@1.0.3: - resolution: - { - integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, - } - - cosmiconfig@7.1.0: - resolution: - { - integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==, - } - engines: { node: '>=10' } - - d3-array@1.2.4: - resolution: - { - integrity: sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==, - } - - d3-dsv@2.0.0: - resolution: - { - integrity: sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==, - } - hasBin: true - - d3-geo@1.12.1: - resolution: - { - integrity: sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==, - } - - d3-hexbin@0.2.2: - resolution: - { - integrity: sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==, - } - - d3-hierarchy@3.1.2: - resolution: - { - integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==, - } - engines: { node: '>=12' } - - date-fns-tz@1.3.8: - resolution: - { - integrity: sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==, - } - peerDependencies: - date-fns: '>=2.0.0' - - date-fns@2.30.0: - resolution: - { - integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==, - } - engines: { node: '>=0.11' } - - dayjs@1.11.13: - resolution: - { - integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==, - } - - debug@4.4.0: - resolution: - { - integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==, - } - engines: { node: '>=6.0' } - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decode-named-character-reference@1.1.0: - resolution: - { - integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==, - } - - delayed-stream@1.0.0: - resolution: - { - integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, - } - engines: { node: '>=0.4.0' } - - dequal@2.0.3: - resolution: - { - integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, - } - engines: { node: '>=6' } - - devlop@1.1.0: - resolution: - { - integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, - } - - dunder-proto@1.0.1: - resolution: - { - integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, - } - engines: { node: '>= 0.4' } - - electron-to-chromium@1.5.130: - resolution: - { - integrity: sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==, - } - - error-ex@1.3.2: - resolution: - { - integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==, - } - - 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.1: - resolution: - { - integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, - } - engines: { node: '>= 0.4' } - - es-set-tostringtag@2.1.0: - resolution: - { - integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, - } - engines: { node: '>= 0.4' } - - esast-util-from-estree@2.0.0: - resolution: - { - integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==, - } - - esast-util-from-js@2.0.1: - resolution: - { - integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==, - } - - 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-string-regexp@5.0.0: - resolution: - { - integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==, - } - engines: { node: '>=12' } - - estree-util-attach-comments@3.0.0: - resolution: - { - integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==, - } - - estree-util-build-jsx@3.0.1: - resolution: - { - integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==, - } - - estree-util-is-identifier-name@3.0.0: - resolution: - { - integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==, - } - - estree-util-scope@1.0.0: - resolution: - { - integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==, - } - - estree-util-to-js@2.0.0: - resolution: - { - integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==, - } - - estree-util-visit@2.0.0: - resolution: - { - integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==, - } - - estree-walker@3.0.3: - resolution: - { - integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, - } - - eventemitter3@4.0.7: - resolution: - { - integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==, - } - - exenv@1.2.2: - resolution: - { - integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==, - } - - extend@3.0.2: - resolution: - { - integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, - } - - fast-copy@3.0.2: - resolution: - { - integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==, - } - - file-selector@2.1.2: - resolution: - { - integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==, - } - engines: { node: '>= 12' } - - file-source@0.6.1: - resolution: - { - integrity: sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA==, - } - - follow-redirects@1.15.9: - resolution: - { - integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==, - } - engines: { node: '>=4.0' } - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - form-data@4.0.2: - resolution: - { - integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==, - } - engines: { node: '>= 6' } - - fs-extra@10.1.0: - resolution: - { - integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==, - } - engines: { node: '>=12' } - - fs-extra@4.0.3: - resolution: - { - integrity: sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==, - } - - fs.realpath@1.0.0: - resolution: - { - integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, - } - - 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==, - } - - gensync@1.0.0-beta.2: - resolution: - { - integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, - } - engines: { node: '>=6.9.0' } - - geobuf@3.0.2: - resolution: - { - integrity: sha512-ASgKwEAQQRnyNFHNvpd5uAwstbVYmiTW0Caw3fBb509tNTqXyAAPMyFs5NNihsLZhLxU1j/kjFhkhLWA9djuVg==, - } - hasBin: true - - geojson-dissolve@3.1.0: - resolution: - { - integrity: sha512-JXHfn+A3tU392HA703gJbjmuHaQOAE/C1KzbELCczFRFux+GdY6zt1nKb1VMBHp4LWeE7gUY2ql+g06vJqhiwQ==, - } - - geojson-flatten@0.2.4: - resolution: - { - integrity: sha512-LiX6Jmot8adiIdZ/fthbcKKPOfWjTQchX/ggHnwMZ2e4b0I243N1ANUos0LvnzepTEsj0+D4fIJ5bKhBrWnAHA==, - } - hasBin: true - - geojson-linestring-dissolve@0.0.1: - resolution: - { - integrity: sha512-Y8I2/Ea28R/Xeki7msBcpMvJL2TaPfaPKP8xqueJfQ9/jEhps+iOJxOR2XCBGgVb12Z6XnDb1CMbaPfLepsLaw==, - } - - 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-stdin@6.0.0: - resolution: - { - integrity: sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==, - } - engines: { node: '>=4' } - - glob@7.2.3: - resolution: - { - integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, - } - deprecated: Glob versions prior to v9 are no longer supported - - globals@11.12.0: - resolution: - { - integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==, - } - engines: { node: '>=4' } - - gopd@1.2.0: - resolution: - { - integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, - } - engines: { node: '>= 0.4' } - - graceful-fs@4.2.11: - resolution: - { - integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, - } - - 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.2: - resolution: - { - integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, - } - engines: { node: '>= 0.4' } - - hast-util-to-estree@3.1.3: - resolution: - { - integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==, - } - - hast-util-to-jsx-runtime@2.3.6: - resolution: - { - integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==, - } - - hast-util-whitespace@3.0.0: - resolution: - { - integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==, - } - - history@5.3.0: - resolution: - { - integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==, - } - - html-parse-stringify@3.0.1: - resolution: - { - integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==, - } - - i18next-browser-languagedetector@7.2.2: - resolution: - { - integrity: sha512-6b7r75uIJDWCcCflmbof+sJ94k9UQO4X0YR62oUfqGI/GjCLVzlCwu8TFdRZIqVLzWbzNcmkmhfqKEr4TLz4HQ==, - } - - i18next@23.16.8: - resolution: - { - integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==, - } - - iconv-lite@0.4.24: - resolution: - { - integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==, - } - engines: { node: '>=0.10.0' } - - ieee754@1.2.1: - resolution: - { - integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, - } - - import-fresh@3.3.1: - resolution: - { - integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, - } - engines: { node: '>=6' } - - inflight@1.0.6: - resolution: - { - integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, - } - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: - { - integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, - } - - inline-style-parser@0.2.4: - resolution: - { - integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==, - } - - is-alphabetical@2.0.1: - resolution: - { - integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==, - } - - is-alphanumerical@2.0.1: - resolution: - { - integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==, - } - - is-arrayish@0.2.1: - resolution: - { - integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, - } - - is-decimal@2.0.1: - resolution: - { - integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==, - } - - is-hexadecimal@2.0.1: - resolution: - { - integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==, - } - - is-plain-obj@4.1.0: - resolution: - { - integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==, - } - engines: { node: '>=12' } - - isarray@0.0.1: - resolution: - { - integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==, - } - - jquery@3.7.1: - resolution: - { - integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==, - } - - js-tokens@4.0.0: - resolution: - { - integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, - } - - jsesc@3.1.0: - resolution: - { - integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, - } - engines: { node: '>=6' } - hasBin: true - - json-parse-even-better-errors@2.3.1: - resolution: - { - integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, - } - - json5@2.2.3: - resolution: - { - integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, - } - engines: { node: '>=6' } - hasBin: true - - jsonc-parser@3.3.1: - resolution: - { - integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==, - } - - jsonfile@4.0.0: - resolution: - { - integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==, - } - - jsonfile@6.1.0: - resolution: - { - integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==, - } - - keyboard-key@1.1.0: - resolution: - { - integrity: sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ==, - } - - lines-and-columns@1.2.4: - resolution: - { - integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, - } - - lodash-es@4.17.21: - resolution: - { - integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==, - } - - lodash@4.17.21: - resolution: - { - integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, - } - - longest-streak@3.1.0: - resolution: - { - integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==, - } - - loose-envify@1.4.0: - resolution: - { - integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, - } - hasBin: true - - lottie-web@5.12.2: - resolution: - { - integrity: sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==, - } - - lru-cache@5.1.1: - resolution: - { - integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, - } - - markdown-extensions@2.0.0: - resolution: - { - integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==, - } - engines: { node: '>=16' } - - markdown-table@3.0.4: - resolution: - { - integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==, - } - - marked@4.3.0: - resolution: - { - integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==, - } - engines: { node: '>= 12' } - hasBin: true - - math-intrinsics@1.1.0: - resolution: - { - integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, - } - engines: { node: '>= 0.4' } - - mdast-util-find-and-replace@3.0.2: - resolution: - { - integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==, - } - - mdast-util-from-markdown@2.0.2: - resolution: - { - integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==, - } - - mdast-util-gfm-autolink-literal@2.0.1: - resolution: - { - integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==, - } - - mdast-util-gfm-footnote@2.1.0: - resolution: - { - integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==, - } - - mdast-util-gfm-strikethrough@2.0.0: - resolution: - { - integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==, - } - - mdast-util-gfm-table@2.0.0: - resolution: - { - integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==, - } - - mdast-util-gfm-task-list-item@2.0.0: - resolution: - { - integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==, - } - - mdast-util-gfm@3.1.0: - resolution: - { - integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==, - } - - mdast-util-mdx-expression@2.0.1: - resolution: - { - integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==, - } - - mdast-util-mdx-jsx@3.2.0: - resolution: - { - integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==, - } - - mdast-util-mdx@3.0.0: - resolution: - { - integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==, - } - - mdast-util-mdxjs-esm@2.0.1: - resolution: - { - integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==, - } - - mdast-util-phrasing@4.1.0: - resolution: - { - integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==, - } - - mdast-util-to-hast@13.2.0: - resolution: - { - integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==, - } - - mdast-util-to-markdown@2.1.2: - resolution: - { - integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==, - } - - mdast-util-to-string@4.0.0: - resolution: - { - integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==, - } - - memoize-one@5.2.1: - resolution: - { - integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==, - } - - micromark-core-commonmark@2.0.3: - resolution: - { - integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==, - } - - micromark-extension-gfm-autolink-literal@2.1.0: - resolution: - { - integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==, - } - - micromark-extension-gfm-footnote@2.1.0: - resolution: - { - integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==, - } - - micromark-extension-gfm-strikethrough@2.1.0: - resolution: - { - integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==, - } - - micromark-extension-gfm-table@2.1.1: - resolution: - { - integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==, - } - - micromark-extension-gfm-tagfilter@2.0.0: - resolution: - { - integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==, - } - - micromark-extension-gfm-task-list-item@2.1.0: - resolution: - { - integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==, - } - - micromark-extension-gfm@3.0.0: - resolution: - { - integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==, - } - - micromark-extension-mdx-expression@3.0.1: - resolution: - { - integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==, - } - - micromark-extension-mdx-jsx@3.0.2: - resolution: - { - integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==, - } - - micromark-extension-mdx-md@2.0.0: - resolution: - { - integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==, - } - - micromark-extension-mdxjs-esm@3.0.0: - resolution: - { - integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==, - } - - micromark-extension-mdxjs@3.0.0: - resolution: - { - integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==, - } - - micromark-factory-destination@2.0.1: - resolution: - { - integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==, - } - - micromark-factory-label@2.0.1: - resolution: - { - integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==, - } - - micromark-factory-mdx-expression@2.0.3: - resolution: - { - integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==, - } - - micromark-factory-space@2.0.1: - resolution: - { - integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==, - } - - micromark-factory-title@2.0.1: - resolution: - { - integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==, - } - - micromark-factory-whitespace@2.0.1: - resolution: - { - integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==, - } - - micromark-util-character@2.1.1: - resolution: - { - integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==, - } - - micromark-util-chunked@2.0.1: - resolution: - { - integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==, - } - - micromark-util-classify-character@2.0.1: - resolution: - { - integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==, - } - - micromark-util-combine-extensions@2.0.1: - resolution: - { - integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==, - } - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: - { - integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==, - } - - micromark-util-decode-string@2.0.1: - resolution: - { - integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==, - } - - micromark-util-encode@2.0.1: - resolution: - { - integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==, - } - - micromark-util-events-to-acorn@2.0.3: - resolution: - { - integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==, - } - - micromark-util-html-tag-name@2.0.1: - resolution: - { - integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==, - } - - micromark-util-normalize-identifier@2.0.1: - resolution: - { - integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==, - } - - micromark-util-resolve-all@2.0.1: - resolution: - { - integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==, - } - - micromark-util-sanitize-uri@2.0.1: - resolution: - { - integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==, - } - - micromark-util-subtokenize@2.1.0: - resolution: - { - integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==, - } - - micromark-util-symbol@2.0.1: - resolution: - { - integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==, - } - - micromark-util-types@2.0.2: - resolution: - { - integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==, - } - - micromark@4.0.2: - resolution: - { - integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==, - } - - mime-db@1.52.0: - resolution: - { - integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, - } - engines: { node: '>= 0.6' } - - mime-types@2.1.35: - resolution: - { - integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, - } - engines: { node: '>= 0.6' } - - minimatch@3.1.2: - resolution: - { - integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, - } - - minimist@1.2.0: - resolution: - { - integrity: sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==, - } - - minimist@1.2.6: - resolution: - { - integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==, - } - - ms@2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, - } - - nanoid@3.3.11: - resolution: - { - integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, - } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } - hasBin: true - - node-releases@2.0.19: - resolution: - { - integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==, - } - - object-assign@4.1.1: - resolution: - { - integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, - } - engines: { node: '>=0.10.0' } - - once@1.4.0: - resolution: - { - integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, - } - - parent-module@1.0.1: - resolution: - { - integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, - } - engines: { node: '>=6' } - - parse-author@2.0.0: - resolution: - { - integrity: sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw==, - } - engines: { node: '>=0.10.0' } - - parse-entities@4.0.2: - resolution: - { - integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==, - } - - parse-json@5.2.0: - resolution: - { - integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, - } - engines: { node: '>=8' } - - parse-svg-path@0.1.2: - resolution: - { - integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==, - } - - path-browserify@1.0.1: - resolution: - { - integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==, - } - - path-data-parser@0.1.0: - resolution: - { - integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==, - } - - path-is-absolute@1.0.1: - resolution: - { - integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, - } - engines: { node: '>=0.10.0' } - - path-source@0.1.3: - resolution: - { - integrity: sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw==, - } - - path-type@4.0.0: - resolution: - { - integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, - } - engines: { node: '>=8' } - - pbf@3.3.0: - resolution: - { - integrity: sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==, - } - hasBin: true - - picocolors@1.1.1: - resolution: - { - integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, - } - - point-at-length@1.1.0: - resolution: - { - integrity: sha512-nNHDk9rNEh/91o2Y8kHLzBLNpLf80RYd2gCun9ss+V0ytRSf6XhryBTx071fesktjbachRmGuUbId+JQmzhRXw==, - } - - points-on-curve@0.2.0: - resolution: - { - integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==, - } - - points-on-path@0.2.1: - resolution: - { - integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==, - } - - postcss@8.5.3: - resolution: - { - integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==, - } - engines: { node: ^10 || ^12 || >=14 } - - prettier-package-json@2.8.0: - resolution: - { - integrity: sha512-WxtodH/wWavfw3MR7yK/GrS4pASEQ+iSTkdtSxPJWvqzG55ir5nvbLt9rw5AOiEcqqPCRM92WCtR1rk3TG3JSQ==, - } - hasBin: true - - prettier-plugin-astro@0.14.1: - resolution: - { - integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==, - } - engines: { node: ^14.15.0 || >=16.0.0 } - - prettier-plugin-curly-and-jsdoc@3.1.0: - resolution: - { - integrity: sha512-4QMOHnLlkP2jTRWS0MFH6j+cuOiXLvXOqCLKbtwwVd8PPyq8NenW5AAwfwqiTNHBQG/DmzViPphRrwgN0XkUVQ==, - } - peerDependencies: - prettier: ^3.0.0 - - prettier-plugin-pkgsort@0.2.1: - resolution: - { - integrity: sha512-/k5MIw84EhgoH7dmq4+6ozHjJ0VYbxbw17g4C+WPGHODkLivGwJoA6U1YPR/KObyRDMQJHXAfXKu++9smg7Jyw==, - } - peerDependencies: - prettier: ^3.0.0 - - prettier@3.5.3: - resolution: - { - integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==, - } - engines: { node: '>=14' } - hasBin: true - - prismjs@1.30.0: - resolution: - { - integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==, - } - engines: { node: '>=6' } - - prop-types@15.8.1: - resolution: - { - integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, - } - - property-information@7.0.0: - resolution: - { - integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==, - } - - protocol-buffers-schema@3.6.0: - resolution: - { - integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==, - } - - react-dom@18.3.1: - resolution: - { - integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==, - } - peerDependencies: - react: ^18.3.1 - - react-draggable@4.4.6: - resolution: - { - integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==, - } - peerDependencies: - react: '>= 16.3.0' - react-dom: '>= 16.3.0' - - react-dropzone@14.3.8: - resolution: - { - integrity: sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==, - } - engines: { node: '>= 10.13' } - peerDependencies: - react: '>= 16.8 || 18.0.0' - - react-fast-compare@3.2.2: - resolution: - { - integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==, - } - - react-fireworks@1.0.4: - resolution: - { - integrity: sha512-jj1a+HTicB4pR6g2lqhVyAox0GTE0TOrZK2XaJFRYOwltgQWeYErZxnvU9+zH/blY+Hpmu9IKyb39OD3KcCMJw==, - } - - react-i18next@13.5.0: - resolution: - { - integrity: sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA==, - } - peerDependencies: - i18next: '>= 23.2.3' - react: '>= 16.8.0' - react-dom: '*' - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - - react-is@16.13.1: - resolution: - { - integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, - } - - react-is@18.3.1: - resolution: - { - integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==, - } - - react-popper@2.3.0: - resolution: - { - integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==, - } - peerDependencies: - '@popperjs/core': ^2.0.0 - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - - react-refresh@0.14.2: - resolution: - { - integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==, - } - engines: { node: '>=0.10.0' } - - react-resizable@3.0.5: - resolution: - { - integrity: sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==, - } - peerDependencies: - react: '>= 16.3' - - react-router-dom@6.30.0: - resolution: - { - integrity: sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==, - } - engines: { node: '>=14.0.0' } - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - react-router@6.30.0: - resolution: - { - integrity: sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==, - } - engines: { node: '>=14.0.0' } - peerDependencies: - react: '>=16.8' - - react-telegram-login@1.1.2: - resolution: - { - integrity: sha512-pDP+bvfaklWgnK5O6yvZnIwgky0nnYUU6Zhk0EjdMSkPsLQoOzZRsXIoZnbxyBXhi7346bsxMH+EwwJPTxClDw==, - } - peerDependencies: - react: ^16.13.1 - - react-toastify@9.1.3: - resolution: - { - integrity: sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==, - } - peerDependencies: - react: '>=16' - react-dom: '>=16' - - react-turnstile@1.1.4: - resolution: - { - integrity: sha512-oluyRWADdsufCt5eMqacW4gfw8/csr6Tk+fmuaMx0PWMKP1SX1iCviLvD2D5w92eAzIYDHi/krUWGHhlfzxTpQ==, - } - peerDependencies: - react: '>= 16.13.1' - react-dom: '>= 16.13.1' - - react-window@1.8.11: - resolution: - { - integrity: sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==, - } - engines: { node: '>8.0.0' } - peerDependencies: - react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - react@18.3.1: - resolution: - { - integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==, - } - engines: { node: '>=0.10.0' } - - readable-stream@1.1.14: - resolution: - { - integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==, - } - - readable-stream@3.6.2: - resolution: - { - integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, - } - engines: { node: '>= 6' } - - recma-build-jsx@1.0.0: - resolution: - { - integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==, - } - - recma-jsx@1.0.0: - resolution: - { - integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==, - } - - recma-parse@1.0.0: - resolution: - { - integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==, - } - - recma-stringify@1.0.0: - resolution: - { - integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==, - } - - regenerator-runtime@0.14.1: - resolution: - { - integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==, - } - - rehype-recma@1.0.0: - resolution: - { - integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==, - } - - remark-gfm@4.0.1: - resolution: - { - integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==, - } - - remark-mdx@3.1.0: - resolution: - { - integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==, - } - - remark-parse@11.0.0: - resolution: - { - integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==, - } - - remark-rehype@11.1.2: - resolution: - { - integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==, - } - - remark-stringify@11.0.0: - resolution: - { - integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==, - } - - resolve-from@4.0.0: - resolution: - { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, - } - engines: { node: '>=4' } - - resolve-protobuf-schema@2.1.0: - resolution: - { - integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==, - } - - rollup@4.39.0: - resolution: - { - integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==, - } - engines: { node: '>=18.0.0', npm: '>=8.0.0' } - hasBin: true - - roughjs@4.5.2: - resolution: - { - integrity: sha512-2xSlLDKdsWyFxrveYWk9YQ/Y9UfK38EAMRNkYkMqYBJvPX8abCa9PN0x3w02H8Oa6/0bcZICJU+U95VumPqseg==, - } - - rw@1.3.3: - resolution: - { - integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==, - } - - s.color@0.0.15: - resolution: - { - integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==, - } - - safe-buffer@5.2.1: - resolution: - { - integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, - } - - safer-buffer@2.1.2: - resolution: - { - integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, - } - - sass-formatter@0.7.9: - resolution: - { - integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==, - } - - scheduler@0.23.2: - resolution: - { - integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==, - } - - scroll-into-view-if-needed@2.2.31: - resolution: - { - integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==, - } - - semantic-ui-offline@2.5.0: - resolution: - { - integrity: sha512-Fldx3SfaVtWx5EeCb/5EiJwYkzrGbtsAwVs02xLkeV5z5l8GJmplWEVOeJVjbEpmyiwPWp7cA48JwT5RjbWBVA==, - } - - semantic-ui-react@2.1.5: - resolution: - { - integrity: sha512-nIqmmUNpFHfovEb+RI2w3E2/maZQutd8UIWyRjf1SLse+XF51hI559xbz/sLN3O6RpLjr/echLOOXwKCirPy3Q==, - } - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - - semver@6.3.1: - resolution: - { - integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, - } - hasBin: true - - shallowequal@1.1.0: - resolution: - { - integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==, - } - - shapefile@0.6.6: - resolution: - { - integrity: sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==, - } - hasBin: true - - simple-statistics@7.8.8: - resolution: - { - integrity: sha512-CUtP0+uZbcbsFpqEyvNDYjJCl+612fNgjT8GaVuvMG7tBuJg8gXGpsP5M7X658zy0IcepWOZ6nPBu1Qb9ezA1w==, - } - - simplify-geojson@1.0.5: - resolution: - { - integrity: sha512-02l1W4UipP5ivNVq6kX15mAzCRIV1oI3tz0FUEyOsNiv1ltuFDjbNhO+nbv/xhbDEtKqWLYuzpWhUsJrjR/ypA==, - } - hasBin: true - - simplify-geometry@0.0.2: - resolution: - { - integrity: sha512-ZEyrplkqgCqDlL7V8GbbYgTLlcnNF+MWWUdy8s8ZeJru50bnI71rDew/I+HG36QS2mPOYAq1ZjwNXxHJ8XOVBw==, - } - - slice-source@0.4.1: - resolution: - { - integrity: sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==, - } - - sort-object-keys@1.1.3: - resolution: - { - integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==, - } - - sort-order@1.1.2: - resolution: - { - integrity: sha512-Q8tOrwB1TSv9fNUXym9st3TZJODtmcOIi2JWCkVNQPrRg17KPwlpwweTEb7pMwUIFMTAgx2/JsQQXEPFzYQj3A==, - } - - source-map-js@1.2.1: - resolution: - { - integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, - } - engines: { node: '>=0.10.0' } - - source-map@0.7.4: - resolution: - { - integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==, - } - engines: { node: '>= 8' } - - space-separated-tokens@2.0.2: - resolution: - { - integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==, - } - - sse.js@https://codeload.github.com/mpetazzoni/sse.js/tar.gz/39b9b82aae95fd58d9d08b487845fe230f4b14e6: - resolution: - { - tarball: https://codeload.github.com/mpetazzoni/sse.js/tar.gz/39b9b82aae95fd58d9d08b487845fe230f4b14e6, - } - version: 2.6.0 - - stream-source@0.3.5: - resolution: - { - integrity: sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g==, - } - - string_decoder@0.10.31: - resolution: - { - integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==, - } - - string_decoder@1.3.0: - resolution: - { - integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, - } - - stringify-entities@4.0.4: - resolution: - { - integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==, - } - - style-to-js@1.1.16: - resolution: - { - integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==, - } - - style-to-object@1.0.8: - resolution: - { - integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==, - } - - suf-log@2.5.3: - resolution: - { - integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==, - } - - text-encoding@0.6.4: - resolution: - { - integrity: sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==, - } - deprecated: no longer maintained - - topojson-client@3.1.0: - resolution: - { - integrity: sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==, - } - hasBin: true - - topojson-server@3.0.1: - resolution: - { - integrity: sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==, - } - hasBin: true - - trim-lines@3.0.1: - resolution: - { - integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==, - } - - trough@2.2.0: - resolution: - { - integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==, - } - - tslib@2.8.1: - resolution: - { - integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, - } - - typedarray@0.0.6: - resolution: - { - integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==, - } - - typedarray@0.0.7: - resolution: - { - integrity: sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ==, - } - - typescript@4.4.2: - resolution: - { - integrity: sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==, - } - engines: { node: '>=4.2.0' } - hasBin: true - - unified@11.0.5: - resolution: - { - integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==, - } - - unist-util-is@6.0.0: - resolution: - { - integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==, - } - - unist-util-position-from-estree@2.0.0: - resolution: - { - integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==, - } - - unist-util-position@5.0.0: - resolution: - { - integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==, - } - - unist-util-stringify-position@4.0.0: - resolution: - { - integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==, - } - - unist-util-visit-parents@6.0.1: - resolution: - { - integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==, - } - - unist-util-visit@5.0.0: - resolution: - { - integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==, - } - - universalify@0.1.2: - resolution: - { - integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, - } - engines: { node: '>= 4.0.0' } - - universalify@2.0.1: - resolution: - { - integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, - } - engines: { node: '>= 10.0.0' } - - update-browserslist-db@1.1.3: - resolution: - { - integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==, - } - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - util-deprecate@1.0.2: - resolution: - { - integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, - } - - utility-types@3.11.0: - resolution: - { - integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==, - } - engines: { node: '>= 4' } - - vfile-message@4.0.2: - resolution: - { - integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==, - } - - vfile@6.0.3: - resolution: - { - integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==, - } - - vite@5.4.16: - resolution: - { - integrity: sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==, - } - 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 - - void-elements@3.1.0: - resolution: - { - integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==, - } - engines: { node: '>=0.10.0' } - - warning@4.0.3: - resolution: - { - integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==, - } - - wrappy@1.0.2: - resolution: - { - integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, - } - - yallist@3.1.1: - resolution: - { - integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, - } - - yaml@1.10.2: - resolution: - { - integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==, - } - engines: { node: '>= 6' } - - zwitch@2.0.4: - resolution: - { - integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, - } - -snapshots: - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - - '@astrojs/compiler@2.11.0': {} - - '@babel/code-frame@7.26.2': - dependencies: - '@babel/helper-validator-identifier': 7.25.9 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.26.8': {} - - '@babel/core@7.26.10': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 - '@babel/helper-compilation-targets': 7.27.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) - '@babel/helpers': 7.27.0 - '@babel/parser': 7.27.0 - '@babel/template': 7.27.0 - '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 - convert-source-map: 2.0.0 - debug: 4.4.0 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.27.0': - dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.27.0': - dependencies: - '@babel/compat-data': 7.26.8 - '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.4 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-module-imports@7.25.9': - dependencies: - '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': - dependencies: - '@babel/core': 7.26.10 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.27.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.26.5': {} - - '@babel/helper-string-parser@7.25.9': {} - - '@babel/helper-validator-identifier@7.25.9': {} - - '@babel/helper-validator-option@7.25.9': {} - - '@babel/helpers@7.27.0': - dependencies: - '@babel/template': 7.27.0 - '@babel/types': 7.27.0 - - '@babel/parser@7.27.0': - dependencies: - '@babel/types': 7.27.0 - - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': - dependencies: - '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': - dependencies: - '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/runtime@7.27.0': - dependencies: - regenerator-runtime: 0.14.1 - - '@babel/template@7.27.0': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 - - '@babel/traverse@7.27.0': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 - '@babel/parser': 7.27.0 - '@babel/template': 7.27.0 - '@babel/types': 7.27.0 - debug: 4.4.0 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.27.0': - dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - - '@dnd-kit/accessibility@3.1.1(react@18.3.1)': - dependencies: - react: 18.3.1 - tslib: 2.8.1 - - '@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@dnd-kit/accessibility': 3.1.1(react@18.3.1) - '@dnd-kit/utilities': 3.2.2(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tslib: 2.8.1 - - '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': - dependencies: - '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@dnd-kit/utilities': 3.2.2(react@18.3.1) - react: 18.3.1 - tslib: 2.8.1 - - '@dnd-kit/utilities@3.2.2(react@18.3.1)': - dependencies: - react: 18.3.1 - tslib: 2.8.1 - - '@douyinfe/semi-animation-react@2.77.1': - dependencies: - '@douyinfe/semi-animation': 2.77.1 - '@douyinfe/semi-animation-styled': 2.77.1 - classnames: 2.5.1 - - '@douyinfe/semi-animation-styled@2.77.1': {} - - '@douyinfe/semi-animation@2.77.1': - dependencies: - bezier-easing: 2.1.0 - - '@douyinfe/semi-foundation@2.77.1(acorn@8.14.1)': - dependencies: - '@douyinfe/semi-animation': 2.77.1 - '@douyinfe/semi-json-viewer-core': 2.77.1 - '@mdx-js/mdx': 3.1.0(acorn@8.14.1) - async-validator: 3.5.2 - classnames: 2.5.1 - date-fns: 2.30.0 - date-fns-tz: 1.3.8(date-fns@2.30.0) - fast-copy: 3.0.2 - lodash: 4.17.21 - lottie-web: 5.12.2 - memoize-one: 5.2.1 - prismjs: 1.30.0 - remark-gfm: 4.0.1 - scroll-into-view-if-needed: 2.2.31 - transitivePeerDependencies: - - acorn - - supports-color - - '@douyinfe/semi-icons@2.77.1(react@18.3.1)': - dependencies: - classnames: 2.5.1 - react: 18.3.1 - - '@douyinfe/semi-illustrations@2.77.1(react@18.3.1)': - dependencies: - react: 18.3.1 - - '@douyinfe/semi-json-viewer-core@2.77.1': - dependencies: - jsonc-parser: 3.3.1 - - '@douyinfe/semi-theme-default@2.77.1': {} - - '@douyinfe/semi-ui@2.77.1(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) - '@dnd-kit/utilities': 3.2.2(react@18.3.1) - '@douyinfe/semi-animation': 2.77.1 - '@douyinfe/semi-animation-react': 2.77.1 - '@douyinfe/semi-foundation': 2.77.1(acorn@8.14.1) - '@douyinfe/semi-icons': 2.77.1(react@18.3.1) - '@douyinfe/semi-illustrations': 2.77.1(react@18.3.1) - '@douyinfe/semi-theme-default': 2.77.1 - async-validator: 3.5.2 - classnames: 2.5.1 - copy-text-to-clipboard: 2.2.0 - date-fns: 2.30.0 - date-fns-tz: 1.3.8(date-fns@2.30.0) - fast-copy: 3.0.2 - jsonc-parser: 3.3.1 - lodash: 4.17.21 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-resizable: 3.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-window: 1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - scroll-into-view-if-needed: 2.2.31 - utility-types: 3.11.0 - transitivePeerDependencies: - - acorn - - supports-color - - '@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 - - '@fluentui/react-component-event-listener@0.63.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.27.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@fluentui/react-component-ref@0.63.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.27.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 16.13.1 - - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@mdx-js/mdx@3.1.0(acorn@8.14.1)': - dependencies: - '@types/estree': 1.0.7 - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdx': 2.0.13 - collapse-white-space: 2.1.0 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - estree-util-scope: 1.0.0 - estree-walker: 3.0.3 - hast-util-to-jsx-runtime: 2.3.6 - markdown-extensions: 2.0.0 - recma-build-jsx: 1.0.0 - recma-jsx: 1.0.0(acorn@8.14.1) - recma-stringify: 1.0.0 - rehype-recma: 1.0.0 - remark-mdx: 3.1.0 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - source-map: 0.7.4 - unified: 11.0.5 - unist-util-position-from-estree: 2.0.0 - unist-util-stringify-position: 4.0.0 - unist-util-visit: 5.0.0 - vfile: 6.0.3 - transitivePeerDependencies: - - acorn - - supports-color - - '@popperjs/core@2.11.8': {} - - '@remix-run/router@1.23.0': {} - - '@resvg/resvg-js-android-arm-eabi@2.4.1': - optional: true - - '@resvg/resvg-js-android-arm64@2.4.1': - optional: true - - '@resvg/resvg-js-darwin-arm64@2.4.1': - optional: true - - '@resvg/resvg-js-darwin-x64@2.4.1': - optional: true - - '@resvg/resvg-js-linux-arm-gnueabihf@2.4.1': - optional: true - - '@resvg/resvg-js-linux-arm64-gnu@2.4.1': - optional: true - - '@resvg/resvg-js-linux-arm64-musl@2.4.1': - optional: true - - '@resvg/resvg-js-linux-x64-gnu@2.4.1': - optional: true - - '@resvg/resvg-js-linux-x64-musl@2.4.1': - optional: true - - '@resvg/resvg-js-win32-arm64-msvc@2.4.1': - optional: true - - '@resvg/resvg-js-win32-ia32-msvc@2.4.1': - optional: true - - '@resvg/resvg-js-win32-x64-msvc@2.4.1': - optional: true - - '@resvg/resvg-js@2.4.1': - optionalDependencies: - '@resvg/resvg-js-android-arm-eabi': 2.4.1 - '@resvg/resvg-js-android-arm64': 2.4.1 - '@resvg/resvg-js-darwin-arm64': 2.4.1 - '@resvg/resvg-js-darwin-x64': 2.4.1 - '@resvg/resvg-js-linux-arm-gnueabihf': 2.4.1 - '@resvg/resvg-js-linux-arm64-gnu': 2.4.1 - '@resvg/resvg-js-linux-arm64-musl': 2.4.1 - '@resvg/resvg-js-linux-x64-gnu': 2.4.1 - '@resvg/resvg-js-linux-x64-musl': 2.4.1 - '@resvg/resvg-js-win32-arm64-msvc': 2.4.1 - '@resvg/resvg-js-win32-ia32-msvc': 2.4.1 - '@resvg/resvg-js-win32-x64-msvc': 2.4.1 - - '@rollup/rollup-android-arm-eabi@4.39.0': - optional: true - - '@rollup/rollup-android-arm64@4.39.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.39.0': - optional: true - - '@rollup/rollup-darwin-x64@4.39.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.39.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.39.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.39.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.39.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.39.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.39.0': - optional: true - - '@rollup/rollup-linux-loongarch64-gnu@4.39.0': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.39.0': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.39.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.39.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.39.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.39.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.39.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.39.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.39.0': - optional: true - - '@semantic-ui-react/event-stack@3.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - exenv: 1.2.2 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@so1ve/prettier-config@3.1.0(prettier@3.5.3)': - dependencies: - '@so1ve/prettier-plugin-toml': 3.1.0(prettier@3.5.3) - prettier: 3.5.3 - prettier-plugin-astro: 0.14.1 - prettier-plugin-curly-and-jsdoc: 3.1.0(prettier@3.5.3) - prettier-plugin-pkgsort: 0.2.1(prettier@3.5.3) - - '@so1ve/prettier-plugin-toml@3.1.0(prettier@3.5.3)': - dependencies: - prettier: 3.5.3 - - '@turf/boolean-clockwise@6.5.0': - dependencies: - '@turf/helpers': 6.5.0 - '@turf/invariant': 6.5.0 - - '@turf/clone@6.5.0': - dependencies: - '@turf/helpers': 6.5.0 - - '@turf/flatten@6.5.0': - dependencies: - '@turf/helpers': 6.5.0 - '@turf/meta': 6.5.0 - - '@turf/helpers@6.5.0': {} - - '@turf/invariant@6.5.0': - dependencies: - '@turf/helpers': 6.5.0 - - '@turf/meta@3.14.0': {} - - '@turf/meta@6.5.0': - dependencies: - '@turf/helpers': 6.5.0 - - '@turf/rewind@6.5.0': - dependencies: - '@turf/boolean-clockwise': 6.5.0 - '@turf/clone': 6.5.0 - '@turf/helpers': 6.5.0 - '@turf/invariant': 6.5.0 - '@turf/meta': 6.5.0 - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.7 - - '@types/babel__generator@7.6.8': - dependencies: - '@babel/types': 7.27.0 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 - - '@types/babel__traverse@7.20.7': - dependencies: - '@babel/types': 7.27.0 - - '@types/debug@4.1.12': - dependencies: - '@types/ms': 2.1.0 - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.7 - - '@types/estree@1.0.7': {} - - '@types/hast@3.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/mdx@2.0.13': {} - - '@types/ms@2.1.0': {} - - '@types/parse-author@2.0.3': {} - - '@types/parse-json@4.0.2': {} - - '@types/unist@2.0.11': {} - - '@types/unist@3.0.3': {} - - '@ungap/structured-clone@1.3.0': {} - - '@visactor/react-vchart@1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@visactor/vchart': 1.8.11 - '@visactor/vgrammar-core': 0.10.11 - '@visactor/vrender-core': 0.17.17 - '@visactor/vrender-kits': 0.17.17 - '@visactor/vutils': 0.17.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 18.3.1 - - '@visactor/vchart-semi-theme@1.8.8(@visactor/vchart@1.8.11)': - dependencies: - '@visactor/vchart': 1.8.11 - '@visactor/vchart-theme-utils': 1.8.8(@visactor/vchart@1.8.11) - - '@visactor/vchart-theme-utils@1.8.8(@visactor/vchart@1.8.11)': - dependencies: - '@visactor/vchart': 1.8.11 - - '@visactor/vchart@1.8.11': - dependencies: - '@visactor/vdataset': 0.17.5 - '@visactor/vgrammar-core': 0.10.11 - '@visactor/vgrammar-hierarchy': 0.10.11 - '@visactor/vgrammar-projection': 0.10.11 - '@visactor/vgrammar-sankey': 0.10.11 - '@visactor/vgrammar-util': 0.10.11 - '@visactor/vgrammar-wordcloud': 0.10.11 - '@visactor/vgrammar-wordcloud-shape': 0.10.11 - '@visactor/vrender-components': 0.17.17 - '@visactor/vrender-core': 0.17.17 - '@visactor/vrender-kits': 0.17.17 - '@visactor/vscale': 0.17.5 - '@visactor/vutils': 0.17.5 - '@visactor/vutils-extension': 1.8.11 - - '@visactor/vdataset@0.17.5': - dependencies: - '@turf/flatten': 6.5.0 - '@turf/helpers': 6.5.0 - '@turf/rewind': 6.5.0 - '@visactor/vutils': 0.17.5 - d3-dsv: 2.0.0 - d3-geo: 1.12.1 - d3-hexbin: 0.2.2 - d3-hierarchy: 3.1.2 - eventemitter3: 4.0.7 - geobuf: 3.0.2 - geojson-dissolve: 3.1.0 - path-browserify: 1.0.1 - pbf: 3.3.0 - point-at-length: 1.1.0 - simple-statistics: 7.8.8 - simplify-geojson: 1.0.5 - topojson-client: 3.1.0 - - '@visactor/vgrammar-coordinate@0.10.11': - dependencies: - '@visactor/vgrammar-util': 0.10.11 - '@visactor/vutils': 0.17.5 - - '@visactor/vgrammar-core@0.10.11': - dependencies: - '@visactor/vdataset': 0.17.5 - '@visactor/vgrammar-coordinate': 0.10.11 - '@visactor/vgrammar-util': 0.10.11 - '@visactor/vrender-components': 0.17.17 - '@visactor/vrender-core': 0.17.17 - '@visactor/vrender-kits': 0.17.17 - '@visactor/vscale': 0.17.5 - '@visactor/vutils': 0.17.5 - - '@visactor/vgrammar-hierarchy@0.10.11': - dependencies: - '@visactor/vgrammar-core': 0.10.11 - '@visactor/vgrammar-util': 0.10.11 - '@visactor/vrender-core': 0.17.17 - '@visactor/vrender-kits': 0.17.17 - '@visactor/vutils': 0.17.5 - - '@visactor/vgrammar-projection@0.10.11': - dependencies: - '@visactor/vgrammar-core': 0.10.11 - '@visactor/vgrammar-util': 0.10.11 - '@visactor/vutils': 0.17.5 - d3-geo: 1.12.1 - - '@visactor/vgrammar-sankey@0.10.11': - dependencies: - '@visactor/vgrammar-core': 0.10.11 - '@visactor/vgrammar-util': 0.10.11 - '@visactor/vrender-core': 0.17.17 - '@visactor/vrender-kits': 0.17.17 - '@visactor/vutils': 0.17.5 - - '@visactor/vgrammar-util@0.10.11': - dependencies: - '@visactor/vutils': 0.17.5 - - '@visactor/vgrammar-wordcloud-shape@0.10.11': - dependencies: - '@visactor/vgrammar-core': 0.10.11 - '@visactor/vgrammar-util': 0.10.11 - '@visactor/vrender-core': 0.17.17 - '@visactor/vrender-kits': 0.17.17 - '@visactor/vscale': 0.17.5 - '@visactor/vutils': 0.17.5 - - '@visactor/vgrammar-wordcloud@0.10.11': - dependencies: - '@visactor/vgrammar-core': 0.10.11 - '@visactor/vgrammar-util': 0.10.11 - '@visactor/vrender-core': 0.17.17 - '@visactor/vrender-kits': 0.17.17 - '@visactor/vutils': 0.17.5 - - '@visactor/vrender-components@0.17.17': - dependencies: - '@visactor/vrender-core': 0.17.17 - '@visactor/vrender-kits': 0.17.17 - '@visactor/vscale': 0.17.5 - '@visactor/vutils': 0.17.5 - - '@visactor/vrender-core@0.17.17': - dependencies: - '@visactor/vutils': 0.17.5 - color-convert: 2.0.1 - - '@visactor/vrender-kits@0.17.17': - dependencies: - '@resvg/resvg-js': 2.4.1 - '@visactor/vrender-core': 0.17.17 - '@visactor/vutils': 0.17.5 - roughjs: 4.5.2 - - '@visactor/vscale@0.17.5': - dependencies: - '@visactor/vutils': 0.17.5 - - '@visactor/vutils-extension@1.8.11': - dependencies: - '@visactor/vrender-core': 0.17.17 - '@visactor/vrender-kits': 0.17.17 - '@visactor/vscale': 0.17.5 - '@visactor/vutils': 0.17.5 - - '@visactor/vutils@0.17.5': - dependencies: - '@turf/helpers': 6.5.0 - '@turf/invariant': 6.5.0 - eventemitter3: 4.0.7 - - '@vitejs/plugin-react@4.3.4(vite@5.4.16)': - dependencies: - '@babel/core': 7.26.10 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.4.16 - transitivePeerDependencies: - - supports-color - - abs-svg-path@0.1.1: {} - - acorn-jsx@5.3.2(acorn@8.14.1): - dependencies: - acorn: 8.14.1 - - acorn@8.14.1: {} - - array-source@0.0.4: {} - - astring@1.9.0: {} - - async-validator@3.5.2: {} - - asynckit@0.4.0: {} - - attr-accept@2.2.5: {} - - author-regex@1.0.0: {} - - axios@0.27.2: - dependencies: - follow-redirects: 1.15.9 - form-data: 4.0.2 - transitivePeerDependencies: - - debug - - bail@2.0.2: {} - - balanced-match@1.0.2: {} - - bezier-easing@2.1.0: {} - - brace-expansion@1.1.11: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - browserslist@4.24.4: - dependencies: - caniuse-lite: 1.0.30001709 - electron-to-chromium: 1.5.130 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.24.4) - - buffer-from@1.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - callsites@3.1.0: {} - - caniuse-lite@1.0.30001709: {} - - ccount@2.0.1: {} - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - - classnames@2.5.1: {} - - clsx@1.2.1: {} - - collapse-white-space@2.1.0: {} - - 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 - - comma-separated-tokens@2.0.3: {} - - commander@2.20.3: {} - - commander@4.1.1: {} - - compute-scroll-into-view@1.0.20: {} - - concat-map@0.0.1: {} - - concat-stream@1.4.11: - dependencies: - inherits: 2.0.4 - readable-stream: 1.1.14 - typedarray: 0.0.7 - - concat-stream@2.0.0: - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 - typedarray: 0.0.6 - - convert-source-map@2.0.0: {} - - copy-text-to-clipboard@2.2.0: {} - - core-util-is@1.0.3: {} - - cosmiconfig@7.1.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 - - d3-array@1.2.4: {} - - d3-dsv@2.0.0: - dependencies: - commander: 2.20.3 - iconv-lite: 0.4.24 - rw: 1.3.3 - - d3-geo@1.12.1: - dependencies: - d3-array: 1.2.4 - - d3-hexbin@0.2.2: {} - - d3-hierarchy@3.1.2: {} - - date-fns-tz@1.3.8(date-fns@2.30.0): - dependencies: - date-fns: 2.30.0 - - date-fns@2.30.0: - dependencies: - '@babel/runtime': 7.27.0 - - dayjs@1.11.13: {} - - debug@4.4.0: - dependencies: - ms: 2.1.3 - - decode-named-character-reference@1.1.0: - dependencies: - character-entities: 2.0.2 - - delayed-stream@1.0.0: {} - - dequal@2.0.3: {} - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - electron-to-chromium@1.5.130: {} - - error-ex@1.3.2: - dependencies: - is-arrayish: 0.2.1 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.1: - 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.2 - - esast-util-from-estree@2.0.0: - dependencies: - '@types/estree-jsx': 1.0.5 - devlop: 1.1.0 - estree-util-visit: 2.0.0 - unist-util-position-from-estree: 2.0.0 - - esast-util-from-js@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - acorn: 8.14.1 - esast-util-from-estree: 2.0.0 - vfile-message: 4.0.2 - - 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-string-regexp@5.0.0: {} - - estree-util-attach-comments@3.0.0: - dependencies: - '@types/estree': 1.0.7 - - estree-util-build-jsx@3.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - estree-walker: 3.0.3 - - estree-util-is-identifier-name@3.0.0: {} - - estree-util-scope@1.0.0: - dependencies: - '@types/estree': 1.0.7 - devlop: 1.1.0 - - estree-util-to-js@2.0.0: - dependencies: - '@types/estree-jsx': 1.0.5 - astring: 1.9.0 - source-map: 0.7.4 - - estree-util-visit@2.0.0: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/unist': 3.0.3 - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.7 - - eventemitter3@4.0.7: {} - - exenv@1.2.2: {} - - extend@3.0.2: {} - - fast-copy@3.0.2: {} - - file-selector@2.1.2: - dependencies: - tslib: 2.8.1 - - file-source@0.6.1: - dependencies: - stream-source: 0.3.5 - - follow-redirects@1.15.9: {} - - form-data@4.0.2: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - mime-types: 2.1.35 - - fs-extra@10.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - - fs-extra@4.0.3: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - - fs.realpath@1.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - gensync@1.0.0-beta.2: {} - - geobuf@3.0.2: - dependencies: - concat-stream: 2.0.0 - pbf: 3.3.0 - shapefile: 0.6.6 - - geojson-dissolve@3.1.0: - dependencies: - '@turf/meta': 3.14.0 - geojson-flatten: 0.2.4 - geojson-linestring-dissolve: 0.0.1 - topojson-client: 3.1.0 - topojson-server: 3.0.1 - - geojson-flatten@0.2.4: - dependencies: - get-stdin: 6.0.0 - minimist: 1.2.0 - - geojson-linestring-dissolve@0.0.1: {} - - 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.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-stdin@6.0.0: {} - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - globals@11.12.0: {} - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - hast-util-to-estree@3.1.3: - dependencies: - '@types/estree': 1.0.7 - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - estree-util-attach-comments: 3.0.0 - estree-util-is-identifier-name: 3.0.0 - hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.1 - mdast-util-mdx-jsx: 3.2.0 - mdast-util-mdxjs-esm: 2.0.1 - property-information: 7.0.0 - space-separated-tokens: 2.0.2 - style-to-js: 1.1.16 - unist-util-position: 5.0.0 - zwitch: 2.0.4 - transitivePeerDependencies: - - supports-color - - hast-util-to-jsx-runtime@2.3.6: - dependencies: - '@types/estree': 1.0.7 - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.1 - mdast-util-mdx-jsx: 3.2.0 - mdast-util-mdxjs-esm: 2.0.1 - property-information: 7.0.0 - space-separated-tokens: 2.0.2 - style-to-js: 1.1.16 - unist-util-position: 5.0.0 - vfile-message: 4.0.2 - transitivePeerDependencies: - - supports-color - - hast-util-whitespace@3.0.0: - dependencies: - '@types/hast': 3.0.4 - - history@5.3.0: - dependencies: - '@babel/runtime': 7.27.0 - - html-parse-stringify@3.0.1: - dependencies: - void-elements: 3.1.0 - - i18next-browser-languagedetector@7.2.2: - dependencies: - '@babel/runtime': 7.27.0 - - i18next@23.16.8: - dependencies: - '@babel/runtime': 7.27.0 - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - - ieee754@1.2.1: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - inline-style-parser@0.2.4: {} - - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - - is-arrayish@0.2.1: {} - - is-decimal@2.0.1: {} - - is-hexadecimal@2.0.1: {} - - is-plain-obj@4.1.0: {} - - isarray@0.0.1: {} - - jquery@3.7.1: {} - - js-tokens@4.0.0: {} - - jsesc@3.1.0: {} - - json-parse-even-better-errors@2.3.1: {} - - json5@2.2.3: {} - - jsonc-parser@3.3.1: {} - - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 - - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - keyboard-key@1.1.0: {} - - lines-and-columns@1.2.4: {} - - lodash-es@4.17.21: {} - - lodash@4.17.21: {} - - longest-streak@3.1.0: {} - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lottie-web@5.12.2: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - markdown-extensions@2.0.0: {} - - markdown-table@3.0.4: {} - - marked@4.3.0: {} - - math-intrinsics@1.1.0: {} - - mdast-util-find-and-replace@3.0.2: - dependencies: - '@types/mdast': 4.0.4 - escape-string-regexp: 5.0.0 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 - - mdast-util-from-markdown@2.0.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.1.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.2 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-autolink-literal@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.2 - micromark-util-character: 2.1.1 - - mdast-util-gfm-footnote@2.1.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - micromark-util-normalize-identifier: 2.0.1 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-strikethrough@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-table@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-task-list-item@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm@3.1.0: - dependencies: - mdast-util-from-markdown: 2.0.2 - mdast-util-gfm-autolink-literal: 2.0.1 - mdast-util-gfm-footnote: 2.1.0 - mdast-util-gfm-strikethrough: 2.0.0 - mdast-util-gfm-table: 2.0.0 - mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-expression@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-jsx@3.2.0: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - parse-entities: 4.0.2 - stringify-entities: 4.0.4 - unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx@3.0.0: - dependencies: - mdast-util-from-markdown: 2.0.2 - mdast-util-mdx-expression: 2.0.1 - mdast-util-mdx-jsx: 3.2.0 - mdast-util-mdxjs-esm: 2.0.1 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdxjs-esm@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-phrasing@4.1.0: - dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.0 - - mdast-util-to-hast@13.2.0: - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.1 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 - vfile: 6.0.3 - - mdast-util-to-markdown@2.1.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 - zwitch: 2.0.4 - - mdast-util-to-string@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - - memoize-one@5.2.1: {} - - micromark-core-commonmark@2.0.3: - dependencies: - decode-named-character-reference: 1.1.0 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-autolink-literal@2.1.0: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-footnote@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-strikethrough@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-table@2.1.1: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-tagfilter@2.0.0: - dependencies: - micromark-util-types: 2.0.2 - - micromark-extension-gfm-task-list-item@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm@3.0.0: - dependencies: - micromark-extension-gfm-autolink-literal: 2.1.0 - micromark-extension-gfm-footnote: 2.1.0 - micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.1 - micromark-extension-gfm-tagfilter: 2.0.0 - micromark-extension-gfm-task-list-item: 2.1.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-mdx-expression@3.0.1: - dependencies: - '@types/estree': 1.0.7 - devlop: 1.1.0 - micromark-factory-mdx-expression: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-events-to-acorn: 2.0.3 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-mdx-jsx@3.0.2: - dependencies: - '@types/estree': 1.0.7 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - micromark-factory-mdx-expression: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-events-to-acorn: 2.0.3 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - vfile-message: 4.0.2 - - micromark-extension-mdx-md@2.0.0: - dependencies: - micromark-util-types: 2.0.2 - - micromark-extension-mdxjs-esm@3.0.0: - dependencies: - '@types/estree': 1.0.7 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-util-character: 2.1.1 - micromark-util-events-to-acorn: 2.0.3 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-position-from-estree: 2.0.0 - vfile-message: 4.0.2 - - micromark-extension-mdxjs@3.0.0: - dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) - micromark-extension-mdx-expression: 3.0.1 - micromark-extension-mdx-jsx: 3.0.2 - micromark-extension-mdx-md: 2.0.0 - micromark-extension-mdxjs-esm: 3.0.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-destination@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-label@2.0.1: - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-mdx-expression@2.0.3: - dependencies: - '@types/estree': 1.0.7 - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-events-to-acorn: 2.0.3 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-position-from-estree: 2.0.0 - vfile-message: 4.0.2 - - micromark-factory-space@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-types: 2.0.2 - - micromark-factory-title@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-whitespace@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-chunked@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-classify-character@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-combine-extensions@2.0.1: - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-decode-numeric-character-reference@2.0.2: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-decode-string@2.0.1: - dependencies: - decode-named-character-reference: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.1 - - micromark-util-encode@2.0.1: {} - - micromark-util-events-to-acorn@2.0.3: - dependencies: - '@types/estree': 1.0.7 - '@types/unist': 3.0.3 - devlop: 1.1.0 - estree-util-visit: 2.0.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - vfile-message: 4.0.2 - - micromark-util-html-tag-name@2.0.1: {} - - micromark-util-normalize-identifier@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-resolve-all@2.0.1: - dependencies: - micromark-util-types: 2.0.2 - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-subtokenize@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.2: {} - - micromark@4.0.2: - dependencies: - '@types/debug': 4.1.12 - debug: 4.4.0 - decode-named-character-reference: 1.1.0 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-encode: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - transitivePeerDependencies: - - supports-color - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.11 - - minimist@1.2.0: {} - - minimist@1.2.6: {} - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - node-releases@2.0.19: {} - - object-assign@4.1.1: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-author@2.0.0: - dependencies: - author-regex: 1.0.0 - - parse-entities@4.0.2: - dependencies: - '@types/unist': 2.0.11 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.1.0 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.26.2 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - parse-svg-path@0.1.2: {} - - path-browserify@1.0.1: {} - - path-data-parser@0.1.0: {} - - path-is-absolute@1.0.1: {} - - path-source@0.1.3: - dependencies: - array-source: 0.0.4 - file-source: 0.6.1 - - path-type@4.0.0: {} - - pbf@3.3.0: - dependencies: - ieee754: 1.2.1 - resolve-protobuf-schema: 2.1.0 - - picocolors@1.1.1: {} - - point-at-length@1.1.0: - dependencies: - abs-svg-path: 0.1.1 - isarray: 0.0.1 - parse-svg-path: 0.1.2 - - points-on-curve@0.2.0: {} - - points-on-path@0.2.1: - dependencies: - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - - postcss@8.5.3: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prettier-package-json@2.8.0: - dependencies: - '@types/parse-author': 2.0.3 - commander: 4.1.1 - cosmiconfig: 7.1.0 - fs-extra: 10.1.0 - glob: 7.2.3 - minimatch: 3.1.2 - parse-author: 2.0.0 - sort-object-keys: 1.1.3 - sort-order: 1.1.2 - - prettier-plugin-astro@0.14.1: - dependencies: - '@astrojs/compiler': 2.11.0 - prettier: 3.5.3 - sass-formatter: 0.7.9 - - prettier-plugin-curly-and-jsdoc@3.1.0(prettier@3.5.3): - dependencies: - prettier: 3.5.3 - - prettier-plugin-pkgsort@0.2.1(prettier@3.5.3): - dependencies: - prettier: 3.5.3 - prettier-package-json: 2.8.0 - - prettier@3.5.3: {} - - prismjs@1.30.0: {} - - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - property-information@7.0.0: {} - - protocol-buffers-schema@3.6.0: {} - - react-dom@18.3.1(react@18.3.1): - dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 - - react-draggable@4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - clsx: 1.2.1 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - react-dropzone@14.3.8(react@18.3.1): - dependencies: - attr-accept: 2.2.5 - file-selector: 2.1.2 - prop-types: 15.8.1 - react: 18.3.1 - - react-fast-compare@3.2.2: {} - - react-fireworks@1.0.4: {} - - react-i18next@13.5.0(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.27.0 - html-parse-stringify: 3.0.1 - i18next: 23.16.8 - react: 18.3.1 - optionalDependencies: - react-dom: 18.3.1(react@18.3.1) - - react-is@16.13.1: {} - - react-is@18.3.1: {} - - react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@popperjs/core': 2.11.8 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-fast-compare: 3.2.2 - warning: 4.0.3 - - react-refresh@0.14.2: {} - - react-resizable@3.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - prop-types: 15.8.1 - react: 18.3.1 - react-draggable: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - transitivePeerDependencies: - - react-dom - - react-router-dom@6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@remix-run/router': 1.23.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-router: 6.30.0(react@18.3.1) - - react-router@6.30.0(react@18.3.1): - dependencies: - '@remix-run/router': 1.23.0 - react: 18.3.1 - - react-telegram-login@1.1.2(react@18.3.1): - dependencies: - react: 18.3.1 - - react-toastify@9.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - clsx: 1.2.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - react-turnstile@1.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - react-window@1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.27.0 - memoize-one: 5.2.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - react@18.3.1: - dependencies: - loose-envify: 1.4.0 - - readable-stream@1.1.14: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 0.0.1 - string_decoder: 0.10.31 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - recma-build-jsx@1.0.0: - dependencies: - '@types/estree': 1.0.7 - estree-util-build-jsx: 3.0.1 - vfile: 6.0.3 - - recma-jsx@1.0.0(acorn@8.14.1): - dependencies: - acorn-jsx: 5.3.2(acorn@8.14.1) - estree-util-to-js: 2.0.0 - recma-parse: 1.0.0 - recma-stringify: 1.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - acorn - - recma-parse@1.0.0: - dependencies: - '@types/estree': 1.0.7 - esast-util-from-js: 2.0.1 - unified: 11.0.5 - vfile: 6.0.3 - - recma-stringify@1.0.0: - dependencies: - '@types/estree': 1.0.7 - estree-util-to-js: 2.0.0 - unified: 11.0.5 - vfile: 6.0.3 - - regenerator-runtime@0.14.1: {} - - rehype-recma@1.0.0: - dependencies: - '@types/estree': 1.0.7 - '@types/hast': 3.0.4 - hast-util-to-estree: 3.1.3 - transitivePeerDependencies: - - supports-color - - remark-gfm@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-gfm: 3.1.0 - micromark-extension-gfm: 3.0.0 - remark-parse: 11.0.0 - remark-stringify: 11.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-mdx@3.1.0: - dependencies: - mdast-util-mdx: 3.0.0 - micromark-extension-mdxjs: 3.0.0 - transitivePeerDependencies: - - supports-color - - remark-parse@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 - micromark-util-types: 2.0.2 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-rehype@11.1.2: - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - mdast-util-to-hast: 13.2.0 - unified: 11.0.5 - vfile: 6.0.3 - - remark-stringify@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.1.2 - unified: 11.0.5 - - resolve-from@4.0.0: {} - - resolve-protobuf-schema@2.1.0: - dependencies: - protocol-buffers-schema: 3.6.0 - - rollup@4.39.0: - dependencies: - '@types/estree': 1.0.7 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.39.0 - '@rollup/rollup-android-arm64': 4.39.0 - '@rollup/rollup-darwin-arm64': 4.39.0 - '@rollup/rollup-darwin-x64': 4.39.0 - '@rollup/rollup-freebsd-arm64': 4.39.0 - '@rollup/rollup-freebsd-x64': 4.39.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 - '@rollup/rollup-linux-arm-musleabihf': 4.39.0 - '@rollup/rollup-linux-arm64-gnu': 4.39.0 - '@rollup/rollup-linux-arm64-musl': 4.39.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 - '@rollup/rollup-linux-riscv64-gnu': 4.39.0 - '@rollup/rollup-linux-riscv64-musl': 4.39.0 - '@rollup/rollup-linux-s390x-gnu': 4.39.0 - '@rollup/rollup-linux-x64-gnu': 4.39.0 - '@rollup/rollup-linux-x64-musl': 4.39.0 - '@rollup/rollup-win32-arm64-msvc': 4.39.0 - '@rollup/rollup-win32-ia32-msvc': 4.39.0 - '@rollup/rollup-win32-x64-msvc': 4.39.0 - fsevents: 2.3.3 - - roughjs@4.5.2: - dependencies: - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - points-on-path: 0.2.1 - - rw@1.3.3: {} - - s.color@0.0.15: {} - - safe-buffer@5.2.1: {} - - safer-buffer@2.1.2: {} - - sass-formatter@0.7.9: - dependencies: - suf-log: 2.5.3 - - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 - - scroll-into-view-if-needed@2.2.31: - dependencies: - compute-scroll-into-view: 1.0.20 - - semantic-ui-offline@2.5.0: - dependencies: - fs-extra: 4.0.3 - jquery: 3.7.1 - - semantic-ui-react@2.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.27.0 - '@fluentui/react-component-event-listener': 0.63.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@fluentui/react-component-ref': 0.63.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@popperjs/core': 2.11.8 - '@semantic-ui-react/event-stack': 3.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - clsx: 1.2.1 - keyboard-key: 1.1.0 - lodash: 4.17.21 - lodash-es: 4.17.21 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 18.3.1 - react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - shallowequal: 1.1.0 - - semver@6.3.1: {} - - shallowequal@1.1.0: {} - - shapefile@0.6.6: - dependencies: - array-source: 0.0.4 - commander: 2.20.3 - path-source: 0.1.3 - slice-source: 0.4.1 - stream-source: 0.3.5 - text-encoding: 0.6.4 - - simple-statistics@7.8.8: {} - - simplify-geojson@1.0.5: - dependencies: - concat-stream: 1.4.11 - minimist: 1.2.6 - simplify-geometry: 0.0.2 - - simplify-geometry@0.0.2: {} - - slice-source@0.4.1: {} - - sort-object-keys@1.1.3: {} - - sort-order@1.1.2: {} - - source-map-js@1.2.1: {} - - source-map@0.7.4: {} - - space-separated-tokens@2.0.2: {} - - sse.js@https://codeload.github.com/mpetazzoni/sse.js/tar.gz/39b9b82aae95fd58d9d08b487845fe230f4b14e6: - {} - - stream-source@0.3.5: {} - - string_decoder@0.10.31: {} - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - style-to-js@1.1.16: - dependencies: - style-to-object: 1.0.8 - - style-to-object@1.0.8: - dependencies: - inline-style-parser: 0.2.4 - - suf-log@2.5.3: - dependencies: - s.color: 0.0.15 - - text-encoding@0.6.4: {} - - topojson-client@3.1.0: - dependencies: - commander: 2.20.3 - - topojson-server@3.0.1: - dependencies: - commander: 2.20.3 - - trim-lines@3.0.1: {} - - trough@2.2.0: {} - - tslib@2.8.1: {} - - typedarray@0.0.6: {} - - typedarray@0.0.7: {} - - typescript@4.4.2: {} - - unified@11.0.5: - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - - unist-util-is@6.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position-from-estree@2.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.0.1: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - - unist-util-visit@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 - - universalify@0.1.2: {} - - universalify@2.0.1: {} - - update-browserslist-db@1.1.3(browserslist@4.24.4): - dependencies: - browserslist: 4.24.4 - escalade: 3.2.0 - picocolors: 1.1.1 - - util-deprecate@1.0.2: {} - - utility-types@3.11.0: {} - - vfile-message@4.0.2: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - - vfile@6.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.2 - - vite@5.4.16: - dependencies: - esbuild: 0.21.5 - postcss: 8.5.3 - rollup: 4.39.0 - optionalDependencies: - fsevents: 2.3.3 - - void-elements@3.1.0: {} - - warning@4.0.3: - dependencies: - loose-envify: 1.4.0 - - wrappy@1.0.2: {} - - yallist@3.1.1: {} - - yaml@1.10.2: {} - - zwitch@2.0.4: {} From 3123d4bb9b8d858a04a67cad38ef1efb0419e117 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Wed, 11 Jun 2025 15:07:01 +0800 Subject: [PATCH 03/61] =?UTF-8?q?=F0=9F=8E=A8=20style(dashboard):=20Optimi?= =?UTF-8?q?ze=20the=20layout=20of=20the=20Uptime=20card=20legend=20on=20th?= =?UTF-8?q?e=20dashboard=20to=20resolve=20the=20issue=20where=20the=20last?= =?UTF-8?q?=20monitoring=20item=20is=20obscured?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/Detail/index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index 129d82ea..e4351088 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -1279,9 +1279,8 @@ const Detail = (props) => { } footer={uptimeData.length > 0 ? (
{uptimeLegendData.map((legend, index) => ( @@ -1296,13 +1295,13 @@ const Detail = (props) => {
) : null} - footerStyle={uptimeData.length > 0 ? { marginTop: '-100px' } : undefined} + footerStyle={uptimeData.length > 0 ? { padding: '0px' } : undefined} >
handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)} > {uptimeData.length > 0 ? ( From 856465ae59a8c1b19e39db211e53036b3eddc9b0 Mon Sep 17 00:00:00 2001 From: a37836323 <37836323@qq.com> Date: Wed, 11 Jun 2025 22:11:47 +0800 Subject: [PATCH 04/61] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAzure=E6=B8=A0=E9=81=93?= =?UTF-8?q?=E5=AF=B9responses=20API=E7=9A=84=E5=85=BC=E5=AE=B9=E6=80=A7?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20-=20=E4=B8=BAAzure=E6=B8=A0=E9=81=93?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9responses=20API=E7=9A=84=E7=89=B9?= =?UTF-8?q?=E6=AE=8A=E5=A4=84=E7=90=86=20-=20=E5=85=BC=E5=AE=B9=E5=BE=AE?= =?UTF-8?q?=E8=BD=AF=E6=96=B0=E7=9A=84API=E6=A0=BC=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8preview=E7=89=88=E6=9C=AC=E7=9A=84api-version?= =?UTF-8?q?=20-=20=E4=BF=AE=E5=A4=8D=E4=BA=86Azure=E6=B8=A0=E9=81=93?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E6=AD=A3=E7=A1=AE=E5=A4=84=E7=90=86responses?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/channel/openai/adaptor.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/relay/channel/openai/adaptor.go b/relay/channel/openai/adaptor.go index f0cf073f..8358f3e2 100644 --- a/relay/channel/openai/adaptor.go +++ b/relay/channel/openai/adaptor.go @@ -88,6 +88,13 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { requestURL := strings.Split(info.RequestURLPath, "?")[0] requestURL = fmt.Sprintf("%s?api-version=%s", requestURL, apiVersion) task := strings.TrimPrefix(requestURL, "/v1/") + + // 特殊处理 responses API + if info.RelayMode == constant.RelayModeResponses { + requestURL = fmt.Sprintf("/openai/v1/responses?api-version=preview") + return relaycommon.GetFullRequestURL(info.BaseUrl, requestURL, info.ChannelType), nil + } + model_ := info.UpstreamModelName // 2025年5月10日后创建的渠道不移除. if info.ChannelCreateTime < constant2.AzureNoRemoveDotTime { From a28ab3628a9faa721ee0f1d056c8a355efd34111 Mon Sep 17 00:00:00 2001 From: creamlike1024 Date: Wed, 11 Jun 2025 23:46:59 +0800 Subject: [PATCH 05/61] =?UTF-8?q?feat:=20=E5=88=86=E7=BB=84=E7=89=B9?= =?UTF-8?q?=E6=AE=8A=E5=80=8D=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/channel-test.go | 2 +- controller/pricing.go | 9 +- model/option.go | 3 + relay/common/relay_info.go | 2 + relay/helper/price.go | 9 +- relay/relay-text.go | 3 +- service/log_info_generate.go | 15 +- service/quota.go | 27 ++- setting/group_ratio.go | 41 +++- .../components/settings/OperationSetting.js | 2 + web/src/components/table/LogsTable.js | 146 ++++++++----- web/src/helpers/render.js | 196 +++++++++++------- .../Setting/Operation/GroupRatioSettings.js | 22 ++ 13 files changed, 323 insertions(+), 154 deletions(-) diff --git a/controller/channel-test.go b/controller/channel-test.go index 970c1768..e51670cb 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -166,7 +166,7 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr milliseconds := tok.Sub(tik).Milliseconds() consumedTime := float64(milliseconds) / 1000.0 other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatio, priceData.CompletionRatio, - usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice) + usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice, priceData.UserGroupRatio) model.RecordConsumeLog(c, 1, channel.Id, usage.PromptTokens, usage.CompletionTokens, info.OriginModelName, "模型测试", quota, "模型测试", 0, quota, int(consumedTime), false, info.Group, other) common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody))) diff --git a/controller/pricing.go b/controller/pricing.go index 1cbfe731..e6a3e57f 100644 --- a/controller/pricing.go +++ b/controller/pricing.go @@ -1,10 +1,11 @@ package controller import ( - "github.com/gin-gonic/gin" "one-api/model" "one-api/setting" "one-api/setting/operation_setting" + + "github.com/gin-gonic/gin" ) func GetPricing(c *gin.Context) { @@ -20,6 +21,12 @@ func GetPricing(c *gin.Context) { user, err := model.GetUserCache(userId.(int)) if err == nil { group = user.Group + for g := range groupRatio { + ratio, ok := setting.GetGroupGroupRatio(group, g) + if ok { + groupRatio[g] = ratio + } + } } } diff --git a/model/option.go b/model/option.go index 7bab819b..c6f7ac6b 100644 --- a/model/option.go +++ b/model/option.go @@ -98,6 +98,7 @@ func InitOptionMap() { common.OptionMap["ModelPrice"] = operation_setting.ModelPrice2JSONString() common.OptionMap["CacheRatio"] = operation_setting.CacheRatio2JSONString() common.OptionMap["GroupRatio"] = setting.GroupRatio2JSONString() + common.OptionMap["GroupGroupRatio"] = setting.GroupGroupRatio2JSONString() common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString() common.OptionMap["CompletionRatio"] = operation_setting.CompletionRatio2JSONString() common.OptionMap["TopUpLink"] = common.TopUpLink @@ -355,6 +356,8 @@ func updateOptionMap(key string, value string) (err error) { err = operation_setting.UpdateModelRatioByJSONString(value) case "GroupRatio": err = setting.UpdateGroupRatioByJSONString(value) + case "GroupGroupRatio": + err = setting.UpdateGroupGroupRatioByJSONString(value) case "UserUsableGroups": err = setting.UpdateUserUsableGroupsByJSONString(value) case "CompletionRatio": diff --git a/relay/common/relay_info.go b/relay/common/relay_info.go index f4fc3c1e..a842a58d 100644 --- a/relay/common/relay_info.go +++ b/relay/common/relay_info.go @@ -61,6 +61,7 @@ type RelayInfo struct { TokenKey string UserId int Group string + UserGroup string TokenUnlimited bool StartTime time.Time FirstResponseTime time.Time @@ -204,6 +205,7 @@ func GenRelayInfo(c *gin.Context) *RelayInfo { TokenKey: tokenKey, UserId: userId, Group: group, + UserGroup: c.GetString(constant.ContextKeyUserGroup), TokenUnlimited: tokenUnlimited, StartTime: startTime, FirstResponseTime: startTime.Add(-time.Second), diff --git a/relay/helper/price.go b/relay/helper/price.go index 89efa1da..1b52bf37 100644 --- a/relay/helper/price.go +++ b/relay/helper/price.go @@ -2,12 +2,13 @@ package helper import ( "fmt" - "github.com/gin-gonic/gin" "one-api/common" constant2 "one-api/constant" relaycommon "one-api/relay/common" "one-api/setting" "one-api/setting/operation_setting" + + "github.com/gin-gonic/gin" ) type PriceData struct { @@ -18,6 +19,7 @@ type PriceData struct { CacheCreationRatio float64 ImageRatio float64 GroupRatio float64 + UserGroupRatio float64 UsePrice bool ShouldPreConsumedQuota int } @@ -29,6 +31,10 @@ func (p PriceData) ToSetting() string { func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens int, maxTokens int) (PriceData, error) { modelPrice, usePrice := operation_setting.GetModelPrice(info.OriginModelName, false) groupRatio := setting.GetGroupRatio(info.Group) + userGroupRatio, ok := setting.GetGroupGroupRatio(info.UserGroup, info.Group) + if ok { + groupRatio = userGroupRatio + } var preConsumedQuota int var modelRatio float64 var completionRatio float64 @@ -69,6 +75,7 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens ModelRatio: modelRatio, CompletionRatio: completionRatio, GroupRatio: groupRatio, + UserGroupRatio: userGroupRatio, UsePrice: usePrice, CacheRatio: cacheRatio, ImageRatio: imageRatio, diff --git a/relay/relay-text.go b/relay/relay-text.go index a48a664a..3aa382e8 100644 --- a/relay/relay-text.go +++ b/relay/relay-text.go @@ -363,6 +363,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio := priceData.ModelRatio groupRatio := priceData.GroupRatio modelPrice := priceData.ModelPrice + userGroupRatio := priceData.UserGroupRatio // Convert values to decimal for precise calculation dPromptTokens := decimal.NewFromInt(int64(promptTokens)) @@ -510,7 +511,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, if extraContent != "" { logContent += ", " + extraContent } - other := service.GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice) + other := service.GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio) if imageTokens != 0 { other["image"] = true other["image_ratio"] = imageRatio diff --git a/service/log_info_generate.go b/service/log_info_generate.go index 75457b97..1edc9073 100644 --- a/service/log_info_generate.go +++ b/service/log_info_generate.go @@ -8,7 +8,7 @@ import ( ) func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64, - cacheTokens int, cacheRatio float64, modelPrice float64) map[string]interface{} { + cacheTokens int, cacheRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} { other := make(map[string]interface{}) other["model_ratio"] = modelRatio other["group_ratio"] = groupRatio @@ -16,6 +16,7 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m other["cache_tokens"] = cacheTokens other["cache_ratio"] = cacheRatio other["model_price"] = modelPrice + other["user_group_ratio"] = userGroupRatio other["frt"] = float64(relayInfo.FirstResponseTime.UnixMilli() - relayInfo.StartTime.UnixMilli()) if relayInfo.ReasoningEffort != "" { other["reasoning_effort"] = relayInfo.ReasoningEffort @@ -30,8 +31,8 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m return other } -func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice float64) map[string]interface{} { - info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice) +func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} { + info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio) info["ws"] = true info["audio_input"] = usage.InputTokenDetails.AudioTokens info["audio_output"] = usage.OutputTokenDetails.AudioTokens @@ -42,8 +43,8 @@ func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, us return info } -func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice float64) map[string]interface{} { - info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice) +func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} { + info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio) info["audio"] = true info["audio_input"] = usage.PromptTokensDetails.AudioTokens info["audio_output"] = usage.CompletionTokenDetails.AudioTokens @@ -55,8 +56,8 @@ func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, } func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64, - cacheTokens int, cacheRatio float64, cacheCreationTokens int, cacheCreationRatio float64, modelPrice float64) map[string]interface{} { - info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice) + cacheTokens int, cacheRatio float64, cacheCreationTokens int, cacheCreationRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} { + info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio) info["claude"] = true info["cache_creation_tokens"] = cacheCreationTokens info["cache_creation_ratio"] = cacheCreationRatio diff --git a/service/quota.go b/service/quota.go index 0d11b4a0..da3dd9b9 100644 --- a/service/quota.go +++ b/service/quota.go @@ -94,6 +94,10 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag audioInputTokens := usage.InputTokenDetails.AudioTokens audioOutTokens := usage.OutputTokenDetails.AudioTokens groupRatio := setting.GetGroupRatio(relayInfo.Group) + userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) + if ok { + groupRatio = userGroupRatio + } modelRatio, _ := operation_setting.GetModelRatio(modelName) quotaInfo := QuotaInfo{ @@ -145,6 +149,11 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod audioRatio := decimal.NewFromFloat(operation_setting.GetAudioRatio(relayInfo.OriginModelName)) audioCompletionRatio := decimal.NewFromFloat(operation_setting.GetAudioCompletionRatio(modelName)) + actualGroupRatio := groupRatio + userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) + if ok { + actualGroupRatio = userGroupRatio + } quotaInfo := QuotaInfo{ InputDetails: TokenDetails{ TextTokens: textInputTokens, @@ -157,7 +166,7 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod ModelName: modelName, UsePrice: usePrice, ModelRatio: modelRatio, - GroupRatio: groupRatio, + GroupRatio: actualGroupRatio, } quota := calculateAudioQuota(quotaInfo) @@ -189,7 +198,7 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod logContent += ", " + extraContent } other := GenerateWssOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio, - completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice) + completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, userGroupRatio) model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.InputTokens, usage.OutputTokens, logModel, tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other) } @@ -207,7 +216,7 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio := priceData.ModelRatio groupRatio := priceData.GroupRatio modelPrice := priceData.ModelPrice - + userGroupRatio := priceData.UserGroupRatio cacheRatio := priceData.CacheRatio cacheTokens := usage.PromptTokensDetails.CachedTokens @@ -256,7 +265,7 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, } other := GenerateClaudeOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, - cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice) + cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice, userGroupRatio) model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, modelName, tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other) } @@ -281,6 +290,12 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelPrice := priceData.ModelPrice usePrice := priceData.UsePrice + actualGroupRatio := groupRatio + userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) + if ok { + actualGroupRatio = userGroupRatio + } + quotaInfo := QuotaInfo{ InputDetails: TokenDetails{ TextTokens: textInputTokens, @@ -293,7 +308,7 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, ModelName: relayInfo.OriginModelName, UsePrice: usePrice, ModelRatio: modelRatio, - GroupRatio: groupRatio, + GroupRatio: actualGroupRatio, } quota := calculateAudioQuota(quotaInfo) @@ -333,7 +348,7 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, logContent += ", " + extraContent } other := GenerateAudioOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio, - completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice) + completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, userGroupRatio) model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.PromptTokens, usage.CompletionTokens, logModel, tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other) } diff --git a/setting/group_ratio.go b/setting/group_ratio.go index 8b163625..1fe523e4 100644 --- a/setting/group_ratio.go +++ b/setting/group_ratio.go @@ -14,10 +14,16 @@ var groupRatio = map[string]float64{ } var groupRatioMutex sync.RWMutex +var GroupGroupRatio = map[string]map[string]float64{ + "vip": { + "edit_this": 0.9, + }, +} + func GetGroupRatioCopy() map[string]float64 { groupRatioMutex.RLock() defer groupRatioMutex.RUnlock() - + groupRatioCopy := make(map[string]float64) for k, v := range groupRatio { groupRatioCopy[k] = v @@ -28,7 +34,7 @@ func GetGroupRatioCopy() map[string]float64 { func ContainsGroupRatio(name string) bool { groupRatioMutex.RLock() defer groupRatioMutex.RUnlock() - + _, ok := groupRatio[name] return ok } @@ -36,7 +42,7 @@ func ContainsGroupRatio(name string) bool { func GroupRatio2JSONString() string { groupRatioMutex.RLock() defer groupRatioMutex.RUnlock() - + jsonBytes, err := json.Marshal(groupRatio) if err != nil { common.SysError("error marshalling model ratio: " + err.Error()) @@ -47,7 +53,7 @@ func GroupRatio2JSONString() string { func UpdateGroupRatioByJSONString(jsonStr string) error { groupRatioMutex.Lock() defer groupRatioMutex.Unlock() - + groupRatio = make(map[string]float64) return json.Unmarshal([]byte(jsonStr), &groupRatio) } @@ -55,7 +61,7 @@ func UpdateGroupRatioByJSONString(jsonStr string) error { func GetGroupRatio(name string) float64 { groupRatioMutex.RLock() defer groupRatioMutex.RUnlock() - + ratio, ok := groupRatio[name] if !ok { common.SysError("group ratio not found: " + name) @@ -64,6 +70,31 @@ func GetGroupRatio(name string) float64 { return ratio } +func GetGroupGroupRatio(group, name string) (float64, bool) { + gp, ok := GroupGroupRatio[group] + if !ok { + return -1, false + } + ratio, ok := gp[name] + if !ok { + return -1, false + } + return ratio, true +} + +func GroupGroupRatio2JSONString() string { + jsonBytes, err := json.Marshal(GroupGroupRatio) + if err != nil { + common.SysError("error marshalling group-group ratio: " + err.Error()) + } + return string(jsonBytes) +} + +func UpdateGroupGroupRatioByJSONString(jsonStr string) error { + GroupGroupRatio = make(map[string]map[string]float64) + return json.Unmarshal([]byte(jsonStr), &GroupGroupRatio) +} + func CheckGroupRatio(jsonStr string) error { checkGroupRatio := make(map[string]float64) err := json.Unmarshal([]byte(jsonStr), &checkGroupRatio) diff --git a/web/src/components/settings/OperationSetting.js b/web/src/components/settings/OperationSetting.js index 2dc0b88e..55e328a3 100644 --- a/web/src/components/settings/OperationSetting.js +++ b/web/src/components/settings/OperationSetting.js @@ -30,6 +30,7 @@ const OperationSetting = () => { CompletionRatio: '', ModelPrice: '', GroupRatio: '', + GroupGroupRatio: '', UserUsableGroups: '', TopUpLink: '', 'general_setting.docs_link': '', @@ -74,6 +75,7 @@ const OperationSetting = () => { if ( item.key === 'ModelRatio' || item.key === 'GroupRatio' || + item.key === 'GroupGroupRatio' || item.key === 'UserUsableGroups' || item.key === 'CompletionRatio' || item.key === 'ModelPrice' || diff --git a/web/src/components/table/LogsTable.js b/web/src/components/table/LogsTable.js index 6c8996a0..e69377db 100644 --- a/web/src/components/table/LogsTable.js +++ b/web/src/components/table/LogsTable.js @@ -20,7 +20,7 @@ import { renderQuota, stringToColor, getLogOther, - renderModelTag + renderModelTag, } from '../../helpers'; import { @@ -39,11 +39,11 @@ import { Card, Typography, Divider, - Form + Form, } from '@douyinfe/semi-ui'; import { IllustrationNoResult, - IllustrationNoResultDark + IllustrationNoResultDark, } from '@douyinfe/semi-illustrations'; import { ITEMS_PER_PAGE } from '../../constants'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; @@ -192,7 +192,7 @@ const LogsTable = () => { if (!modelMapped) { return renderModelTag(record.model_name, { onClick: (event) => { - copyText(event, record.model_name).then((r) => { }); + copyText(event, record.model_name).then((r) => {}); }, }); } else { @@ -209,7 +209,7 @@ const LogsTable = () => { {renderModelTag(record.model_name, { onClick: (event) => { - copyText(event, record.model_name).then((r) => { }); + copyText(event, record.model_name).then((r) => {}); }, })}
@@ -220,7 +220,7 @@ const LogsTable = () => { {renderModelTag(other.upstream_model_name, { onClick: (event) => { copyText(event, other.upstream_model_name).then( - (r) => { }, + (r) => {}, ); }, })} @@ -231,7 +231,7 @@ const LogsTable = () => { > {renderModelTag(record.model_name, { onClick: (event) => { - copyText(event, record.model_name).then((r) => { }); + copyText(event, record.model_name).then((r) => {}); }, suffixIcon: ( { } let content = other?.claude ? renderClaudeModelPriceSimple( - other.model_ratio, - other.model_price, - other.group_ratio, - other.cache_tokens || 0, - other.cache_ratio || 1.0, - other.cache_creation_tokens || 0, - other.cache_creation_ratio || 1.0, - ) + other.model_ratio, + other.model_price, + other.group_ratio, + other?.user_group_ratio, + other.cache_tokens || 0, + other.cache_ratio || 1.0, + other.cache_creation_tokens || 0, + other.cache_creation_ratio || 1.0, + ) : renderModelPriceSimple( - other.model_ratio, - other.model_price, - other.group_ratio, - other.cache_tokens || 0, - other.cache_ratio || 1.0, - ); + other.model_ratio, + other.model_price, + other.group_ratio, + other?.user_group_ratio, + other.cache_tokens || 0, + other.cache_ratio || 1.0, + ); return ( { group: '', dateRange: [ timestamp2string(getTodayStartTimestamp()), - timestamp2string(now.getTime() / 1000 + 3600) + timestamp2string(now.getTime() / 1000 + 3600), ], logType: '0', }; @@ -763,7 +765,11 @@ const LogsTable = () => { let start_timestamp = timestamp2string(getTodayStartTimestamp()); let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600); - if (formValues.dateRange && Array.isArray(formValues.dateRange) && formValues.dateRange.length === 2) { + if ( + formValues.dateRange && + Array.isArray(formValues.dateRange) && + formValues.dateRange.length === 2 + ) { start_timestamp = formValues.dateRange[0]; end_timestamp = formValues.dateRange[1]; } @@ -941,27 +947,28 @@ const LogsTable = () => { key: t('日志详情'), value: other?.claude ? renderClaudeLogContent( - other?.model_ratio, - other.completion_ratio, - other.model_price, - other.group_ratio, - other.cache_ratio || 1.0, - other.cache_creation_ratio || 1.0, - ) + other?.model_ratio, + other.completion_ratio, + other.model_price, + other.group_ratio, + other?.user_group_ratio, + other.cache_ratio || 1.0, + other.cache_creation_ratio || 1.0, + ) : renderLogContent( - other?.model_ratio, - other.completion_ratio, - other.model_price, - other.group_ratio, - other?.user_group_ratio, - false, - 1.0, - undefined, - other.web_search || false, - other.web_search_call_count || 0, - other.file_search || false, - other.file_search_call_count || 0, - ), + other?.model_ratio, + other.completion_ratio, + other.model_price, + other.group_ratio, + other?.user_group_ratio, + false, + 1.0, + undefined, + other.web_search || false, + other.web_search_call_count || 0, + other.file_search || false, + other.file_search_call_count || 0, + ), }); } if (logs[i].type === 2) { @@ -992,6 +999,7 @@ const LogsTable = () => { other?.audio_ratio, other?.audio_completion_ratio, other?.group_ratio, + other?.user_group_ratio, other?.cache_tokens || 0, other?.cache_ratio || 1.0, ); @@ -1003,6 +1011,7 @@ const LogsTable = () => { other.model_price, other.completion_ratio, other.group_ratio, + other?.user_group_ratio, other.cache_tokens || 0, other.cache_ratio || 1.0, other.cache_creation_tokens || 0, @@ -1016,6 +1025,7 @@ const LogsTable = () => { other?.model_price, other?.completion_ratio, other?.group_ratio, + other?.user_group_ratio, other?.cache_tokens || 0, other?.cache_ratio || 1.0, other?.image || false, @@ -1066,7 +1076,12 @@ const LogsTable = () => { } = getFormValues(); // 使用传入的 logType 或者表单中的 logType 或者状态中的 logType - const currentLogType = customLogType !== null ? customLogType : formLogType !== undefined ? formLogType : logType; + const currentLogType = + customLogType !== null + ? customLogType + : formLogType !== undefined + ? formLogType + : logType; let localStartTimestamp = Date.parse(start_timestamp) / 1000; let localEndTimestamp = Date.parse(end_timestamp) / 1000; @@ -1093,7 +1108,7 @@ const LogsTable = () => { const handlePageChange = (page) => { setActivePage(page); - loadLogs(page, pageSize).then((r) => { }); // 不传入logType,让其从表单获取最新值 + loadLogs(page, pageSize).then((r) => {}); // 不传入logType,让其从表单获取最新值 }; const handlePageSizeChange = async (size) => { @@ -1208,9 +1223,9 @@ const LogsTable = () => { getFormApi={(api) => setFormApi(api)} onSubmit={refresh} allowEmpty={true} - autoComplete="off" - layout="vertical" - trigger="change" + autoComplete='off' + layout='vertical' + trigger='change' stopValidateWithError={false} >
@@ -1294,12 +1309,24 @@ const LogsTable = () => { }, 0); }} > - {t('全部')} - {t('充值')} - {t('消费')} - {t('管理')} - {t('系统')} - {t('错误')} + + {t('全部')} + + + {t('充值')} + + + {t('消费')} + + + {t('管理')} + + + {t('系统')} + + + {t('错误')} +
@@ -1351,7 +1378,8 @@ const LogsTable = () => { {...(hasExpandableRows() && { expandedRowRender: expandRowRender, expandRowByClick: true, - rowExpandable: (record) => expandData[record.key] && expandData[record.key].length > 0 + rowExpandable: (record) => + expandData[record.key] && expandData[record.key].length > 0, })} dataSource={logs} rowKey='key' @@ -1361,8 +1389,12 @@ const LogsTable = () => { size='middle' empty={ } - darkModeImage={} + image={ + + } + darkModeImage={ + + } description={t('搜索无结果')} style={{ padding: 30 }} /> diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 3d1bca0d..d2ad9be4 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -30,7 +30,7 @@ import { Dify, Coze, SiliconCloud, - FastGPT + FastGPT, } from '@lobehub/icons'; import { @@ -46,7 +46,7 @@ import { Gift, User, Settings, - CircleUser + CircleUser, } from 'lucide-react'; // 侧边栏图标颜色映射 @@ -315,7 +315,6 @@ export const getModelCategories = (() => { }; })(); - /** * 根据渠道类型返回对应的厂商图标 * @param {number} channelType - 渠道类型值 @@ -868,6 +867,10 @@ export function renderQuota(quota, digits = 2) { return renderNumber(quota); } +function isValidGroupRatio(ratio) { + return ratio !== undefined && ratio !== null && ratio !== -1; +} + export function renderModelPrice( inputTokens, completionTokens, @@ -875,6 +878,7 @@ export function renderModelPrice( modelPrice = -1, completionRatio, groupRatio, + user_group_ratio, cacheTokens = 0, cacheRatio = 1.0, image = false, @@ -890,13 +894,19 @@ export function renderModelPrice( audioInputTokens = 0, audioInputPrice = 0, ) { + const useUserGroupRatio = isValidGroupRatio(user_group_ratio); + const ratioLabel = useUserGroupRatio + ? i18next.t('专属倍率') + : i18next.t('分组倍率'); + groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio; if (modelPrice !== -1) { return i18next.t( - '模型价格:${{price}} * 分组倍率:{{ratio}} = ${{total}}', + '模型价格:${{price}} * {{ratioType}}:{{ratio}} = ${{total}}', { price: modelPrice, ratio: groupRatio, total: modelPrice * groupRatio, + ratioType: ratioLabel, }, ); } else { @@ -1033,11 +1043,12 @@ export function renderModelPrice( // 构建输出部分描述 const outputDesc = i18next.t( - '输出 {{completion}} tokens / 1M tokens * ${{compPrice}}) * 分组倍率 {{ratio}}', + '输出 {{completion}} tokens / 1M tokens * ${{compPrice}}) * {{ratioType}} {{ratio}}', { completion: completionTokens, compPrice: completionRatioPrice, ratio: groupRatio, + ratioType: ratioLabel, }, ); @@ -1045,23 +1056,25 @@ export function renderModelPrice( const extraServices = [ webSearch && webSearchCallCount > 0 ? i18next.t( - ' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * 分组倍率 {{ratio}}', - { - count: webSearchCallCount, - price: webSearchPrice, - ratio: groupRatio, - }, - ) + ' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', + { + count: webSearchCallCount, + price: webSearchPrice, + ratio: groupRatio, + ratioType: ratioLabel, + }, + ) : '', fileSearch && fileSearchCallCount > 0 ? i18next.t( - ' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * 分组倍率 {{ratio}}', - { - count: fileSearchCallCount, - price: fileSearchPrice, - ratio: groupRatio, - }, - ) + ' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}', + { + count: fileSearchCallCount, + price: fileSearchPrice, + ratio: groupRatio, + ratioType: ratioLabel, + }, + ) : '', ].join(''); @@ -1097,6 +1110,7 @@ export function renderLogContent( fileSearch = false, fileSearchCallCount = 0, ) { + useUserGroupRatio = isValidGroupRatio(user_group_ratio); const ratioLabel = useUserGroupRatio ? i18next.t('专属倍率') : i18next.t('分组倍率'); @@ -1149,14 +1163,21 @@ export function renderModelPriceSimple( modelRatio, modelPrice = -1, groupRatio, + user_group_ratio, cacheTokens = 0, cacheRatio = 1.0, image = false, imageRatio = 1.0, ) { + const useUserGroupRatio = isValidGroupRatio(user_group_ratio); + const ratioLabel = useUserGroupRatio + ? i18next.t('专属倍率') + : i18next.t('分组倍率'); + groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio; if (modelPrice !== -1) { - return i18next.t('价格:${{price}} * 分组:{{ratio}}', { + return i18next.t('价格:${{price}} * {{ratioType}}:{{ratio}}', { price: modelPrice, + ratioType: ratioLabel, ratio: groupRatio, }); } else { @@ -1191,8 +1212,9 @@ export function renderModelPriceSimple( }, ); } else { - return i18next.t('模型: {{ratio}} * 分组: {{groupRatio}}', { + return i18next.t('模型: {{ratio}} * {{ratioType}}:{{groupRatio}}', { ratio: modelRatio, + ratioType: ratioLabel, groupRatio: groupRatio, }); } @@ -1210,17 +1232,24 @@ export function renderAudioModelPrice( audioRatio, audioCompletionRatio, groupRatio, + user_group_ratio, cacheTokens = 0, cacheRatio = 1.0, ) { + const useUserGroupRatio = isValidGroupRatio(user_group_ratio); + const ratioLabel = useUserGroupRatio + ? i18next.t('专属倍率') + : i18next.t('分组倍率'); + groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio; // 1 ratio = $0.002 / 1K tokens if (modelPrice !== -1) { return i18next.t( - '模型价格:${{price}} * 分组倍率:{{ratio}} = ${{total}}', + '模型价格:${{price}} * {{ratioType}}:{{ratio}} = ${{total}}', { price: modelPrice, ratio: groupRatio, total: modelPrice * groupRatio, + ratioType: ratioLabel, }, ); } else { @@ -1245,10 +1274,10 @@ export function renderAudioModelPrice( let audioPrice = (audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio + (audioCompletionTokens / 1000000) * - inputRatioPrice * - audioRatio * - audioCompletionRatio * - groupRatio; + inputRatioPrice * + audioRatio * + audioCompletionRatio * + groupRatio; let price = textPrice + audioPrice; return ( <> @@ -1304,27 +1333,27 @@ export function renderAudioModelPrice(

{cacheTokens > 0 ? i18next.t( - '文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', - { - nonCacheInput: inputTokens - cacheTokens, - cacheInput: cacheTokens, - cachePrice: inputRatioPrice * cacheRatio, - price: inputRatioPrice, - completion: completionTokens, - compPrice: completionRatioPrice, - total: textPrice.toFixed(6), - }, - ) + '文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', + { + nonCacheInput: inputTokens - cacheTokens, + cacheInput: cacheTokens, + cachePrice: inputRatioPrice * cacheRatio, + price: inputRatioPrice, + completion: completionTokens, + compPrice: completionRatioPrice, + total: textPrice.toFixed(6), + }, + ) : i18next.t( - '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', - { - input: inputTokens, - price: inputRatioPrice, - completion: completionTokens, - compPrice: completionRatioPrice, - total: textPrice.toFixed(6), - }, - )} + '文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}', + { + input: inputTokens, + price: inputRatioPrice, + completion: completionTokens, + compPrice: completionRatioPrice, + total: textPrice.toFixed(6), + }, + )}

{i18next.t( @@ -1374,12 +1403,17 @@ export function renderClaudeModelPrice( modelPrice = -1, completionRatio, groupRatio, + user_group_ratio, cacheTokens = 0, cacheRatio = 1.0, cacheCreationTokens = 0, cacheCreationRatio = 1.0, ) { - const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组倍率'); + const useUserGroupRatio = isValidGroupRatio(user_group_ratio); + const ratioLabel = useUserGroupRatio + ? i18next.t('专属倍率') + : i18next.t('分组倍率'); + groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio; if (modelPrice !== -1) { return i18next.t( @@ -1461,33 +1495,35 @@ export function renderClaudeModelPrice(

{cacheTokens > 0 || cacheCreationTokens > 0 ? i18next.t( - '提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}', - { - nonCacheInput: nonCachedTokens, - cacheInput: cacheTokens, - cacheRatio: cacheRatio, - cacheCreationInput: cacheCreationTokens, - cacheCreationRatio: cacheCreationRatio, - cachePrice: cacheRatioPrice, - cacheCreationPrice: cacheCreationRatioPrice, - price: inputRatioPrice, - completion: completionTokens, - compPrice: completionRatioPrice, - ratio: groupRatio, - total: price.toFixed(6), - }, - ) + '提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', + { + nonCacheInput: nonCachedTokens, + cacheInput: cacheTokens, + cacheRatio: cacheRatio, + cacheCreationInput: cacheCreationTokens, + cacheCreationRatio: cacheCreationRatio, + cachePrice: cacheRatioPrice, + cacheCreationPrice: cacheCreationRatioPrice, + price: inputRatioPrice, + completion: completionTokens, + compPrice: completionRatioPrice, + ratio: groupRatio, + ratioType: ratioLabel, + total: price.toFixed(6), + }, + ) : i18next.t( - '提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}', - { - input: inputTokens, - price: inputRatioPrice, - completion: completionTokens, - compPrice: completionRatioPrice, - ratio: groupRatio, - total: price.toFixed(6), - }, - )} + '提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}', + { + input: inputTokens, + price: inputRatioPrice, + completion: completionTokens, + compPrice: completionRatioPrice, + ratio: groupRatio, + ratioType: ratioLabel, + total: price.toFixed(6), + }, + )}

{i18next.t('仅供参考,以实际扣费为准')}

@@ -1501,10 +1537,15 @@ export function renderClaudeLogContent( completionRatio, modelPrice = -1, groupRatio, + user_group_ratio, cacheRatio = 1.0, cacheCreationRatio = 1.0, ) { - const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组倍率'); + const useUserGroupRatio = isValidGroupRatio(user_group_ratio); + const ratioLabel = useUserGroupRatio + ? i18next.t('专属倍率') + : i18next.t('分组倍率'); + groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio; if (modelPrice !== -1) { return i18next.t('模型价格 ${{price}},{{ratioType}} {{ratio}}', { @@ -1531,12 +1572,17 @@ export function renderClaudeModelPriceSimple( modelRatio, modelPrice = -1, groupRatio, + user_group_ratio, cacheTokens = 0, cacheRatio = 1.0, cacheCreationTokens = 0, cacheCreationRatio = 1.0, ) { - const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组'); + const useUserGroupRatio = isValidGroupRatio(user_group_ratio); + const ratioLabel = useUserGroupRatio + ? i18next.t('专属倍率') + : i18next.t('分组倍率'); + groupRatio = useUserGroupRatio ? user_group_ratio : groupRatio; if (modelPrice !== -1) { return i18next.t('价格:${{price}} * {{ratioType}}:{{ratio}}', { diff --git a/web/src/pages/Setting/Operation/GroupRatioSettings.js b/web/src/pages/Setting/Operation/GroupRatioSettings.js index c58d8cd1..8afc14e1 100644 --- a/web/src/pages/Setting/Operation/GroupRatioSettings.js +++ b/web/src/pages/Setting/Operation/GroupRatioSettings.js @@ -16,6 +16,7 @@ export default function GroupRatioSettings(props) { const [inputs, setInputs] = useState({ GroupRatio: '', UserUsableGroups: '', + GroupGroupRatio: '', }); const refForm = useRef(); const [inputsRow, setInputsRow] = useState(inputs); @@ -136,6 +137,27 @@ export default function GroupRatioSettings(props) { /> + + + verifyJSON(value), + message: t('不是合法的 JSON 字符串'), + }, + ]} + onChange={(value) => + setInputs({ ...inputs, GroupGroupRatio: value }) + } + /> + + From 21edb750810cf9c5073e7b1dcd16938e5dcf937e Mon Sep 17 00:00:00 2001 From: skynono Date: Thu, 12 Jun 2025 17:12:54 +0800 Subject: [PATCH 06/61] fix: enabled hot reload SyncOptions --- main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index c286650f..30ba8092 100644 --- a/main.go +++ b/main.go @@ -105,10 +105,12 @@ func main() { model.InitChannelCache() }() - go model.SyncOptions(common.SyncFrequency) go model.SyncChannelCache(common.SyncFrequency) } + // 热更新配置 + go model.SyncOptions(common.SyncFrequency) + // 数据看板 go model.UpdateQuotaData() From dcefd7dfb49aeef05ec27773a8ea61fb1b83ffc1 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Thu, 12 Jun 2025 17:25:25 +0800 Subject: [PATCH 07/61] =?UTF-8?q?=F0=9F=9A=80=20feat(pagination):=20unify?= =?UTF-8?q?=20backend-driven=20pagination=20&=20improve=20channel=20tag=20?= =?UTF-8?q?aggregation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SUMMARY • Migrated Token, Task, Midjourney, Channel, Redemption tables to true server-side pagination. • Added total / page / page_size metadata in API responses; switched all affected React tables to consume new structure. • Implemented counting helpers: – model/token.go CountUserTokens – model/task.go TaskCountAllTasks / TaskCountAllUserTask – model/midjourney.go CountAllTasks / CountAllUserTask – model/channel.go CountAllChannels / CountAllTags • Refactored controllers (token, task, midjourney, channel) for 1-based paging & aggregated returns. • Redesigned `ChannelsTable.js`: – `loadChannels`, `syncPageData`, `enrichChannels` for tag-mode grouping without recursion. – Fixed runtime white-screen (maximum call-stack) by removing child duplication. – Pagination, search, tag-mode, idSort all hot-reload correctly. • Removed unused `log` import in controller/midjourney.go. BREAKING CHANGES Front-end consumers must now expect data.items / total / page / page_size from list endpoints (`/api/channel`, `/api/task`, `/api/mj`, `/api/token`, etc.). --- controller/channel.go | 35 +++++---- controller/midjourney.go | 54 ++++++++------ controller/task.go | 44 ++++++++---- controller/token.go | 16 +++-- model/channel.go | 14 ++++ model/midjourney.go | 37 ++++++++++ model/task.go | 61 ++++++++++++++++ model/token.go | 7 ++ web/src/components/table/ChannelsTable.js | 47 ++++--------- web/src/components/table/MjLogsTable.js | 72 +++++++------------ web/src/components/table/TaskLogsTable.js | 86 +++++++++-------------- web/src/components/table/TokensTable.js | 62 +++++++--------- 12 files changed, 317 insertions(+), 218 deletions(-) diff --git a/controller/channel.go b/controller/channel.go index a4ef87c3..1cfb7906 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -43,22 +43,23 @@ type OpenAIModelsResponse struct { func GetAllChannels(c *gin.Context) { p, _ := strconv.Atoi(c.Query("p")) pageSize, _ := strconv.Atoi(c.Query("page_size")) - if p < 0 { - p = 0 + if p < 1 { + p = 1 } - if pageSize < 0 { + if pageSize < 1 { pageSize = common.ItemsPerPage } channelData := make([]*model.Channel, 0) idSort, _ := strconv.ParseBool(c.Query("id_sort")) enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode")) + + var total int64 + if enableTagMode { - tags, err := model.GetPaginatedTags(p*pageSize, pageSize) + // tag 分页:先分页 tag,再取各 tag 下 channels + tags, err := model.GetPaginatedTags((p-1)*pageSize, pageSize) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) return } for _, tag := range tags { @@ -69,21 +70,27 @@ func GetAllChannels(c *gin.Context) { } } } + // 计算 tag 总数用于分页 + total, _ = model.CountAllTags() } else { - channels, err := model.GetAllChannels(p*pageSize, pageSize, false, idSort) + channels, err := model.GetAllChannels((p-1)*pageSize, pageSize, false, idSort) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) + c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) return } channelData = channels + total, _ = model.CountAllChannels() } + c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", - "data": channelData, + "data": gin.H{ + "items": channelData, + "total": total, + "page": p, + "page_size": pageSize, + }, }) return } diff --git a/controller/midjourney.go b/controller/midjourney.go index 21027d8f..56bdcb80 100644 --- a/controller/midjourney.go +++ b/controller/midjourney.go @@ -7,7 +7,6 @@ import ( "fmt" "github.com/gin-gonic/gin" "io" - "log" "net/http" "one-api/common" "one-api/dto" @@ -215,8 +214,12 @@ func checkMjTaskNeedUpdate(oldTask *model.Midjourney, newTask dto.MidjourneyDto) func GetAllMidjourney(c *gin.Context) { p, _ := strconv.Atoi(c.Query("p")) - if p < 0 { - p = 0 + if p < 1 { + p = 1 + } + pageSize, _ := strconv.Atoi(c.Query("page_size")) + if pageSize <= 0 { + pageSize = common.ItemsPerPage } // 解析其他查询参数 @@ -227,31 +230,38 @@ func GetAllMidjourney(c *gin.Context) { EndTimestamp: c.Query("end_timestamp"), } - logs := model.GetAllTasks(p*common.ItemsPerPage, common.ItemsPerPage, queryParams) - if logs == nil { - logs = make([]*model.Midjourney, 0) - } + items := model.GetAllTasks((p-1)*pageSize, pageSize, queryParams) + total := model.CountAllTasks(queryParams) + if setting.MjForwardUrlEnabled { - for i, midjourney := range logs { + for i, midjourney := range items { midjourney.ImageUrl = setting.ServerAddress + "/mj/image/" + midjourney.MjId - logs[i] = midjourney + items[i] = midjourney } } c.JSON(200, gin.H{ "success": true, "message": "", - "data": logs, + "data": gin.H{ + "items": items, + "total": total, + "page": p, + "page_size": pageSize, + }, }) } func GetUserMidjourney(c *gin.Context) { p, _ := strconv.Atoi(c.Query("p")) - if p < 0 { - p = 0 + if p < 1 { + p = 1 + } + pageSize, _ := strconv.Atoi(c.Query("page_size")) + if pageSize <= 0 { + pageSize = common.ItemsPerPage } userId := c.GetInt("id") - log.Printf("userId = %d \n", userId) queryParams := model.TaskQueryParams{ MjID: c.Query("mj_id"), @@ -259,19 +269,23 @@ func GetUserMidjourney(c *gin.Context) { EndTimestamp: c.Query("end_timestamp"), } - logs := model.GetAllUserTask(userId, p*common.ItemsPerPage, common.ItemsPerPage, queryParams) - if logs == nil { - logs = make([]*model.Midjourney, 0) - } + items := model.GetAllUserTask(userId, (p-1)*pageSize, pageSize, queryParams) + total := model.CountAllUserTask(userId, queryParams) + if setting.MjForwardUrlEnabled { - for i, midjourney := range logs { + for i, midjourney := range items { midjourney.ImageUrl = setting.ServerAddress + "/mj/image/" + midjourney.MjId - logs[i] = midjourney + items[i] = midjourney } } c.JSON(200, gin.H{ "success": true, "message": "", - "data": logs, + "data": gin.H{ + "items": items, + "total": total, + "page": p, + "page_size": pageSize, + }, }) } diff --git a/controller/task.go b/controller/task.go index 65f79ead..34e14f3f 100644 --- a/controller/task.go +++ b/controller/task.go @@ -224,9 +224,14 @@ func checkTaskNeedUpdate(oldTask *model.Task, newTask dto.SunoDataResponse) bool func GetAllTask(c *gin.Context) { p, _ := strconv.Atoi(c.Query("p")) - if p < 0 { - p = 0 + if p < 1 { + p = 1 } + pageSize, _ := strconv.Atoi(c.Query("page_size")) + if pageSize <= 0 { + pageSize = common.ItemsPerPage + } + startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64) endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64) // 解析其他查询参数 @@ -237,24 +242,32 @@ func GetAllTask(c *gin.Context) { Action: c.Query("action"), StartTimestamp: startTimestamp, EndTimestamp: endTimestamp, + ChannelID: c.Query("channel_id"), } - logs := model.TaskGetAllTasks(p*common.ItemsPerPage, common.ItemsPerPage, queryParams) - if logs == nil { - logs = make([]*model.Task, 0) - } + items := model.TaskGetAllTasks((p-1)*pageSize, pageSize, queryParams) + total := model.TaskCountAllTasks(queryParams) c.JSON(200, gin.H{ "success": true, "message": "", - "data": logs, + "data": gin.H{ + "items": items, + "total": total, + "page": p, + "page_size": pageSize, + }, }) } func GetUserTask(c *gin.Context) { p, _ := strconv.Atoi(c.Query("p")) - if p < 0 { - p = 0 + if p < 1 { + p = 1 + } + pageSize, _ := strconv.Atoi(c.Query("page_size")) + if pageSize <= 0 { + pageSize = common.ItemsPerPage } userId := c.GetInt("id") @@ -271,14 +284,17 @@ func GetUserTask(c *gin.Context) { EndTimestamp: endTimestamp, } - logs := model.TaskGetAllUserTask(userId, p*common.ItemsPerPage, common.ItemsPerPage, queryParams) - if logs == nil { - logs = make([]*model.Task, 0) - } + items := model.TaskGetAllUserTask(userId, (p-1)*pageSize, pageSize, queryParams) + total := model.TaskCountAllUserTask(userId, queryParams) c.JSON(200, gin.H{ "success": true, "message": "", - "data": logs, + "data": gin.H{ + "items": items, + "total": total, + "page": p, + "page_size": pageSize, + }, }) } diff --git a/controller/token.go b/controller/token.go index a8803279..c57552c0 100644 --- a/controller/token.go +++ b/controller/token.go @@ -12,15 +12,15 @@ func GetAllTokens(c *gin.Context) { userId := c.GetInt("id") p, _ := strconv.Atoi(c.Query("p")) size, _ := strconv.Atoi(c.Query("size")) - if p < 0 { - p = 0 + if p < 1 { + p = 1 } if size <= 0 { size = common.ItemsPerPage } else if size > 100 { size = 100 } - tokens, err := model.GetAllUserTokens(userId, p*size, size) + tokens, err := model.GetAllUserTokens(userId, (p-1)*size, size) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -28,10 +28,18 @@ func GetAllTokens(c *gin.Context) { }) return } + // Get total count for pagination + total, _ := model.CountUserTokens(userId) + c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", - "data": tokens, + "data": gin.H{ + "items": tokens, + "total": total, + "page": p, + "page_size": size, + }, }) return } diff --git a/model/channel.go b/model/channel.go index ed7a0a7e..a302df40 100644 --- a/model/channel.go +++ b/model/channel.go @@ -583,3 +583,17 @@ func BatchSetChannelTag(ids []int, tag *string) error { // 提交事务 return tx.Commit().Error } + +// CountAllChannels returns total channels in DB +func CountAllChannels() (int64, error) { + var total int64 + err := DB.Model(&Channel{}).Count(&total).Error + return total, err +} + +// CountAllTags returns number of non-empty distinct tags +func CountAllTags() (int64, error) { + var total int64 + err := DB.Model(&Channel{}).Where("tag is not null AND tag != ''").Distinct("tag").Count(&total).Error + return total, err +} diff --git a/model/midjourney.go b/model/midjourney.go index 5f85abfd..e8140447 100644 --- a/model/midjourney.go +++ b/model/midjourney.go @@ -166,3 +166,40 @@ func MjBulkUpdateByTaskIds(taskIDs []int, params map[string]any) error { Where("id in (?)", taskIDs). Updates(params).Error } + +// CountAllTasks returns total midjourney tasks for admin query +func CountAllTasks(queryParams TaskQueryParams) int64 { + var total int64 + query := DB.Model(&Midjourney{}) + if queryParams.ChannelID != "" { + query = query.Where("channel_id = ?", queryParams.ChannelID) + } + if queryParams.MjID != "" { + query = query.Where("mj_id = ?", queryParams.MjID) + } + if queryParams.StartTimestamp != "" { + query = query.Where("submit_time >= ?", queryParams.StartTimestamp) + } + if queryParams.EndTimestamp != "" { + query = query.Where("submit_time <= ?", queryParams.EndTimestamp) + } + _ = query.Count(&total).Error + return total +} + +// CountAllUserTask returns total midjourney tasks for user +func CountAllUserTask(userId int, queryParams TaskQueryParams) int64 { + var total int64 + query := DB.Model(&Midjourney{}).Where("user_id = ?", userId) + if queryParams.MjID != "" { + query = query.Where("mj_id = ?", queryParams.MjID) + } + if queryParams.StartTimestamp != "" { + query = query.Where("submit_time >= ?", queryParams.StartTimestamp) + } + if queryParams.EndTimestamp != "" { + query = query.Where("submit_time <= ?", queryParams.EndTimestamp) + } + _ = query.Count(&total).Error + return total +} diff --git a/model/task.go b/model/task.go index df221edf..9e4177ba 100644 --- a/model/task.go +++ b/model/task.go @@ -302,3 +302,64 @@ func SumUsedTaskQuota(queryParams SyncTaskQueryParams) (stat []TaskQuotaUsage, e err = query.Select("mode, sum(quota) as count").Group("mode").Find(&stat).Error return stat, err } + +// TaskCountAllTasks returns total tasks that match the given query params (admin usage) +func TaskCountAllTasks(queryParams SyncTaskQueryParams) int64 { + var total int64 + query := DB.Model(&Task{}) + if queryParams.ChannelID != "" { + query = query.Where("channel_id = ?", queryParams.ChannelID) + } + if queryParams.Platform != "" { + query = query.Where("platform = ?", queryParams.Platform) + } + if queryParams.UserID != "" { + query = query.Where("user_id = ?", queryParams.UserID) + } + if len(queryParams.UserIDs) != 0 { + query = query.Where("user_id in (?)", queryParams.UserIDs) + } + if queryParams.TaskID != "" { + query = query.Where("task_id = ?", queryParams.TaskID) + } + if queryParams.Action != "" { + query = query.Where("action = ?", queryParams.Action) + } + if queryParams.Status != "" { + query = query.Where("status = ?", queryParams.Status) + } + if queryParams.StartTimestamp != 0 { + query = query.Where("submit_time >= ?", queryParams.StartTimestamp) + } + if queryParams.EndTimestamp != 0 { + query = query.Where("submit_time <= ?", queryParams.EndTimestamp) + } + _ = query.Count(&total).Error + return total +} + +// TaskCountAllUserTask returns total tasks for given user +func TaskCountAllUserTask(userId int, queryParams SyncTaskQueryParams) int64 { + var total int64 + query := DB.Model(&Task{}).Where("user_id = ?", userId) + if queryParams.TaskID != "" { + query = query.Where("task_id = ?", queryParams.TaskID) + } + if queryParams.Action != "" { + query = query.Where("action = ?", queryParams.Action) + } + if queryParams.Status != "" { + query = query.Where("status = ?", queryParams.Status) + } + if queryParams.Platform != "" { + query = query.Where("platform = ?", queryParams.Platform) + } + if queryParams.StartTimestamp != 0 { + query = query.Where("submit_time >= ?", queryParams.StartTimestamp) + } + if queryParams.EndTimestamp != 0 { + query = query.Where("submit_time <= ?", queryParams.EndTimestamp) + } + _ = query.Count(&total).Error + return total +} diff --git a/model/token.go b/model/token.go index 8587ea62..d4b26afe 100644 --- a/model/token.go +++ b/model/token.go @@ -320,3 +320,10 @@ func decreaseTokenQuota(id int, quota int) (err error) { ).Error return err } + +// CountUserTokens returns total number of tokens for the given user, used for pagination +func CountUserTokens(userId int) (int64, error) { + var total int64 + err := DB.Model(&Token{}).Where("user_id = ?", userId).Count(&total).Error + return total, err +} diff --git a/web/src/components/table/ChannelsTable.js b/web/src/components/table/ChannelsTable.js index 6eeeab9a..f5a78490 100644 --- a/web/src/components/table/ChannelsTable.js +++ b/web/src/components/table/ChannelsTable.js @@ -865,32 +865,22 @@ const ChannelsTable = () => { tagChannelDates.response_time = tagChannelDates.response_time / 2; } } - // data.key = '' + data.id setChannels(channelDates); - if (channelDates.length >= pageSize) { - setChannelCount(channelDates.length + pageSize); - } else { - setChannelCount(channelDates.length); - } }; - const loadChannels = async (startIdx, pageSize, idSort, enableTagMode) => { + const loadChannels = async (page, pageSize, idSort, enableTagMode) => { setLoading(true); const res = await API.get( - `/api/channel/?p=${startIdx}&page_size=${pageSize}&id_sort=${idSort}&tag_mode=${enableTagMode}`, + `/api/channel/?p=${page}&page_size=${pageSize}&id_sort=${idSort}&tag_mode=${enableTagMode}`, ); if (res === undefined) { return; } const { success, message, data } = res.data; if (success) { - if (startIdx === 0) { - setChannelFormat(data, enableTagMode); - } else { - let newChannels = [...channels]; - newChannels.splice(startIdx * pageSize, data.length, ...data); - setChannelFormat(newChannels, enableTagMode); - } + const { items, total } = data; + setChannelFormat(items, enableTagMode); + setChannelCount(total); } else { showError(message); } @@ -903,7 +893,6 @@ const ChannelsTable = () => { channelToCopy.created_time = null; channelToCopy.balance = 0; channelToCopy.used_quota = 0; - // 删除可能导致类型不匹配的字段 delete channelToCopy.test_time; delete channelToCopy.response_time; if (!channelToCopy) { @@ -927,7 +916,7 @@ const ChannelsTable = () => { const refresh = async () => { const { searchKeyword, searchGroup, searchModel } = getFormValues(); if (searchKeyword === '' && searchGroup === '' && searchModel === '') { - await loadChannels(activePage - 1, pageSize, idSort, enableTagMode); + await loadChannels(activePage, pageSize, idSort, enableTagMode); } else { await searchChannels(enableTagMode); } @@ -944,7 +933,7 @@ const ChannelsTable = () => { setPageSize(localPageSize); setEnableTagMode(localEnableTagMode); setEnableBatchDelete(localEnableBatchDelete); - loadChannels(0, localPageSize, localIdSort, localEnableTagMode) + loadChannels(1, localPageSize, localIdSort, localEnableTagMode) .then() .catch((reason) => { showError(reason); @@ -1052,7 +1041,6 @@ const ChannelsTable = () => { try { if (searchKeyword === '' && searchGroup === '' && searchModel === '') { await loadChannels(activePage - 1, pageSize, idSort, enableTagMode); - // setActivePage(1); return; } @@ -1191,24 +1179,18 @@ const ChannelsTable = () => { } }; - let pageData = channels.slice( - (activePage - 1) * pageSize, - activePage * pageSize, - ); + let pageData = channels; const handlePageChange = (page) => { setActivePage(page); - if (page === Math.ceil(channels.length / pageSize) + 1) { - // In this case we have to load more data and then append them. - loadChannels(page - 1, pageSize, idSort, enableTagMode).then((r) => { }); - } + loadChannels(page, pageSize, idSort, enableTagMode).then(() => { }); }; const handlePageSizeChange = async (size) => { localStorage.setItem('page-size', size + ''); setPageSize(size); setActivePage(1); - loadChannels(0, size, idSort, enableTagMode) + loadChannels(1, size, idSort, enableTagMode) .then() .catch((reason) => { showError(reason); @@ -1218,8 +1200,6 @@ const ChannelsTable = () => { const fetchGroups = async () => { try { let res = await API.get(`/api/group/`); - // add 'all' option - // res.data.data.unshift('all'); if (res === undefined) { return; } @@ -1514,7 +1494,7 @@ const ChannelsTable = () => { onChange={(v) => { localStorage.setItem('id-sort', v + ''); setIdSort(v); - loadChannels(0, pageSize, v, enableTagMode); + loadChannels(activePage, pageSize, v, enableTagMode); }} />
@@ -1541,7 +1521,8 @@ const ChannelsTable = () => { onChange={(v) => { localStorage.setItem('enable-tag-mode', v + ''); setEnableTagMode(v); - loadChannels(0, pageSize, idSort, v); + setActivePage(1); + loadChannels(1, pageSize, idSort, v); }} /> @@ -1703,7 +1684,7 @@ const ChannelsTable = () => { formatPageText: (page) => t('第 {{start}} - {{end}} 条,共 {{total}} 条', { start: page.currentStart, end: page.currentEnd, - total: channels.length, + total: channelCount, }), onPageSizeChange: (size) => { handlePageSizeChange(size); diff --git a/web/src/components/table/MjLogsTable.js b/web/src/components/table/MjLogsTable.js index 08376641..869db485 100644 --- a/web/src/components/table/MjLogsTable.js +++ b/web/src/components/table/MjLogsTable.js @@ -601,7 +601,7 @@ const LogsTable = () => { const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [activePage, setActivePage] = useState(1); - const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); + const [logCount, setLogCount] = useState(0); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [isModalOpenurl, setIsModalOpenurl] = useState(false); const [showBanner, setShowBanner] = useState(false); @@ -649,69 +649,53 @@ const LogsTable = () => { }; }; - const setLogsFormat = (logs) => { - for (let i = 0; i < logs.length; i++) { - logs[i].timestamp2string = timestamp2string(logs[i].created_at); - logs[i].key = '' + logs[i].id; - } - // data.key = '' + data.id - setLogs(logs); - setLogCount(logs.length + pageSize); - // console.log(logCount); + const enrichLogs = (items) => { + return items.map((log) => ({ + ...log, + timestamp2string: timestamp2string(log.created_at), + key: '' + log.id, + })); }; - const loadLogs = async (startIdx, pageSize = ITEMS_PER_PAGE) => { - setLoading(true); + const syncPageData = (payload) => { + const items = enrichLogs(payload.items || []); + setLogs(items); + setLogCount(payload.total || 0); + setActivePage(payload.page || 1); + setPageSize(payload.page_size || pageSize); + }; - let url = ''; + const loadLogs = async (page = 1, size = pageSize) => { + setLoading(true); const { channel_id, mj_id, start_timestamp, end_timestamp } = getFormValues(); let localStartTimestamp = Date.parse(start_timestamp); let localEndTimestamp = Date.parse(end_timestamp); - if (isAdminUser) { - url = `/api/mj/?p=${startIdx}&page_size=${pageSize}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; - } else { - url = `/api/mj/self/?p=${startIdx}&page_size=${pageSize}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; - } + const url = isAdminUser + ? `/api/mj/?p=${page}&page_size=${size}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}` + : `/api/mj/self/?p=${page}&page_size=${size}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; const res = await API.get(url); const { success, message, data } = res.data; if (success) { - if (startIdx === 0) { - setLogsFormat(data); - } else { - let newLogs = [...logs]; - newLogs.splice(startIdx * pageSize, data.length, ...data); - setLogsFormat(newLogs); - } + syncPageData(data); } else { showError(message); } setLoading(false); }; - const pageData = logs.slice( - (activePage - 1) * pageSize, - activePage * pageSize, - ); + const pageData = logs; const handlePageChange = (page) => { - setActivePage(page); - if (page === Math.ceil(logs.length / pageSize) + 1) { - // In this case we have to load more data and then append them. - loadLogs(page - 1, pageSize).then((r) => { }); - } + loadLogs(page, pageSize).then(); }; const handlePageSizeChange = async (size) => { localStorage.setItem('mj-page-size', size + ''); - setPageSize(size); - setActivePage(1); - await loadLogs(0, size); + await loadLogs(1, size); }; const refresh = async () => { - // setLoading(true); - setActivePage(1); - await loadLogs(0, pageSize); + await loadLogs(1, pageSize); }; const copyText = async (text) => { @@ -726,7 +710,7 @@ const LogsTable = () => { useEffect(() => { const localPageSize = parseInt(localStorage.getItem('mj-page-size')) || ITEMS_PER_PAGE; setPageSize(localPageSize); - loadLogs(0, localPageSize).then(); + loadLogs(1, localPageSize).then(); }, []); useEffect(() => { @@ -936,7 +920,7 @@ const LogsTable = () => { > { total: logCount, pageSizeOptions: [10, 20, 50, 100], showSizeChanger: true, - onPageSizeChange: (size) => { - handlePageSizeChange(size); - }, + onPageSizeChange: handlePageSizeChange, onPageChange: handlePageChange, }} /> diff --git a/web/src/components/table/TaskLogsTable.js b/web/src/components/table/TaskLogsTable.js index 4f802515..449b3d55 100644 --- a/web/src/components/table/TaskLogsTable.js +++ b/web/src/components/table/TaskLogsTable.js @@ -451,10 +451,16 @@ const LogsTable = () => { return allColumns.filter((column) => visibleColumns[column.key]); }; - const [logs, setLogs] = useState([]); - const [loading, setLoading] = useState(true); const [activePage, setActivePage] = useState(1); - const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); + const [logCount, setLogCount] = useState(0); + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const localPageSize = parseInt(localStorage.getItem('task-page-size')) || ITEMS_PER_PAGE; + setPageSize(localPageSize); + loadLogs(1, localPageSize).then(); + }, []); let now = new Date(); // 初始化start_timestamp为前一天 @@ -494,67 +500,53 @@ const LogsTable = () => { }; }; - const setLogsFormat = (logs) => { - for (let i = 0; i < logs.length; i++) { - logs[i].timestamp2string = timestamp2string(logs[i].created_at); - logs[i].key = '' + logs[i].id; - } - // data.key = '' + data.id - setLogs(logs); - setLogCount(logs.length + ITEMS_PER_PAGE); - // console.log(logCount); + const enrichLogs = (items) => { + return items.map((log) => ({ + ...log, + timestamp2string: timestamp2string(log.created_at), + key: '' + log.id, + })); }; - const loadLogs = async (startIdx, pageSize = ITEMS_PER_PAGE) => { - setLoading(true); + const syncPageData = (payload) => { + const items = enrichLogs(payload.items || []); + setLogs(items); + setLogCount(payload.total || 0); + setActivePage(payload.page || 1); + setPageSize(payload.page_size || pageSize); + }; - let url = ''; + const loadLogs = async (page = 1, size = pageSize) => { + setLoading(true); const { channel_id, task_id, start_timestamp, end_timestamp } = getFormValues(); let localStartTimestamp = parseInt(Date.parse(start_timestamp) / 1000); let localEndTimestamp = parseInt(Date.parse(end_timestamp) / 1000); - if (isAdminUser) { - url = `/api/task/?p=${startIdx}&page_size=${pageSize}&channel_id=${channel_id}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; - } else { - url = `/api/task/self?p=${startIdx}&page_size=${pageSize}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; - } + let url = isAdminUser + ? `/api/task/?p=${page}&page_size=${size}&channel_id=${channel_id}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}` + : `/api/task/self?p=${page}&page_size=${size}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; const res = await API.get(url); - let { success, message, data } = res.data; + const { success, message, data } = res.data; if (success) { - if (startIdx === 0) { - setLogsFormat(data); - } else { - let newLogs = [...logs]; - newLogs.splice(startIdx * pageSize, data.length, ...data); - setLogsFormat(newLogs); - } + syncPageData(data); } else { showError(message); } setLoading(false); }; - const pageData = logs.slice( - (activePage - 1) * pageSize, - activePage * pageSize, - ); + const pageData = logs; const handlePageChange = (page) => { - setActivePage(page); - if (page === Math.ceil(logs.length / pageSize) + 1) { - loadLogs(page - 1, pageSize).then((r) => { }); - } + loadLogs(page, pageSize).then(); }; const handlePageSizeChange = async (size) => { localStorage.setItem('task-page-size', size + ''); - setPageSize(size); - setActivePage(1); - await loadLogs(0, size); + await loadLogs(1, size); }; const refresh = async () => { - setActivePage(1); - await loadLogs(0, pageSize); + await loadLogs(1, pageSize); }; const copyText = async (text) => { @@ -565,12 +557,6 @@ const LogsTable = () => { } }; - useEffect(() => { - const localPageSize = parseInt(localStorage.getItem('task-page-size')) || ITEMS_PER_PAGE; - setPageSize(localPageSize); - loadLogs(0, localPageSize).then(); - }, []); - // 列选择器模态框 const renderColumnSelector = () => { return ( @@ -763,7 +749,7 @@ const LogsTable = () => { >
{ total: logCount, pageSizeOptions: [10, 20, 50, 100], showSizeChanger: true, - onPageSizeChange: (size) => { - handlePageSizeChange(size); - }, + onPageSizeChange: handlePageSizeChange, onPageChange: handlePageChange, }} /> diff --git a/web/src/components/table/TokensTable.js b/web/src/components/table/TokensTable.js index 4fc26c6c..dc82d50f 100644 --- a/web/src/components/table/TokensTable.js +++ b/web/src/components/table/TokensTable.js @@ -408,31 +408,20 @@ const TokensTable = () => { }, 500); }; - const setTokensFormat = (tokens) => { - setTokens(tokens); - if (tokens.length >= pageSize) { - setTokenCount(tokens.length + pageSize); - } else { - setTokenCount(tokens.length); - } + // 将后端返回的数据写入状态 + const syncPageData = (payload) => { + setTokens(payload.items || []); + setTokenCount(payload.total || 0); + setActivePage(payload.page || 1); + setPageSize(payload.page_size || pageSize); }; - let pageData = tokens.slice( - (activePage - 1) * pageSize, - activePage * pageSize, - ); - const loadTokens = async (startIdx) => { + const loadTokens = async (page = 1, size = pageSize) => { setLoading(true); - const res = await API.get(`/api/token/?p=${startIdx}&size=${pageSize}`); + const res = await API.get(`/api/token/?p=${page}&size=${size}`); const { success, message, data } = res.data; if (success) { - if (startIdx === 0) { - setTokensFormat(data); - } else { - let newTokens = [...tokens]; - newTokens.splice(startIdx * pageSize, data.length, ...data); - setTokensFormat(newTokens); - } + syncPageData(data); } else { showError(message); } @@ -440,7 +429,7 @@ const TokensTable = () => { }; const refresh = async () => { - await loadTokens(activePage - 1); + await loadTokens(1); }; const copyText = async (text) => { @@ -473,7 +462,7 @@ const TokensTable = () => { }; useEffect(() => { - loadTokens(0) + loadTokens(1) .then() .catch((reason) => { showError(reason); @@ -487,7 +476,7 @@ const TokensTable = () => { if (idx > -1) { newDataSource.splice(idx, 1); - setTokensFormat(newDataSource); + setTokens(newDataSource); } } }; @@ -518,7 +507,7 @@ const TokensTable = () => { } else { record.status = token.status; } - setTokensFormat(newTokens); + setTokens(newTokens); } else { showError(message); } @@ -528,8 +517,7 @@ const TokensTable = () => { const searchTokens = async () => { const { searchKeyword, searchToken } = getFormValues(); if (searchKeyword === '' && searchToken === '') { - await loadTokens(0); - setActivePage(1); + await loadTokens(1); return; } setSearching(true); @@ -538,7 +526,8 @@ const TokensTable = () => { ); const { success, message, data } = res.data; if (success) { - setTokensFormat(data); + setTokens(data); + setTokenCount(data.length); setActivePage(1); } else { showError(message); @@ -561,10 +550,12 @@ const TokensTable = () => { }; const handlePageChange = (page) => { - setActivePage(page); - if (page === Math.ceil(tokens.length / pageSize) + 1) { - loadTokens(page - 1).then((r) => { }); - } + loadTokens(page, pageSize).then(); + }; + + const handlePageSizeChange = async (size) => { + setPageSize(size); + await loadTokens(1, size); }; const rowSelection = { @@ -707,7 +698,7 @@ const TokensTable = () => { >
{ t('第 {{start}} - {{end}} 条,共 {{total}} 条', { start: page.currentStart, end: page.currentEnd, - total: tokens.length, + total: tokenCount, }), - onPageSizeChange: (size) => { - setPageSize(size); - setActivePage(1); - }, + onPageSizeChange: handlePageSizeChange, onPageChange: handlePageChange, }} loading={loading} From 1a7da8397bda06aca3046e69f1bdabd65e25d290 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Thu, 12 Jun 2025 17:40:32 +0800 Subject: [PATCH 08/61] =?UTF-8?q?=F0=9F=8E=A8style:=20Standardize=20pagina?= =?UTF-8?q?tion=20text=20format=20in=20Dashboard=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace `showTotal` with `formatPageText` in Dashboard table components - Unify pagination text format to match table components pattern - Update SettingsAnnouncements.js, SettingsAPIInfo.js, and SettingsFAQ.js - Change from "共 X 条记录,显示第 Y-Z 条" to "第 Y - Z 条,共 X 条" format - Ensure consistent user experience across all table components This change improves UI consistency by standardizing the pagination text format across Dashboard and table components. --- web/src/pages/Setting/Dashboard/SettingsAPIInfo.js | 6 +++++- web/src/pages/Setting/Dashboard/SettingsAnnouncements.js | 6 +++++- web/src/pages/Setting/Dashboard/SettingsFAQ.js | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js index 4e87c697..67a475de 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js +++ b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js @@ -367,7 +367,11 @@ const SettingsAPIInfo = ({ options, refresh }) => { total: apiInfoList.length, showSizeChanger: true, showQuickJumper: true, - showTotal: (total, range) => t(`共 ${total} 条记录,显示第 ${range[0]}-${range[1]} 条`), + formatPageText: (page) => t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: apiInfoList.length, + }), pageSizeOptions: ['5', '10', '20', '50'], onChange: (page, size) => { setCurrentPage(page); diff --git a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js index 3a9f71ca..2646f7bf 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js +++ b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js @@ -392,7 +392,11 @@ const SettingsAnnouncements = ({ options, refresh }) => { total: announcementsList.length, showSizeChanger: true, showQuickJumper: true, - showTotal: (total, range) => t(`共 ${total} 条记录,显示第 ${range[0]}-${range[1]} 条`), + formatPageText: (page) => t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: announcementsList.length, + }), pageSizeOptions: ['5', '10', '20', '50'], onChange: (page, size) => { setCurrentPage(page); diff --git a/web/src/pages/Setting/Dashboard/SettingsFAQ.js b/web/src/pages/Setting/Dashboard/SettingsFAQ.js index 3e1ff805..c5c65c1b 100644 --- a/web/src/pages/Setting/Dashboard/SettingsFAQ.js +++ b/web/src/pages/Setting/Dashboard/SettingsFAQ.js @@ -330,7 +330,11 @@ const SettingsFAQ = ({ options, refresh }) => { total: faqList.length, showSizeChanger: true, showQuickJumper: true, - showTotal: (total, range) => t(`共 ${total} 条记录,显示第 ${range[0]}-${range[1]} 条`), + formatPageText: (page) => t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: faqList.length, + }), pageSizeOptions: ['5', '10', '20', '50'], onChange: (page, size) => { setCurrentPage(page); From a0ae6644ee328bd0cd2d0af7bf68da0e54a36faa Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Thu, 12 Jun 2025 17:48:20 +0800 Subject: [PATCH 09/61] =?UTF-8?q?=F0=9F=90=9B=20fix:=20correct=20loading?= =?UTF-8?q?=20state=20for=20search=20button=20in=20TokensTable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the search button loading state to be consistent with other table components. The search button now properly shows loading animation when the table data is being fetched. Changes: - Update search button loading prop from `loading={searching}` to `loading={loading || searching}` in TokensTable.js - This ensures loading state is shown both when searching with keywords (searching=true) and when loading default data (loading=true) - Aligns with the behavior of other table components like ChannelsTable, UsersTable, and RedemptionsTable Before: Search button only showed loading when searching with keywords After: Search button shows loading for all table data fetch operations --- web/src/components/table/TokensTable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/table/TokensTable.js b/web/src/components/table/TokensTable.js index dc82d50f..9a959e1b 100644 --- a/web/src/components/table/TokensTable.js +++ b/web/src/components/table/TokensTable.js @@ -654,7 +654,7 @@ const TokensTable = () => { + + - diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 98de048b..3b523cbf 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1655,5 +1655,9 @@ "设置保存失败": "Settings save failed", "已新增 {{count}} 个模型:{{list}}": "Added {{count}} models: {{list}}", "未发现新增模型": "No new models were added", - "令牌用于API访问认证,可以设置额度限制和模型权限。": "Tokens are used for API access authentication, and can set quota limits and model permissions." + "令牌用于API访问认证,可以设置额度限制和模型权限。": "Tokens are used for API access authentication, and can set quota limits and model permissions.", + "清除失效兑换码": "Clear invalid redemption codes", + "确定清除所有失效兑换码?": "Are you sure you want to clear all invalid redemption codes?", + "将删除已使用、已禁用及过期的兑换码,此操作不可撤销。": "This will delete all used, disabled, and expired redemption codes, this operation cannot be undone.", + "选择过期时间(可选,留空为永久)": "Select expiration time (optional, leave blank for permanent)" } \ No newline at end of file diff --git a/web/src/pages/Redemption/EditRedemption.js b/web/src/pages/Redemption/EditRedemption.js index e720f90c..a3664d0c 100644 --- a/web/src/pages/Redemption/EditRedemption.js +++ b/web/src/pages/Redemption/EditRedemption.js @@ -20,6 +20,8 @@ import { Typography, Card, Tag, + Form, + DatePicker, } from '@douyinfe/semi-ui'; import { IconCreditCard, @@ -40,9 +42,10 @@ const EditRedemption = (props) => { name: '', quota: 100000, count: 1, + expired_time: 0, }; const [inputs, setInputs] = useState(originInputs); - const { name, quota, count } = inputs; + const { name, quota, count, expired_time } = inputs; const handleCancel = () => { props.handleClose(); @@ -85,6 +88,9 @@ const EditRedemption = (props) => { localInputs.count = parseInt(localInputs.count); localInputs.quota = parseInt(localInputs.quota); localInputs.name = name; + if (localInputs.expired_time === null || localInputs.expired_time === undefined) { + localInputs.expired_time = 0; + } let res; if (isEdit) { res = await API.put(`/api/redemption/`, { @@ -220,6 +226,25 @@ const EditRedemption = (props) => { required={!isEdit} /> +
+ {t('过期时间')} + { + if (value === null || value === undefined) { + handleInputChange('expired_time', 0); + } else { + const timestamp = Math.floor(value.getTime() / 1000); + handleInputChange('expired_time', timestamp); + } + }} + size="large" + className="!rounded-lg w-full" + /> +
From c554015526aa7bd829348a5bab54ef9193d762ec Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sat, 14 Jun 2025 00:40:29 +0800 Subject: [PATCH 24/61] =?UTF-8?q?=E2=9C=A8=20refactor(console=5Fsetting):?= =?UTF-8?q?=20migrate=20console=20settings=20to=20model=5Fsetting=20auto-i?= =?UTF-8?q?njection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend - Introduce `setting/console_setting` package that defines `ConsoleSetting` struct with JSON tags and validation rules. - Register the new module with `config.GlobalConfig` to enable automatic injection/export of configuration values. - Remove legacy `setting/console.go` and the manual `OptionMap` hooks; clean up `model/option.go`. - Add `controller/console_migrate.go` providing `/api/option/migrate_console_setting` endpoint for one-off data migration. - Update controllers (`misc`, `option`, `uptime_kuma`) and router to consume namespaced keys `console_setting.*`. Frontend - Refactor dashboard pages (`SettingsAPIInfo`, `SettingsAnnouncements`, `SettingsFAQ`, `SettingsUptimeKuma`) and detail page to read/write the new keys. - Simplify `DashboardSetting.js` state to only include namespaced options. BREAKING CHANGE: All console-related option keys are now stored under `console_setting.*`. Run the migration endpoint once after deployment to preserve existing data. --- controller/misc.go | 7 +- controller/option.go | 13 +- controller/uptime_kuma.go | 9 +- model/option.go | 3 - setting/console.go | 327 ------------------ setting/console_setting/config.go | 33 ++ setting/console_setting/validation.go | 212 ++++++++++++ .../components/settings/DashboardSetting.js | 10 +- web/src/pages/Detail/index.js | 4 +- .../Setting/Dashboard/SettingsAPIInfo.js | 9 +- .../Dashboard/SettingsAnnouncements.js | 9 +- .../pages/Setting/Dashboard/SettingsFAQ.js | 38 +- .../Setting/Dashboard/SettingsUptimeKuma.js | 16 +- 13 files changed, 304 insertions(+), 386 deletions(-) delete mode 100644 setting/console.go create mode 100644 setting/console_setting/config.go create mode 100644 setting/console_setting/validation.go diff --git a/controller/misc.go b/controller/misc.go index 8fa8e8f6..ccbdecb5 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -11,6 +11,7 @@ import ( "one-api/setting" "one-api/setting/operation_setting" "one-api/setting/system_setting" + "one-api/setting/console_setting" "strings" "github.com/gin-gonic/gin" @@ -79,9 +80,9 @@ func GetStatus(c *gin.Context) { "oidc_client_id": system_setting.GetOIDCSettings().ClientId, "oidc_authorization_endpoint": system_setting.GetOIDCSettings().AuthorizationEndpoint, "setup": constant.Setup, - "api_info": setting.GetApiInfo(), - "announcements": setting.GetAnnouncements(), - "faq": setting.GetFAQ(), + "api_info": console_setting.GetApiInfo(), + "announcements": console_setting.GetAnnouncements(), + "faq": console_setting.GetFAQ(), }, }) return diff --git a/controller/option.go b/controller/option.go index b52012fd..b782c1f2 100644 --- a/controller/option.go +++ b/controller/option.go @@ -6,6 +6,7 @@ import ( "one-api/common" "one-api/model" "one-api/setting" + "one-api/setting/console_setting" "one-api/setting/system_setting" "strings" @@ -119,8 +120,8 @@ func UpdateOption(c *gin.Context) { }) return } - case "ApiInfo": - err = setting.ValidateApiInfo(option.Value) + case "console_setting.api_info": + err = console_setting.ValidateApiInfo(option.Value) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -128,8 +129,8 @@ func UpdateOption(c *gin.Context) { }) return } - case "Announcements": - err = setting.ValidateConsoleSettings(option.Value, "Announcements") + case "console_setting.announcements": + err = console_setting.ValidateConsoleSettings(option.Value, "Announcements") if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -137,8 +138,8 @@ func UpdateOption(c *gin.Context) { }) return } - case "FAQ": - err = setting.ValidateConsoleSettings(option.Value, "FAQ") + case "console_setting.faq": + err = console_setting.ValidateConsoleSettings(option.Value, "FAQ") if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/controller/uptime_kuma.go b/controller/uptime_kuma.go index 6ceaa1f3..e89b2b9d 100644 --- a/controller/uptime_kuma.go +++ b/controller/uptime_kuma.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "net/http" - "one-api/common" + "one-api/setting/console_setting" "strings" "time" @@ -77,10 +77,9 @@ func getAndDecode(ctx context.Context, client *http.Client, url string, dest int } func GetUptimeKumaStatus(c *gin.Context) { - common.OptionMapRWMutex.RLock() - uptimeKumaUrl := common.OptionMap["UptimeKumaUrl"] - slug := common.OptionMap["UptimeKumaSlug"] - common.OptionMapRWMutex.RUnlock() + cs := console_setting.GetConsoleSetting() + uptimeKumaUrl := cs.UptimeKumaUrl + slug := cs.UptimeKumaSlug if uptimeKumaUrl == "" || slug == "" { c.JSON(http.StatusOK, gin.H{ diff --git a/model/option.go b/model/option.go index 0cb4fc35..d1689cb7 100644 --- a/model/option.go +++ b/model/option.go @@ -123,9 +123,6 @@ func InitOptionMap() { common.OptionMap["SensitiveWords"] = setting.SensitiveWordsToString() common.OptionMap["StreamCacheQueueLength"] = strconv.Itoa(setting.StreamCacheQueueLength) common.OptionMap["AutomaticDisableKeywords"] = operation_setting.AutomaticDisableKeywordsToString() - common.OptionMap["ApiInfo"] = "" - common.OptionMap["UptimeKumaUrl"] = "" - common.OptionMap["UptimeKumaSlug"] = "" // 自动添加所有注册的模型配置 modelConfigs := config.GlobalConfig.ExportAllConfigs() diff --git a/setting/console.go b/setting/console.go deleted file mode 100644 index 94023666..00000000 --- a/setting/console.go +++ /dev/null @@ -1,327 +0,0 @@ -package setting - -import ( - "encoding/json" - "fmt" - "net/url" - "one-api/common" - "regexp" - "sort" - "strings" - "time" -) - -// ValidateConsoleSettings 验证控制台设置信息格式 -func ValidateConsoleSettings(settingsStr string, settingType string) error { - if settingsStr == "" { - return nil // 空字符串是合法的 - } - - switch settingType { - case "ApiInfo": - return validateApiInfo(settingsStr) - case "Announcements": - return validateAnnouncements(settingsStr) - case "FAQ": - return validateFAQ(settingsStr) - default: - return fmt.Errorf("未知的设置类型:%s", settingType) - } -} - -// validateApiInfo 验证API信息格式 -func validateApiInfo(apiInfoStr string) error { - var apiInfoList []map[string]interface{} - if err := json.Unmarshal([]byte(apiInfoStr), &apiInfoList); err != nil { - return fmt.Errorf("API信息格式错误:%s", err.Error()) - } - - // 验证数组长度 - if len(apiInfoList) > 50 { - return fmt.Errorf("API信息数量不能超过50个") - } - - // 允许的颜色值 - validColors := map[string]bool{ - "blue": true, "green": true, "cyan": true, "purple": true, "pink": true, - "red": true, "orange": true, "amber": true, "yellow": true, "lime": true, - "light-green": true, "teal": true, "light-blue": true, "indigo": true, - "violet": true, "grey": true, - } - - // URL正则表达式,支持域名和IP地址格式 - // 域名格式:https://example.com 或 https://sub.example.com:8080 - // IP地址格式:https://192.168.1.1 或 https://192.168.1.1:8080 - urlRegex := regexp.MustCompile(`^https?://(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(?::[0-9]{1,5})?(?:/.*)?$`) - - for i, apiInfo := range apiInfoList { - // 检查必填字段 - urlStr, ok := apiInfo["url"].(string) - if !ok || urlStr == "" { - return fmt.Errorf("第%d个API信息缺少URL字段", i+1) - } - - route, ok := apiInfo["route"].(string) - if !ok || route == "" { - return fmt.Errorf("第%d个API信息缺少线路描述字段", i+1) - } - - description, ok := apiInfo["description"].(string) - if !ok || description == "" { - return fmt.Errorf("第%d个API信息缺少说明字段", i+1) - } - - color, ok := apiInfo["color"].(string) - if !ok || color == "" { - return fmt.Errorf("第%d个API信息缺少颜色字段", i+1) - } - - // 验证URL格式 - if !urlRegex.MatchString(urlStr) { - return fmt.Errorf("第%d个API信息的URL格式不正确", i+1) - } - - // 验证URL可解析性 - if _, err := url.Parse(urlStr); err != nil { - return fmt.Errorf("第%d个API信息的URL无法解析:%s", i+1, err.Error()) - } - - // 验证字段长度 - if len(urlStr) > 500 { - return fmt.Errorf("第%d个API信息的URL长度不能超过500字符", i+1) - } - - if len(route) > 100 { - return fmt.Errorf("第%d个API信息的线路描述长度不能超过100字符", i+1) - } - - if len(description) > 200 { - return fmt.Errorf("第%d个API信息的说明长度不能超过200字符", i+1) - } - - // 验证颜色值 - if !validColors[color] { - return fmt.Errorf("第%d个API信息的颜色值不合法", i+1) - } - - // 检查并过滤危险字符(防止XSS) - dangerousChars := []string{" 100 { - return fmt.Errorf("系统公告数量不能超过100个") - } - - // 允许的类型值 - validTypes := map[string]bool{ - "default": true, "ongoing": true, "success": true, "warning": true, "error": true, - } - - for i, announcement := range announcementsList { - // 检查必填字段 - content, ok := announcement["content"].(string) - if !ok || content == "" { - return fmt.Errorf("第%d个公告缺少内容字段", i+1) - } - - // 检查发布日期字段 - publishDate, exists := announcement["publishDate"] - if !exists { - return fmt.Errorf("第%d个公告缺少发布日期字段", i+1) - } - - publishDateStr, ok := publishDate.(string) - if !ok || publishDateStr == "" { - return fmt.Errorf("第%d个公告的发布日期不能为空", i+1) - } - - // 验证ISO日期格式 - if _, err := time.Parse(time.RFC3339, publishDateStr); err != nil { - return fmt.Errorf("第%d个公告的发布日期格式错误", i+1) - } - - // 验证可选字段 - if announcementType, exists := announcement["type"]; exists { - if typeStr, ok := announcementType.(string); ok { - if !validTypes[typeStr] { - return fmt.Errorf("第%d个公告的类型值不合法", i+1) - } - } - } - - // 验证字段长度 - if len(content) > 500 { - return fmt.Errorf("第%d个公告的内容长度不能超过500字符", i+1) - } - - if extra, exists := announcement["extra"]; exists { - if extraStr, ok := extra.(string); ok && len(extraStr) > 200 { - return fmt.Errorf("第%d个公告的说明长度不能超过200字符", i+1) - } - } - - // 检查并过滤危险字符(防止XSS) - dangerousChars := []string{" 100 { - return fmt.Errorf("常见问答数量不能超过100个") - } - - for i, faq := range faqList { - // 检查必填字段 - title, ok := faq["title"].(string) - if !ok || title == "" { - return fmt.Errorf("第%d个问答缺少标题字段", i+1) - } - - content, ok := faq["content"].(string) - if !ok || content == "" { - return fmt.Errorf("第%d个问答缺少内容字段", i+1) - } - - // 验证字段长度 - if len(title) > 200 { - return fmt.Errorf("第%d个问答的标题长度不能超过200字符", i+1) - } - - if len(content) > 1000 { - return fmt.Errorf("第%d个问答的内容长度不能超过1000字符", i+1) - } - - // 检查并过滤危险字符(防止XSS) - dangerousChars := []string{" 20 { - announcements = announcements[:20] - } - - return announcements -} - -// GetFAQ 获取常见问答列表 -func GetFAQ() []map[string]interface{} { - common.OptionMapRWMutex.RLock() - faqStr, exists := common.OptionMap["FAQ"] - common.OptionMapRWMutex.RUnlock() - - if !exists || faqStr == "" { - return []map[string]interface{}{} - } - - var faq []map[string]interface{} - if err := json.Unmarshal([]byte(faqStr), &faq); err != nil { - return []map[string]interface{}{} - } - - return faq -} \ No newline at end of file diff --git a/setting/console_setting/config.go b/setting/console_setting/config.go new file mode 100644 index 00000000..1379380e --- /dev/null +++ b/setting/console_setting/config.go @@ -0,0 +1,33 @@ +package console_setting + +import "one-api/setting/config" + +type ConsoleSetting struct { + ApiInfo string `json:"api_info"` // 控制台 API 信息 (JSON 数组字符串) + UptimeKumaUrl string `json:"uptime_kuma_url"` // Uptime Kuma 服务地址(如 https://status.example.com ) + UptimeKumaSlug string `json:"uptime_kuma_slug"` // Uptime Kuma Status Page Slug + Announcements string `json:"announcements"` // 系统公告 (JSON 数组字符串) + FAQ string `json:"faq"` // 常见问题 (JSON 数组字符串) +} + +// 默认配置 +var defaultConsoleSetting = ConsoleSetting{ + ApiInfo: "", + UptimeKumaUrl: "", + UptimeKumaSlug: "", + Announcements: "", + FAQ: "", +} + +// 全局实例 +var consoleSetting = defaultConsoleSetting + +func init() { + // 注册到全局配置管理器,键名为 console_setting + config.GlobalConfig.Register("console_setting", &consoleSetting) +} + +// GetConsoleSetting 获取 ConsoleSetting 配置实例 +func GetConsoleSetting() *ConsoleSetting { + return &consoleSetting +} \ No newline at end of file diff --git a/setting/console_setting/validation.go b/setting/console_setting/validation.go new file mode 100644 index 00000000..fbab6e87 --- /dev/null +++ b/setting/console_setting/validation.go @@ -0,0 +1,212 @@ +package console_setting + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + "strings" + "time" +) + +// ValidateConsoleSettings 验证控制台设置信息格式 +func ValidateConsoleSettings(settingsStr string, settingType string) error { + if settingsStr == "" { + return nil // 空字符串是合法的 + } + + switch settingType { + case "ApiInfo": + return validateApiInfo(settingsStr) + case "Announcements": + return validateAnnouncements(settingsStr) + case "FAQ": + return validateFAQ(settingsStr) + default: + return fmt.Errorf("未知的设置类型:%s", settingType) + } +} + +// validateApiInfo 验证API信息格式 +func validateApiInfo(apiInfoStr string) error { + var apiInfoList []map[string]interface{} + if err := json.Unmarshal([]byte(apiInfoStr), &apiInfoList); err != nil { + return fmt.Errorf("API信息格式错误:%s", err.Error()) + } + + // 验证数组长度 + if len(apiInfoList) > 50 { + return fmt.Errorf("API信息数量不能超过50个") + } + + // 允许的颜色值 + validColors := map[string]bool{ + "blue": true, "green": true, "cyan": true, "purple": true, "pink": true, + "red": true, "orange": true, "amber": true, "yellow": true, "lime": true, + "light-green": true, "teal": true, "light-blue": true, "indigo": true, + "violet": true, "grey": true, + } + + // URL 正则,支持域名 / IP + urlRegex := regexp.MustCompile(`^https?://(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(?:\:[0-9]{1,5})?(?:/.*)?$`) + + for i, apiInfo := range apiInfoList { + urlStr, ok := apiInfo["url"].(string) + if !ok || urlStr == "" { + return fmt.Errorf("第%d个API信息缺少URL字段", i+1) + } + route, ok := apiInfo["route"].(string) + if !ok || route == "" { + return fmt.Errorf("第%d个API信息缺少线路描述字段", i+1) + } + description, ok := apiInfo["description"].(string) + if !ok || description == "" { + return fmt.Errorf("第%d个API信息缺少说明字段", i+1) + } + color, ok := apiInfo["color"].(string) + if !ok || color == "" { + return fmt.Errorf("第%d个API信息缺少颜色字段", i+1) + } + if !urlRegex.MatchString(urlStr) { + return fmt.Errorf("第%d个API信息的URL格式不正确", i+1) + } + if _, err := url.Parse(urlStr); err != nil { + return fmt.Errorf("第%d个API信息的URL无法解析:%s", i+1, err.Error()) + } + if len(urlStr) > 500 { + return fmt.Errorf("第%d个API信息的URL长度不能超过500字符", i+1) + } + if len(route) > 100 { + return fmt.Errorf("第%d个API信息的线路描述长度不能超过100字符", i+1) + } + if len(description) > 200 { + return fmt.Errorf("第%d个API信息的说明长度不能超过200字符", i+1) + } + if !validColors[color] { + return fmt.Errorf("第%d个API信息的颜色值不合法", i+1) + } + dangerousChars := []string{" 100 { + return fmt.Errorf("系统公告数量不能超过100个") + } + validTypes := map[string]bool{ + "default": true, "ongoing": true, "success": true, "warning": true, "error": true, + } + for i, ann := range list { + content, ok := ann["content"].(string) + if !ok || content == "" { + return fmt.Errorf("第%d个公告缺少内容字段", i+1) + } + publishDateAny, exists := ann["publishDate"] + if !exists { + return fmt.Errorf("第%d个公告缺少发布日期字段", i+1) + } + publishDateStr, ok := publishDateAny.(string) + if !ok || publishDateStr == "" { + return fmt.Errorf("第%d个公告的发布日期不能为空", i+1) + } + if _, err := time.Parse(time.RFC3339, publishDateStr); err != nil { + return fmt.Errorf("第%d个公告的发布日期格式错误", i+1) + } + if t, exists := ann["type"]; exists { + if typeStr, ok := t.(string); ok { + if !validTypes[typeStr] { + return fmt.Errorf("第%d个公告的类型值不合法", i+1) + } + } + } + if len(content) > 500 { + return fmt.Errorf("第%d个公告的内容长度不能超过500字符", i+1) + } + if extra, exists := ann["extra"]; exists { + if extraStr, ok := extra.(string); ok && len(extraStr) > 200 { + return fmt.Errorf("第%d个公告的说明长度不能超过200字符", i+1) + } + } + } + return nil +} + +func validateFAQ(faqStr string) error { + var list []map[string]interface{} + if err := json.Unmarshal([]byte(faqStr), &list); err != nil { + return fmt.Errorf("FAQ信息格式错误:%s", err.Error()) + } + if len(list) > 100 { + return fmt.Errorf("FAQ数量不能超过100个") + } + for i, faq := range list { + question, ok := faq["question"].(string) + if !ok || question == "" { + return fmt.Errorf("第%d个FAQ缺少问题字段", i+1) + } + answer, ok := faq["answer"].(string) + if !ok || answer == "" { + return fmt.Errorf("第%d个FAQ缺少答案字段", i+1) + } + if len(question) > 200 { + return fmt.Errorf("第%d个FAQ的问题长度不能超过200字符", i+1) + } + if len(answer) > 1000 { + return fmt.Errorf("第%d个FAQ的答案长度不能超过1000字符", i+1) + } + } + return nil +} + +// GetAnnouncements 获取系统公告 +func GetAnnouncements() []map[string]interface{} { + annStr := GetConsoleSetting().Announcements + if annStr == "" { + return []map[string]interface{}{} + } + var ann []map[string]interface{} + _ = json.Unmarshal([]byte(annStr), &ann) + return ann +} + +// GetFAQ 获取常见问题 +func GetFAQ() []map[string]interface{} { + faqStr := GetConsoleSetting().FAQ + if faqStr == "" { + return []map[string]interface{}{} + } + var faq []map[string]interface{} + _ = json.Unmarshal([]byte(faqStr), &faq) + return faq +} \ No newline at end of file diff --git a/web/src/components/settings/DashboardSetting.js b/web/src/components/settings/DashboardSetting.js index 649e0ffa..f52add07 100644 --- a/web/src/components/settings/DashboardSetting.js +++ b/web/src/components/settings/DashboardSetting.js @@ -8,11 +8,11 @@ import SettingsUptimeKuma from '../../pages/Setting/Dashboard/SettingsUptimeKuma const DashboardSetting = () => { let [inputs, setInputs] = useState({ - ApiInfo: '', - Announcements: '', - FAQ: '', - UptimeKumaUrl: '', - UptimeKumaSlug: '', + 'console_setting.api_info': '', + 'console_setting.announcements': '', + 'console_setting.faq': '', + 'console_setting.uptime_kuma_url': '', + 'console_setting.uptime_kuma_slug': '', }); let [loading, setLoading] = useState(false); diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index e4351088..f1d23871 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -1231,10 +1231,10 @@ const Detail = (props) => { {faqData.map((item, index) => ( -

{item.content}

+

{item.answer}

))} diff --git a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js index 67a475de..7cb7275b 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js +++ b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js @@ -85,7 +85,7 @@ const SettingsAPIInfo = ({ options, refresh }) => { try { setLoading(true); const apiInfoJson = JSON.stringify(apiInfoList); - await updateOption('ApiInfo', apiInfoJson); + await updateOption('console_setting.api_info', apiInfoJson); setHasChanges(false); } catch (error) { console.error('API信息更新失败', error); @@ -185,10 +185,11 @@ const SettingsAPIInfo = ({ options, refresh }) => { }; useEffect(() => { - if (options.ApiInfo !== undefined) { - parseApiInfo(options.ApiInfo); + const apiInfoStr = options['console_setting.api_info'] ?? options.ApiInfo; + if (apiInfoStr !== undefined) { + parseApiInfo(apiInfoStr); } - }, [options.ApiInfo]); + }, [options['console_setting.api_info'], options.ApiInfo]); const columns = [ { diff --git a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js index 2646f7bf..caa632e1 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js +++ b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js @@ -176,7 +176,7 @@ const SettingsAnnouncements = ({ options, refresh }) => { try { setLoading(true); const announcementsJson = JSON.stringify(announcementsList); - await updateOption('Announcements', announcementsJson); + await updateOption('console_setting.announcements', announcementsJson); setHasChanges(false); } catch (error) { console.error('系统公告更新失败', error); @@ -288,10 +288,11 @@ const SettingsAnnouncements = ({ options, refresh }) => { }; useEffect(() => { - if (options.Announcements !== undefined) { - parseAnnouncements(options.Announcements); + const annStr = options['console_setting.announcements'] ?? options.Announcements; + if (annStr !== undefined) { + parseAnnouncements(annStr); } - }, [options.Announcements]); + }, [options['console_setting.announcements'], options.Announcements]); const handleBatchDelete = () => { if (selectedRowKeys.length === 0) { diff --git a/web/src/pages/Setting/Dashboard/SettingsFAQ.js b/web/src/pages/Setting/Dashboard/SettingsFAQ.js index c5c65c1b..d1211899 100644 --- a/web/src/pages/Setting/Dashboard/SettingsFAQ.js +++ b/web/src/pages/Setting/Dashboard/SettingsFAQ.js @@ -37,8 +37,8 @@ const SettingsFAQ = ({ options, refresh }) => { const [loading, setLoading] = useState(false); const [hasChanges, setHasChanges] = useState(false); const [faqForm, setFaqForm] = useState({ - title: '', - content: '' + question: '', + answer: '' }); const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); @@ -47,8 +47,8 @@ const SettingsFAQ = ({ options, refresh }) => { const columns = [ { title: t('问题标题'), - dataIndex: 'title', - key: 'title', + dataIndex: 'question', + key: 'question', render: (text) => (
{ }, { title: t('回答内容'), - dataIndex: 'content', - key: 'content', + dataIndex: 'answer', + key: 'answer', render: (text) => (
{ try { setLoading(true); const faqJson = JSON.stringify(faqList); - await updateOption('FAQ', faqJson); + await updateOption('console_setting.faq', faqJson); setHasChanges(false); } catch (error) { console.error('常见问答更新失败', error); @@ -137,8 +137,8 @@ const SettingsFAQ = ({ options, refresh }) => { const handleAddFaq = () => { setEditingFaq(null); setFaqForm({ - title: '', - content: '' + question: '', + answer: '' }); setShowFaqModal(true); }; @@ -146,8 +146,8 @@ const SettingsFAQ = ({ options, refresh }) => { const handleEditFaq = (faq) => { setEditingFaq(faq); setFaqForm({ - title: faq.title, - content: faq.content + question: faq.question, + answer: faq.answer }); setShowFaqModal(true); }; @@ -169,7 +169,7 @@ const SettingsFAQ = ({ options, refresh }) => { }; const handleSaveFaq = async () => { - if (!faqForm.title || !faqForm.content) { + if (!faqForm.question || !faqForm.answer) { showError('请填写完整的问答信息'); return; } @@ -226,10 +226,10 @@ const SettingsFAQ = ({ options, refresh }) => { }; useEffect(() => { - if (options.FAQ !== undefined) { - parseFAQ(options.FAQ); + if (options['console_setting.faq'] !== undefined) { + parseFAQ(options['console_setting.faq']); } - }, [options.FAQ]); + }, [options['console_setting.faq']]); const handleBatchDelete = () => { if (selectedRowKeys.length === 0) { @@ -372,21 +372,21 @@ const SettingsFAQ = ({ options, refresh }) => { >
setFaqForm({ ...faqForm, title: value })} + onChange={(value) => setFaqForm({ ...faqForm, question: value })} /> setFaqForm({ ...faqForm, content: value })} + onChange={(value) => setFaqForm({ ...faqForm, answer: value })} /> diff --git a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js index 3a7b4896..58f8fedf 100644 --- a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js +++ b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js @@ -22,9 +22,9 @@ const SettingsUptimeKuma = ({ options, refresh }) => { const formApiRef = useRef(null); const initValues = useMemo(() => ({ - uptimeKumaUrl: options?.UptimeKumaUrl || '', - uptimeKumaSlug: options?.UptimeKumaSlug || '' - }), [options?.UptimeKumaUrl, options?.UptimeKumaSlug]); + uptimeKumaUrl: options?.['console_setting.uptime_kuma_url'] || '', + uptimeKumaSlug: options?.['console_setting.uptime_kuma_slug'] || '' + }), [options?.['console_setting.uptime_kuma_url'], options?.['console_setting.uptime_kuma_slug']]); useEffect(() => { if (formApiRef.current) { @@ -46,18 +46,18 @@ const SettingsUptimeKuma = ({ options, refresh }) => { const trimmedUrl = (uptimeKumaUrl || '').trim(); const trimmedSlug = (uptimeKumaSlug || '').trim(); - if (trimmedUrl === options?.UptimeKumaUrl && trimmedSlug === options?.UptimeKumaSlug) { + if (trimmedUrl === options?.['console_setting.uptime_kuma_url'] && trimmedSlug === options?.['console_setting.uptime_kuma_slug']) { showSuccess(t('无需保存,配置未变动')); return; } const [urlRes, slugRes] = await Promise.all([ - trimmedUrl === options?.UptimeKumaUrl ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { - key: 'UptimeKumaUrl', + trimmedUrl === options?.['console_setting.uptime_kuma_url'] ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { + key: 'console_setting.uptime_kuma_url', value: trimmedUrl }), - trimmedSlug === options?.UptimeKumaSlug ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { - key: 'UptimeKumaSlug', + trimmedSlug === options?.['console_setting.uptime_kuma_slug'] ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { + key: 'console_setting.uptime_kuma_slug', value: trimmedSlug }) ]); From 66403275b76a98acc71cc07714204b1cb2fccffe Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sat, 14 Jun 2025 00:59:38 +0800 Subject: [PATCH 25/61] =?UTF-8?q?=F0=9F=A7=B9=20refactor:=20drop=20obsolet?= =?UTF-8?q?e=20ValidateApiInfo=20API=20&=20update=20callers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend • Removed the exported function `ValidateApiInfo` from `setting/console_setting/validation.go`; it was only a legacy wrapper and is no longer required. • Updated `controller/option.go` to call `ValidateConsoleSettings(value, "ApiInfo")` directly when validating `console_setting.api_info`. • Confirmed there are no remaining references to `ValidateApiInfo` in the codebase. This commit eliminates the last piece of compatibility code related to the old validation interface, keeping the API surface clean and consistent. --- controller/option.go | 2 +- setting/console_setting/validation.go | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/controller/option.go b/controller/option.go index b782c1f2..521d7327 100644 --- a/controller/option.go +++ b/controller/option.go @@ -121,7 +121,7 @@ func UpdateOption(c *gin.Context) { return } case "console_setting.api_info": - err = console_setting.ValidateApiInfo(option.Value) + err = console_setting.ValidateConsoleSettings(option.Value, "ApiInfo") if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/setting/console_setting/validation.go b/setting/console_setting/validation.go index fbab6e87..3a9f3c83 100644 --- a/setting/console_setting/validation.go +++ b/setting/console_setting/validation.go @@ -96,11 +96,6 @@ func validateApiInfo(apiInfoStr string) error { return nil } -// ValidateApiInfo 保持向后兼容 -func ValidateApiInfo(apiInfoStr string) error { - return validateApiInfo(apiInfoStr) -} - // GetApiInfo 获取 API 信息列表 func GetApiInfo() []map[string]interface{} { apiInfoStr := GetConsoleSetting().ApiInfo From a9cdbce9de9ac272988dc00616cd6c4ee55469a3 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sat, 14 Jun 2025 01:05:09 +0800 Subject: [PATCH 26/61] =?UTF-8?q?=E2=9C=A8=20feat:=20Add=20`controller/con?= =?UTF-8?q?sole=5Fmigrate.go`=20providing=20`/api/option/migrate=5Fconsole?= =?UTF-8?q?=5Fsetting`=20endpoint=20for=20one-off=20data=20migration.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/console_migrate.go | 88 +++++++++++++++++++ router/api-router.go | 1 + .../components/settings/DashboardSetting.js | 57 +++++++++++- 3 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 controller/console_migrate.go diff --git a/controller/console_migrate.go b/controller/console_migrate.go new file mode 100644 index 00000000..937fa2ee --- /dev/null +++ b/controller/console_migrate.go @@ -0,0 +1,88 @@ +// 用于迁移检测的旧键,该文件下个版本会删除 + +package controller + +import ( + "encoding/json" + "net/http" + "one-api/common" + "one-api/model" + "github.com/gin-gonic/gin" +) + +// MigrateConsoleSetting 迁移旧的控制台相关配置到 console_setting.* +func MigrateConsoleSetting(c *gin.Context) { + // 读取全部 option + opts, err := model.AllOption() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) + return + } + // 建立 map + valMap := map[string]string{} + for _, o := range opts { + valMap[o.Key] = o.Value + } + + // 处理 APIInfo + if v := valMap["ApiInfo"]; v != "" { + var arr []map[string]interface{} + if err := json.Unmarshal([]byte(v), &arr); err == nil { + if len(arr) > 50 { + arr = arr[:50] + } + bytes, _ := json.Marshal(arr) + model.UpdateOption("console_setting.api_info", string(bytes)) + } + model.UpdateOption("ApiInfo", "") + } + // Announcements 直接搬 + if v := valMap["Announcements"]; v != "" { + model.UpdateOption("console_setting.announcements", v) + model.UpdateOption("Announcements", "") + } + // FAQ 转换 + if v := valMap["FAQ"]; v != "" { + var arr []map[string]interface{} + if err := json.Unmarshal([]byte(v), &arr); err == nil { + out := []map[string]interface{}{} + for _, item := range arr { + q, _ := item["question"].(string) + if q == "" { + q, _ = item["title"].(string) + } + a, _ := item["answer"].(string) + if a == "" { + a, _ = item["content"].(string) + } + if q != "" && a != "" { + out = append(out, map[string]interface{}{"question": q, "answer": a}) + } + } + if len(out) > 50 { + out = out[:50] + } + bytes, _ := json.Marshal(out) + model.UpdateOption("console_setting.faq", string(bytes)) + } + model.UpdateOption("FAQ", "") + } + // Uptime + if v := valMap["UptimeKumaUrl"]; v != "" { + model.UpdateOption("console_setting.uptime_kuma_url", v) + model.UpdateOption("UptimeKumaUrl", "") + } + if v := valMap["UptimeKumaSlug"]; v != "" { + model.UpdateOption("console_setting.uptime_kuma_slug", v) + model.UpdateOption("UptimeKumaSlug", "") + } + + // 删除旧键记录 + oldKeys := []string{"ApiInfo", "Announcements", "FAQ", "UptimeKumaUrl", "UptimeKumaSlug"} + model.DB.Where("key IN ?", oldKeys).Delete(&model.Option{}) + + // 重新加载 OptionMap + model.InitOptionMap() + common.SysLog("console setting migrated") + c.JSON(http.StatusOK, gin.H{"success": true, "message": "migrated"}) +} \ No newline at end of file diff --git a/router/api-router.go b/router/api-router.go index 851e9193..45930246 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -81,6 +81,7 @@ func SetApiRouter(router *gin.Engine) { optionRoute.GET("/", controller.GetOptions) optionRoute.PUT("/", controller.UpdateOption) optionRoute.POST("/rest_model_ratio", controller.ResetModelRatio) + optionRoute.POST("/migrate_console_setting", controller.MigrateConsoleSetting) // 用于迁移检测的旧键,下个版本会删除 } channelRoute := apiRouter.Group("/channel") channelRoute.Use(middleware.AdminAuth()) diff --git a/web/src/components/settings/DashboardSetting.js b/web/src/components/settings/DashboardSetting.js index f52add07..7021c7ca 100644 --- a/web/src/components/settings/DashboardSetting.js +++ b/web/src/components/settings/DashboardSetting.js @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from 'react'; -import { Card, Spin } from '@douyinfe/semi-ui'; -import { API, showError } from '../../helpers'; +import React, { useEffect, useState, useMemo } from 'react'; +import { Card, Spin, Button, Modal } from '@douyinfe/semi-ui'; +import { API, showError, showSuccess } from '../../helpers'; import SettingsAPIInfo from '../../pages/Setting/Dashboard/SettingsAPIInfo.js'; import SettingsAnnouncements from '../../pages/Setting/Dashboard/SettingsAnnouncements.js'; import SettingsFAQ from '../../pages/Setting/Dashboard/SettingsFAQ.js'; @@ -13,9 +13,17 @@ const DashboardSetting = () => { 'console_setting.faq': '', 'console_setting.uptime_kuma_url': '', 'console_setting.uptime_kuma_slug': '', + + // 用于迁移检测的旧键,下个版本会删除 + ApiInfo: '', + Announcements: '', + FAQ: '', + UptimeKumaUrl: '', + UptimeKumaSlug: '', }); let [loading, setLoading] = useState(false); + const [showMigrateModal, setShowMigrateModal] = useState(false); // 下个版本会删除 const getOptions = async () => { const res = await API.get('/api/option/'); @@ -49,9 +57,52 @@ const DashboardSetting = () => { onRefresh(); }, []); + // 用于迁移检测的旧键,下个版本会删除 + const hasLegacyData = useMemo(() => { + const legacyKeys = ['ApiInfo', 'Announcements', 'FAQ', 'UptimeKumaUrl', 'UptimeKumaSlug']; + return legacyKeys.some(k => inputs[k]); + }, [inputs]); + + useEffect(() => { + if (hasLegacyData) { + setShowMigrateModal(true); + } + }, [hasLegacyData]); + + const handleMigrate = async () => { + try { + setLoading(true); + await API.post('/api/option/migrate_console_setting'); + showSuccess('旧配置迁移完成'); + await onRefresh(); + setShowMigrateModal(false); + } catch (err) { + console.error(err); + showError('迁移失败: ' + (err.message || '未知错误')); + } finally { + setLoading(false); + } + }; + return ( <> + {/* 用于迁移检测的旧键模态框,下个版本会删除 */} + setShowMigrateModal(false)} + confirmLoading={loading} + okText="确认迁移" + cancelText="取消" + > +

检测到旧版本的配置数据,是否要迁移到新的配置格式?

+

+ 注意:迁移过程中会自动处理数据格式转换,迁移完成后旧配置将被清除,请在迁移前在数据库中备份好旧配置。 +

+
+ {/* API信息管理 */} From 4c05377c870268387bbb3a29e226ea50e6556071 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sat, 14 Jun 2025 01:39:23 +0800 Subject: [PATCH 27/61] =?UTF-8?q?=F0=9F=8E=9B=EF=B8=8F=20feat(dashboard):?= =?UTF-8?q?=20add=20per-panel=20enable=20switches=20&=20conditional=20back?= =?UTF-8?q?end=20payload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: • ConsoleSetting - Introduce `ApiInfoEnabled`, `UptimeKumaEnabled`, `AnnouncementsEnabled`, `FAQEnabled` (default true). • misc.GetStatus - Refactor to build response map dynamically. - Return the four *_enabled flags. - Only append `api_info`, `announcements`, `faq` when their respective flags are true. Frontend: • Detail page - Remove all `self_use_mode_enabled` checks. - Render API, Announcement, FAQ and Uptime panels based on the new *_enabled flags. • Dashboard → Settings - Added `Switch` controls in: · SettingsAPIInfo.js · SettingsAnnouncements.js · SettingsFAQ.js · SettingsUptimeKuma.js - Each switch persists its state via `/api/option` to the corresponding `console_setting._enabled` key and reflects current status on load. - DashboardSetting.js now initialises and refreshes the four *_enabled keys so child components receive accurate panel states. Fixes: • Switches previously defaulted to “on” because *_enabled keys were missing. They are now included, ensuring correct visual state when panels are disabled. No breaking changes; existing functionality remains untouched aside from the new per-panel visibility control. --- controller/misc.go | 105 +++-- setting/console_setting/config.go | 8 + .../components/settings/DashboardSetting.js | 4 + web/src/pages/Detail/index.js | 417 +++++++++--------- .../Setting/Dashboard/SettingsAPIInfo.js | 39 +- .../Dashboard/SettingsAnnouncements.js | 36 +- .../pages/Setting/Dashboard/SettingsFAQ.js | 36 +- .../Setting/Dashboard/SettingsUptimeKuma.js | 31 +- 8 files changed, 428 insertions(+), 248 deletions(-) diff --git a/controller/misc.go b/controller/misc.go index ccbdecb5..33a41302 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -38,52 +38,71 @@ func TestStatus(c *gin.Context) { func GetStatus(c *gin.Context) { + cs := console_setting.GetConsoleSetting() + + data := gin.H{ + "version": common.Version, + "start_time": common.StartTime, + "email_verification": common.EmailVerificationEnabled, + "github_oauth": common.GitHubOAuthEnabled, + "github_client_id": common.GitHubClientId, + "linuxdo_oauth": common.LinuxDOOAuthEnabled, + "linuxdo_client_id": common.LinuxDOClientId, + "telegram_oauth": common.TelegramOAuthEnabled, + "telegram_bot_name": common.TelegramBotName, + "system_name": common.SystemName, + "logo": common.Logo, + "footer_html": common.Footer, + "wechat_qrcode": common.WeChatAccountQRCodeImageURL, + "wechat_login": common.WeChatAuthEnabled, + "server_address": setting.ServerAddress, + "price": setting.Price, + "min_topup": setting.MinTopUp, + "turnstile_check": common.TurnstileCheckEnabled, + "turnstile_site_key": common.TurnstileSiteKey, + "top_up_link": common.TopUpLink, + "docs_link": operation_setting.GetGeneralSetting().DocsLink, + "quota_per_unit": common.QuotaPerUnit, + "display_in_currency": common.DisplayInCurrencyEnabled, + "enable_batch_update": common.BatchUpdateEnabled, + "enable_drawing": common.DrawingEnabled, + "enable_task": common.TaskEnabled, + "enable_data_export": common.DataExportEnabled, + "data_export_default_time": common.DataExportDefaultTime, + "default_collapse_sidebar": common.DefaultCollapseSidebar, + "enable_online_topup": setting.PayAddress != "" && setting.EpayId != "" && setting.EpayKey != "", + "mj_notify_enabled": setting.MjNotifyEnabled, + "chats": setting.Chats, + "demo_site_enabled": operation_setting.DemoSiteEnabled, + "self_use_mode_enabled": operation_setting.SelfUseModeEnabled, + + // 面板启用开关 + "api_info_enabled": cs.ApiInfoEnabled, + "uptime_kuma_enabled": cs.UptimeKumaEnabled, + "announcements_enabled": cs.AnnouncementsEnabled, + "faq_enabled": cs.FAQEnabled, + + "oidc_enabled": system_setting.GetOIDCSettings().Enabled, + "oidc_client_id": system_setting.GetOIDCSettings().ClientId, + "oidc_authorization_endpoint": system_setting.GetOIDCSettings().AuthorizationEndpoint, + "setup": constant.Setup, + } + + // 根据启用状态注入可选内容 + if cs.ApiInfoEnabled { + data["api_info"] = console_setting.GetApiInfo() + } + if cs.AnnouncementsEnabled { + data["announcements"] = console_setting.GetAnnouncements() + } + if cs.FAQEnabled { + data["faq"] = console_setting.GetFAQ() + } + c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", - "data": gin.H{ - "version": common.Version, - "start_time": common.StartTime, - "email_verification": common.EmailVerificationEnabled, - "github_oauth": common.GitHubOAuthEnabled, - "github_client_id": common.GitHubClientId, - "linuxdo_oauth": common.LinuxDOOAuthEnabled, - "linuxdo_client_id": common.LinuxDOClientId, - "telegram_oauth": common.TelegramOAuthEnabled, - "telegram_bot_name": common.TelegramBotName, - "system_name": common.SystemName, - "logo": common.Logo, - "footer_html": common.Footer, - "wechat_qrcode": common.WeChatAccountQRCodeImageURL, - "wechat_login": common.WeChatAuthEnabled, - "server_address": setting.ServerAddress, - "price": setting.Price, - "min_topup": setting.MinTopUp, - "turnstile_check": common.TurnstileCheckEnabled, - "turnstile_site_key": common.TurnstileSiteKey, - "top_up_link": common.TopUpLink, - "docs_link": operation_setting.GetGeneralSetting().DocsLink, - "quota_per_unit": common.QuotaPerUnit, - "display_in_currency": common.DisplayInCurrencyEnabled, - "enable_batch_update": common.BatchUpdateEnabled, - "enable_drawing": common.DrawingEnabled, - "enable_task": common.TaskEnabled, - "enable_data_export": common.DataExportEnabled, - "data_export_default_time": common.DataExportDefaultTime, - "default_collapse_sidebar": common.DefaultCollapseSidebar, - "enable_online_topup": setting.PayAddress != "" && setting.EpayId != "" && setting.EpayKey != "", - "mj_notify_enabled": setting.MjNotifyEnabled, - "chats": setting.Chats, - "demo_site_enabled": operation_setting.DemoSiteEnabled, - "self_use_mode_enabled": operation_setting.SelfUseModeEnabled, - "oidc_enabled": system_setting.GetOIDCSettings().Enabled, - "oidc_client_id": system_setting.GetOIDCSettings().ClientId, - "oidc_authorization_endpoint": system_setting.GetOIDCSettings().AuthorizationEndpoint, - "setup": constant.Setup, - "api_info": console_setting.GetApiInfo(), - "announcements": console_setting.GetAnnouncements(), - "faq": console_setting.GetFAQ(), - }, + "data": data, }) return } diff --git a/setting/console_setting/config.go b/setting/console_setting/config.go index 1379380e..063130fc 100644 --- a/setting/console_setting/config.go +++ b/setting/console_setting/config.go @@ -8,6 +8,10 @@ type ConsoleSetting struct { UptimeKumaSlug string `json:"uptime_kuma_slug"` // Uptime Kuma Status Page Slug Announcements string `json:"announcements"` // 系统公告 (JSON 数组字符串) FAQ string `json:"faq"` // 常见问题 (JSON 数组字符串) + ApiInfoEnabled bool `json:"api_info_enabled"` // 是否启用 API 信息面板 + UptimeKumaEnabled bool `json:"uptime_kuma_enabled"` // 是否启用 Uptime Kuma 面板 + AnnouncementsEnabled bool `json:"announcements_enabled"` // 是否启用系统公告面板 + FAQEnabled bool `json:"faq_enabled"` // 是否启用常见问答面板 } // 默认配置 @@ -17,6 +21,10 @@ var defaultConsoleSetting = ConsoleSetting{ UptimeKumaSlug: "", Announcements: "", FAQ: "", + ApiInfoEnabled: true, + UptimeKumaEnabled: true, + AnnouncementsEnabled: true, + FAQEnabled: true, } // 全局实例 diff --git a/web/src/components/settings/DashboardSetting.js b/web/src/components/settings/DashboardSetting.js index 7021c7ca..0546ca21 100644 --- a/web/src/components/settings/DashboardSetting.js +++ b/web/src/components/settings/DashboardSetting.js @@ -13,6 +13,10 @@ const DashboardSetting = () => { 'console_setting.faq': '', 'console_setting.uptime_kuma_url': '', 'console_setting.uptime_kuma_slug': '', + 'console_setting.api_info_enabled': '', + 'console_setting.announcements_enabled': '', + 'console_setting.faq_enabled': '', + 'console_setting.uptime_kuma_enabled': '', // 用于迁移检测的旧键,下个版本会删除 ApiInfo: '', diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index f1d23871..747c1cf6 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -90,6 +90,15 @@ const Detail = (props) => { let now = new Date(); const isAdminUser = isAdmin(); + // ========== Panel enable flags ========== + const apiInfoEnabled = statusState?.status?.api_info_enabled ?? true; + const announcementsEnabled = statusState?.status?.announcements_enabled ?? true; + const faqEnabled = statusState?.status?.faq_enabled ?? true; + const uptimeEnabled = statusState?.status?.uptime_kuma_enabled ?? true; + + const hasApiInfoPanel = apiInfoEnabled; + const hasInfoPanels = announcementsEnabled || faqEnabled || uptimeEnabled; + // ========== Helper Functions ========== const getDefaultTime = useCallback(() => { return localStorage.getItem('data_export_default_time') || 'hour'; @@ -1015,10 +1024,10 @@ const Detail = (props) => {
-
+
@@ -1061,7 +1070,7 @@ const Detail = (props) => {
- {!statusState?.status?.self_use_mode_enabled && ( + {hasApiInfoPanel && ( {
{/* 系统公告和常见问答卡片 */} - {!statusState?.status?.self_use_mode_enabled && ( + {hasInfoPanels && (
{/* 公告卡片 */} - -
- - {t('系统公告')} - - {t('显示最新20条')} - -
- {/* 图例 */} -
- {announcementLegendData.map((legend, index) => ( -
-
- {legend.label} -
- ))} -
-
- } - > -
-
handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)} - > - {announcementData.length > 0 ? ( - - ) : ( -
- } - darkModeImage={} - title={t('暂无系统公告')} - description={t('请联系管理员在系统设置中配置公告信息')} - style={{ padding: '12px' }} - /> + {announcementsEnabled && ( + +
+ + {t('系统公告')} + + {t('显示最新20条')} +
- )} -
-
-
- - - {/* 常见问答卡片 */} - - - {t('常见问答')} -
- } - > -
-
handleCardScroll(faqScrollRef, setShowFaqScrollHint)} - > - {faqData.length > 0 ? ( - } - collapseIcon={} - > - {faqData.map((item, index) => ( - -

{item.answer}

-
- ))} -
- ) : ( -
- } - darkModeImage={} - title={t('暂无常见问答')} - description={t('请联系管理员在系统设置中配置常见问答')} - style={{ padding: '12px' }} - /> -
- )} -
-
-
- - - {/* 服务可用性卡片 */} - -
- - {t('服务可用性')} -
- } - onClick={loadUptimeData} - loading={uptimeLoading} - size="small" - theme="borderless" - className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full" - /> -
- } - footer={uptimeData.length > 0 ? ( - -
- {uptimeLegendData.map((legend, index) => ( -
-
- {legend.label} -
- ))} -
- - ) : null} - footerStyle={uptimeData.length > 0 ? { padding: '0px' } : undefined} - > -
- -
handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)} - > - {uptimeData.length > 0 ? ( - uptimeData.map((monitor, idx) => ( -
-
-
-
- {monitor.name} -
- {((monitor.uptime || 0) * 100).toFixed(2)}% -
-
- {getUptimeStatusText(monitor.status)} -
- -
-
+ {/* 图例 */} +
+ {announcementLegendData.map((legend, index) => ( +
+
+ {legend.label}
- )) + ))} +
+
+ } + > +
+
handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)} + > + {announcementData.length > 0 ? ( + ) : (
} darkModeImage={} - title={t('暂无监控数据')} - description={t('请联系管理员在系统设置中配置Uptime')} + title={t('暂无系统公告')} + description={t('请联系管理员在系统设置中配置公告信息')} style={{ padding: '12px' }} />
)}
- -
-
- +
+
+ + )} + + {/* 常见问答卡片 */} + {faqEnabled && ( + + + {t('常见问答')} +
+ } + > +
+
handleCardScroll(faqScrollRef, setShowFaqScrollHint)} + > + {faqData.length > 0 ? ( + } + collapseIcon={} + > + {faqData.map((item, index) => ( + +

{item.answer}

+
+ ))} +
+ ) : ( +
+ } + darkModeImage={} + title={t('暂无常见问答')} + description={t('请联系管理员在系统设置中配置常见问答')} + style={{ padding: '12px' }} + /> +
+ )} +
+
+
+ + )} + + {/* 服务可用性卡片 */} + {uptimeEnabled && ( + +
+ + {t('服务可用性')} +
+ } + onClick={loadUptimeData} + loading={uptimeLoading} + size="small" + theme="borderless" + className="text-gray-500 hover:text-blue-500 hover:bg-blue-50 !rounded-full" + /> +
+ } + footer={uptimeData.length > 0 ? ( + +
+ {uptimeLegendData.map((legend, index) => ( +
+
+ {legend.label} +
+ ))} +
+ + ) : null} + footerStyle={uptimeData.length > 0 ? { padding: '0px' } : undefined} + > +
+ +
handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)} + > + {uptimeData.length > 0 ? ( + uptimeData.map((monitor, idx) => ( +
+
+
+
+ {monitor.name} +
+ {((monitor.uptime || 0) * 100).toFixed(2)}% +
+
+ {getUptimeStatusText(monitor.status)} +
+ +
+
+
+ )) + ) : ( +
+ } + darkModeImage={} + title={t('暂无监控数据')} + description={t('请联系管理员在系统设置中配置Uptime')} + style={{ padding: '12px' }} + /> +
+ )} +
+ +
+
+ + )}
)} diff --git a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js index 7cb7275b..6a80b358 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js +++ b/web/src/pages/Setting/Dashboard/SettingsAPIInfo.js @@ -9,7 +9,8 @@ import { Divider, Avatar, Modal, - Tag + Tag, + Switch } from '@douyinfe/semi-ui'; import { IllustrationNoResult, @@ -48,6 +49,9 @@ const SettingsAPIInfo = ({ options, refresh }) => { const [pageSize, setPageSize] = useState(10); const [selectedRowKeys, setSelectedRowKeys] = useState([]); + // 面板启用状态 state + const [panelEnabled, setPanelEnabled] = useState(true); + const colorOptions = [ { value: 'blue', label: 'blue' }, { value: 'green', label: 'green' }, @@ -191,6 +195,30 @@ const SettingsAPIInfo = ({ options, refresh }) => { } }, [options['console_setting.api_info'], options.ApiInfo]); + useEffect(() => { + const enabledStr = options['console_setting.api_info_enabled']; + setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true); + }, [options['console_setting.api_info_enabled']]); + + const handleToggleEnabled = async (checked) => { + const newValue = checked ? 'true' : 'false'; + try { + const res = await API.put('/api/option/', { + key: 'console_setting.api_info_enabled', + value: newValue, + }); + if (res.data.success) { + setPanelEnabled(checked); + showSuccess(t('设置已保存')); + refresh?.(); + } else { + showError(res.data.message); + } + } catch (err) { + showError(err.message); + } + }; + const columns = [ { title: 'ID', @@ -325,6 +353,15 @@ const SettingsAPIInfo = ({ options, refresh }) => { {t('保存设置')}
+ + {/* 启用开关 */} +
+ + {panelEnabled ? t('已启用') : t('已禁用')} +
); diff --git a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js index caa632e1..f1b99f43 100644 --- a/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js +++ b/web/src/pages/Setting/Dashboard/SettingsAnnouncements.js @@ -8,7 +8,8 @@ import { Empty, Divider, Modal, - Tag + Tag, + Switch } from '@douyinfe/semi-ui'; import { IllustrationNoResult, @@ -47,6 +48,9 @@ const SettingsAnnouncements = ({ options, refresh }) => { const [pageSize, setPageSize] = useState(10); const [selectedRowKeys, setSelectedRowKeys] = useState([]); + // 面板启用状态 + const [panelEnabled, setPanelEnabled] = useState(true); + const typeOptions = [ { value: 'default', label: t('默认') }, { value: 'ongoing', label: t('进行中') }, @@ -294,6 +298,30 @@ const SettingsAnnouncements = ({ options, refresh }) => { } }, [options['console_setting.announcements'], options.Announcements]); + useEffect(() => { + const enabledStr = options['console_setting.announcements_enabled']; + setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true); + }, [options['console_setting.announcements_enabled']]); + + const handleToggleEnabled = async (checked) => { + const newValue = checked ? 'true' : 'false'; + try { + const res = await API.put('/api/option/', { + key: 'console_setting.announcements_enabled', + value: newValue, + }); + if (res.data.success) { + setPanelEnabled(checked); + showSuccess(t('设置已保存')); + refresh?.(); + } else { + showError(res.data.message); + } + } catch (err) { + showError(err.message); + } + }; + const handleBatchDelete = () => { if (selectedRowKeys.length === 0) { showError('请先选择要删除的系统公告'); @@ -350,6 +378,12 @@ const SettingsAnnouncements = ({ options, refresh }) => { {t('保存设置')}
+ + {/* 启用开关 */} +
+ + {panelEnabled ? t('已启用') : t('已禁用')} +
); diff --git a/web/src/pages/Setting/Dashboard/SettingsFAQ.js b/web/src/pages/Setting/Dashboard/SettingsFAQ.js index d1211899..0e029e13 100644 --- a/web/src/pages/Setting/Dashboard/SettingsFAQ.js +++ b/web/src/pages/Setting/Dashboard/SettingsFAQ.js @@ -7,7 +7,8 @@ import { Typography, Empty, Divider, - Modal + Modal, + Switch } from '@douyinfe/semi-ui'; import { IllustrationNoResult, @@ -44,6 +45,9 @@ const SettingsFAQ = ({ options, refresh }) => { const [pageSize, setPageSize] = useState(10); const [selectedRowKeys, setSelectedRowKeys] = useState([]); + // 面板启用状态 + const [panelEnabled, setPanelEnabled] = useState(true); + const columns = [ { title: t('问题标题'), @@ -231,6 +235,30 @@ const SettingsFAQ = ({ options, refresh }) => { } }, [options['console_setting.faq']]); + useEffect(() => { + const enabledStr = options['console_setting.faq_enabled']; + setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true); + }, [options['console_setting.faq_enabled']]); + + const handleToggleEnabled = async (checked) => { + const newValue = checked ? 'true' : 'false'; + try { + const res = await API.put('/api/option/', { + key: 'console_setting.faq_enabled', + value: newValue, + }); + if (res.data.success) { + setPanelEnabled(checked); + showSuccess(t('设置已保存')); + refresh?.(); + } else { + showError(res.data.message); + } + } catch (err) { + showError(err.message); + } + }; + const handleBatchDelete = () => { if (selectedRowKeys.length === 0) { showError('请先选择要删除的常见问答'); @@ -287,6 +315,12 @@ const SettingsFAQ = ({ options, refresh }) => { {t('保存设置')}
+ + {/* 启用开关 */} +
+ + {panelEnabled ? t('已启用') : t('已禁用')} +
); diff --git a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js index 58f8fedf..d489b683 100644 --- a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js +++ b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js @@ -5,6 +5,7 @@ import { Typography, Row, Col, + Switch, } from '@douyinfe/semi-ui'; import { Save, @@ -19,6 +20,7 @@ const SettingsUptimeKuma = ({ options, refresh }) => { const { t } = useTranslation(); const [loading, setLoading] = useState(false); + const [panelEnabled, setPanelEnabled] = useState(true); const formApiRef = useRef(null); const initValues = useMemo(() => ({ @@ -32,6 +34,11 @@ const SettingsUptimeKuma = ({ options, refresh }) => { } }, [initValues]); + useEffect(() => { + const enabledStr = options?.['console_setting.uptime_kuma_enabled']; + setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true); + }, [options?.['console_setting.uptime_kuma_enabled']]); + const handleSave = async () => { const api = formApiRef.current; if (!api) { @@ -75,6 +82,25 @@ const SettingsUptimeKuma = ({ options, refresh }) => { } }; + const handleToggleEnabled = async (checked) => { + const newValue = checked ? 'true' : 'false'; + try { + const res = await API.put('/api/option/', { + key: 'console_setting.uptime_kuma_enabled', + value: newValue, + }); + if (res.data.success) { + setPanelEnabled(checked); + showSuccess(t('设置已保存')); + refresh?.(); + } else { + showError(res.data.message); + } + } catch (err) { + showError(err.message); + } + }; + const isValidUrl = useCallback((string) => { try { new URL(string); @@ -103,7 +129,7 @@ const SettingsUptimeKuma = ({ options, refresh }) => {
-
+
+ + + {panelEnabled ? t('已启用') : t('已禁用')}
From b9b4b2496173a4285a8c0caa9780554c8ba7f779 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sat, 14 Jun 2025 17:51:05 +0800 Subject: [PATCH 28/61] fix: Resolving conflicts caused by mixing multiple databases --- common/database.go | 7 +++ model/ability.go | 12 ++-- model/channel.go | 20 +++---- model/log.go | 28 ++++++--- model/main.go | 140 ++++++++++++++++++++++++++------------------- model/token.go | 4 +- model/user.go | 6 +- 7 files changed, 129 insertions(+), 88 deletions(-) diff --git a/common/database.go b/common/database.go index 3c0a944b..9cbaf46a 100644 --- a/common/database.go +++ b/common/database.go @@ -1,7 +1,14 @@ package common +const ( + DatabaseTypeMySQL = "mysql" + DatabaseTypeSQLite = "sqlite" + DatabaseTypePostgreSQL = "postgres" +) + var UsingSQLite = false var UsingPostgreSQL = false +var LogSqlType = DatabaseTypeSQLite // Default to SQLite for logging SQL queries var UsingMySQL = false var UsingClickHouse = false diff --git a/model/ability.go b/model/ability.go index dd1a11be..c4df5d07 100644 --- a/model/ability.go +++ b/model/ability.go @@ -24,7 +24,7 @@ type Ability struct { func GetGroupModels(group string) []string { var models []string // Find distinct models - DB.Table("abilities").Where(groupCol+" = ? and enabled = ?", group, true).Distinct("model").Pluck("model", &models) + DB.Table("abilities").Where(commonGroupCol+" = ? and enabled = ?", group, true).Distinct("model").Pluck("model", &models) return models } @@ -50,8 +50,8 @@ func getPriority(group string, model string, retry int) (int, error) { var priorities []int err := DB.Model(&Ability{}). Select("DISTINCT(priority)"). - Where(groupCol+" = ? and model = ? and enabled = "+trueVal, group, model). - Order("priority DESC"). // 按优先级降序排序 + Where(commonGroupCol+" = ? and model = ? and enabled = "+trueVal, group, model). + Order("priority DESC"). // 按优先级降序排序 Pluck("priority", &priorities).Error // Pluck用于将查询的结果直接扫描到一个切片中 if err != nil { @@ -80,14 +80,14 @@ func getChannelQuery(group string, model string, retry int) *gorm.DB { if common.UsingPostgreSQL { trueVal = "true" } - maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(groupCol+" = ? and model = ? and enabled = "+trueVal, group, model) - channelQuery := DB.Where(groupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = (?)", group, model, maxPrioritySubQuery) + maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(commonGroupCol+" = ? and model = ? and enabled = "+trueVal, group, model) + channelQuery := DB.Where(commonGroupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = (?)", group, model, maxPrioritySubQuery) if retry != 0 { priority, err := getPriority(group, model, retry) if err != nil { common.SysError(fmt.Sprintf("Get priority failed: %s", err.Error())) } else { - channelQuery = DB.Where(groupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = ?", group, model, priority) + channelQuery = DB.Where(commonGroupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = ?", group, model, priority) } } diff --git a/model/channel.go b/model/channel.go index a302df40..598e1b6e 100644 --- a/model/channel.go +++ b/model/channel.go @@ -145,7 +145,7 @@ func SearchChannels(keyword string, group string, model string, idSort bool) ([] } // 构造基础查询 - baseQuery := DB.Model(&Channel{}).Omit(keyCol) + baseQuery := DB.Model(&Channel{}).Omit(commonKeyCol) // 构造WHERE子句 var whereClause string @@ -153,15 +153,15 @@ func SearchChannels(keyword string, group string, model string, idSort bool) ([] if group != "" && group != "null" { var groupCondition string if common.UsingMySQL { - groupCondition = `CONCAT(',', ` + groupCol + `, ',') LIKE ?` + groupCondition = `CONCAT(',', ` + commonGroupCol + `, ',') LIKE ?` } else { // sqlite, PostgreSQL - groupCondition = `(',' || ` + groupCol + ` || ',') LIKE ?` + groupCondition = `(',' || ` + commonGroupCol + ` || ',') LIKE ?` } - whereClause = "(id = ? OR name LIKE ? OR " + keyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + ` LIKE ? AND ` + groupCondition + whereClause = "(id = ? OR name LIKE ? OR " + commonKeyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + ` LIKE ? AND ` + groupCondition args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+keyword+"%", "%"+model+"%", "%,"+group+",%") } else { - whereClause = "(id = ? OR name LIKE ? OR " + keyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + " LIKE ?" + whereClause = "(id = ? OR name LIKE ? OR " + commonKeyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + " LIKE ?" args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+keyword+"%", "%"+model+"%") } @@ -478,7 +478,7 @@ func SearchTags(keyword string, group string, model string, idSort bool) ([]*str } // 构造基础查询 - baseQuery := DB.Model(&Channel{}).Omit(keyCol) + baseQuery := DB.Model(&Channel{}).Omit(commonKeyCol) // 构造WHERE子句 var whereClause string @@ -486,15 +486,15 @@ func SearchTags(keyword string, group string, model string, idSort bool) ([]*str if group != "" && group != "null" { var groupCondition string if common.UsingMySQL { - groupCondition = `CONCAT(',', ` + groupCol + `, ',') LIKE ?` + groupCondition = `CONCAT(',', ` + commonGroupCol + `, ',') LIKE ?` } else { // sqlite, PostgreSQL - groupCondition = `(',' || ` + groupCol + ` || ',') LIKE ?` + groupCondition = `(',' || ` + commonGroupCol + ` || ',') LIKE ?` } - whereClause = "(id = ? OR name LIKE ? OR " + keyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + ` LIKE ? AND ` + groupCondition + whereClause = "(id = ? OR name LIKE ? OR " + commonKeyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + ` LIKE ? AND ` + groupCondition args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+keyword+"%", "%"+model+"%", "%,"+group+",%") } else { - whereClause = "(id = ? OR name LIKE ? OR " + keyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + " LIKE ?" + whereClause = "(id = ? OR name LIKE ? OR " + commonKeyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + " LIKE ?" args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+keyword+"%", "%"+model+"%") } diff --git a/model/log.go b/model/log.go index 3df961e1..674f399a 100644 --- a/model/log.go +++ b/model/log.go @@ -63,7 +63,7 @@ func formatUserLogs(logs []*Log) { func GetLogByKey(key string) (logs []*Log, err error) { if os.Getenv("LOG_SQL_DSN") != "" { var tk Token - if err = DB.Model(&Token{}).Where(keyCol+"=?", strings.TrimPrefix(key, "sk-")).First(&tk).Error; err != nil { + if err = DB.Model(&Token{}).Where(logKeyCol+"=?", strings.TrimPrefix(key, "sk-")).First(&tk).Error; err != nil { return nil, err } err = LOG_DB.Model(&Log{}).Where("token_id=?", tk.Id).Find(&logs).Error @@ -122,8 +122,13 @@ func RecordErrorLog(c *gin.Context, userId int, channelId int, modelName string, UseTime: useTimeSeconds, IsStream: isStream, Group: group, - Ip: func() string { if needRecordIp { return c.ClientIP() }; return "" }(), - Other: otherStr, + Ip: func() string { + if needRecordIp { + return c.ClientIP() + } + return "" + }(), + Other: otherStr, } err := LOG_DB.Create(log).Error if err != nil { @@ -165,8 +170,13 @@ func RecordConsumeLog(c *gin.Context, userId int, channelId int, promptTokens in UseTime: useTimeSeconds, IsStream: isStream, Group: group, - Ip: func() string { if needRecordIp { return c.ClientIP() }; return "" }(), - Other: otherStr, + Ip: func() string { + if needRecordIp { + return c.ClientIP() + } + return "" + }(), + Other: otherStr, } err := LOG_DB.Create(log).Error if err != nil { @@ -206,7 +216,7 @@ func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName tx = tx.Where("logs.channel_id = ?", channel) } if group != "" { - tx = tx.Where("logs."+groupCol+" = ?", group) + tx = tx.Where("logs."+logGroupCol+" = ?", group) } err = tx.Model(&Log{}).Count(&total).Error if err != nil { @@ -264,7 +274,7 @@ func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int tx = tx.Where("logs.created_at <= ?", endTimestamp) } if group != "" { - tx = tx.Where("logs."+groupCol+" = ?", group) + tx = tx.Where("logs."+logGroupCol+" = ?", group) } err = tx.Model(&Log{}).Count(&total).Error if err != nil { @@ -325,8 +335,8 @@ func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64, modelNa rpmTpmQuery = rpmTpmQuery.Where("channel_id = ?", channel) } if group != "" { - tx = tx.Where(groupCol+" = ?", group) - rpmTpmQuery = rpmTpmQuery.Where(groupCol+" = ?", group) + tx = tx.Where(logGroupCol+" = ?", group) + rpmTpmQuery = rpmTpmQuery.Where(logGroupCol+" = ?", group) } tx = tx.Where("type = ?", LogTypeConsume) diff --git a/model/main.go b/model/main.go index 61d6bb10..f3929b5d 100644 --- a/model/main.go +++ b/model/main.go @@ -1,6 +1,7 @@ package model import ( + "fmt" "log" "one-api/common" "one-api/constant" @@ -15,18 +16,33 @@ import ( "gorm.io/gorm" ) -var groupCol string -var keyCol string +var commonGroupCol string +var commonKeyCol string + +var logKeyCol string +var logGroupCol string func initCol() { + // init common column names if common.UsingPostgreSQL { - groupCol = `"group"` - keyCol = `"key"` - + commonGroupCol = `"group"` + commonKeyCol = `"key"` } else { - groupCol = "`group`" - keyCol = "`key`" + commonGroupCol = "`group`" + commonKeyCol = "`key`" } + if DB != LOG_DB { + switch common.LogSqlType { + case common.DatabaseTypePostgreSQL: + logGroupCol = `"group"` + logKeyCol = `"key"` + default: + logGroupCol = commonGroupCol + logKeyCol = commonKeyCol + } + } + // log sql type and database type + common.SysLog("Using Log SQL Type: " + common.LogSqlType) } var DB *gorm.DB @@ -83,7 +99,7 @@ func CheckSetup() { } } -func chooseDB(envName string) (*gorm.DB, error) { +func chooseDB(envName string, isLog bool) (*gorm.DB, error) { defer func() { initCol() }() @@ -92,7 +108,11 @@ func chooseDB(envName string) (*gorm.DB, error) { if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { // Use PostgreSQL common.SysLog("using PostgreSQL as database") - common.UsingPostgreSQL = true + if !isLog { + common.UsingPostgreSQL = true + } else { + common.LogSqlType = common.DatabaseTypePostgreSQL + } return gorm.Open(postgres.New(postgres.Config{ DSN: dsn, PreferSimpleProtocol: true, // disables implicit prepared statement usage @@ -102,7 +122,11 @@ func chooseDB(envName string) (*gorm.DB, error) { } if strings.HasPrefix(dsn, "local") { common.SysLog("SQL_DSN not set, using SQLite as database") - common.UsingSQLite = true + if !isLog { + common.UsingSQLite = true + } else { + common.LogSqlType = common.DatabaseTypeSQLite + } return gorm.Open(sqlite.Open(common.SQLitePath), &gorm.Config{ PrepareStmt: true, // precompile SQL }) @@ -117,7 +141,11 @@ func chooseDB(envName string) (*gorm.DB, error) { dsn += "?parseTime=true" } } - common.UsingMySQL = true + if !isLog { + common.UsingMySQL = true + } else { + common.LogSqlType = common.DatabaseTypeMySQL + } return gorm.Open(mysql.Open(dsn), &gorm.Config{ PrepareStmt: true, // precompile SQL }) @@ -131,7 +159,7 @@ func chooseDB(envName string) (*gorm.DB, error) { } func InitDB() (err error) { - db, err := chooseDB("SQL_DSN") + db, err := chooseDB("SQL_DSN", false) if err == nil { if common.DebugEnabled { db = db.Debug() @@ -149,7 +177,7 @@ func InitDB() (err error) { return nil } if common.UsingMySQL { - _, _ = sqlDB.Exec("ALTER TABLE channels MODIFY model_mapping TEXT;") // TODO: delete this line when most users have upgraded + //_, _ = sqlDB.Exec("ALTER TABLE channels MODIFY model_mapping TEXT;") // TODO: delete this line when most users have upgraded } common.SysLog("database migration started") err = migrateDB() @@ -165,7 +193,7 @@ func InitLogDB() (err error) { LOG_DB = DB return } - db, err := chooseDB("LOG_SQL_DSN") + db, err := chooseDB("LOG_SQL_DSN", true) if err == nil { if common.DebugEnabled { db = db.Debug() @@ -198,54 +226,50 @@ func InitLogDB() (err error) { } func migrateDB() error { - err := DB.AutoMigrate(&Channel{}) - if err != nil { - return err + var wg sync.WaitGroup + errChan := make(chan error, 12) // Buffer size matches number of migrations + + migrations := []struct { + model interface{} + name string + }{ + {&Channel{}, "Channel"}, + {&Token{}, "Token"}, + {&User{}, "User"}, + {&Option{}, "Option"}, + {&Redemption{}, "Redemption"}, + {&Ability{}, "Ability"}, + {&Log{}, "Log"}, + {&Midjourney{}, "Midjourney"}, + {&TopUp{}, "TopUp"}, + {&QuotaData{}, "QuotaData"}, + {&Task{}, "Task"}, + {&Setup{}, "Setup"}, } - err = DB.AutoMigrate(&Token{}) - if err != nil { - return err + + for _, m := range migrations { + wg.Add(1) + go func(model interface{}, name string) { + defer wg.Done() + if err := DB.AutoMigrate(model); err != nil { + errChan <- fmt.Errorf("failed to migrate %s: %v", name, err) + } + }(m.model, m.name) } - err = DB.AutoMigrate(&User{}) - if err != nil { - return err + + // Wait for all migrations to complete + wg.Wait() + close(errChan) + + // Check for any errors + for err := range errChan { + if err != nil { + return err + } } - err = DB.AutoMigrate(&Option{}) - if err != nil { - return err - } - err = DB.AutoMigrate(&Redemption{}) - if err != nil { - return err - } - err = DB.AutoMigrate(&Ability{}) - if err != nil { - return err - } - err = DB.AutoMigrate(&Log{}) - if err != nil { - return err - } - err = DB.AutoMigrate(&Midjourney{}) - if err != nil { - return err - } - err = DB.AutoMigrate(&TopUp{}) - if err != nil { - return err - } - err = DB.AutoMigrate(&QuotaData{}) - if err != nil { - return err - } - err = DB.AutoMigrate(&Task{}) - if err != nil { - return err - } - err = DB.AutoMigrate(&Setup{}) + common.SysLog("database migrated") - //err = createRootAccountIfNeed() - return err + return nil } func migrateLOGDB() error { diff --git a/model/token.go b/model/token.go index d4b26afe..2ed2c09a 100644 --- a/model/token.go +++ b/model/token.go @@ -66,7 +66,7 @@ func SearchUserTokens(userId int, keyword string, token string) (tokens []*Token if token != "" { token = strings.Trim(token, "sk-") } - err = DB.Where("user_id = ?", userId).Where("name LIKE ?", "%"+keyword+"%").Where(keyCol+" LIKE ?", "%"+token+"%").Find(&tokens).Error + err = DB.Where("user_id = ?", userId).Where("name LIKE ?", "%"+keyword+"%").Where(commonKeyCol+" LIKE ?", "%"+token+"%").Find(&tokens).Error return tokens, err } @@ -161,7 +161,7 @@ func GetTokenByKey(key string, fromDB bool) (token *Token, err error) { // Don't return error - fall through to DB } fromDB = true - err = DB.Where(keyCol+" = ?", key).First(&token).Error + err = DB.Where(commonKeyCol+" = ?", key).First(&token).Error return token, err } diff --git a/model/user.go b/model/user.go index 1a3372aa..1b3a04b6 100644 --- a/model/user.go +++ b/model/user.go @@ -175,7 +175,7 @@ func SearchUsers(keyword string, group string, startIdx int, num int) ([]*User, // 如果是数字,同时搜索ID和其他字段 likeCondition = "id = ? OR " + likeCondition if group != "" { - query = query.Where("("+likeCondition+") AND "+groupCol+" = ?", + query = query.Where("("+likeCondition+") AND "+commonGroupCol+" = ?", keywordInt, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", group) } else { query = query.Where(likeCondition, @@ -184,7 +184,7 @@ func SearchUsers(keyword string, group string, startIdx int, num int) ([]*User, } else { // 非数字关键字,只搜索字符串字段 if group != "" { - query = query.Where("("+likeCondition+") AND "+groupCol+" = ?", + query = query.Where("("+likeCondition+") AND "+commonGroupCol+" = ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", group) } else { query = query.Where(likeCondition, @@ -615,7 +615,7 @@ func GetUserGroup(id int, fromDB bool) (group string, err error) { // Don't return error - fall through to DB } fromDB = true - err = DB.Model(&User{}).Where("id = ?", id).Select(groupCol).Find(&group).Error + err = DB.Model(&User{}).Where("id = ?", id).Select(commonGroupCol).Find(&group).Error if err != nil { return "", err } From d57e6425e58949cd8133aac182a53ea99adc459f Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sat, 14 Jun 2025 18:15:45 +0800 Subject: [PATCH 29/61] =?UTF-8?q?=F0=9F=90=9B=20fix(ability,=20channel):?= =?UTF-8?q?=20standardize=20boolean=20value=20handling=20across=20database?= =?UTF-8?q?=20queries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/ability.go | 16 ++++------------ model/channel.go | 4 ++-- model/main.go | 8 +++++++- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/model/ability.go b/model/ability.go index c4df5d07..96a9ef6a 100644 --- a/model/ability.go +++ b/model/ability.go @@ -42,15 +42,11 @@ func GetAllEnableAbilities() []Ability { } func getPriority(group string, model string, retry int) (int, error) { - trueVal := "1" - if common.UsingPostgreSQL { - trueVal = "true" - } var priorities []int err := DB.Model(&Ability{}). Select("DISTINCT(priority)"). - Where(commonGroupCol+" = ? and model = ? and enabled = "+trueVal, group, model). + Where(commonGroupCol+" = ? and model = ? and enabled = ?", group, model, commonTrueVal). Order("priority DESC"). // 按优先级降序排序 Pluck("priority", &priorities).Error // Pluck用于将查询的结果直接扫描到一个切片中 @@ -76,18 +72,14 @@ func getPriority(group string, model string, retry int) (int, error) { } func getChannelQuery(group string, model string, retry int) *gorm.DB { - trueVal := "1" - if common.UsingPostgreSQL { - trueVal = "true" - } - maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(commonGroupCol+" = ? and model = ? and enabled = "+trueVal, group, model) - channelQuery := DB.Where(commonGroupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = (?)", group, model, maxPrioritySubQuery) + maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(commonGroupCol+" = ? and model = ? and enabled = ?", group, model, commonTrueVal) + channelQuery := DB.Where(commonGroupCol+" = ? and model = ? and enabled = ? and priority = (?)", group, model, commonTrueVal, maxPrioritySubQuery) if retry != 0 { priority, err := getPriority(group, model, retry) if err != nil { common.SysError(fmt.Sprintf("Get priority failed: %s", err.Error())) } else { - channelQuery = DB.Where(commonGroupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = ?", group, model, priority) + channelQuery = DB.Where(commonGroupCol+" = ? and model = ? and enabled = ? and priority = ?", group, model, commonTrueVal, priority) } } diff --git a/model/channel.go b/model/channel.go index 598e1b6e..b5503eee 100644 --- a/model/channel.go +++ b/model/channel.go @@ -145,7 +145,7 @@ func SearchChannels(keyword string, group string, model string, idSort bool) ([] } // 构造基础查询 - baseQuery := DB.Model(&Channel{}).Omit(commonKeyCol) + baseQuery := DB.Model(&Channel{}).Omit("key") // 构造WHERE子句 var whereClause string @@ -478,7 +478,7 @@ func SearchTags(keyword string, group string, model string, idSort bool) ([]*str } // 构造基础查询 - baseQuery := DB.Model(&Channel{}).Omit(commonKeyCol) + baseQuery := DB.Model(&Channel{}).Omit("key") // 构造WHERE子句 var whereClause string diff --git a/model/main.go b/model/main.go index f3929b5d..289baa2f 100644 --- a/model/main.go +++ b/model/main.go @@ -18,6 +18,8 @@ import ( var commonGroupCol string var commonKeyCol string +var commonTrueVal string +var commonFalseVal string var logKeyCol string var logGroupCol string @@ -27,11 +29,15 @@ func initCol() { if common.UsingPostgreSQL { commonGroupCol = `"group"` commonKeyCol = `"key"` + commonTrueVal = "true" + commonFalseVal = "false" } else { commonGroupCol = "`group`" commonKeyCol = "`key`" + commonTrueVal = "1" + commonFalseVal = "0" } - if DB != LOG_DB { + if os.Getenv("LOG_SQL_DSN") != "" { switch common.LogSqlType { case common.DatabaseTypePostgreSQL: logGroupCol = `"group"` From 8978dc7a8bd76f9eae7a40e2fa77b81c879a76e5 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sat, 14 Jun 2025 18:23:25 +0800 Subject: [PATCH 30/61] =?UTF-8?q?=F0=9F=90=9B=20fix(log):=20optimize=20cha?= =?UTF-8?q?nnel=20ID=20collection=20by=20using=20a=20map=20to=20prevent=20?= =?UTF-8?q?duplicates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/log.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/model/log.go b/model/log.go index 674f399a..b3fd1ad2 100644 --- a/model/log.go +++ b/model/log.go @@ -227,13 +227,18 @@ func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName return nil, 0, err } - channelIds := make([]int, 0) + channelIdsMap := make(map[int]struct{}) channelMap := make(map[int]string) for _, log := range logs { if log.ChannelId != 0 { - channelIds = append(channelIds, log.ChannelId) + channelIdsMap[log.ChannelId] = struct{}{} } } + + channelIds := make([]int, 0, len(channelIdsMap)) + for channelId := range channelIdsMap { + channelIds = append(channelIds, channelId) + } if len(channelIds) > 0 { var channels []struct { Id int `gorm:"column:id"` From e1a93a1b82c0cbe395c428ac414254fc6ffcc0aa Mon Sep 17 00:00:00 2001 From: RedwindA Date: Sat, 14 Jun 2025 19:36:58 +0800 Subject: [PATCH 31/61] =?UTF-8?q?=F0=9F=92=AC=20fix(GeminiHelper):=20clean?= =?UTF-8?q?=20up=20empty=20system=20instructions=20in=20request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/relay-gemini.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/relay/relay-gemini.go b/relay/relay-gemini.go index 93a2b7aa..21cf5e12 100644 --- a/relay/relay-gemini.go +++ b/relay/relay-gemini.go @@ -136,6 +136,20 @@ func GeminiHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) { adaptor.Init(relayInfo) + // Clean up empty system instruction + if req.SystemInstructions != nil { + hasContent := false + for _, part := range req.SystemInstructions.Parts { + if part.Text != "" { + hasContent = true + break + } + } + if !hasContent { + req.SystemInstructions = nil + } + } + requestBody, err := json.Marshal(req) if err != nil { return service.OpenAIErrorWrapperLocal(err, "marshal_text_request_failed", http.StatusInternalServerError) From ab0ba9f38cc1288ef0427a27416aace6df816bd4 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sat, 14 Jun 2025 19:47:44 +0800 Subject: [PATCH 32/61] =?UTF-8?q?=E2=9C=A8=20feat(database):=20implement?= =?UTF-8?q?=20database=20migration=20logic=20for=20PostgreSQL=20and=20add?= =?UTF-8?q?=20fast=20migration=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/main.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/model/main.go b/model/main.go index 289baa2f..965bba93 100644 --- a/model/main.go +++ b/model/main.go @@ -232,6 +232,30 @@ func InitLogDB() (err error) { } func migrateDB() error { + if !common.UsingPostgreSQL { + return migrateDBFast() + } + err := DB.AutoMigrate( + &Channel{}, + &Token{}, + &User{}, + &Option{}, + &Redemption{}, + &Ability{}, + &Log{}, + &Midjourney{}, + &TopUp{}, + &QuotaData{}, + &Task{}, + &Setup{}, + ) + if err != nil { + return err + } + return nil +} + +func migrateDBFast() error { var wg sync.WaitGroup errChan := make(chan error, 12) // Buffer size matches number of migrations @@ -273,7 +297,6 @@ func migrateDB() error { return err } } - common.SysLog("database migrated") return nil } From abfb3f40067461711b4d58468df532d04f113d5e Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sun, 15 Jun 2025 02:54:54 +0800 Subject: [PATCH 33/61] =?UTF-8?q?=F0=9F=9A=A6=20feat(uptime-kuma):=20suppo?= =?UTF-8?q?rt=20custom=20category=20names=20&=20monitor=20subgroup=20rende?= =?UTF-8?q?ring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend • controller/uptime_kuma.go - Added Group field to Monitor struct to carry publicGroupList.name. - Extended status page parsing to capture group Name and inject it into each monitor. - Re-worked fetchGroupData loop: aggregate all sub-groups, drop unnecessary pre-allocation/breaks. Frontend • web/src/pages/Detail/index.js - renderMonitorList now buckets monitors by the new group field and renders a lightweight header per subgroup. - Fallback gracefully when group is empty to preserve previous single-list behaviour. Other • Expanded anonymous struct definition for statusData.PublicGroupList to include ID/Name, enabling JSON unmarshalling of group names. Result Custom CategoryName continues to work while each uptime group’s internal sub-groups are now clearly displayed in the UI, providing finer-grained visibility without impacting performance or existing validation logic. --- controller/option.go | 9 + controller/uptime_kuma.go | 214 ++++--- setting/console_setting/config.go | 18 +- setting/console_setting/validation.go | 205 +++++-- .../components/settings/DashboardSetting.js | 3 +- web/src/i18n/locales/en.json | 19 +- web/src/pages/Detail/index.js | 251 +++++--- .../Setting/Dashboard/SettingsUptimeKuma.js | 541 +++++++++++++----- 8 files changed, 851 insertions(+), 409 deletions(-) diff --git a/controller/option.go b/controller/option.go index 521d7327..79ba2ffe 100644 --- a/controller/option.go +++ b/controller/option.go @@ -147,6 +147,15 @@ func UpdateOption(c *gin.Context) { }) return } + case "console_setting.uptime_kuma_groups": + err = console_setting.ValidateConsoleSettings(option.Value, "UptimeKumaGroups") + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } } err = model.UpdateOption(option.Key, option.Value) if err != nil { diff --git a/controller/uptime_kuma.go b/controller/uptime_kuma.go index e89b2b9d..05d6297e 100644 --- a/controller/uptime_kuma.go +++ b/controller/uptime_kuma.go @@ -4,9 +4,9 @@ import ( "context" "encoding/json" "errors" - "fmt" "net/http" "one-api/setting/console_setting" + "strconv" "strings" "time" @@ -14,45 +14,25 @@ import ( "golang.org/x/sync/errgroup" ) -type UptimeKumaMonitor struct { - ID int `json:"id"` - Name string `json:"name"` - Type string `json:"type"` -} +const ( + requestTimeout = 30 * time.Second + httpTimeout = 10 * time.Second + uptimeKeySuffix = "_24" + apiStatusPath = "/api/status-page/" + apiHeartbeatPath = "/api/status-page/heartbeat/" +) -type UptimeKumaGroup struct { - ID int `json:"id"` - Name string `json:"name"` - Weight int `json:"weight"` - MonitorList []UptimeKumaMonitor `json:"monitorList"` -} - -type UptimeKumaHeartbeat struct { - Status int `json:"status"` - Time string `json:"time"` - Msg string `json:"msg"` - Ping *float64 `json:"ping"` -} - -type UptimeKumaStatusResponse struct { - PublicGroupList []UptimeKumaGroup `json:"publicGroupList"` -} - -type UptimeKumaHeartbeatResponse struct { - HeartbeatList map[string][]UptimeKumaHeartbeat `json:"heartbeatList"` - UptimeList map[string]float64 `json:"uptimeList"` -} - -type MonitorStatus struct { +type Monitor struct { Name string `json:"name"` Uptime float64 `json:"uptime"` Status int `json:"status"` + Group string `json:"group,omitempty"` } -var ( - ErrUpstreamNon200 = errors.New("upstream non-200") - ErrTimeout = errors.New("context deadline exceeded") -) +type UptimeGroupResult struct { + CategoryName string `json:"categoryName"` + Monitors []Monitor `json:"monitors"` +} func getAndDecode(ctx context.Context, client *http.Client, url string, dest interface{}) error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) @@ -62,107 +42,113 @@ func getAndDecode(ctx context.Context, client *http.Client, url string, dest int resp, err := client.Do(req) if err != nil { - if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - return ErrTimeout - } return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return ErrUpstreamNon200 + return errors.New("non-200 status") } return json.NewDecoder(resp.Body).Decode(dest) } -func GetUptimeKumaStatus(c *gin.Context) { - cs := console_setting.GetConsoleSetting() - uptimeKumaUrl := cs.UptimeKumaUrl - slug := cs.UptimeKumaSlug - - if uptimeKumaUrl == "" || slug == "" { - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": []MonitorStatus{}, - }) - return +func fetchGroupData(ctx context.Context, client *http.Client, groupConfig map[string]interface{}) UptimeGroupResult { + url, _ := groupConfig["url"].(string) + slug, _ := groupConfig["slug"].(string) + categoryName, _ := groupConfig["categoryName"].(string) + + result := UptimeGroupResult{ + CategoryName: categoryName, + Monitors: []Monitor{}, + } + + if url == "" || slug == "" { + return result } - uptimeKumaUrl = strings.TrimSuffix(uptimeKumaUrl, "/") - - ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second) - defer cancel() - - client := &http.Client{} - - statusPageUrl := fmt.Sprintf("%s/api/status-page/%s", uptimeKumaUrl, slug) - heartbeatUrl := fmt.Sprintf("%s/api/status-page/heartbeat/%s", uptimeKumaUrl, slug) - - var ( - statusData UptimeKumaStatusResponse - heartbeatData UptimeKumaHeartbeatResponse - ) + baseURL := strings.TrimSuffix(url, "/") + + var statusData struct { + PublicGroupList []struct { + ID int `json:"id"` + Name string `json:"name"` + MonitorList []struct { + ID int `json:"id"` + Name string `json:"name"` + } `json:"monitorList"` + } `json:"publicGroupList"` + } + + var heartbeatData struct { + HeartbeatList map[string][]struct { + Status int `json:"status"` + } `json:"heartbeatList"` + UptimeList map[string]float64 `json:"uptimeList"` + } g, gCtx := errgroup.WithContext(ctx) - - g.Go(func() error { - return getAndDecode(gCtx, client, statusPageUrl, &statusData) + g.Go(func() error { + return getAndDecode(gCtx, client, baseURL+apiStatusPath+slug, &statusData) + }) + g.Go(func() error { + return getAndDecode(gCtx, client, baseURL+apiHeartbeatPath+slug, &heartbeatData) }) - g.Go(func() error { - return getAndDecode(gCtx, client, heartbeatUrl, &heartbeatData) - }) + if g.Wait() != nil { + return result + } - if err := g.Wait(); err != nil { - switch err { - case ErrUpstreamNon200: - c.JSON(http.StatusBadRequest, gin.H{ - "success": false, - "message": "上游接口出现问题", - }) - case ErrTimeout: - c.JSON(http.StatusRequestTimeout, gin.H{ - "success": false, - "message": "请求上游接口超时", - }) - default: - c.JSON(http.StatusBadRequest, gin.H{ - "success": false, - "message": err.Error(), - }) + for _, pg := range statusData.PublicGroupList { + if len(pg.MonitorList) == 0 { + continue } + + for _, m := range pg.MonitorList { + monitor := Monitor{ + Name: m.Name, + Group: pg.Name, + } + + monitorID := strconv.Itoa(m.ID) + + if uptime, exists := heartbeatData.UptimeList[monitorID+uptimeKeySuffix]; exists { + monitor.Uptime = uptime + } + + if heartbeats, exists := heartbeatData.HeartbeatList[monitorID]; exists && len(heartbeats) > 0 { + monitor.Status = heartbeats[0].Status + } + + result.Monitors = append(result.Monitors, monitor) + } + } + + return result +} + +func GetUptimeKumaStatus(c *gin.Context) { + groups := console_setting.GetUptimeKumaGroups() + if len(groups) == 0 { + c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": []UptimeGroupResult{}}) return } - var monitors []MonitorStatus - for _, group := range statusData.PublicGroupList { - for _, monitor := range group.MonitorList { - monitorStatus := MonitorStatus{ - Name: monitor.Name, - Uptime: 0.0, - Status: 0, - } + ctx, cancel := context.WithTimeout(c.Request.Context(), requestTimeout) + defer cancel() - uptimeKey := fmt.Sprintf("%d_24", monitor.ID) - if uptime, exists := heartbeatData.UptimeList[uptimeKey]; exists { - monitorStatus.Uptime = uptime - } - - heartbeatKey := fmt.Sprintf("%d", monitor.ID) - if heartbeats, exists := heartbeatData.HeartbeatList[heartbeatKey]; exists && len(heartbeats) > 0 { - latestHeartbeat := heartbeats[0] - monitorStatus.Status = latestHeartbeat.Status - } - - monitors = append(monitors, monitorStatus) - } + client := &http.Client{Timeout: httpTimeout} + results := make([]UptimeGroupResult, len(groups)) + + g, gCtx := errgroup.WithContext(ctx) + for i, group := range groups { + i, group := i, group + g.Go(func() error { + results[i] = fetchGroupData(gCtx, client, group) + return nil + }) } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": monitors, - }) + + g.Wait() + c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": results}) } \ No newline at end of file diff --git a/setting/console_setting/config.go b/setting/console_setting/config.go index 063130fc..6327e558 100644 --- a/setting/console_setting/config.go +++ b/setting/console_setting/config.go @@ -3,11 +3,10 @@ package console_setting import "one-api/setting/config" type ConsoleSetting struct { - ApiInfo string `json:"api_info"` // 控制台 API 信息 (JSON 数组字符串) - UptimeKumaUrl string `json:"uptime_kuma_url"` // Uptime Kuma 服务地址(如 https://status.example.com ) - UptimeKumaSlug string `json:"uptime_kuma_slug"` // Uptime Kuma Status Page Slug - Announcements string `json:"announcements"` // 系统公告 (JSON 数组字符串) - FAQ string `json:"faq"` // 常见问题 (JSON 数组字符串) + ApiInfo string `json:"api_info"` // 控制台 API 信息 (JSON 数组字符串) + UptimeKumaGroups string `json:"uptime_kuma_groups"` // Uptime Kuma 分组配置 (JSON 数组字符串) + Announcements string `json:"announcements"` // 系统公告 (JSON 数组字符串) + FAQ string `json:"faq"` // 常见问题 (JSON 数组字符串) ApiInfoEnabled bool `json:"api_info_enabled"` // 是否启用 API 信息面板 UptimeKumaEnabled bool `json:"uptime_kuma_enabled"` // 是否启用 Uptime Kuma 面板 AnnouncementsEnabled bool `json:"announcements_enabled"` // 是否启用系统公告面板 @@ -16,11 +15,10 @@ type ConsoleSetting struct { // 默认配置 var defaultConsoleSetting = ConsoleSetting{ - ApiInfo: "", - UptimeKumaUrl: "", - UptimeKumaSlug: "", - Announcements: "", - FAQ: "", + ApiInfo: "", + UptimeKumaGroups: "", + Announcements: "", + FAQ: "", ApiInfoEnabled: true, UptimeKumaEnabled: true, AnnouncementsEnabled: true, diff --git a/setting/console_setting/validation.go b/setting/console_setting/validation.go index 3a9f3c83..51a84849 100644 --- a/setting/console_setting/validation.go +++ b/setting/console_setting/validation.go @@ -9,10 +9,58 @@ import ( "time" ) -// ValidateConsoleSettings 验证控制台设置信息格式 +var ( + urlRegex = regexp.MustCompile(`^https?://(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(?:\:[0-9]{1,5})?(?:/.*)?$`) + dangerousChars = []string{" 50 { return fmt.Errorf("API信息数量不能超过50个") } - // 允许的颜色值 - validColors := map[string]bool{ - "blue": true, "green": true, "cyan": true, "purple": true, "pink": true, - "red": true, "orange": true, "amber": true, "yellow": true, "lime": true, - "light-green": true, "teal": true, "light-blue": true, "indigo": true, - "violet": true, "grey": true, - } - - // URL 正则,支持域名 / IP - urlRegex := regexp.MustCompile(`^https?://(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(?:\:[0-9]{1,5})?(?:/.*)?$`) - for i, apiInfo := range apiInfoList { urlStr, ok := apiInfo["url"].(string) if !ok || urlStr == "" { @@ -67,12 +104,11 @@ func validateApiInfo(apiInfoStr string) error { if !ok || color == "" { return fmt.Errorf("第%d个API信息缺少颜色字段", i+1) } - if !urlRegex.MatchString(urlStr) { - return fmt.Errorf("第%d个API信息的URL格式不正确", i+1) - } - if _, err := url.Parse(urlStr); err != nil { - return fmt.Errorf("第%d个API信息的URL无法解析:%s", i+1, err.Error()) + + if err := validateURL(urlStr, i+1, "API信息"); err != nil { + return err } + if len(urlStr) > 500 { return fmt.Errorf("第%d个API信息的URL长度不能超过500字符", i+1) } @@ -82,39 +118,29 @@ func validateApiInfo(apiInfoStr string) error { if len(description) > 200 { return fmt.Errorf("第%d个API信息的说明长度不能超过200字符", i+1) } + if !validColors[color] { return fmt.Errorf("第%d个API信息的颜色值不合法", i+1) } - dangerousChars := []string{" 100 { return fmt.Errorf("系统公告数量不能超过100个") @@ -158,9 +184,9 @@ func validateAnnouncements(announcementsStr string) error { } func validateFAQ(faqStr string) error { - var list []map[string]interface{} - if err := json.Unmarshal([]byte(faqStr), &list); err != nil { - return fmt.Errorf("FAQ信息格式错误:%s", err.Error()) + list, err := parseJSONArray(faqStr, "FAQ信息") + if err != nil { + return err } if len(list) > 100 { return fmt.Errorf("FAQ数量不能超过100个") @@ -184,24 +210,79 @@ func validateFAQ(faqStr string) error { return nil } -// GetAnnouncements 获取系统公告 func GetAnnouncements() []map[string]interface{} { - annStr := GetConsoleSetting().Announcements - if annStr == "" { - return []map[string]interface{}{} - } - var ann []map[string]interface{} - _ = json.Unmarshal([]byte(annStr), &ann) - return ann + return getJSONList(GetConsoleSetting().Announcements) } -// GetFAQ 获取常见问题 func GetFAQ() []map[string]interface{} { - faqStr := GetConsoleSetting().FAQ - if faqStr == "" { - return []map[string]interface{}{} + return getJSONList(GetConsoleSetting().FAQ) +} + +func validateUptimeKumaGroups(groupsStr string) error { + groups, err := parseJSONArray(groupsStr, "Uptime Kuma分组配置") + if err != nil { + return err } - var faq []map[string]interface{} - _ = json.Unmarshal([]byte(faqStr), &faq) - return faq + + if len(groups) > 20 { + return fmt.Errorf("Uptime Kuma分组数量不能超过20个") + } + + nameSet := make(map[string]bool) + + for i, group := range groups { + categoryName, ok := group["categoryName"].(string) + if !ok || categoryName == "" { + return fmt.Errorf("第%d个分组缺少分类名称字段", i+1) + } + if nameSet[categoryName] { + return fmt.Errorf("第%d个分组的分类名称与其他分组重复", i+1) + } + nameSet[categoryName] = true + urlStr, ok := group["url"].(string) + if !ok || urlStr == "" { + return fmt.Errorf("第%d个分组缺少URL字段", i+1) + } + slug, ok := group["slug"].(string) + if !ok || slug == "" { + return fmt.Errorf("第%d个分组缺少Slug字段", i+1) + } + description, ok := group["description"].(string) + if !ok { + description = "" + } + + if err := validateURL(urlStr, i+1, "分组"); err != nil { + return err + } + + if len(categoryName) > 50 { + return fmt.Errorf("第%d个分组的分类名称长度不能超过50字符", i+1) + } + if len(urlStr) > 500 { + return fmt.Errorf("第%d个分组的URL长度不能超过500字符", i+1) + } + if len(slug) > 100 { + return fmt.Errorf("第%d个分组的Slug长度不能超过100字符", i+1) + } + if len(description) > 200 { + return fmt.Errorf("第%d个分组的描述长度不能超过200字符", i+1) + } + + if !slugRegex.MatchString(slug) { + return fmt.Errorf("第%d个分组的Slug只能包含字母、数字、下划线和连字符", i+1) + } + + if err := checkDangerousContent(description, i+1, "分组"); err != nil { + return err + } + if err := checkDangerousContent(categoryName, i+1, "分组"); err != nil { + return err + } + } + return nil +} + +func GetUptimeKumaGroups() []map[string]interface{} { + return getJSONList(GetConsoleSetting().UptimeKumaGroups) } \ No newline at end of file diff --git a/web/src/components/settings/DashboardSetting.js b/web/src/components/settings/DashboardSetting.js index 0546ca21..bf4a26a3 100644 --- a/web/src/components/settings/DashboardSetting.js +++ b/web/src/components/settings/DashboardSetting.js @@ -11,8 +11,7 @@ const DashboardSetting = () => { 'console_setting.api_info': '', 'console_setting.announcements': '', 'console_setting.faq': '', - 'console_setting.uptime_kuma_url': '', - 'console_setting.uptime_kuma_slug': '', + 'console_setting.uptime_kuma_groups': '', 'console_setting.api_info_enabled': '', 'console_setting.announcements_enabled': '', 'console_setting.faq_enabled': '', diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 3b523cbf..11bea1e4 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1628,16 +1628,15 @@ "常见问答管理,为用户提供常见问题的答案(最多50个,前端显示最新20条)": "FAQ management, providing answers to common questions for users (maximum 50, display latest 20 on the front end)", "暂无常见问答": "No FAQ", "显示最新20条": "Display latest 20", - "Uptime Kuma 服务地址": "Uptime Kuma service address", - "状态页面 Slug": "Status page slug", - "请输入 Uptime Kuma 服务的完整地址,例如:https://uptime.example.com": "Please enter the complete address of Uptime Kuma, for example: https://uptime.example.com", - "请输入状态页面的 slug 标识符,例如:my-status": "Please enter the slug identifier for the status page, for example: my-status", - "Uptime Kuma 服务地址不能为空": "Uptime Kuma service address cannot be empty", - "请输入有效的 URL 地址": "Please enter a valid URL address", - "状态页面 Slug 不能为空": "Status page slug cannot be empty", - "Slug 只能包含字母、数字、下划线和连字符": "Slug can only contain letters, numbers, underscores, and hyphens", - "请输入 Uptime Kuma 服务地址": "Please enter the Uptime Kuma service address", - "请输入状态页面 Slug": "Please enter the status page slug", + "Uptime Kuma监控分类管理,可以配置多个监控分类用于服务状态展示(最多20个)": "Uptime Kuma monitoring category management, you can configure multiple monitoring categories for service status display (maximum 20)", + "添加分类": "Add Category", + "分类名称": "Category Name", + "Uptime Kuma地址": "Uptime Kuma Address", + "状态页面Slug": "Status Page Slug", + "请输入分类名称,如:OpenAI、Claude等": "Please enter the category name, such as: OpenAI, Claude, etc.", + "请输入Uptime Kuma服务地址,如:https://status.example.com": "Please enter the Uptime Kuma service address, such as: https://status.example.com", + "请输入状态页面的Slug,如:my-status": "Please enter the slug for the status page, such as: my-status", + "确定要删除此分类吗?": "Are you sure you want to delete this category?", "配置": "Configure", "服务监控地址,用于展示服务状态信息": "service monitoring address for displaying status information", "服务可用性": "Service Status", diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index 747c1cf6..777924e0 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -16,7 +16,8 @@ import { Tag, Timeline, Collapse, - Progress + Progress, + Divider } from '@douyinfe/semi-ui'; import { IconRefresh, @@ -215,6 +216,7 @@ const Detail = (props) => { const announcementScrollRef = useRef(null); const faqScrollRef = useRef(null); const uptimeScrollRef = useRef(null); + const uptimeTabScrollRefs = useRef({}); // ========== Additional State for scroll hints ========== const [showAnnouncementScrollHint, setShowAnnouncementScrollHint] = useState(false); @@ -224,6 +226,7 @@ const Detail = (props) => { // ========== Uptime data ========== const [uptimeData, setUptimeData] = useState([]); const [uptimeLoading, setUptimeLoading] = useState(false); + const [activeUptimeTab, setActiveUptimeTab] = useState(''); // ========== Props Destructuring ========== const { username, model_name, start_timestamp, end_timestamp, channel } = inputs; @@ -579,6 +582,9 @@ const Detail = (props) => { const { success, message, data } = res.data; if (success) { setUptimeData(data || []); + if (data && data.length > 0 && !activeUptimeTab) { + setActiveUptimeTab(data[0].categoryName); + } } else { showError(message); } @@ -587,7 +593,7 @@ const Detail = (props) => { } finally { setUptimeLoading(false); } - }, []); + }, [activeUptimeTab]); const refresh = useCallback(async () => { await Promise.all([loadQuotaData(), loadUptimeData()]); @@ -644,10 +650,18 @@ const Detail = (props) => { checkApiScrollable(); checkCardScrollable(announcementScrollRef, setShowAnnouncementScrollHint); checkCardScrollable(faqScrollRef, setShowFaqScrollHint); - checkCardScrollable(uptimeScrollRef, setShowUptimeScrollHint); + + if (uptimeData.length === 1) { + checkCardScrollable(uptimeScrollRef, setShowUptimeScrollHint); + } else if (uptimeData.length > 1 && activeUptimeTab) { + const activeTabRef = uptimeTabScrollRefs.current[activeUptimeTab]; + if (activeTabRef) { + checkCardScrollable(activeTabRef, setShowUptimeScrollHint); + } + } }, 100); return () => clearTimeout(timer); - }, [uptimeData]); + }, [uptimeData, activeUptimeTab]); const getUserData = async () => { let res = await API.get(`/api/user/self`); @@ -883,7 +897,6 @@ const Detail = (props) => { const announcementData = useMemo(() => { const announcements = statusState?.status?.announcements || []; - // 处理后台配置的公告数据,自动生成相对时间 return announcements.map(item => ({ ...item, time: getRelativeTime(item.publishDate) @@ -894,6 +907,68 @@ const Detail = (props) => { return statusState?.status?.faq || []; }, [statusState?.status?.faq]); + const renderMonitorList = useCallback((monitors) => { + if (!monitors || monitors.length === 0) { + return ( +
+ } + darkModeImage={} + title={t('暂无监控数据')} + style={{ padding: '8px' }} + /> +
+ ); + } + + const grouped = {}; + monitors.forEach((m) => { + const g = m.group || ''; + if (!grouped[g]) grouped[g] = []; + grouped[g].push(m); + }); + + const renderItem = (monitor, idx) => ( +
+
+
+
+ {monitor.name} +
+ {((monitor.uptime || 0) * 100).toFixed(2)}% +
+
+ {getUptimeStatusText(monitor.status)} +
+ +
+
+
+ ); + + return Object.entries(grouped).map(([gname, list]) => ( +
+ {gname && ( + <> +
+ {gname} +
+ + + )} + {list.map(renderItem)} +
+ )); + }, [t, getUptimeStatusColor, getUptimeStatusText]); + // ========== Hooks - Effects ========== useEffect(() => { getUserData(); @@ -1127,8 +1202,8 @@ const Detail = (props) => { ) : (
} - darkModeImage={} + image={} + darkModeImage={} title={t('暂无API信息')} description={t('请联系管理员在系统设置中配置API信息')} style={{ padding: '12px' }} @@ -1199,8 +1274,8 @@ const Detail = (props) => { ) : (
} - darkModeImage={} + image={} + darkModeImage={} title={t('暂无系统公告')} description={t('请联系管理员在系统设置中配置公告信息')} style={{ padding: '12px' }} @@ -1227,6 +1302,7 @@ const Detail = (props) => { {t('常见问答')}
} + bodyStyle={{ padding: 0 }} >
{ ) : (
} - darkModeImage={} + image={} + darkModeImage={} title={t('暂无常见问答')} description={t('请联系管理员在系统设置中配置常见问答')} style={{ padding: '12px' }} @@ -1274,7 +1350,7 @@ const Detail = (props) => { {uptimeEnabled && (
@@ -1291,11 +1367,93 @@ const Detail = (props) => { />
} - footer={uptimeData.length > 0 ? ( - + bodyStyle={{ padding: 0 }} + > + {/* 内容区域 */} +
+ + {uptimeData.length > 0 ? ( + uptimeData.length === 1 ? ( +
+
handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)} + > + {renderMonitorList(uptimeData[0].monitors)} +
+
+
+ ) : ( + + {uptimeData.map((group, groupIdx) => { + if (!uptimeTabScrollRefs.current[group.categoryName]) { + uptimeTabScrollRefs.current[group.categoryName] = React.createRef(); + } + const tabScrollRef = uptimeTabScrollRefs.current[group.categoryName]; + + return ( + + + {group.categoryName} + + {group.monitors ? group.monitors.length : 0} + + + } + itemKey={group.categoryName} + key={groupIdx} + > +
+
handleCardScroll(tabScrollRef, setShowUptimeScrollHint)} + > + {renderMonitorList(group.monitors)} +
+
+
+ + ); + })} + + ) + ) : ( +
+ } + darkModeImage={} + title={t('暂无监控数据')} + description={t('请联系管理员在系统设置中配置Uptime分组')} + style={{ padding: '12px' }} + /> +
+ )} + +
+ + {/* 固定在底部的图例 */} + {uptimeData.length > 0 && ( +
{uptimeLegendData.map((legend, index) => (
@@ -1307,63 +1465,8 @@ const Detail = (props) => {
))}
- - ) : null} - footerStyle={uptimeData.length > 0 ? { padding: '0px' } : undefined} - > -
- -
handleCardScroll(uptimeScrollRef, setShowUptimeScrollHint)} - > - {uptimeData.length > 0 ? ( - uptimeData.map((monitor, idx) => ( -
-
-
-
- {monitor.name} -
- {((monitor.uptime || 0) * 100).toFixed(2)}% -
-
- {getUptimeStatusText(monitor.status)} -
- -
-
-
- )) - ) : ( -
- } - darkModeImage={} - title={t('暂无监控数据')} - description={t('请联系管理员在系统设置中配置Uptime')} - style={{ padding: '12px' }} - /> -
- )} -
- -
-
+
+ )} )}
diff --git a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js index d489b683..0b1a2749 100644 --- a/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js +++ b/web/src/pages/Setting/Dashboard/SettingsUptimeKuma.js @@ -1,13 +1,23 @@ -import React, { useEffect, useState, useRef, useMemo, useCallback } from 'react'; +import React, { useEffect, useState } from 'react'; import { - Form, Button, + Space, + Table, + Form, Typography, - Row, - Col, - Switch, + Empty, + Divider, + Modal, + Switch } from '@douyinfe/semi-ui'; import { + IllustrationNoResult, + IllustrationNoResultDark +} from '@douyinfe/semi-illustrations'; +import { + Plus, + Edit, + Trash2, Save, Activity } from 'lucide-react'; @@ -19,69 +29,242 @@ const { Text } = Typography; const SettingsUptimeKuma = ({ options, refresh }) => { const { t } = useTranslation(); + const [uptimeGroupsList, setUptimeGroupsList] = useState([]); + const [showUptimeModal, setShowUptimeModal] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [deletingGroup, setDeletingGroup] = useState(null); + const [editingGroup, setEditingGroup] = useState(null); + const [modalLoading, setModalLoading] = useState(false); const [loading, setLoading] = useState(false); + const [hasChanges, setHasChanges] = useState(false); + const [uptimeForm, setUptimeForm] = useState({ + categoryName: '', + url: '', + slug: '', + }); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [panelEnabled, setPanelEnabled] = useState(true); - const formApiRef = useRef(null); - const initValues = useMemo(() => ({ - uptimeKumaUrl: options?.['console_setting.uptime_kuma_url'] || '', - uptimeKumaSlug: options?.['console_setting.uptime_kuma_slug'] || '' - }), [options?.['console_setting.uptime_kuma_url'], options?.['console_setting.uptime_kuma_slug']]); - - useEffect(() => { - if (formApiRef.current) { - formApiRef.current.setValues(initValues, { isOverride: true }); + const columns = [ + { + title: t('分类名称'), + dataIndex: 'categoryName', + key: 'categoryName', + render: (text) => ( +
+ {text} +
+ ) + }, + { + title: t('Uptime Kuma地址'), + dataIndex: 'url', + key: 'url', + render: (text) => ( +
+ {text} +
+ ) + }, + { + title: t('状态页面Slug'), + dataIndex: 'slug', + key: 'slug', + render: (text) => ( +
+ {text} +
+ ) + }, + { + title: t('操作'), + key: 'action', + fixed: 'right', + width: 150, + render: (text, record) => ( + + + + + ) } - }, [initValues]); + ]; - useEffect(() => { - const enabledStr = options?.['console_setting.uptime_kuma_enabled']; - setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true); - }, [options?.['console_setting.uptime_kuma_enabled']]); - - const handleSave = async () => { - const api = formApiRef.current; - if (!api) { - showError(t('表单未初始化')); - return; + const updateOption = async (key, value) => { + const res = await API.put('/api/option/', { + key, + value, + }); + const { success, message } = res.data; + if (success) { + showSuccess('Uptime Kuma配置已更新'); + if (refresh) refresh(); + } else { + showError(message); } + }; + const submitUptimeGroups = async () => { try { setLoading(true); - const { uptimeKumaUrl, uptimeKumaSlug } = await api.validate(); - - const trimmedUrl = (uptimeKumaUrl || '').trim(); - const trimmedSlug = (uptimeKumaSlug || '').trim(); - - if (trimmedUrl === options?.['console_setting.uptime_kuma_url'] && trimmedSlug === options?.['console_setting.uptime_kuma_slug']) { - showSuccess(t('无需保存,配置未变动')); - return; - } - - const [urlRes, slugRes] = await Promise.all([ - trimmedUrl === options?.['console_setting.uptime_kuma_url'] ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { - key: 'console_setting.uptime_kuma_url', - value: trimmedUrl - }), - trimmedSlug === options?.['console_setting.uptime_kuma_slug'] ? Promise.resolve({ data: { success: true } }) : API.put('/api/option/', { - key: 'console_setting.uptime_kuma_slug', - value: trimmedSlug - }) - ]); - - if (!urlRes.data.success) throw new Error(urlRes.data.message || t('URL 保存失败')); - if (!slugRes.data.success) throw new Error(slugRes.data.message || t('Slug 保存失败')); - - showSuccess(t('Uptime Kuma 设置保存成功')); - refresh?.(); - } catch (err) { - console.error(err); - showError(err.message || t('保存失败,请重试')); + const groupsJson = JSON.stringify(uptimeGroupsList); + await updateOption('console_setting.uptime_kuma_groups', groupsJson); + setHasChanges(false); + } catch (error) { + console.error('Uptime Kuma配置更新失败', error); + showError('Uptime Kuma配置更新失败'); } finally { setLoading(false); } }; + const handleAddGroup = () => { + setEditingGroup(null); + setUptimeForm({ + categoryName: '', + url: '', + slug: '', + }); + setShowUptimeModal(true); + }; + + const handleEditGroup = (group) => { + setEditingGroup(group); + setUptimeForm({ + categoryName: group.categoryName, + url: group.url, + slug: group.slug, + }); + setShowUptimeModal(true); + }; + + const handleDeleteGroup = (group) => { + setDeletingGroup(group); + setShowDeleteModal(true); + }; + + const confirmDeleteGroup = () => { + if (deletingGroup) { + const newList = uptimeGroupsList.filter(item => item.id !== deletingGroup.id); + setUptimeGroupsList(newList); + setHasChanges(true); + showSuccess('分类已删除,请及时点击“保存设置”进行保存'); + } + setShowDeleteModal(false); + setDeletingGroup(null); + }; + + const handleSaveGroup = async () => { + if (!uptimeForm.categoryName || !uptimeForm.url || !uptimeForm.slug) { + showError('请填写完整的分类信息'); + return; + } + + try { + new URL(uptimeForm.url); + } catch (error) { + showError('请输入有效的URL地址'); + return; + } + + if (!/^[a-zA-Z0-9_-]+$/.test(uptimeForm.slug)) { + showError('Slug只能包含字母、数字、下划线和连字符'); + return; + } + + try { + setModalLoading(true); + + let newList; + if (editingGroup) { + newList = uptimeGroupsList.map(item => + item.id === editingGroup.id + ? { ...item, ...uptimeForm } + : item + ); + } else { + const newId = Math.max(...uptimeGroupsList.map(item => item.id), 0) + 1; + const newGroup = { + id: newId, + ...uptimeForm + }; + newList = [...uptimeGroupsList, newGroup]; + } + + setUptimeGroupsList(newList); + setHasChanges(true); + setShowUptimeModal(false); + showSuccess(editingGroup ? '分类已更新,请及时点击“保存设置”进行保存' : '分类已添加,请及时点击“保存设置”进行保存'); + } catch (error) { + showError('操作失败: ' + error.message); + } finally { + setModalLoading(false); + } + }; + + const parseUptimeGroups = (groupsStr) => { + if (!groupsStr) { + setUptimeGroupsList([]); + return; + } + + try { + const parsed = JSON.parse(groupsStr); + const list = Array.isArray(parsed) ? parsed : []; + const listWithIds = list.map((item, index) => ({ + ...item, + id: item.id || index + 1 + })); + setUptimeGroupsList(listWithIds); + } catch (error) { + console.error('解析Uptime Kuma配置失败:', error); + setUptimeGroupsList([]); + } + }; + + useEffect(() => { + const groupsStr = options['console_setting.uptime_kuma_groups']; + if (groupsStr !== undefined) { + parseUptimeGroups(groupsStr); + } + }, [options['console_setting.uptime_kuma_groups']]); + + useEffect(() => { + const enabledStr = options['console_setting.uptime_kuma_enabled']; + setPanelEnabled(enabledStr === undefined ? true : enabledStr === 'true' || enabledStr === true); + }, [options['console_setting.uptime_kuma_enabled']]); + const handleToggleEnabled = async (checked) => { const newValue = checked ? 'true' : 'false'; try { @@ -101,46 +284,65 @@ const SettingsUptimeKuma = ({ options, refresh }) => { } }; - const isValidUrl = useCallback((string) => { - try { - new URL(string); - return true; - } catch (_) { - return false; + const handleBatchDelete = () => { + if (selectedRowKeys.length === 0) { + showError('请先选择要删除的分类'); + return; } - }, []); + + const newList = uptimeGroupsList.filter(item => !selectedRowKeys.includes(item.id)); + setUptimeGroupsList(newList); + setSelectedRowKeys([]); + setHasChanges(true); + showSuccess(`已删除 ${selectedRowKeys.length} 个分类,请及时点击“保存设置”进行保存`); + }; const renderHeader = () => (
-
+
- - {t('配置')}  - - Uptime Kuma - -  {t('服务监控地址,用于展示服务状态信息')} - + {t('Uptime Kuma监控分类管理,可以配置多个监控分类用于服务状态展示(最多20个)')}
+
-
+ + +
+
+ + +
+ {/* 启用开关 */} +
{panelEnabled ? t('已启用') : t('已禁用')}
@@ -148,67 +350,132 @@ const SettingsUptimeKuma = ({ options, refresh }) => {
); + const getCurrentPageData = () => { + const startIndex = (currentPage - 1) * pageSize; + const endIndex = startIndex + pageSize; + return uptimeGroupsList.slice(startIndex, endIndex); + }; + + const rowSelection = { + selectedRowKeys, + onChange: (selectedRowKeys, selectedRows) => { + setSelectedRowKeys(selectedRowKeys); + }, + onSelect: (record, selected, selectedRows) => { + console.log(`选择行: ${selected}`, record); + }, + onSelectAll: (selected, selectedRows) => { + console.log(`全选: ${selected}`, selectedRows); + }, + getCheckboxProps: (record) => ({ + disabled: false, + name: record.id, + }), + }; + return ( - -
{ - formApiRef.current = api; + <> + +
t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: uptimeGroupsList.length, + }), + pageSizeOptions: ['5', '10', '20', '50'], + onChange: (page, size) => { + setCurrentPage(page); + setPageSize(size); + }, + onShowSizeChange: (current, size) => { + setCurrentPage(1); + setPageSize(size); + } + }} + size='middle' + loading={loading} + empty={ + } + darkModeImage={} + description={t('暂无监控数据')} + style={{ padding: 30 }} + /> + } + className="rounded-xl overflow-hidden" + /> + + + setShowUptimeModal(false)} + okText={t('保存')} + cancelText={t('取消')} + className="rounded-xl" + confirmLoading={modalLoading} + width={600} + > + + setUptimeForm({ ...uptimeForm, categoryName: value })} + /> + setUptimeForm({ ...uptimeForm, url: value })} + /> + setUptimeForm({ ...uptimeForm, slug: value })} + /> + + + + { + setShowDeleteModal(false); + setDeletingGroup(null); + }} + okText={t('确认删除')} + cancelText={t('取消')} + type="warning" + className="rounded-xl" + okButtonProps={{ + type: 'danger', + theme: 'solid' }} > - - - { - const url = (value || '').trim(); - - if (url && !isValidUrl(url)) { - return Promise.reject(t('请输入有效的 URL 地址')); - } - - return Promise.resolve(); - } - } - ]} - /> - - - - { - const slug = (value || '').trim(); - - if (slug && !/^[a-zA-Z0-9_-]+$/.test(slug)) { - return Promise.reject(t('Slug 只能包含字母、数字、下划线和连字符')); - } - - return Promise.resolve(); - } - } - ]} - /> - - - - + {t('确定要删除此分类吗?')} + + ); }; From 5adf1e272d81476fb7c07619ccdc92708fb13cb7 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sun, 15 Jun 2025 03:12:34 +0800 Subject: [PATCH 34/61] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(console=5Fm?= =?UTF-8?q?igrate):=20migrate=20legacy=20UptimeKumaUrl/Slug=20to=20new=20u?= =?UTF-8?q?ptime=5Fkuma=5Fgroups=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added migration logic in `controller/console_migrate.go` * Detects both `UptimeKumaUrl` and `UptimeKumaSlug` * Creates a single-group JSON array under `console_setting.uptime_kuma_groups` - Uses `categoryName: "old"` to mark migrated data - Preserves original `url` and `slug` values * Clears and removes obsolete `UptimeKumaUrl` and `UptimeKumaSlug` keys * Removes outdated code paths that wrote to `console_setting.uptime_kuma_url` and `console_setting.uptime_kuma_slug` * Keeps frontend `DashboardSetting.js` compatible — no additional changes required * Aligns migration behavior with previous `ApiInfo` refactor for consistent console settings management --- controller/console_migrate.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/controller/console_migrate.go b/controller/console_migrate.go index 937fa2ee..d25f199b 100644 --- a/controller/console_migrate.go +++ b/controller/console_migrate.go @@ -67,13 +67,28 @@ func MigrateConsoleSetting(c *gin.Context) { } model.UpdateOption("FAQ", "") } - // Uptime - if v := valMap["UptimeKumaUrl"]; v != "" { - model.UpdateOption("console_setting.uptime_kuma_url", v) + // Uptime Kuma 迁移到新的 groups 结构(console_setting.uptime_kuma_groups) + url := valMap["UptimeKumaUrl"] + slug := valMap["UptimeKumaSlug"] + if url != "" && slug != "" { + // 仅当同时存在 URL 与 Slug 时才进行迁移 + groups := []map[string]interface{}{ + { + "id": 1, + "categoryName": "old", + "url": url, + "slug": slug, + "description": "", + }, + } + bytes, _ := json.Marshal(groups) + model.UpdateOption("console_setting.uptime_kuma_groups", string(bytes)) + } + // 清空旧键内容 + if url != "" { model.UpdateOption("UptimeKumaUrl", "") } - if v := valMap["UptimeKumaSlug"]; v != "" { - model.UpdateOption("console_setting.uptime_kuma_slug", v) + if slug != "" { model.UpdateOption("UptimeKumaSlug", "") } From af506608870fc970cea7c81fb67101af7bf00634 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sun, 15 Jun 2025 03:22:23 +0800 Subject: [PATCH 35/61] =?UTF-8?q?=F0=9F=8E=A8=20style(dashboard):=20Standa?= =?UTF-8?q?rdize=20Empty=20component=20visuals=20in=20Detail=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Refactored the `Detail` page to deliver a more consistent and compact visual experience when displaying empty states. Key changes: 1. Introduced a reusable `ILLUSTRATION_SIZE` constant (96 × 96) to ensure all `IllustrationConstruction` / `IllustrationConstructionDark` icons render at a uniform, reduced size. 2. Applied the new size to every `Empty` component instance within the file. 3. Ensured Empty‐state content (title, description, icon) is centrally aligned for better readability. 4. Updated the Uptime panel’s empty description text for greater clarity. These adjustments improve UI cohesion, reduce visual noise, and make empty messages easier to scan. --- web/src/i18n/locales/en.json | 2 +- web/src/pages/Detail/index.js | 29 +++++++++++++---------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 11bea1e4..ba23ca5c 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1598,6 +1598,7 @@ "请联系管理员在系统设置中配置API信息": "Please contact the administrator to configure API information in the system settings.", "请联系管理员在系统设置中配置公告信息": "Please contact the administrator to configure notice information in the system settings.", "请联系管理员在系统设置中配置常见问答": "Please contact the administrator to configure FAQ information in the system settings.", + "请联系管理员在系统设置中配置Uptime": "Please contact the administrator to configure Uptime in the system settings.", "确定要删除此API信息吗?": "Are you sure you want to delete this API information?", "测速": "Speed Test", "批量删除": "Batch Delete", @@ -1645,7 +1646,6 @@ "高延迟": "High latency", "维护中": "Maintenance", "暂无监控数据": "No monitoring data", - "请联系管理员在系统设置中配置Uptime": "Please contact the administrator to configure Uptime in the system settings.", "IP记录": "IP Record", "记录请求与错误日志 IP": "Record request and error log IP", "开启后,仅“消费”和“错误”日志将记录您的客户端 IP 地址": "After enabling, only \"consumption\" and \"error\" logs will record your client IP address", diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index 777924e0..0fd18d16 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -87,6 +87,8 @@ const Detail = (props) => { const ICON_BUTTON_CLASS = "text-white hover:bg-opacity-80 !rounded-full"; const FLEX_CENTER_GAP2 = "flex items-center gap-2"; + const ILLUSTRATION_SIZE = { width: 96, height: 96 }; + // ========== Constants ========== let now = new Date(); const isAdminUser = isAdmin(); @@ -912,10 +914,9 @@ const Detail = (props) => { return (
} - darkModeImage={} + image={} + darkModeImage={} title={t('暂无监控数据')} - style={{ padding: '8px' }} />
); @@ -1202,11 +1203,10 @@ const Detail = (props) => { ) : (
} - darkModeImage={} + image={} + darkModeImage={} title={t('暂无API信息')} description={t('请联系管理员在系统设置中配置API信息')} - style={{ padding: '12px' }} />
)} @@ -1274,11 +1274,10 @@ const Detail = (props) => { ) : (
} - darkModeImage={} + image={} + darkModeImage={} title={t('暂无系统公告')} description={t('请联系管理员在系统设置中配置公告信息')} - style={{ padding: '12px' }} />
)} @@ -1329,11 +1328,10 @@ const Detail = (props) => { ) : (
} - darkModeImage={} + image={} + darkModeImage={} title={t('暂无常见问答')} description={t('请联系管理员在系统设置中配置常见问答')} - style={{ padding: '12px' }} />
)} @@ -1440,11 +1438,10 @@ const Detail = (props) => { ) : (
} - darkModeImage={} + image={} + darkModeImage={} title={t('暂无监控数据')} - description={t('请联系管理员在系统设置中配置Uptime分组')} - style={{ padding: '12px' }} + description={t('请联系管理员在系统设置中配置Uptime')} />
)} From d2b47969da2484a17027826d4e5be376bee8cba4 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Sun, 15 Jun 2025 03:28:06 +0800 Subject: [PATCH 36/61] =?UTF-8?q?=F0=9F=92=84=20style:=20hide=20announceme?= =?UTF-8?q?nt=20modal=20scrollbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve UX by hiding the vertical scrollbar inside the announcement (NoticeModal) while keeping the content scrollable. Changes • NoticeModal.js - Introduce `notice-content-scroll` class to the content wrapper. - Remove inline custom scrollbar styling for cleaner code. • index.css - Add `.notice-content-scroll` to the global hidden-scrollbar rules, ensuring scrollbars are hidden across browsers. Result Users can still scroll through long announcements, but no scrollbar is shown, giving the modal a cleaner and more consistent appearance. --- web/src/components/layout/NoticeModal.js | 6 +----- web/src/index.css | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/web/src/components/layout/NoticeModal.js b/web/src/components/layout/NoticeModal.js index 9bb062a1..1bbaa554 100644 --- a/web/src/components/layout/NoticeModal.js +++ b/web/src/components/layout/NoticeModal.js @@ -64,11 +64,7 @@ const NoticeModal = ({ visible, onClose, isMobile }) => { return (
); }; diff --git a/web/src/index.css b/web/src/index.css index bb6c3b48..c1254fcc 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -375,23 +375,25 @@ code { } /* 隐藏卡片内容区域的滚动条 */ -.card-content-scroll::-webkit-scrollbar, -.model-settings-scroll::-webkit-scrollbar, -.thinking-content-scroll::-webkit-scrollbar, -.custom-request-textarea .semi-input::-webkit-scrollbar, -.custom-request-textarea textarea::-webkit-scrollbar { - display: none; -} - .card-content-scroll, .model-settings-scroll, .thinking-content-scroll, .custom-request-textarea .semi-input, -.custom-request-textarea textarea { +.custom-request-textarea textarea, +.notice-content-scroll { -ms-overflow-style: none; scrollbar-width: none; } +.card-content-scroll::-webkit-scrollbar, +.model-settings-scroll::-webkit-scrollbar, +.thinking-content-scroll::-webkit-scrollbar, +.custom-request-textarea .semi-input::-webkit-scrollbar, +.custom-request-textarea textarea::-webkit-scrollbar, +.notice-content-scroll::-webkit-scrollbar { + display: none; +} + /* 图片列表滚动条样式 */ .image-list-scroll::-webkit-scrollbar { width: 6px; From e7353772184b31d13758504574758f0e8dead6f6 Mon Sep 17 00:00:00 2001 From: RedwindA Date: Sun, 15 Jun 2025 21:12:56 +0800 Subject: [PATCH 37/61] feat: implement thinking budget control in model name --- relay/channel/gemini/adaptor.go | 7 ++- relay/channel/gemini/relay-gemini.go | 43 ++++++++++++++++++- setting/operation_setting/model-ratio.go | 18 ++++++-- web/src/i18n/locales/en.json | 3 ++ .../pages/Setting/Model/SettingGeminiModel.js | 7 +-- 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/relay/channel/gemini/adaptor.go b/relay/channel/gemini/adaptor.go index e6f66d5f..a81eb3a9 100644 --- a/relay/channel/gemini/adaptor.go +++ b/relay/channel/gemini/adaptor.go @@ -72,8 +72,11 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) { func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { if model_setting.GetGeminiSettings().ThinkingAdapterEnabled { - // suffix -thinking and -nothinking - if strings.HasSuffix(info.OriginModelName, "-thinking") { + // 新增逻辑:处理 -thinking- 格式 + if strings.Contains(info.OriginModelName, "-thinking-") { + parts := strings.Split(info.UpstreamModelName, "-thinking-") + info.UpstreamModelName = parts[0] + } else if strings.HasSuffix(info.OriginModelName, "-thinking") { // 旧的适配 info.UpstreamModelName = strings.TrimSuffix(info.UpstreamModelName, "-thinking") } else if strings.HasSuffix(info.OriginModelName, "-nothinking") { info.UpstreamModelName = strings.TrimSuffix(info.UpstreamModelName, "-nothinking") diff --git a/relay/channel/gemini/relay-gemini.go b/relay/channel/gemini/relay-gemini.go index e2288faf..b65d5af7 100644 --- a/relay/channel/gemini/relay-gemini.go +++ b/relay/channel/gemini/relay-gemini.go @@ -12,6 +12,7 @@ import ( "one-api/relay/helper" "one-api/service" "one-api/setting/model_setting" + "strconv" "strings" "unicode/utf8" @@ -36,6 +37,13 @@ var geminiSupportedMimeTypes = map[string]bool{ "video/flv": true, } +// Gemini 允许的思考预算范围 +const ( + pro25MinBudget = 128 + pro25MaxBudget = 32768 + flash25MaxBudget = 24576 +) + // Setting safety to the lowest possible values since Gemini is already powerless enough func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon.RelayInfo) (*GeminiChatRequest, error) { @@ -57,7 +65,40 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon } if model_setting.GetGeminiSettings().ThinkingAdapterEnabled { - if strings.HasSuffix(info.OriginModelName, "-thinking") { + // 新增逻辑:处理 -thinking- 格式 + if strings.Contains(info.OriginModelName, "-thinking-") { + parts := strings.SplitN(info.OriginModelName, "-thinking-", 2) + if len(parts) == 2 && parts[1] != "" { + if budgetTokens, err := strconv.Atoi(parts[1]); err == nil { + // 从模型名称成功解析预算 + isNew25Pro := strings.HasPrefix(info.OriginModelName, "gemini-2.5-pro") && + !strings.HasPrefix(info.OriginModelName, "gemini-2.5-pro-preview-05-06") && + !strings.HasPrefix(info.OriginModelName, "gemini-2.5-pro-preview-03-25") + + if isNew25Pro { + // 新的2.5pro模型:ThinkingBudget范围为128-32768 + if budgetTokens < pro25MinBudget { + budgetTokens = pro25MinBudget + } else if budgetTokens > pro25MaxBudget { + budgetTokens = pro25MaxBudget + } + } else { + // 其他模型:ThinkingBudget范围为0-24576 + if budgetTokens < 0 { + budgetTokens = 0 + } else if budgetTokens > flash25MaxBudget { + budgetTokens = flash25MaxBudget + } + } + + geminiRequest.GenerationConfig.ThinkingConfig = &GeminiThinkingConfig{ + ThinkingBudget: common.GetPointer(budgetTokens), + IncludeThoughts: true, + } + } + // 如果解析失败,则不设置ThinkingConfig,静默处理 + } + } else if strings.HasSuffix(info.OriginModelName, "-thinking") { // 保留旧逻辑以兼容 // 硬编码不支持 ThinkingBudget 的旧模型 unsupportedModels := []string{ "gemini-2.5-pro-preview-05-06", diff --git a/setting/operation_setting/model-ratio.go b/setting/operation_setting/model-ratio.go index 700a7c4e..fa6f9560 100644 --- a/setting/operation_setting/model-ratio.go +++ b/setting/operation_setting/model-ratio.go @@ -142,6 +142,11 @@ var defaultModelRatio = map[string]float64{ "gemini-2.5-flash-preview-04-17": 0.075, "gemini-2.5-flash-preview-04-17-thinking": 0.075, "gemini-2.5-flash-preview-04-17-nothinking": 0.075, + "gemini-2.5-flash-preview-05-20": 0.075, + "gemini-2.5-flash-preview-05-20-thinking": 0.075, + "gemini-2.5-flash-preview-05-20-nothinking": 0.075, + "gemini-2.5-flash-thinking-*": 0.075, // 用于为后续所有2.5 flash thinking budget 模型设置默认倍率 + "gemini-2.5-pro-thinking-*": 0.625, // 用于为后续所有2.5 pro thinking budget 模型设置默认倍率 "text-embedding-004": 0.001, "chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens "chatglm_pro": 0.7143, // ¥0.01 / 1k tokens @@ -345,7 +350,14 @@ func UpdateModelRatioByJSONString(jsonStr string) error { func GetModelRatio(name string) (float64, bool) { modelRatioMapMutex.RLock() defer modelRatioMapMutex.RUnlock() - + // 处理带有思考预算的模型名称,方便统一定价 + handleThinkingBudgetModel := func(prefix, wildcard string) { + if strings.HasPrefix(name, prefix) && strings.Contains(name, "-thinking-") { + name = wildcard + } + } + handleThinkingBudgetModel("gemini-2.5-flash", "gemini-2.5-flash-thinking-*") + handleThinkingBudgetModel("gemini-2.5-pro", "gemini-2.5-pro-thinking-*") if strings.HasPrefix(name, "gpt-4-gizmo") { name = "gpt-4-gizmo-*" } @@ -470,9 +482,9 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) { return 4, true } else if strings.HasPrefix(name, "gemini-2.0") { return 4, true - } else if strings.HasPrefix(name, "gemini-2.5-pro-preview") { + } else if strings.HasPrefix(name, "gemini-2.5-pro") { // 移除preview来增加兼容性,这里假设正式版的倍率和preview一致 return 8, true - } else if strings.HasPrefix(name, "gemini-2.5-flash-preview") { + } else if strings.HasPrefix(name, "gemini-2.5-flash") { // 同上 if strings.HasSuffix(name, "-nothinking") { return 4, false } else { diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index ba23ca5c..d563aaad 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1373,6 +1373,9 @@ "示例": "Example", "缺省 MaxTokens": "Default MaxTokens", "启用Claude思考适配(-thinking后缀)": "Enable Claude thinking adaptation (-thinking suffix)", + "启用Gemini思考后缀适配": "Enable Gemini thinking suffix adaptation", + "适配-thinking、-thinking-预算数字和-nothinking后缀": "Adapt -thinking, -thinking-budgetNumber, and -nothinking suffixes", + "思考预算占比": "Thinking budget ratio", "Claude思考适配 BudgetTokens = MaxTokens * BudgetTokens 百分比": "Claude thinking adaptation BudgetTokens = MaxTokens * BudgetTokens percentage", "思考适配 BudgetTokens 百分比": "Thinking adaptation BudgetTokens percentage", "0.1-1之间的小数": "Decimal between 0.1 and 1", diff --git a/web/src/pages/Setting/Model/SettingGeminiModel.js b/web/src/pages/Setting/Model/SettingGeminiModel.js index b802af1a..1d28ae92 100644 --- a/web/src/pages/Setting/Model/SettingGeminiModel.js +++ b/web/src/pages/Setting/Model/SettingGeminiModel.js @@ -173,7 +173,8 @@ export default function SettingGeminiModel(props) { {t( "和Claude不同,默认情况下Gemini的思考模型会自动决定要不要思考,就算不开启适配模型也可以正常使用," + - "如果您需要计费,推荐设置无后缀模型价格按思考价格设置" + "如果您需要计费,推荐设置无后缀模型价格按思考价格设置。" + + "支持使用 gemini-2.5-pro-preview-06-05-thinking-128 格式来精确传递思考预算。" )} @@ -183,7 +184,7 @@ export default function SettingGeminiModel(props) { setInputs({ ...inputs, @@ -205,7 +206,7 @@ export default function SettingGeminiModel(props) {
Date: Sun, 15 Jun 2025 23:40:58 +0800 Subject: [PATCH 38/61] update i18n --- web/src/i18n/locales/en.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index d563aaad..8316f8a2 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1373,6 +1373,9 @@ "示例": "Example", "缺省 MaxTokens": "Default MaxTokens", "启用Claude思考适配(-thinking后缀)": "Enable Claude thinking adaptation (-thinking suffix)", + "和Claude不同,默认情况下Gemini的思考模型会自动决定要不要思考,就算不开启适配模型也可以正常使用,": "Unlike Claude, Gemini's thinking model automatically decides whether to think by default, and can be used normally even without enabling the adaptation model.", + "如果您需要计费,推荐设置无后缀模型价格按思考价格设置。": "If you need billing, it is recommended to set the no-suffix model price according to the thinking price.", + "支持使用 gemini-2.5-pro-preview-06-05-thinking-128 格式来精确传递思考预算。": "Supports using gemini-2.5-pro-preview-06-05-thinking-128 format to precisely pass thinking budget.", "启用Gemini思考后缀适配": "Enable Gemini thinking suffix adaptation", "适配-thinking、-thinking-预算数字和-nothinking后缀": "Adapt -thinking, -thinking-budgetNumber, and -nothinking suffixes", "思考预算占比": "Thinking budget ratio", From a9160804a3fd30634717ef4467bc205b49da5ead Mon Sep 17 00:00:00 2001 From: RedwindA Date: Mon, 16 Jun 2025 01:12:18 +0800 Subject: [PATCH 39/61] =?UTF-8?q?=F0=9F=90=9B=20fix(api):=20include=20grou?= =?UTF-8?q?p=20in=20payload=20for=20playground?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/helpers/api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/helpers/api.js b/web/src/helpers/api.js index aef01287..e00a5bdb 100644 --- a/web/src/helpers/api.js +++ b/web/src/helpers/api.js @@ -83,6 +83,7 @@ export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled const payload = { model: inputs.model, messages: processedMessages, + group: inputs.group, stream: inputs.stream, }; From 3ac02879dedd591d13e52068c68de8ddd9238212 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Mon, 16 Jun 2025 03:20:54 +0800 Subject: [PATCH 40/61] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20admin-only=20"r?= =?UTF-8?q?emark"=20support=20for=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * backend - model: add `Remark` field (varchar 255, `json:"remark,omitempty"`); AutoMigrate handles schema change automatically - controller: * accept `remark` on user create/update endpoints * hide remark from regular users (`GetSelf`) by zero-ing the field before JSON marshalling * clarify inline comment explaining the omitempty behaviour * frontend (React / Semi UI) - AddUser.js & EditUser.js: add “Remark” input for admins - UsersTable.js: * remove standalone “Remark” column * show remark as a truncated Tag next to username with Tooltip for full text * import Tooltip component - i18n: reuse existing translations where applicable This commit enables administrators to label users with private notes while ensuring those notes are never exposed to the users themselves. --- controller/user.go | 3 +++ model/user.go | 2 ++ web/src/components/table/UsersTable.js | 22 ++++++++++++++++++++++ web/src/i18n/locales/en.json | 3 ++- web/src/pages/User/AddUser.js | 18 +++++++++++++++++- web/src/pages/User/EditUser.js | 17 +++++++++++++++++ 6 files changed, 63 insertions(+), 2 deletions(-) diff --git a/controller/user.go b/controller/user.go index d7eb42d7..ecaf2583 100644 --- a/controller/user.go +++ b/controller/user.go @@ -459,6 +459,9 @@ func GetSelf(c *gin.Context) { }) return } + // Hide admin remarks: set to empty to trigger omitempty tag, ensuring the remark field is not included in JSON returned to regular users + user.Remark = "" + c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", diff --git a/model/user.go b/model/user.go index 1b3a04b6..6a695457 100644 --- a/model/user.go +++ b/model/user.go @@ -41,6 +41,7 @@ type User struct { DeletedAt gorm.DeletedAt `gorm:"index"` LinuxDOId string `json:"linux_do_id" gorm:"column:linux_do_id;index"` Setting string `json:"setting" gorm:"type:text;column:setting"` + Remark string `json:"remark,omitempty" gorm:"type:varchar(255)" validate:"max=255"` } func (user *User) ToBaseUser() *UserBase { @@ -366,6 +367,7 @@ func (user *User) Edit(updatePassword bool) error { "display_name": newUser.DisplayName, "group": newUser.Group, "quota": newUser.Quota, + "remark": newUser.Remark, } if updatePassword { updates["password"] = newUser.Password diff --git a/web/src/components/table/UsersTable.js b/web/src/components/table/UsersTable.js index a027af59..d245c56f 100644 --- a/web/src/components/table/UsersTable.js +++ b/web/src/components/table/UsersTable.js @@ -26,6 +26,7 @@ import { Space, Table, Tag, + Tooltip, Typography } from '@douyinfe/semi-ui'; import { @@ -110,6 +111,27 @@ const UsersTable = () => { { title: t('用户名'), dataIndex: 'username', + render: (text, record) => { + const remark = record.remark; + if (!remark) { + return {text}; + } + const maxLen = 10; + const displayRemark = remark.length > maxLen ? remark.slice(0, maxLen) + '…' : remark; + return ( + + {text} + + +
+
+ {displayRemark} +
+ + + + ); + }, }, { title: t('分组'), diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index ba23ca5c..358c86bb 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1658,5 +1658,6 @@ "清除失效兑换码": "Clear invalid redemption codes", "确定清除所有失效兑换码?": "Are you sure you want to clear all invalid redemption codes?", "将删除已使用、已禁用及过期的兑换码,此操作不可撤销。": "This will delete all used, disabled, and expired redemption codes, this operation cannot be undone.", - "选择过期时间(可选,留空为永久)": "Select expiration time (optional, leave blank for permanent)" + "选择过期时间(可选,留空为永久)": "Select expiration time (optional, leave blank for permanent)", + "请输入备注(仅管理员可见)": "Please enter a remark (only visible to administrators)" } \ No newline at end of file diff --git a/web/src/pages/User/AddUser.js b/web/src/pages/User/AddUser.js index 99620cfe..259a7750 100644 --- a/web/src/pages/User/AddUser.js +++ b/web/src/pages/User/AddUser.js @@ -16,6 +16,7 @@ import { IconClose, IconKey, IconUserAdd, + IconEdit, } from '@douyinfe/semi-icons'; import { useTranslation } from 'react-i18next'; @@ -27,10 +28,11 @@ const AddUser = (props) => { username: '', display_name: '', password: '', + remark: '', }; const [inputs, setInputs] = useState(originInputs); const [loading, setLoading] = useState(false); - const { username, display_name, password } = inputs; + const { username, display_name, password, remark } = inputs; const handleInputChange = (name, value) => { setInputs((inputs) => ({ ...inputs, [name]: value })); @@ -175,6 +177,20 @@ const AddUser = (props) => { required />
+ +
+ {t('备注')} + handleInputChange('remark', value)} + value={remark} + autoComplete="off" + size="large" + className="!rounded-lg" + prefix={} + showClear + /> +
diff --git a/web/src/pages/User/EditUser.js b/web/src/pages/User/EditUser.js index dceb670a..8c028d74 100644 --- a/web/src/pages/User/EditUser.js +++ b/web/src/pages/User/EditUser.js @@ -22,6 +22,7 @@ import { IconLink, IconUserGroup, IconPlus, + IconEdit, } from '@douyinfe/semi-icons'; import { useTranslation } from 'react-i18next'; @@ -42,6 +43,7 @@ const EditUser = (props) => { email: '', quota: 0, group: 'default', + remark: '', }); const [groupOptions, setGroupOptions] = useState([]); const { @@ -55,6 +57,7 @@ const EditUser = (props) => { email, quota, group, + remark, } = inputs; const handleInputChange = (name, value) => { setInputs((inputs) => ({ ...inputs, [name]: value })); @@ -247,6 +250,20 @@ const EditUser = (props) => { showClear /> + +
+ {t('备注')} + handleInputChange('remark', value)} + value={remark} + autoComplete="off" + size="large" + className="!rounded-lg" + prefix={} + showClear + /> +
From b77574dad5fb730049b0f1d1de414fdf0a4899c4 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Mon, 16 Jun 2025 18:29:49 +0800 Subject: [PATCH 41/61] =?UTF-8?q?=F0=9F=94=A7=20refactor(dto):=20update=20?= =?UTF-8?q?BudgetTokens=20handling=20in=20Thinking=20struct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dto/claude.go | 9 ++++++++- relay/channel/claude/relay-claude.go | 2 +- relay/claude_handler.go | 2 +- service/convert.go | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dto/claude.go b/dto/claude.go index 4d24bc70..98e09c78 100644 --- a/dto/claude.go +++ b/dto/claude.go @@ -178,7 +178,14 @@ type ClaudeRequest struct { type Thinking struct { Type string `json:"type"` - BudgetTokens int `json:"budget_tokens"` + BudgetTokens *int `json:"budget_tokens,omitempty"` +} + +func (c *Thinking) GetBudgetTokens() int { + if c.BudgetTokens == nil { + return 0 + } + return *c.BudgetTokens } func (c *ClaudeRequest) IsStringSystem() bool { diff --git a/relay/channel/claude/relay-claude.go b/relay/channel/claude/relay-claude.go index cb2c75b1..8c74af08 100644 --- a/relay/channel/claude/relay-claude.go +++ b/relay/channel/claude/relay-claude.go @@ -113,7 +113,7 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*dto.Cla // BudgetTokens 为 max_tokens 的 80% claudeRequest.Thinking = &dto.Thinking{ Type: "enabled", - BudgetTokens: int(float64(claudeRequest.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage), + BudgetTokens: common.GetPointer[int](int(float64(claudeRequest.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)), } // TODO: 临时处理 // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking diff --git a/relay/claude_handler.go b/relay/claude_handler.go index fb68a88a..e8805255 100644 --- a/relay/claude_handler.go +++ b/relay/claude_handler.go @@ -98,7 +98,7 @@ func ClaudeHelper(c *gin.Context) (claudeError *dto.ClaudeErrorWithStatusCode) { // BudgetTokens 为 max_tokens 的 80% textRequest.Thinking = &dto.Thinking{ Type: "enabled", - BudgetTokens: int(float64(textRequest.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage), + BudgetTokens: common.GetPointer[int](int(float64(textRequest.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)), } // TODO: 临时处理 // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking diff --git a/service/convert.go b/service/convert.go index cb964a46..7a9e8403 100644 --- a/service/convert.go +++ b/service/convert.go @@ -21,10 +21,10 @@ func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.Re isOpenRouter := info.ChannelType == common.ChannelTypeOpenRouter - if claudeRequest.Thinking != nil { + if claudeRequest.Thinking != nil && claudeRequest.Thinking.Type == "enabled" { if isOpenRouter { reasoning := openrouter.RequestReasoning{ - MaxTokens: claudeRequest.Thinking.BudgetTokens, + MaxTokens: claudeRequest.Thinking.GetBudgetTokens(), } reasoningJSON, err := json.Marshal(reasoning) if err != nil { From 1294d286ee0decfd73e14bc907cdeb736bc4b4de Mon Sep 17 00:00:00 2001 From: RedwindA Date: Mon, 16 Jun 2025 19:41:42 +0800 Subject: [PATCH 42/61] refactor: replace inline closure with a helper function --- setting/operation_setting/model-ratio.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/setting/operation_setting/model-ratio.go b/setting/operation_setting/model-ratio.go index fa6f9560..5155b2fc 100644 --- a/setting/operation_setting/model-ratio.go +++ b/setting/operation_setting/model-ratio.go @@ -347,17 +347,20 @@ func UpdateModelRatioByJSONString(jsonStr string) error { return json.Unmarshal([]byte(jsonStr), &modelRatioMap) } +// 处理带有思考预算的模型名称,方便统一定价 +func handleThinkingBudgetModel(name, prefix, wildcard string) string { + if strings.HasPrefix(name, prefix) && strings.Contains(name, "-thinking-") { + return wildcard + } + return name +} + func GetModelRatio(name string) (float64, bool) { modelRatioMapMutex.RLock() defer modelRatioMapMutex.RUnlock() - // 处理带有思考预算的模型名称,方便统一定价 - handleThinkingBudgetModel := func(prefix, wildcard string) { - if strings.HasPrefix(name, prefix) && strings.Contains(name, "-thinking-") { - name = wildcard - } - } - handleThinkingBudgetModel("gemini-2.5-flash", "gemini-2.5-flash-thinking-*") - handleThinkingBudgetModel("gemini-2.5-pro", "gemini-2.5-pro-thinking-*") + + name = handleThinkingBudgetModel(name, "gemini-2.5-flash", "gemini-2.5-flash-thinking-*") + name = handleThinkingBudgetModel(name, "gemini-2.5-pro", "gemini-2.5-pro-thinking-*") if strings.HasPrefix(name, "gpt-4-gizmo") { name = "gpt-4-gizmo-*" } From d5c96cb036514fb1f0b504da31a47da79a2403df Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Mon, 16 Jun 2025 20:05:54 +0800 Subject: [PATCH 43/61] =?UTF-8?q?=F0=9F=90=9B=20fix(console-setting):=20en?= =?UTF-8?q?sure=20announcements=20are=20returned=20in=20newest-first=20ord?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary • Added stable, descending sort to `GetAnnouncements()` so that the API always returns the latest announcements first. • Introduced helper `getPublishTime()` to safely parse `publishDate` (RFC 3339) and fall back to zero value on failure. • Switched to `sort.SliceStable` for deterministic ordering when timestamps are identical. • Imported the standard `sort` package and removed redundant, duplicate date parsing. Impact Front-end no longer needs to perform client-side sorting; the latest announcement is guaranteed to appear at the top on all platforms and clients. --- setting/console_setting/validation.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/setting/console_setting/validation.go b/setting/console_setting/validation.go index 51a84849..fda6453d 100644 --- a/setting/console_setting/validation.go +++ b/setting/console_setting/validation.go @@ -7,6 +7,7 @@ import ( "regexp" "strings" "time" + "sort" ) var ( @@ -210,8 +211,23 @@ func validateFAQ(faqStr string) error { return nil } +func getPublishTime(item map[string]interface{}) time.Time { + if v, ok := item["publishDate"]; ok { + if s, ok2 := v.(string); ok2 { + if t, err := time.Parse(time.RFC3339, s); err == nil { + return t + } + } + } + return time.Time{} +} + func GetAnnouncements() []map[string]interface{} { - return getJSONList(GetConsoleSetting().Announcements) + list := getJSONList(GetConsoleSetting().Announcements) + sort.SliceStable(list, func(i, j int) bool { + return getPublishTime(list[i]).After(getPublishTime(list[j])) + }) + return list } func GetFAQ() []map[string]interface{} { From 6b7295bbdf2e48fad0ddbe286e06b5e51d0ce35a Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Mon, 16 Jun 2025 21:02:27 +0800 Subject: [PATCH 44/61] =?UTF-8?q?=F0=9F=94=A7=20refactor(relay):=20replace?= =?UTF-8?q?=20UUID=20generation=20with=20helper=20function=20for=20respons?= =?UTF-8?q?e=20IDs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/channel/cohere/relay-cohere.go | 3 +-- relay/channel/gemini/relay-gemini.go | 8 ++++---- relay/channel/palm/relay-palm.go | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/relay/channel/cohere/relay-cohere.go b/relay/channel/cohere/relay-cohere.go index 10c4328b..8a044bf2 100644 --- a/relay/channel/cohere/relay-cohere.go +++ b/relay/channel/cohere/relay-cohere.go @@ -3,7 +3,6 @@ package cohere import ( "bufio" "encoding/json" - "fmt" "github.com/gin-gonic/gin" "io" "net/http" @@ -78,7 +77,7 @@ func stopReasonCohere2OpenAI(reason string) string { } func cohereStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) { - responseId := fmt.Sprintf("chatcmpl-%s", common.GetUUID()) + responseId := helper.GetResponseID(c) createdTime := common.GetTimestamp() usage := &dto.Usage{} responseText := "" diff --git a/relay/channel/gemini/relay-gemini.go b/relay/channel/gemini/relay-gemini.go index e2288faf..e0b70805 100644 --- a/relay/channel/gemini/relay-gemini.go +++ b/relay/channel/gemini/relay-gemini.go @@ -611,9 +611,9 @@ func getResponseToolCall(item *GeminiPart) *dto.ToolCallResponse { } } -func responseGeminiChat2OpenAI(response *GeminiChatResponse) *dto.OpenAITextResponse { +func responseGeminiChat2OpenAI(c *gin.Context, response *GeminiChatResponse) *dto.OpenAITextResponse { fullTextResponse := dto.OpenAITextResponse{ - Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()), + Id: helper.GetResponseID(c), Object: "chat.completion", Created: common.GetTimestamp(), Choices: make([]dto.OpenAITextResponseChoice, 0, len(response.Candidates)), @@ -754,7 +754,7 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) (*dto.C func GeminiChatStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) { // responseText := "" - id := fmt.Sprintf("chatcmpl-%s", common.GetUUID()) + id := helper.GetResponseID(c) createAt := common.GetTimestamp() var usage = &dto.Usage{} var imageCount int @@ -849,7 +849,7 @@ func GeminiChatHandler(c *gin.Context, resp *http.Response, info *relaycommon.Re StatusCode: resp.StatusCode, }, nil } - fullTextResponse := responseGeminiChat2OpenAI(&geminiResponse) + fullTextResponse := responseGeminiChat2OpenAI(c, &geminiResponse) fullTextResponse.Model = info.UpstreamModelName usage := dto.Usage{ PromptTokens: geminiResponse.UsageMetadata.PromptTokenCount, diff --git a/relay/channel/palm/relay-palm.go b/relay/channel/palm/relay-palm.go index 5c398b5e..1f301009 100644 --- a/relay/channel/palm/relay-palm.go +++ b/relay/channel/palm/relay-palm.go @@ -73,7 +73,7 @@ func streamResponsePaLM2OpenAI(palmResponse *PaLMChatResponse) *dto.ChatCompleti func palmStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorWithStatusCode, string) { responseText := "" - responseId := fmt.Sprintf("chatcmpl-%s", common.GetUUID()) + responseId := helper.GetResponseID(c) createdTime := common.GetTimestamp() dataChan := make(chan string) stopChan := make(chan bool) From 7fa21ce95fc412abc975388454ca86ae0e6fe439 Mon Sep 17 00:00:00 2001 From: creamlike1024 Date: Mon, 16 Jun 2025 22:15:12 +0800 Subject: [PATCH 45/61] =?UTF-8?q?feat:=20auto=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/group.go | 9 +- controller/misc.go | 79 ++-- controller/model.go | 18 +- controller/playground.go | 7 +- controller/relay.go | 4 +- controller/user.go | 3 + middleware/distributor.go | 15 +- model/cache.go | 38 +- model/option.go | 6 + relay/helper/price.go | 12 +- service/quota.go | 30 +- setting/auto_group.go | 31 ++ setting/user_usable_group.go | 7 + .../components/settings/OperationSetting.js | 6 +- .../Setting/Operation/GroupRatioSettings.js | 36 ++ web/src/pages/Token/EditToken.js | 362 +++++++++++------- 16 files changed, 477 insertions(+), 186 deletions(-) create mode 100644 setting/auto_group.go diff --git a/controller/group.go b/controller/group.go index 2c725a4d..632b6cd5 100644 --- a/controller/group.go +++ b/controller/group.go @@ -1,10 +1,11 @@ package controller import ( - "github.com/gin-gonic/gin" "net/http" "one-api/model" "one-api/setting" + + "github.com/gin-gonic/gin" ) func GetGroups(c *gin.Context) { @@ -34,6 +35,12 @@ func GetUserGroups(c *gin.Context) { } } } + if setting.GroupInUserUsableGroups("auto") { + usableGroups["auto"] = map[string]interface{}{ + "ratio": "自动", + "desc": setting.GetUsableGroupDescription("auto"), + } + } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", diff --git a/controller/misc.go b/controller/misc.go index 33a41302..1caaf640 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -9,9 +9,9 @@ import ( "one-api/middleware" "one-api/model" "one-api/setting" + "one-api/setting/console_setting" "one-api/setting/operation_setting" "one-api/setting/system_setting" - "one-api/setting/console_setting" "strings" "github.com/gin-gonic/gin" @@ -41,46 +41,47 @@ func GetStatus(c *gin.Context) { cs := console_setting.GetConsoleSetting() data := gin.H{ - "version": common.Version, - "start_time": common.StartTime, - "email_verification": common.EmailVerificationEnabled, - "github_oauth": common.GitHubOAuthEnabled, - "github_client_id": common.GitHubClientId, - "linuxdo_oauth": common.LinuxDOOAuthEnabled, - "linuxdo_client_id": common.LinuxDOClientId, - "telegram_oauth": common.TelegramOAuthEnabled, - "telegram_bot_name": common.TelegramBotName, - "system_name": common.SystemName, - "logo": common.Logo, - "footer_html": common.Footer, - "wechat_qrcode": common.WeChatAccountQRCodeImageURL, - "wechat_login": common.WeChatAuthEnabled, - "server_address": setting.ServerAddress, - "price": setting.Price, - "min_topup": setting.MinTopUp, - "turnstile_check": common.TurnstileCheckEnabled, - "turnstile_site_key": common.TurnstileSiteKey, - "top_up_link": common.TopUpLink, - "docs_link": operation_setting.GetGeneralSetting().DocsLink, - "quota_per_unit": common.QuotaPerUnit, - "display_in_currency": common.DisplayInCurrencyEnabled, - "enable_batch_update": common.BatchUpdateEnabled, - "enable_drawing": common.DrawingEnabled, - "enable_task": common.TaskEnabled, - "enable_data_export": common.DataExportEnabled, - "data_export_default_time": common.DataExportDefaultTime, - "default_collapse_sidebar": common.DefaultCollapseSidebar, - "enable_online_topup": setting.PayAddress != "" && setting.EpayId != "" && setting.EpayKey != "", - "mj_notify_enabled": setting.MjNotifyEnabled, - "chats": setting.Chats, - "demo_site_enabled": operation_setting.DemoSiteEnabled, - "self_use_mode_enabled": operation_setting.SelfUseModeEnabled, + "version": common.Version, + "start_time": common.StartTime, + "email_verification": common.EmailVerificationEnabled, + "github_oauth": common.GitHubOAuthEnabled, + "github_client_id": common.GitHubClientId, + "linuxdo_oauth": common.LinuxDOOAuthEnabled, + "linuxdo_client_id": common.LinuxDOClientId, + "telegram_oauth": common.TelegramOAuthEnabled, + "telegram_bot_name": common.TelegramBotName, + "system_name": common.SystemName, + "logo": common.Logo, + "footer_html": common.Footer, + "wechat_qrcode": common.WeChatAccountQRCodeImageURL, + "wechat_login": common.WeChatAuthEnabled, + "server_address": setting.ServerAddress, + "price": setting.Price, + "min_topup": setting.MinTopUp, + "turnstile_check": common.TurnstileCheckEnabled, + "turnstile_site_key": common.TurnstileSiteKey, + "top_up_link": common.TopUpLink, + "docs_link": operation_setting.GetGeneralSetting().DocsLink, + "quota_per_unit": common.QuotaPerUnit, + "display_in_currency": common.DisplayInCurrencyEnabled, + "enable_batch_update": common.BatchUpdateEnabled, + "enable_drawing": common.DrawingEnabled, + "enable_task": common.TaskEnabled, + "enable_data_export": common.DataExportEnabled, + "data_export_default_time": common.DataExportDefaultTime, + "default_collapse_sidebar": common.DefaultCollapseSidebar, + "enable_online_topup": setting.PayAddress != "" && setting.EpayId != "" && setting.EpayKey != "", + "mj_notify_enabled": setting.MjNotifyEnabled, + "chats": setting.Chats, + "demo_site_enabled": operation_setting.DemoSiteEnabled, + "self_use_mode_enabled": operation_setting.SelfUseModeEnabled, + "default_use_auto_group": setting.DefaultUseAutoGroup, // 面板启用开关 - "api_info_enabled": cs.ApiInfoEnabled, - "uptime_kuma_enabled": cs.UptimeKumaEnabled, - "announcements_enabled": cs.AnnouncementsEnabled, - "faq_enabled": cs.FAQEnabled, + "api_info_enabled": cs.ApiInfoEnabled, + "uptime_kuma_enabled": cs.UptimeKumaEnabled, + "announcements_enabled": cs.AnnouncementsEnabled, + "faq_enabled": cs.FAQEnabled, "oidc_enabled": system_setting.GetOIDCSettings().Enabled, "oidc_client_id": system_setting.GetOIDCSettings().ClientId, diff --git a/controller/model.go b/controller/model.go index df7e59a6..134217a3 100644 --- a/controller/model.go +++ b/controller/model.go @@ -2,7 +2,6 @@ package controller import ( "fmt" - "github.com/gin-gonic/gin" "net/http" "one-api/common" "one-api/constant" @@ -15,6 +14,9 @@ import ( "one-api/relay/channel/moonshot" relaycommon "one-api/relay/common" relayconstant "one-api/relay/constant" + "one-api/setting" + + "github.com/gin-gonic/gin" ) // https://platform.openai.com/docs/api-reference/models/list @@ -179,7 +181,19 @@ func ListModels(c *gin.Context) { if tokenGroup != "" { group = tokenGroup } - models := model.GetGroupModels(group) + var models []string + if tokenGroup == "auto" { + for _, autoGroup := range setting.AutoGroups { + groupModels := model.GetGroupModels(autoGroup) + for _, g := range groupModels { + if !common.StringsContains(models, g) { + models = append(models, g) + } + } + } + } else { + models = model.GetGroupModels(group) + } for _, s := range models { if _, ok := openAIModelsMap[s]; ok { userOpenAiModels = append(userOpenAiModels, openAIModelsMap[s]) diff --git a/controller/playground.go b/controller/playground.go index a2b54790..37a5c7b0 100644 --- a/controller/playground.go +++ b/controller/playground.go @@ -3,7 +3,6 @@ package controller import ( "errors" "fmt" - "github.com/gin-gonic/gin" "net/http" "one-api/common" "one-api/constant" @@ -13,6 +12,8 @@ import ( "one-api/service" "one-api/setting" "time" + + "github.com/gin-gonic/gin" ) func Playground(c *gin.Context) { @@ -57,9 +58,9 @@ func Playground(c *gin.Context) { c.Set("group", group) } c.Set("token_name", "playground-"+group) - channel, err := model.CacheGetRandomSatisfiedChannel(group, playgroundRequest.Model, 0) + channel, finalGroup, err := model.CacheGetRandomSatisfiedChannel(c, group, playgroundRequest.Model, 0) if err != nil { - message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", group, playgroundRequest.Model) + message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", finalGroup, playgroundRequest.Model) openaiErr = service.OpenAIErrorWrapperLocal(errors.New(message), "get_playground_channel_failed", http.StatusInternalServerError) return } diff --git a/controller/relay.go b/controller/relay.go index 1a875dbc..c1c45114 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -259,7 +259,7 @@ func getChannel(c *gin.Context, group, originalModel string, retryCount int) (*m AutoBan: &autoBanInt, }, nil } - channel, err := model.CacheGetRandomSatisfiedChannel(group, originalModel, retryCount) + channel, _, err := model.CacheGetRandomSatisfiedChannel(c, group, originalModel, retryCount) if err != nil { return nil, errors.New(fmt.Sprintf("获取重试渠道失败: %s", err.Error())) } @@ -388,7 +388,7 @@ func RelayTask(c *gin.Context) { retryTimes = 0 } for i := 0; shouldRetryTaskRelay(c, channelId, taskErr, retryTimes) && i < retryTimes; i++ { - channel, err := model.CacheGetRandomSatisfiedChannel(group, originalModel, i) + channel, _, err := model.CacheGetRandomSatisfiedChannel(c, group, originalModel, i) if err != nil { common.LogError(c, fmt.Sprintf("CacheGetRandomSatisfiedChannel failed: %s", err.Error())) break diff --git a/controller/user.go b/controller/user.go index ecaf2583..e8ce3c3d 100644 --- a/controller/user.go +++ b/controller/user.go @@ -226,6 +226,9 @@ func Register(c *gin.Context) { UnlimitedQuota: true, ModelLimitsEnabled: false, } + if setting.DefaultUseAutoGroup { + token.Group = "auto" + } if err := token.Insert(); err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/middleware/distributor.go b/middleware/distributor.go index 1bfe1821..5d1c3641 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -49,8 +49,10 @@ func Distribute() func(c *gin.Context) { } // check group in common.GroupRatio if !setting.ContainsGroupRatio(tokenGroup) { - abortWithOpenAiMessage(c, http.StatusForbidden, fmt.Sprintf("分组 %s 已被弃用", tokenGroup)) - return + if tokenGroup != "auto" { + abortWithOpenAiMessage(c, http.StatusForbidden, fmt.Sprintf("分组 %s 已被弃用", tokenGroup)) + return + } } userGroup = tokenGroup } @@ -95,9 +97,14 @@ func Distribute() func(c *gin.Context) { } if shouldSelectChannel { - channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, modelRequest.Model, 0) + var selectGroup string + channel, selectGroup, err = model.CacheGetRandomSatisfiedChannel(c, userGroup, modelRequest.Model, 0) if err != nil { - message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", userGroup, modelRequest.Model) + showGroup := userGroup + if userGroup == "auto" { + showGroup = fmt.Sprintf("auto(%s)", selectGroup) + } + message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", showGroup, modelRequest.Model) // 如果错误,但是渠道不为空,说明是数据库一致性问题 if channel != nil { common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id)) diff --git a/model/cache.go b/model/cache.go index e2f83e22..1d7d2f25 100644 --- a/model/cache.go +++ b/model/cache.go @@ -3,12 +3,16 @@ package model import ( "errors" "fmt" + "log" "math/rand" "one-api/common" + "one-api/setting" "sort" "strings" "sync" "time" + + "github.com/gin-gonic/gin" ) var group2model2channels map[string]map[string][]*Channel @@ -75,7 +79,39 @@ func SyncChannelCache(frequency int) { } } -func CacheGetRandomSatisfiedChannel(group string, model string, retry int) (*Channel, error) { +func CacheGetRandomSatisfiedChannel(c *gin.Context, group string, model string, retry int) (*Channel, string, error) { + var channel *Channel + var err error + selectGroup := group + if group == "auto" { + if len(setting.AutoGroups) == 0 { + return nil, selectGroup, errors.New("auto groups is not enabled") + } + for _, autoGroup := range setting.AutoGroups { + log.Printf("autoGroup: %s", autoGroup) + channel, _ = getRandomSatisfiedChannel(autoGroup, model, retry) + if channel == nil { + continue + } else { + c.Set("auto_group", autoGroup) + selectGroup = autoGroup + log.Printf("selectGroup: %s", selectGroup) + break + } + } + } else { + channel, err = getRandomSatisfiedChannel(group, model, retry) + if err != nil { + return nil, group, err + } + } + if channel == nil { + return nil, group, errors.New("channel not found") + } + return channel, selectGroup, nil +} + +func getRandomSatisfiedChannel(group string, model string, retry int) (*Channel, error) { if strings.HasPrefix(model, "gpt-4-gizmo") { model = "gpt-4-gizmo-*" } diff --git a/model/option.go b/model/option.go index d1689cb7..89ab8506 100644 --- a/model/option.go +++ b/model/option.go @@ -76,6 +76,8 @@ func InitOptionMap() { common.OptionMap["MinTopUp"] = strconv.Itoa(setting.MinTopUp) common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString() common.OptionMap["Chats"] = setting.Chats2JsonString() + common.OptionMap["AutoGroups"] = setting.AutoGroups2JsonString() + common.OptionMap["DefaultUseAutoGroup"] = strconv.FormatBool(setting.DefaultUseAutoGroup) common.OptionMap["GitHubClientId"] = "" common.OptionMap["GitHubClientSecret"] = "" common.OptionMap["TelegramBotToken"] = "" @@ -287,6 +289,10 @@ func updateOptionMap(key string, value string) (err error) { setting.PayAddress = value case "Chats": err = setting.UpdateChatsByJsonString(value) + case "AutoGroups": + err = setting.UpdateAutoGroupsByJsonString(value) + case "DefaultUseAutoGroup": + setting.DefaultUseAutoGroup = value == "true" case "CustomCallbackAddress": setting.CustomCallbackAddress = value case "EpayId": diff --git a/relay/helper/price.go b/relay/helper/price.go index 1b52bf37..6ecebac5 100644 --- a/relay/helper/price.go +++ b/relay/helper/price.go @@ -2,6 +2,7 @@ package helper import ( "fmt" + "log" "one-api/common" constant2 "one-api/constant" relaycommon "one-api/relay/common" @@ -31,10 +32,19 @@ func (p PriceData) ToSetting() string { func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens int, maxTokens int) (PriceData, error) { modelPrice, usePrice := operation_setting.GetModelPrice(info.OriginModelName, false) groupRatio := setting.GetGroupRatio(info.Group) + var userGroupRatio float64 + autoGroup, exists := c.Get("auto_group") + if exists { + groupRatio = setting.GetGroupRatio(autoGroup.(string)) + log.Printf("final group ratio: %f", groupRatio) + info.Group = autoGroup.(string) + } + actualGroupRatio := groupRatio userGroupRatio, ok := setting.GetGroupGroupRatio(info.UserGroup, info.Group) if ok { - groupRatio = userGroupRatio + actualGroupRatio = userGroupRatio } + groupRatio = actualGroupRatio var preConsumedQuota int var modelRatio float64 var completionRatio float64 diff --git a/service/quota.go b/service/quota.go index da3dd9b9..75b186ae 100644 --- a/service/quota.go +++ b/service/quota.go @@ -3,6 +3,7 @@ package service import ( "errors" "fmt" + "log" "one-api/common" constant2 "one-api/constant" "one-api/dto" @@ -94,11 +95,20 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag audioInputTokens := usage.InputTokenDetails.AudioTokens audioOutTokens := usage.OutputTokenDetails.AudioTokens groupRatio := setting.GetGroupRatio(relayInfo.Group) + modelRatio, _ := operation_setting.GetModelRatio(modelName) + + autoGroup, exists := ctx.Get("auto_group") + if exists { + groupRatio = setting.GetGroupRatio(autoGroup.(string)) + log.Printf("final group ratio: %f", groupRatio) + relayInfo.Group = autoGroup.(string) + } + + actualGroupRatio := groupRatio userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) if ok { - groupRatio = userGroupRatio + actualGroupRatio = userGroupRatio } - modelRatio, _ := operation_setting.GetModelRatio(modelName) quotaInfo := QuotaInfo{ InputDetails: TokenDetails{ @@ -112,7 +122,7 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag ModelName: modelName, UsePrice: relayInfo.UsePrice, ModelRatio: modelRatio, - GroupRatio: groupRatio, + GroupRatio: actualGroupRatio, } quota := calculateAudioQuota(quotaInfo) @@ -149,6 +159,13 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod audioRatio := decimal.NewFromFloat(operation_setting.GetAudioRatio(relayInfo.OriginModelName)) audioCompletionRatio := decimal.NewFromFloat(operation_setting.GetAudioCompletionRatio(modelName)) + autoGroup, exists := ctx.Get("auto_group") + if exists { + groupRatio = setting.GetGroupRatio(autoGroup.(string)) + log.Printf("final group ratio: %f", groupRatio) + relayInfo.Group = autoGroup.(string) + } + actualGroupRatio := groupRatio userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) if ok { @@ -290,6 +307,13 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelPrice := priceData.ModelPrice usePrice := priceData.UsePrice + autoGroup, exists := ctx.Get("auto_group") + if exists { + groupRatio = setting.GetGroupRatio(autoGroup.(string)) + log.Printf("final group ratio: %f", groupRatio) + relayInfo.Group = autoGroup.(string) + } + actualGroupRatio := groupRatio userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) if ok { diff --git a/setting/auto_group.go b/setting/auto_group.go new file mode 100644 index 00000000..5a87ae56 --- /dev/null +++ b/setting/auto_group.go @@ -0,0 +1,31 @@ +package setting + +import "encoding/json" + +var AutoGroups = []string{ + "default", +} + +var DefaultUseAutoGroup = false + +func ContainsAutoGroup(group string) bool { + for _, autoGroup := range AutoGroups { + if autoGroup == group { + return true + } + } + return false +} + +func UpdateAutoGroupsByJsonString(jsonString string) error { + AutoGroups = make([]string, 0) + return json.Unmarshal([]byte(jsonString), &AutoGroups) +} + +func AutoGroups2JsonString() string { + jsonBytes, err := json.Marshal(AutoGroups) + if err != nil { + return "[]" + } + return string(jsonBytes) +} diff --git a/setting/user_usable_group.go b/setting/user_usable_group.go index 7082b683..fdf2f723 100644 --- a/setting/user_usable_group.go +++ b/setting/user_usable_group.go @@ -50,3 +50,10 @@ func GroupInUserUsableGroups(groupName string) bool { _, ok := userUsableGroups[groupName] return ok } + +func GetUsableGroupDescription(groupName string) string { + if desc, ok := userUsableGroups[groupName]; ok { + return desc + } + return groupName +} diff --git a/web/src/components/settings/OperationSetting.js b/web/src/components/settings/OperationSetting.js index 55e328a3..7bd9bf62 100644 --- a/web/src/components/settings/OperationSetting.js +++ b/web/src/components/settings/OperationSetting.js @@ -31,6 +31,8 @@ const OperationSetting = () => { ModelPrice: '', GroupRatio: '', GroupGroupRatio: '', + AutoGroups: '', + DefaultUseAutoGroup: false, UserUsableGroups: '', TopUpLink: '', 'general_setting.docs_link': '', @@ -76,6 +78,7 @@ const OperationSetting = () => { item.key === 'ModelRatio' || item.key === 'GroupRatio' || item.key === 'GroupGroupRatio' || + item.key === 'AutoGroups' || item.key === 'UserUsableGroups' || item.key === 'CompletionRatio' || item.key === 'ModelPrice' || @@ -85,7 +88,8 @@ const OperationSetting = () => { } if ( item.key.endsWith('Enabled') || - ['DefaultCollapseSidebar'].includes(item.key) + ['DefaultCollapseSidebar'].includes(item.key) || + ['DefaultUseAutoGroup'].includes(item.key) ) { newInputs[item.key] = item.value === 'true' ? true : false; } else { diff --git a/web/src/pages/Setting/Operation/GroupRatioSettings.js b/web/src/pages/Setting/Operation/GroupRatioSettings.js index 6d212746..c0e1ed24 100644 --- a/web/src/pages/Setting/Operation/GroupRatioSettings.js +++ b/web/src/pages/Setting/Operation/GroupRatioSettings.js @@ -17,6 +17,8 @@ export default function GroupRatioSettings(props) { GroupRatio: '', UserUsableGroups: '', GroupGroupRatio: '', + AutoGroups: '', + DefaultUseAutoGroup: false, }); const refForm = useRef(); const [inputsRow, setInputsRow] = useState(inputs); @@ -167,6 +169,40 @@ export default function GroupRatioSettings(props) { /> + +
+ verifyJSON(value), + message: t('不是合法的 JSON 字符串'), + }, + ]} + onChange={(value) => + setInputs({ ...inputs, AutoGroups: value }) + } + /> + + + + + + setInputs({ ...inputs, DefaultUseAutoGroup: value }) + } + /> + + diff --git a/web/src/pages/Token/EditToken.js b/web/src/pages/Token/EditToken.js index 71f611bd..782562a3 100644 --- a/web/src/pages/Token/EditToken.js +++ b/web/src/pages/Token/EditToken.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import { API, @@ -7,7 +7,7 @@ import { showSuccess, timestamp2string, renderGroupOption, - renderQuotaWithPrompt + renderQuotaWithPrompt, } from '../../helpers'; import { AutoComplete, @@ -37,11 +37,13 @@ import { IconPlusCircle, } from '@douyinfe/semi-icons'; import { useTranslation } from 'react-i18next'; +import { StatusContext } from '../../context/Status'; const { Text, Title } = Typography; const EditToken = (props) => { const { t } = useTranslation(); + const [statusState, statusDispatch] = useContext(StatusContext); const [isEdit, setIsEdit] = useState(false); const [loading, setLoading] = useState(isEdit); const originInputs = { @@ -119,7 +121,19 @@ const EditToken = (props) => { value: group, ratio: info.ratio, })); + if (statusState?.status?.default_use_auto_group) { + // if contain auto, add it to the first position + if (localGroupOptions.some((group) => group.value === 'auto')) { + // 排序 + localGroupOptions.sort((a, b) => (a.value === 'auto' ? -1 : 1)); + } else { + localGroupOptions.unshift({ label: t('自动选择'), value: 'auto' }); + } + } setGroups(localGroupOptions); + if (statusState?.status?.default_use_auto_group) { + setInputs({ ...inputs, group: 'auto' }); + } } else { showError(t(message)); } @@ -268,32 +282,37 @@ const EditToken = (props) => { placement={isEdit ? 'right' : 'left'} title={ - {isEdit ? - {t('更新')} : - {t('新建')} - } - + {isEdit ? ( + <Tag color='blue' shape='circle'> + {t('更新')} + </Tag> + ) : ( + <Tag color='green' shape='circle'> + {t('新建')} + </Tag> + )} + <Title heading={4} className='m-0'> {isEdit ? t('更新令牌信息') : t('创建新的令牌')} } headerStyle={{ borderBottom: '1px solid var(--semi-color-border)', - padding: '24px' + padding: '24px', }} bodyStyle={{ backgroundColor: 'var(--semi-color-bg-0)', - padding: '0' + padding: '0', }} visible={props.visiable} width={isMobile() ? '100%' : 600} footer={ -
+
- -
-
-
-
+ +
+
+
+
-
- +
+
-
- {t('额度设置')} -
{t('设置令牌可用额度和数量')}
+
+ + {t('额度设置')} + +
+ {t('设置令牌可用额度和数量')} +
-
+
-
+
{t('额度')} - {renderQuotaWithPrompt(remain_quota)} + + {renderQuotaWithPrompt(remain_quota)} +
handleInputChange('remain_quota', value)} value={remain_quota} - autoComplete="new-password" - type="number" - size="large" - className="w-full !rounded-lg" + autoComplete='new-password' + type='number' + size='large' + className='w-full !rounded-lg' prefix={} data={[ { value: 500000, label: '1$' }, @@ -460,16 +517,18 @@ const EditToken = (props) => { {!isEdit && (
- {t('新建数量')} + + {t('新建数量')} + handleTokenCountChange(value)} onSelect={(value) => handleTokenCountChange(value)} value={tokenCount.toString()} - autoComplete="off" - type="number" - className="w-full !rounded-lg" - size="large" + autoComplete='off' + type='number' + className='w-full !rounded-lg' + size='large' prefix={} data={[ { value: 10, label: t('10个') }, @@ -482,12 +541,12 @@ const EditToken = (props) => {
)} -
+
@@ -495,92 +554,137 @@ const EditToken = (props) => {
- -
-
-
-
+ +
+
+
+
-
- +
+
-
- {t('访问限制')} -
{t('设置令牌的访问限制')}
+
+ + {t('访问限制')} + +
+ {t('设置令牌的访问限制')} +
-
+
- {t('IP白名单')} + + {t('IP白名单')} +