feat: using tremor charts instead of react-chartjs

This commit is contained in:
Zhang Minghan 2024-02-20 17:13:33 +08:00
parent 8708bca503
commit a17003f120
21 changed files with 745 additions and 471 deletions

View File

@ -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",

366
app/pnpm-lock.yaml generated
View File

@ -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:

View File

@ -1,13 +1,12 @@
export const modelColorMapper: Record<string, string> = {
"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<string, string> = {
"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";
}

View File

@ -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 {

View File

@ -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));
}

View File

@ -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 (
<>
<PopupDialog
type={popupTypes.Text}
title={t("admin.charge.sync")}
name={t("admin.charge.sync-site")}
placeholder={t("admin.charge.sync-placeholder")}

View File

@ -1,5 +1,5 @@
import ModelChart from "@/components/admin/assemblies/ModelChart.tsx";
import { useEffect, useState } from "react";
import { useState } from "react";
import {
BillingChartResponse,
ErrorChartResponse,
@ -7,20 +7,6 @@ import {
RequestChartResponse,
UserTypeChartResponse,
} from "@/admin/types.ts";
import { ArcElement, Chart, Filler, LineElement, PointElement } from "chart.js";
import {
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from "chart.js";
import { useSelector } from "react-redux";
import { selectMenu } from "@/store/menu.ts";
import { getMemory } from "@/utils/memory.ts";
import { themeEvent } from "@/events/theme.ts";
import RequestChart from "@/components/admin/assemblies/RequestChart.tsx";
import BillingChart from "@/components/admin/assemblies/BillingChart.tsx";
import ErrorChart from "@/components/admin/assemblies/ErrorChart.tsx";
@ -35,51 +21,7 @@ import {
import ModelUsageChart from "@/components/admin/assemblies/ModelUsageChart.tsx";
import UserTypeChart from "@/components/admin/assemblies/UserTypeChart.tsx";
Chart.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
ArcElement,
PointElement,
LineElement,
Filler,
);
function resize(task: number): number {
Object.values(Chart.instances).forEach((chart) => {
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<boolean>(getMemory("theme") !== "light");
themeEvent.bind((theme: string) => setDark(theme === "dark"));
const [model, setModel] = useState<ModelChartResponse>({
date: [],
value: [],
@ -120,34 +62,22 @@ function ChartBox() {
return (
<div className={`chart-boxes`}>
<div className={`chart-box`}>
<ModelChart labels={model.date} datasets={model.value} dark={dark} />
<ModelChart labels={model.date} datasets={model.value} />
</div>
<div className={`chart-box`}>
<ModelUsageChart
labels={model.date}
datasets={model.value}
dark={dark}
/>
<ModelUsageChart labels={model.date} datasets={model.value} />
</div>
<div className={`chart-box`}>
<BillingChart
labels={billing.date}
datasets={billing.value}
dark={dark}
/>
<BillingChart labels={billing.date} datasets={billing.value} />
</div>
<div className={`chart-box`}>
<UserTypeChart data={user} dark={dark} />
<UserTypeChart data={user} />
</div>
<div className={`chart-box`}>
<RequestChart
labels={request.date}
datasets={request.value}
dark={dark}
/>
<RequestChart labels={request.date} datasets={request.value} />
</div>
<div className={`chart-box`}>
<ErrorChart labels={error.date} datasets={error.value} dark={dark} />
<ErrorChart labels={error.date} datasets={error.value} />
</div>
</div>
);

View File

@ -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]);
return datasets.map((data, index) => ({
date: labels[index],
[t("admin.billing")]: data,
}));
}, [labels, datasets, t("admin.billing")]);
return (
<div className={`chart`}>
@ -69,7 +25,15 @@ function BillingChart({ labels, datasets, dark }: BillingChartProps) {
<Loader2 className={`h-4 w-4 inline-block animate-spin`} />
)}
</p>
<Line id={`billing-chart`} data={data} options={options} />
<AreaChart
className={`common-chart`}
data={data}
categories={[t("admin.billing")]}
index={"date"}
colors={["orange"]}
showAnimation={true}
valueFormatter={(value) => `${value.toFixed(2)}`}
/>
</div>
);
}

View File

@ -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 (
<div className={`chart`}>
@ -69,7 +25,15 @@ function ErrorChart({ labels, datasets, dark }: ErrorChartProps) {
<Loader2 className={`h-4 w-4 inline-block animate-spin`} />
)}
</p>
<Line id={`error-chart`} data={data} options={options} />
<AreaChart
className={`common-chart`}
data={data}
categories={[t("admin.times")]}
index={"date"}
colors={["red"]}
showAnimation={true}
valueFormatter={(value) => getReadableNumber(value, 1)}
/>
</div>
);
}

View File

@ -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<string, any> = { 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 (
<div className={`chart`}>
@ -81,10 +49,17 @@ function ModelChart({ labels, datasets, dark }: ModelChartProps) {
<Loader2 className={`h-4 w-4 inline-block animate-spin`} />
)}
</p>
{
//@ts-ignore
<Bar id={`model-chart`} data={data} options={options} />
}
<BarChart
className={`common-chart`}
data={data}
index={"date"}
layout={`horizontal`}
stack={true}
categories={categories}
colors={colors}
valueFormatter={(value) => getReadableNumber(value, 1, true)}
showLegend={false}
/>
</div>
);
}

