From a17003f120e40d6a28b1b92f8533029e3e29083a Mon Sep 17 00:00:00 2001 From: Zhang Minghan Date: Tue, 20 Feb 2024 17:13:33 +0800 Subject: [PATCH] feat: using tremor charts instead of react-chartjs --- app/package.json | 6 +- app/pnpm-lock.yaml | 366 +++++++++++++++++- app/src/admin/colors.ts | 123 +++--- app/src/assets/admin/dashboard.less | 5 +- app/src/assets/ui.less | 19 + app/src/components/admin/ChargeWidget.tsx | 3 +- app/src/components/admin/ChartBox.tsx | 84 +--- .../admin/assemblies/BillingChart.tsx | 70 +--- .../admin/assemblies/ErrorChart.tsx | 70 +--- .../admin/assemblies/ModelChart.tsx | 91 ++--- .../admin/assemblies/ModelUsageChart.tsx | 89 +++-- .../admin/assemblies/RequestChart.tsx | 72 +--- .../admin/assemblies/UserTypeChart.tsx | 71 ++-- app/src/components/ui/command.tsx | 2 +- app/src/dialogs/SettingsDialog.tsx | 2 +- app/src/resources/i18n/cn.json | 1 + app/src/resources/i18n/en.json | 3 +- app/src/resources/i18n/ja.json | 3 +- app/src/resources/i18n/ru.json | 3 +- app/src/utils/processor.ts | 12 + app/tailwind.config.js | 121 +++++- 21 files changed, 745 insertions(+), 471 deletions(-) diff --git a/app/package.json b/app/package.json index 3c64da5f..a2f32774 100644 --- a/app/package.json +++ b/app/package.json @@ -12,6 +12,8 @@ "preview": "vite preview" }, "dependencies": { + "@headlessui/react": "^1.7.18", + "@headlessui/tailwindcss": "^0.2.0", "@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-context-menu": "^2.1.4", @@ -34,8 +36,8 @@ "@radix-ui/react-tooltip": "^1.0.6", "@reduxjs/toolkit": "^1.9.5", "@tanem/react-nprogress": "^5.0.51", + "@tremor/react": "^3.14.0", "axios": "^1.5.0", - "chart.js": "^4.4.0", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", @@ -47,7 +49,6 @@ "next-themes": "^0.2.1", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", - "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", "react-i18next": "^13.2.2", "react-markdown": "^8.0.7", @@ -66,6 +67,7 @@ "workbox-window": "^7.0.0" }, "devDependencies": { + "@tailwindcss/forms": "^0.5.7", "@tauri-apps/cli": "^1.5.6", "@types/node": "^20.5.9", "@types/react": "^18.2.15", diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 04d624f8..d286053c 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -5,6 +5,12 @@ settings: excludeLinksFromLockfile: false dependencies: + '@headlessui/react': + specifier: ^1.7.18 + version: 1.7.18(react-dom@18.2.0)(react@18.2.0) + '@headlessui/tailwindcss': + specifier: ^0.2.0 + version: 0.2.0(tailwindcss@3.3.5) '@radix-ui/react-alert-dialog': specifier: ^1.0.4 version: 1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0) @@ -71,12 +77,12 @@ dependencies: '@tanem/react-nprogress': specifier: ^5.0.51 version: 5.0.51(react-dom@18.2.0)(react@18.2.0) + '@tremor/react': + specifier: ^3.14.0 + version: 3.14.0(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.3.5) axios: specifier: ^1.5.0 version: 1.5.1 - chart.js: - specifier: ^4.4.0 - version: 4.4.0 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -110,9 +116,6 @@ dependencies: react-beautiful-dnd: specifier: ^13.1.1 version: 13.1.1(react-dom@18.2.0)(react@18.2.0) - react-chartjs-2: - specifier: ^5.2.0 - version: 5.2.0(chart.js@4.4.0)(react@18.2.0) react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) @@ -163,6 +166,9 @@ dependencies: version: 7.0.0 devDependencies: + '@tailwindcss/forms': + specifier: ^0.5.7 + version: 0.5.7(tailwindcss@3.3.5) '@tauri-apps/cli': specifier: ^1.5.6 version: 1.5.6 @@ -493,6 +499,17 @@ packages: '@floating-ui/utils': 0.1.6 dev: false + /@floating-ui/react-dom@1.3.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/dom': 1.5.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@floating-ui/react-dom@2.0.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==} peerDependencies: @@ -504,10 +521,45 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@floating-ui/react@0.19.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/react-dom': 1.3.0(react-dom@18.2.0)(react@18.2.0) + aria-hidden: 1.2.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tabbable: 6.2.0 + dev: false + /@floating-ui/utils@0.1.6: resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} dev: false + /@headlessui/react@1.7.18(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ==} + engines: {node: '>=10'} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + dependencies: + '@tanstack/react-virtual': 3.1.1(react-dom@18.2.0)(react@18.2.0) + client-only: 0.0.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@headlessui/tailwindcss@0.2.0(tailwindcss@3.3.5): + resolution: {integrity: sha512-fpL830Fln1SykOCboExsWr3JIVeQKieLJ3XytLe/tt1A0XzqUthOftDmjcCYLW62w7mQI7wXcoPXr3tZ9QfGxw==} + engines: {node: '>=10'} + peerDependencies: + tailwindcss: ^3.0 + dependencies: + tailwindcss: 3.3.5 + dev: false + /@humanwhocodes/config-array@0.11.13: resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} engines: {node: '>=10.10.0'} @@ -567,10 +619,6 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@kurkle/color@0.3.2: - resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==} - dev: false - /@next/env@14.0.4: resolution: {integrity: sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==} dev: false @@ -2041,6 +2089,15 @@ packages: resolution: {integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==} dev: true + /@tailwindcss/forms@0.5.7(tailwindcss@3.3.5): + resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.3.5 + dev: true + /@tanem/react-nprogress@5.0.51(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-YxNUCpznuBVA+PhjEzFmxaa1czXgU+5Ojchw5JBK7DQS6SHIgNudpFohWpNBWMu2KWByGJ2OLH2OwbM/XyP18Q==} peerDependencies: @@ -2053,6 +2110,21 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@tanstack/react-virtual@3.1.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-9tW9xwEW7exSa/8bxu29IPCcB5c9Xlq+whETixIIgYZYKuUY4ZOr000q3oLpL4bkOkolQbB4WXM0MoQGgJXqDg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@tanstack/virtual-core': 3.1.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@tanstack/virtual-core@3.1.1: + resolution: {integrity: sha512-I5lerX+RWxLM+zw35gwwQIoLvtkOm0ecuQUlEjNey+Ga6TnR66WKLBnSHre59onugxhpDLT2nofRYzxf+izDFQ==} + dev: false + /@tauri-apps/cli-darwin-arm64@1.5.6: resolution: {integrity: sha512-NNvG3XLtciCMsBahbDNUEvq184VZmOveTGOuy0So2R33b/6FDkuWaSgWZsR1mISpOuP034htQYW0VITCLelfqg==} engines: {node: '>= 10'} @@ -2160,6 +2232,68 @@ packages: '@tauri-apps/cli-win32-x64-msvc': 1.5.6 dev: true + /@tremor/react@3.14.0(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.3.5): + resolution: {integrity: sha512-bDIaId3js6S0LMhSypLN31l98t13XwPmhF6B1NIZUId/zZwnuE25z95VbKUL8NzHuCETIXSAp+Mm+OyA9EeAFw==} + peerDependencies: + react: ^18.0.0 + react-dom: '>=16.6.0' + dependencies: + '@floating-ui/react': 0.19.2(react-dom@18.2.0)(react@18.2.0) + '@headlessui/react': 1.7.18(react-dom@18.2.0)(react@18.2.0) + '@headlessui/tailwindcss': 0.2.0(tailwindcss@3.3.5) + date-fns: 2.30.0 + react: 18.2.0 + react-day-picker: 8.10.0(date-fns@2.30.0)(react@18.2.0) + react-dom: 18.2.0(react@18.2.0) + react-transition-state: 2.1.1(react-dom@18.2.0)(react@18.2.0) + recharts: 2.12.0(react-dom@18.2.0)(react@18.2.0) + tailwind-merge: 1.14.0 + transitivePeerDependencies: + - tailwindcss + dev: false + + /@types/d3-array@3.2.1: + resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} + dev: false + + /@types/d3-color@3.1.3: + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + dev: false + + /@types/d3-ease@3.0.2: + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + dev: false + + /@types/d3-interpolate@3.0.4: + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + dependencies: + '@types/d3-color': 3.1.3 + dev: false + + /@types/d3-path@3.1.0: + resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==} + dev: false + + /@types/d3-scale@4.0.8: + resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} + dependencies: + '@types/d3-time': 3.0.3 + dev: false + + /@types/d3-shape@3.1.6: + resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} + dependencies: + '@types/d3-path': 3.1.0 + dev: false + + /@types/d3-time@3.0.3: + resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==} + dev: false + + /@types/d3-timer@3.0.2: + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + dev: false + /@types/debug@4.1.10: resolution: {integrity: sha512-tOSCru6s732pofZ+sMv9o4o3Zc+Sa8l3bxd/tweTQudFn06vAzb13ZX46Zi6m6EJ+RUbRTHvgQJ1gBtSgkaUYA==} dependencies: @@ -2802,13 +2936,6 @@ packages: resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} dev: false - /chart.js@4.4.0: - resolution: {integrity: sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==} - engines: {pnpm: '>=7'} - dependencies: - '@kurkle/color': 0.3.2 - dev: false - /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -2966,6 +3093,84 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + dependencies: + internmap: 2.0.3 + dev: false + + /d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + dev: false + + /d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + dev: false + + /d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + dev: false + + /d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + dev: false + + /d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + dev: false + + /d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + dev: false + + /d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.1.0 + dev: false + + /d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + dependencies: + d3-time: 3.1.0 + dev: false + + /d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + dev: false + + /d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + dev: false + + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.23.2 + dev: false + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -2977,6 +3182,10 @@ packages: dependencies: ms: 2.1.2 + /decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + dev: false + /decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} dependencies: @@ -3032,6 +3241,13 @@ packages: esutils: 2.0.3 dev: true + /dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dependencies: + '@babel/runtime': 7.23.2 + csstype: 3.1.2 + dev: false + /dom-serializer@1.4.1: resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} dependencies: @@ -3294,6 +3510,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + dev: false + /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -3307,6 +3527,11 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true + /fast-equals@5.0.1: + resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==} + engines: {node: '>=6.0.0'} + dev: false + /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -3756,6 +3981,11 @@ packages: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} dev: false + /internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + dev: false + /invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: @@ -3978,6 +4208,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + /longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} dev: false @@ -4530,6 +4764,11 @@ packages: dev: true optional: true + /mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + dev: true + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -4954,13 +5193,13 @@ packages: - react-native dev: false - /react-chartjs-2@5.2.0(chart.js@4.4.0)(react@18.2.0): - resolution: {integrity: sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==} + /react-day-picker@8.10.0(date-fns@2.30.0)(react@18.2.0): + resolution: {integrity: sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg==} peerDependencies: - chart.js: ^4.1.1 + date-fns: ^2.28.0 || ^3.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - chart.js: 4.4.0 + date-fns: 2.30.0 react: 18.2.0 dev: false @@ -5166,6 +5405,19 @@ packages: react: 18.2.0 dev: false + /react-smooth@4.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + fast-equals: 5.0.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + dev: false + /react-style-singleton@2.2.1(@types/react@18.2.33)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -5196,6 +5448,30 @@ packages: refractor: 3.6.0 dev: false + /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.23.2 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react-transition-state@2.1.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-kQx5g1FVu9knoz1T1WkapjUgFz08qQ/g1OmuWGi3/AoEFfS0kStxrPlZx81urjCXdz2d+1DqLpU6TyLW/Ro04Q==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -5214,6 +5490,31 @@ packages: dependencies: picomatch: 2.3.1 + /recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + dependencies: + decimal.js-light: 2.5.1 + dev: false + + /recharts@2.12.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-rVNcdNQ5b7+40Ue7mcEKZJyEv+3SUk2bDEVvOyXPDXXVE7TU3lrvnJUgAvO36hSzhRP2DnAamKXvHLFIFOU0Ww==} + engines: {node: '>=14'} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + clsx: 2.0.0 + eventemitter3: 4.0.7 + lodash: 4.17.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 16.13.1 + react-smooth: 4.0.0(react-dom@18.2.0)(react@18.2.0) + recharts-scale: 0.4.5 + tiny-invariant: 1.3.1 + victory-vendor: 36.9.1 + dev: false + /redux-thunk@2.4.2(redux@4.2.1): resolution: {integrity: sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==} peerDependencies: @@ -5543,6 +5844,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + dev: false + /tailwind-merge@1.14.0: resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} dev: false @@ -5953,6 +6258,25 @@ packages: vfile-message: 4.0.2 dev: false + /victory-vendor@36.9.1: + resolution: {integrity: sha512-+pZIP+U3pEJdDCeFmsXwHzV7vNHQC/eIbHklfe2ZCZqayYRH7lQbHcVgsJ0XOOv27hWs4jH4MONgXxHMObTMSA==} + dependencies: + '@types/d3-array': 3.2.1 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.8 + '@types/d3-shape': 3.1.6 + '@types/d3-time': 3.0.3 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + dev: false + /vite-plugin-html@3.2.0(vite@4.5.0): resolution: {integrity: sha512-2VLCeDiHmV/BqqNn5h2V+4280KRgQzCFN47cst3WiNK848klESPQnzuC3okH5XHtgwHH/6s1Ho/YV6yIO0pgoQ==} peerDependencies: diff --git a/app/src/admin/colors.ts b/app/src/admin/colors.ts index e893e1ff..956f13d1 100644 --- a/app/src/admin/colors.ts +++ b/app/src/admin/colors.ts @@ -1,13 +1,12 @@ export const modelColorMapper: Record = { - "gpt-3.5-turbo": "#34bf49", - "gpt-3.5-turbo-instruct": "#34bf49", - "gpt-3.5-turbo-0613": "#34bf49", - "gpt-3.5-turbo-0301": "#34bf49", - "gpt-3.5-turbo-1106": "#11ba2b", - "gpt-3.5-turbo-0125": "#11ba2b", - dalle: "#e4e5e5", - "dall-e-2": "#e4e5e5", - "dall-e-3": "#e4e5e5", + "gpt-3.5-turbo": "green-500", + "gpt-3.5-turbo-instruct": "green-500", + "gpt-3.5-turbo-0613": "green-500", + "gpt-3.5-turbo-0301": "green-500", + "gpt-3.5-turbo-1106": "green-500", + "gpt-3.5-turbo-0125": "green-500", + dalle: "green-600", + "dall-e-2": "green-600", midjourney: "#7300ff", "midjourney-fast": "#7300ff", @@ -18,68 +17,70 @@ export const modelColorMapper: Record = { "gpt-3.5-turbo-16k-0613": "#0abf53", "gpt-3.5-turbo-16k-0301": "#0abf53", - "gpt-4": "#8e43e7", - "gpt-4-1106-preview": "#8e43e7", - "gpt-4-0125-preview": "#8e43e7", - "gpt-4-turbo-preview": "#8e43e7", - "gpt-4-1106-vision-preview": "#8e43e7", - "gpt-4-vision-preview": "#8e43e7", - "gpt-4-0613": "#8e43e7", - "gpt-4-0314": "#8e43e7", - "gpt-4-all": "#8e43e7", - "gpt-4-v": "#8e43e7", - "gpt-4-dalle": "#8e43e7", - "gpt-4-free": "#424242", + "gpt-4": "purple-600", + "gpt-4-1106-preview": "purple-600", + "gpt-4-0125-preview": "purple-600", + "gpt-4-turbo-preview": "purple-600", + "gpt-4-1106-vision-preview": "purple-600", + "gpt-4-vision-preview": "purple-600", + "gpt-4-0613": "purple-600", + "gpt-4-0314": "purple-600", + "gpt-4-all": "purple-600", + "gpt-4-v": "purple-600", + "gpt-4-dalle": "purple-600", + "gpt-4-32k": "purple-600", + "gpt-4-32k-0613": "purple-600", + "gpt-4-32k-0314": "purple-600", - "gpt-4-32k": "#8329f1", - "gpt-4-32k-0613": "#8329f1", - "gpt-4-32k-0314": "#8329f1", + "dall-e-3": "purple-700", - "claude-1": "#ff9d3b", - "claude-1-100k": "#ff9d3b", - "claude-slack": "#ff9d3b", - "claude-2": "#ff840b", - "claude-2.1": "#ff840b", - "claude-2-100k": "#ff840b", + "claude-1": "orange-400", + "claude-1-100k": "orange-400", + "claude-slack": "orange-400", + "claude-2": "orange-400", + "claude-2.1": "orange-400", + "claude-2-100k": "orange-400", - "spark-desk-v1.5": "#06b3e8", - "spark-desk-v2": "#06b3e8", - "spark-desk-v3": "#06b3e8", + "spark-desk-v1.5": "blue-400", + "spark-desk-v2": "blue-400", + "spark-desk-v3": "blue-400", + "spark-desk-v3.5": "blue-400", - "chat-bison-001": "#f82a53", - "gemini-pro": "#f82a53", - "gemini-pro-vision": "#f82a53", + "chat-bison-001": "red-500", + "gemini-pro": "red-500", + "gemini-pro-vision": "red-500", - "bing-creative": "#2673e7", - "bing-balanced": "#2673e7", - "bing-precise": "#2673e7", + "bing-creative": "blue-700", + "bing-balanced": "blue-700", + "bing-precise": "blue-700", - "zhipu-chatglm-turbo": "#008272", - "zhipu-chatglm-pro": "#008272", - "zhipu-chatglm-std": "#008272", - "zhipu-chatglm-lite": "#008272", + "zhipu-chatglm-turbo": "lime-500", + "zhipu-chatglm-pro": "lime-500", + "zhipu-chatglm-std": "lime-500", + "zhipu-chatglm-lite": "lime-500", - "qwen-plus": "#615ced", - "qwen-plus-net": "#615ced", - "qwen-turbo": "#716cfd", - "qwen-turbo-net": "#716cfd", + "qwen-plus": "indigo-600", + "qwen-plus-net": "indigo-600", + "qwen-turbo": "indigo-600", + "qwen-turbo-net": "indigo-600", - "llama-2-70b": "#01a9f0", - "llama-2-13b": "#01a9f0", - "llama-2-7b": "#01a9f0", - "code-llama-34b": "#01a9f0", - "code-llama-13b": "#01a9f0", - "code-llama-7b": "#01a9f0", + "llama-2-70b": "sky-400", + "llama-2-13b": "sky-400", + "llama-2-7b": "sky-400", + "code-llama-34b": "sky-400", + "code-llama-13b": "sky-400", + "code-llama-7b": "sky-400", - hunyuan: "#0052d9", - "360-gpt-v9": "#1db91e", - "baichuan-53b": "#ff9800", - "skylark-lite-public": "#a4f2ff", - "skylark-plus-public": "#a4f2ff", - "skylark-pro-public": "#a4f2ff", - "skylark-chat": "#a4f2ff", + hunyuan: "blue-500", + "360-gpt-v9": "stone-500", + "baichuan-53b": "orange-700", + + "skylark-lite-public": "sky-300", + "skylark-plus-public": "sky-300", + "skylark-pro-public": "sky-300", + "skylark-chat": "sky-300", }; export function getModelColor(model: string): string { - return modelColorMapper[model] || "#111"; + return modelColorMapper[model] || "gray-700"; } diff --git a/app/src/assets/admin/dashboard.less b/app/src/assets/admin/dashboard.less index 444810c1..08a46303 100644 --- a/app/src/assets/admin/dashboard.less +++ b/app/src/assets/admin/dashboard.less @@ -136,10 +136,13 @@ flex-shrink: 0; } - canvas { + .common-chart { min-height: 10rem !important; max-height: 10rem !important; flex-shrink: 0; + + font-size: 0.8rem !important; + font-family: var(--font-family) !important; } .chart-title { diff --git a/app/src/assets/ui.less b/app/src/assets/ui.less index ce3d7e10..20228473 100644 --- a/app/src/assets/ui.less +++ b/app/src/assets/ui.less @@ -326,3 +326,22 @@ input[type="number"] { .text-secondary { color: hsl(var(--text-secondary)) !important; } + +.chart-tooltip, +.recharts-tooltip { + @keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + + animation: fadeIn 0.5s; + position: absolute; +} + +.border-input:focus { + border-color: hsl(var(--border)); +} \ No newline at end of file diff --git a/app/src/components/admin/ChargeWidget.tsx b/app/src/components/admin/ChargeWidget.tsx index 837082a4..79acfcb1 100644 --- a/app/src/components/admin/ChargeWidget.tsx +++ b/app/src/components/admin/ChargeWidget.tsx @@ -65,7 +65,7 @@ import { allModels } from "@/conf"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx"; import Tips from "@/components/Tips.tsx"; import { getQuerySelector, scrollUp } from "@/utils/dom.ts"; -import PopupDialog from "@/components/PopupDialog.tsx"; +import PopupDialog, { popupTypes } from "@/components/PopupDialog.tsx"; import { getApiCharge, getV1Path } from "@/api/v1.ts"; import { Dialog, @@ -167,6 +167,7 @@ function SyncDialog({ current, open, setOpen, onRefresh }: SyncDialogProps) { return ( <> { - chart.resize(); - }); - - return Number( - setTimeout(() => { - clearTimeout(task); - window.addEventListener("resize", () => { - Object.values(Chart.instances).forEach((chart) => { - chart.resize(); - }); - }); - }, 500), - ); -} - function ChartBox() { - const open = useSelector(selectMenu); - let timeout: number = 0; - - useEffect(() => { - timeout = resize(timeout); - - return () => { - clearTimeout(timeout); - }; - }, [open]); - - const [dark, setDark] = useState(getMemory("theme") !== "light"); - themeEvent.bind((theme: string) => setDark(theme === "dark")); - const [model, setModel] = useState({ date: [], value: [], @@ -120,34 +62,22 @@ function ChartBox() { return (
- +
- +
- +
- +
- +
- +
); diff --git a/app/src/components/admin/assemblies/BillingChart.tsx b/app/src/components/admin/assemblies/BillingChart.tsx index eab28558..4afa6504 100644 --- a/app/src/components/admin/assemblies/BillingChart.tsx +++ b/app/src/components/admin/assemblies/BillingChart.tsx @@ -1,65 +1,21 @@ import { useTranslation } from "react-i18next"; import { useMemo } from "react"; -import { Line } from "react-chartjs-2"; import { Loader2 } from "lucide-react"; +import { AreaChart } from "@tremor/react"; type BillingChartProps = { labels: string[]; datasets: number[]; - dark?: boolean; }; -function BillingChart({ labels, datasets, dark }: BillingChartProps) { +function BillingChart({ labels, datasets }: BillingChartProps) { const { t } = useTranslation(); - const data = useMemo(() => { - return { - labels, - datasets: [ - { - label: "CNY", - fill: true, - data: datasets, - backgroundColor: "rgba(255,205,111,0.78)", - }, - ], - }; - }, [labels, datasets]); - - const options = useMemo(() => { - const text = dark ? "#fff" : "#000"; - return { - scales: { - x: { - stacked: true, - grid: { - drawBorder: false, - display: false, - }, - }, - y: { - beginAtZero: true, - stacked: true, - grid: { - drawBorder: false, - display: false, - }, - }, - }, - plugins: { - title: { - display: false, - }, - legend: { - display: true, - labels: { - color: text, - }, - }, - }, - color: text, - borderWidth: 0, - }; - }, [dark]); + const data = useMemo(() => { + return datasets.map((data, index) => ({ + date: labels[index], + [t("admin.billing")]: data, + })); + }, [labels, datasets, t("admin.billing")]); return (
@@ -69,7 +25,15 @@ function BillingChart({ labels, datasets, dark }: BillingChartProps) { )}

- + `¥${value.toFixed(2)}`} + />
); } diff --git a/app/src/components/admin/assemblies/ErrorChart.tsx b/app/src/components/admin/assemblies/ErrorChart.tsx index 582f8a22..3e74cc11 100644 --- a/app/src/components/admin/assemblies/ErrorChart.tsx +++ b/app/src/components/admin/assemblies/ErrorChart.tsx @@ -1,65 +1,21 @@ import { useTranslation } from "react-i18next"; import { useMemo } from "react"; -import { Line } from "react-chartjs-2"; import { Loader2 } from "lucide-react"; +import { AreaChart } from "@tremor/react"; +import { getReadableNumber } from "@/utils/processor.ts"; type ErrorChartProps = { labels: string[]; datasets: number[]; - dark?: boolean; }; -function ErrorChart({ labels, datasets, dark }: ErrorChartProps) { +function ErrorChart({ labels, datasets }: ErrorChartProps) { const { t } = useTranslation(); const data = useMemo(() => { - return { - labels, - datasets: [ - { - label: t("admin.times"), - fill: true, - data: datasets, - backgroundColor: "rgba(255,85,85,0.6)", - }, - ], - }; - }, [labels, datasets]); - - const options = useMemo(() => { - const text = dark ? "#fff" : "#000"; - - return { - scales: { - x: { - stacked: true, - grid: { - drawBorder: false, - display: false, - }, - }, - y: { - beginAtZero: true, - stacked: true, - grid: { - drawBorder: false, - display: false, - }, - }, - }, - plugins: { - title: { - display: false, - }, - legend: { - display: true, - labels: { - color: text, - }, - }, - }, - color: text, - borderWidth: 0, - }; - }, [dark]); + return datasets.map((data, index) => ({ + date: labels[index], + [t("admin.times")]: data, + })); + }, [labels, datasets, t("admin.times")]); return (
@@ -69,7 +25,15 @@ function ErrorChart({ labels, datasets, dark }: ErrorChartProps) { )}