View File

@ -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<string, number> => {
@ -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 (
<div className="chart-tooltip min-w-56 w-max z-10 rounded-tremor-default border border-tremor-border bg-tremor-background p-2 text-tremor-default shadow-tremor-dropdown">
<div className="flex flex-1 space-x-2.5">
<div
className={`flex w-1.5 flex-col bg-${categoryPayload?.color}-500 rounded`}
/>
<div className="w-full">
<div className="flex items-center justify-between space-x-8">
<p className="whitespace-nowrap text-right text-tremor-content">
{categoryPayload.name}
</p>
<p className="whitespace-nowrap text-right font-medium text-tremor-content-emphasis">
{getReadableNumber(categoryPayload.value, 1)} tokens
</p>
</div>
</div>
</div>
</div>
);
};
return (
<div className={`chart`}>
@ -84,10 +89,22 @@ function ModelUsageChart({ labels, datasets, dark }: ModelChartProps) {
<Loader2 className={`h-4 w-4 inline-block animate-spin`} />
)}
</p>
{
// @ts-ignore
<Doughnut id={`model-usage-chart`} data={chartData} options={options} />
}
<div className={`flex flex-row`}>
<DonutChart
className={`common-chart p-4 w-[50%]`}
variant={`donut`}
data={chart}
showAnimation={true}
valueFormatter={(value) => `${getReadableNumber(value, 1)} tokens`}
customTooltip={customTooltip}
/>
<Legend
className={`common-chart p-2 w-[50%] z-0`}
categories={chart.map(
(item) => `${item.name} (${getReadableNumber(item.value, 1)})`,
)}
/>
</div>
</div>
);
}

View File

@ -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 (
<div className={`chart`}>
@ -70,7 +26,15 @@ function RequestChart({ labels, datasets, dark }: RequestChartProps) {
<Loader2 className={`h-4 w-4 inline-block animate-spin`} />
)}
</p>
<Line id={`request-chart`} data={data} options={options} />
<AreaChart
className={`common-chart`}
data={data}
categories={[t("admin.requests")]}
index={"date"}
colors={["blue"]}
showAnimation={true}
valueFormatter={(value) => getReadableNumber(value, 1)}
/>
</div>
);
}

View File

@ -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 (
<div className={`chart`}>
<p className={`chart-title mb-2`}>
@ -73,10 +38,20 @@ function UserTypeChart({ data, dark }: UserTypeChartProps) {
<Loader2 className={`h-4 w-4 inline-block animate-spin`} />
)}
</p>
{
// @ts-ignore
<Doughnut id={`user-type-chart`} data={chart} options={options} />
}
<div className={`flex flex-row`}>
<DonutChart
className={`common-chart p-4 w-[65%]`}
variant={`donut`}
data={chart}
showAnimation={true}
colors={["blue", "cyan", "indigo", "violet", "fuchsia"]}
/>
<Legend
className={`common-chart p-4 w-[35%]`}
categories={chart.map((item) => item.name)}
colors={["blue", "cyan", "indigo", "violet", "fuchsia"]}
/>
</div>
</div>
);
}

View File

@ -44,7 +44,7 @@ const CommandInput = React.forwardRef<
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 border-none",
className,
)}
{...props}

View File

@ -72,7 +72,7 @@ function SettingsDialog() {
open={open}
onOpenChange={(open) => dispatch(settings.setDialog(open))}
>
<DialogContent className={`fixed-dialog settings-dialog`}>
<DialogContent className={`flex-dialog settings-dialog`}>
<DialogHeader>
<DialogTitle>{t("settings.title")}</DialogTitle>
<DialogDescription asChild>

View File

@ -398,6 +398,7 @@
"settings": "系统设置",
"prize": "价格设定",
"subscription": "订阅管理",
"billing": "收入",
"billing-today": "今日入账",
"billing-month": "本月入账",
"subscription-users": "订阅用户",

View File

@ -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",

View File

@ -653,7 +653,8 @@
"ban-action": "ゴーストユーザー",
"ban-action-desc": "このユーザーを禁止してもよろしいですか?",
"unban-action": "ユーザーのブロックを解除する",
"unban-action-desc": "このユーザーのブロックを解除してもよろしいですか?"
"unban-action-desc": "このユーザーのブロックを解除してもよろしいですか?",
"billing": "収入"
},
"mask": {
"title": "プリセット設定",

View File

@ -653,7 +653,8 @@
"ban-action": "Пользователь-призрак",
"ban-action-desc": "Вы уверены, что хотите заблокировать этого пользователя?",
"unban-action": "Разблокировать пользователя",
"unban-action-desc": "Вы уверены, что хотите разблокировать этого пользователя?"
"unban-action-desc": "Вы уверены, что хотите разблокировать этого пользователя?",
"billing": "Доходы"
},
"mask": {
"title": "Настройки маски",

View File

@ -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);
}

View File

@ -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'),
],
}