- + getReadableNumber(value, 1)} + />
); } diff --git a/app/src/components/admin/assemblies/ModelChart.tsx b/app/src/components/admin/assemblies/ModelChart.tsx index dbad5032..52f349ca 100644 --- a/app/src/components/admin/assemblies/ModelChart.tsx +++ b/app/src/components/admin/assemblies/ModelChart.tsx @@ -1,9 +1,10 @@ -import { Bar } from "react-chartjs-2"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { getModelColor } from "@/admin/colors.ts"; import { Loader2 } from "lucide-react"; import Tips from "@/components/Tips.tsx"; +import { BarChart } from "@tremor/react"; +import { getReadableNumber } from "@/utils/processor.ts"; +import { getModelColor } from "@/admin/colors.ts"; type ModelChartProps = { labels: string[]; @@ -11,64 +12,31 @@ type ModelChartProps = { model: string; data: number[]; }[]; - dark?: boolean; }; -function ModelChart({ labels, datasets, dark }: ModelChartProps) { + +function ModelChart({ labels, datasets }: ModelChartProps) { const { t } = useTranslation(); const data = useMemo(() => { - return { - labels, - datasets: datasets.map((dataset) => { - return { - label: dataset.model, - data: dataset.data, - backgroundColor: getModelColor(dataset.model), - }; - }), - }; + return labels.map((label, idx) => { + const v: Record = { date: label }; + datasets.forEach((dataset) => { + if (dataset.data[idx] === 0) return; + v[dataset.model] = dataset.data[idx]; + }); + + return v; + }); }, [labels, datasets]); - const options = useMemo(() => { - const text = dark ? "#fff" : "#000"; + const categories = useMemo( + () => datasets.map((dataset) => dataset.model), + [datasets], + ); - return { - responsive: true, - scales: { - x: { - stacked: true, - grid: { - drawBorder: false, - display: false, - }, - }, - y: { - beginAtZero: true, - stacked: true, - grid: { - drawBorder: false, - display: false, - }, - }, - }, - plugins: { - title: { - display: false, - }, - legend: { - position: "right", - display: true, - labels: { - color: text, - }, - }, - }, - color: text, - borderWidth: 0, - defaultFontColor: text, - defaultFontSize: 16, - defaultFontFamily: "Andika", - }; - }, [dark]); + const colors = useMemo( + () => datasets.map((dataset) => getModelColor(dataset.model)), + [datasets], + ); return (
@@ -81,10 +49,17 @@ function ModelChart({ labels, datasets, dark }: ModelChartProps) { )}

- { - //@ts-ignore - - } + getReadableNumber(value, 1, true)} + showLegend={false} + />
); } diff --git a/app/src/components/admin/assemblies/ModelUsageChart.tsx b/app/src/components/admin/assemblies/ModelUsageChart.tsx index 10d1b6f2..7dcac469 100644 --- a/app/src/components/admin/assemblies/ModelUsageChart.tsx +++ b/app/src/components/admin/assemblies/ModelUsageChart.tsx @@ -1,10 +1,10 @@ -import { Doughnut } from "react-chartjs-2"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Loader2 } from "lucide-react"; import Tips from "@/components/Tips.tsx"; import { sum } from "@/utils/base.ts"; -import { getModelColor } from "@/admin/colors.ts"; +import { DonutChart, Legend } from "@tremor/react"; +import { getReadableNumber } from "@/utils/processor.ts"; type ModelChartProps = { labels: string[]; @@ -12,7 +12,6 @@ type ModelChartProps = { model: string; data: number[]; }[]; - dark?: boolean; }; type DataUsage = { @@ -20,7 +19,7 @@ type DataUsage = { usage: number; }; -function ModelUsageChart({ labels, datasets, dark }: ModelChartProps) { +function ModelUsageChart({ labels, datasets }: ModelChartProps) { const { t } = useTranslation(); const usage = useMemo((): Record => { @@ -41,37 +40,43 @@ function ModelUsageChart({ labels, datasets, dark }: ModelChartProps) { .sort((a, b) => b.usage - a.usage); }, [usage]); - const chartData = useMemo(() => { - return { - labels: data.map((item) => item.model), - datasets: [ - { - data: data.map((item) => item.usage), - backgroundColor: data.map((item) => getModelColor(item.model)), - borderWidth: 0, - }, - ], - }; + const chart = useMemo(() => { + return data.map((item) => { + return { name: item.model, value: item.usage }; + }); }, [labels, datasets]); - const options = useMemo(() => { - const text = dark ? "#fff" : "#000"; + type CustomTooltipTypeDonut = { + payload: any; + active: boolean | undefined; + label: any; + }; - return { - responsive: true, - color: text, - borderWidth: 0, - defaultFontColor: text, - defaultFontSize: 16, - defaultFontFamily: "Andika", - // set labels to right side - plugins: { - legend: { - position: "right", - }, - }, - }; - }, [dark]); + const customTooltip = (props: CustomTooltipTypeDonut) => { + const { payload, active } = props; + if (!active || !payload) return null; + const categoryPayload = payload?.[0]; + if (!categoryPayload) return null; + return ( +
+
+
+
+
+

+ {categoryPayload.name} +

+

+ {getReadableNumber(categoryPayload.value, 1)} tokens +

+
+
+
+
+ ); + }; return (
@@ -84,10 +89,22 @@ function ModelUsageChart({ labels, datasets, dark }: ModelChartProps) { )}

- { - // @ts-ignore - - } +
+ `${getReadableNumber(value, 1)} tokens`} + customTooltip={customTooltip} + /> + `${item.name} (${getReadableNumber(item.value, 1)})`, + )} + /> +
); } diff --git a/app/src/components/admin/assemblies/RequestChart.tsx b/app/src/components/admin/assemblies/RequestChart.tsx index c0c649d5..3b6baef4 100644 --- a/app/src/components/admin/assemblies/RequestChart.tsx +++ b/app/src/components/admin/assemblies/RequestChart.tsx @@ -1,66 +1,22 @@ import { useTranslation } from "react-i18next"; import { useMemo } from "react"; -import { Line } from "react-chartjs-2"; import { Loader2 } from "lucide-react"; +import { AreaChart } from "@tremor/react"; +import { getReadableNumber } from "@/utils/processor.ts"; type RequestChartProps = { labels: string[]; datasets: number[]; - dark?: boolean; }; -function RequestChart({ labels, datasets, dark }: RequestChartProps) { + +function RequestChart({ labels, datasets }: RequestChartProps) { const { t } = useTranslation(); const data = useMemo(() => { - return { - labels, - datasets: [ - { - label: t("admin.requests"), - fill: true, - data: datasets, - borderColor: "rgba(109,179,255,1)", - backgroundColor: "rgba(109,179,255,0.5)", - }, - ], - }; - }, [labels, datasets]); - - const options = useMemo(() => { - const text = dark ? "#fff" : "#000"; - - return { - scales: { - x: { - stacked: true, - grid: { - drawBorder: false, - display: false, - }, - }, - y: { - beginAtZero: true, - stacked: true, - grid: { - drawBorder: false, - display: false, - }, - }, - }, - plugins: { - title: { - display: false, - }, - legend: { - display: true, - labels: { - color: text, - }, - }, - }, - color: text, - borderWidth: 0, - }; - }, [dark]); + return datasets.map((data, index) => ({ + date: labels[index], + [t("admin.requests")]: data, + })); + }, [labels, datasets, t("admin.requests")]); return (
@@ -70,7 +26,15 @@ function RequestChart({ labels, datasets, dark }: RequestChartProps) { )}

- + getReadableNumber(value, 1)} + />
); } diff --git a/app/src/components/admin/assemblies/UserTypeChart.tsx b/app/src/components/admin/assemblies/UserTypeChart.tsx index 1e48f7f4..a6eccf83 100644 --- a/app/src/components/admin/assemblies/UserTypeChart.tsx +++ b/app/src/components/admin/assemblies/UserTypeChart.tsx @@ -1,62 +1,27 @@ -import { Doughnut } from "react-chartjs-2"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Loader2 } from "lucide-react"; import { UserTypeChartResponse } from "@/admin/types.ts"; import Tips from "@/components/Tips.tsx"; +import { DonutChart, Legend } from "@tremor/react"; type UserTypeChartProps = { data: UserTypeChartResponse; - dark?: boolean; }; -function UserTypeChart({ data, dark }: UserTypeChartProps) { +function UserTypeChart({ data }: UserTypeChartProps) { const { t } = useTranslation(); const chart = useMemo(() => { - return { - labels: [ - t("admin.identity.normal"), - t("admin.identity.api_paid"), - t("admin.identity.basic_plan"), - t("admin.identity.standard_plan"), - t("admin.identity.pro_plan"), - ], - datasets: [ - { - data: [ - data.normal, - data.api_paid, - data.basic_plan, - data.standard_plan, - data.pro_plan, - ], - backgroundColor: ["#fff", "#aaa", "#ffa64e", "#ff840b", "#ff7e00"], - borderWidth: 0, - }, - ], - }; + return [ + { name: t("admin.identity.normal"), value: data.normal }, + { name: t("admin.identity.api_paid"), value: data.api_paid }, + { name: t("admin.identity.basic_plan"), value: data.basic_plan }, + { name: t("admin.identity.standard_plan"), value: data.standard_plan }, + { name: t("admin.identity.pro_plan"), value: data.pro_plan }, + ]; }, [data]); - const options = useMemo(() => { - const text = dark ? "#fff" : "#000"; - - return { - responsive: true, - color: text, - borderWidth: 0, - defaultFontColor: text, - defaultFontSize: 16, - defaultFontFamily: "Andika", - // set labels to right side - plugins: { - legend: { - position: "right", - }, - }, - }; - }, [dark]); - return (

@@ -73,10 +38,20 @@ function UserTypeChart({ data, dark }: UserTypeChartProps) { )}

- { - // @ts-ignore - - } +
+ + item.name)} + colors={["blue", "cyan", "indigo", "violet", "fuchsia"]} + /> +
); } diff --git a/app/src/components/ui/command.tsx b/app/src/components/ui/command.tsx index 2b8f24c3..886f56ca 100644 --- a/app/src/components/ui/command.tsx +++ b/app/src/components/ui/command.tsx @@ -44,7 +44,7 @@ const CommandInput = React.forwardRef< dispatch(settings.setDialog(open))} > - + {t("settings.title")} diff --git a/app/src/resources/i18n/cn.json b/app/src/resources/i18n/cn.json index 19a12751..fb983ca1 100644 --- a/app/src/resources/i18n/cn.json +++ b/app/src/resources/i18n/cn.json @@ -398,6 +398,7 @@ "settings": "系统设置", "prize": "价格设定", "subscription": "订阅管理", + "billing": "收入", "billing-today": "今日入账", "billing-month": "本月入账", "subscription-users": "订阅用户", diff --git a/app/src/resources/i18n/en.json b/app/src/resources/i18n/en.json index db798f71..c2065f34 100644 --- a/app/src/resources/i18n/en.json +++ b/app/src/resources/i18n/en.json @@ -653,7 +653,8 @@ "ban-action": "Ban User", "ban-action-desc": "Are you sure you want to ban this user?", "unban-action": " unBlock User", - "unban-action-desc": "Are you sure you want to unblock this user?" + "unban-action-desc": "Are you sure you want to unblock this user?", + "billing": "Income" }, "mask": { "title": "Mask Settings", diff --git a/app/src/resources/i18n/ja.json b/app/src/resources/i18n/ja.json index 4ceefce3..c545aa68 100644 --- a/app/src/resources/i18n/ja.json +++ b/app/src/resources/i18n/ja.json @@ -653,7 +653,8 @@ "ban-action": "ゴーストユーザー", "ban-action-desc": "このユーザーを禁止してもよろしいですか?", "unban-action": "ユーザーのブロックを解除する", - "unban-action-desc": "このユーザーのブロックを解除してもよろしいですか?" + "unban-action-desc": "このユーザーのブロックを解除してもよろしいですか?", + "billing": "収入" }, "mask": { "title": "プリセット設定", diff --git a/app/src/resources/i18n/ru.json b/app/src/resources/i18n/ru.json index 1f2d46c1..7e76dc01 100644 --- a/app/src/resources/i18n/ru.json +++ b/app/src/resources/i18n/ru.json @@ -653,7 +653,8 @@ "ban-action": "Пользователь-призрак", "ban-action-desc": "Вы уверены, что хотите заблокировать этого пользователя?", "unban-action": "Разблокировать пользователя", - "unban-action-desc": "Вы уверены, что хотите разблокировать этого пользователя?" + "unban-action-desc": "Вы уверены, что хотите разблокировать этого пользователя?", + "billing": "Доходы" }, "mask": { "title": "Настройки маски", diff --git a/app/src/utils/processor.ts b/app/src/utils/processor.ts index 12d2c662..4d9d4325 100644 --- a/app/src/utils/processor.ts +++ b/app/src/utils/processor.ts @@ -60,3 +60,15 @@ export function handleGenerationData(data: string): string { .replace(/}\s*$/g, ""); return handleLine(escapeRegExp(data), 6); } + +export function getReadableNumber( + num: number, + fixed?: number, + must_k?: boolean, +): string { + if (num >= 1e9) return (num / 1e9).toFixed(fixed) + "b"; + if (num >= 1e6) return (num / 1e6).toFixed(fixed) + "m"; + if (num >= 1e3 || (num !== 0 && must_k)) + return (num / 1e3).toFixed(fixed) + "k"; + return num.toFixed(0); +} diff --git a/app/tailwind.config.js b/app/tailwind.config.js index 25a661ac..f1cd66d1 100644 --- a/app/tailwind.config.js +++ b/app/tailwind.config.js @@ -1,4 +1,6 @@ /** @type {import('tailwindcss').Config} */ +import colors from 'tailwindcss/colors'; + module.exports = { darkMode: ["class"], content: [ @@ -6,8 +8,11 @@ module.exports = { './components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}', + "./node_modules/@tremor/**/*.{js,ts,jsx,tsx}", ], theme: { + transparent: 'transparent', + current: 'currentColor', container: { center: true, padding: "2rem", @@ -57,11 +62,72 @@ module.exports = { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, + tremor: { + brand: { + faint: colors.blue[50], + muted: colors.blue[200], + subtle: colors.blue[400], + DEFAULT: colors.blue[500], + emphasis: colors.blue[700], + inverted: colors.white, + }, + background: { + muted: colors.gray[50], + subtle: colors.gray[100], + DEFAULT: colors.white, + emphasis: colors.gray[700], + }, + border: { + DEFAULT: colors.gray[200], + }, + ring: { + DEFAULT: colors.gray[200], + }, + content: { + subtle: colors.gray[400], + DEFAULT: colors.gray[500], + emphasis: colors.gray[700], + strong: colors.gray[900], + inverted: colors.white, + }, + }, + 'dark-tremor': { + brand: { + faint: '#0B1229', + muted: colors.blue[950], + subtle: colors.blue[800], + DEFAULT: colors.blue[500], + emphasis: colors.blue[400], + inverted: colors.blue[950], + }, + background: { + muted: '#131A2B', + subtle: colors.gray[800], + DEFAULT: colors.gray[900], + emphasis: colors.gray[300], + }, + border: { + DEFAULT: colors.gray[800], + }, + ring: { + DEFAULT: colors.gray[800], + }, + content: { + subtle: colors.gray[600], + DEFAULT: colors.gray[500], + emphasis: colors.gray[200], + strong: colors.gray[50], + inverted: colors.gray[950], + }, + }, }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", + 'tremor-small': '0.375rem', + 'tremor-default': '0.5rem', + 'tremor-full': '9999px', }, keyframes: { "accordion-down": { @@ -77,7 +143,60 @@ module.exports = { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, + boxShadow: { + // light + 'tremor-input': '0 1px 2px 0 rgb(0 0 0 / 0.05)', + 'tremor-card': + '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', + 'tremor-dropdown': + '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)', + // dark + 'dark-tremor-input': '0 1px 2px 0 rgb(0 0 0 / 0.05)', + 'dark-tremor-card': + '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', + 'dark-tremor-dropdown': + '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)', + }, + fontSize: { + 'tremor-label': ['0.75rem', { lineHeight: '1rem' }], + 'tremor-default': ['0.875rem', { lineHeight: '1.25rem' }], + 'tremor-title': ['1.125rem', { lineHeight: '1.75rem' }], + 'tremor-metric': ['1.875rem', { lineHeight: '2.25rem' }], + }, }, }, - plugins: [require("tailwindcss-animate")], + safelist: [ + { + pattern: + /^(bg-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + variants: ['hover', 'ui-selected'], + }, + { + pattern: + /^(text-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + variants: ['hover', 'ui-selected'], + }, + { + pattern: + /^(border-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + variants: ['hover', 'ui-selected'], + }, + { + pattern: + /^(ring-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + }, + { + pattern: + /^(stroke-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + }, + { + pattern: + /^(fill-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, + }, + ], + plugins: [ + require('@headlessui/tailwindcss'), + require("tailwindcss-animate"), + require('@tailwindcss/forms'), +], }