mirror of
https://github.com/mfakbar127/Claude-Samurai
synced 2026-06-21 13:57:42 +00:00
activity grid
This commit is contained in:
@@ -18,7 +18,14 @@
|
||||
"Bash(gh workflow run:*)",
|
||||
"Skill(web-fetch)",
|
||||
"Bash(gh workflow list:*)",
|
||||
"Bash(gh release view:*)"
|
||||
"Bash(gh release view:*)",
|
||||
"mcp__shadcn__view_items_in_registries",
|
||||
"Bash(pnpm remove:*)",
|
||||
"Bash(for:*)",
|
||||
"Bash(do echo:*)",
|
||||
"Bash(jq:*)",
|
||||
"Bash(done)",
|
||||
"Bash(pnpm exec biome format:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
+3
-1
@@ -30,6 +30,8 @@
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-toggle": "^1.1.10",
|
||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@remixicon/react": "^4.6.0",
|
||||
"@tanstack/react-query": "^5.90.2",
|
||||
@@ -45,7 +47,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"i18next": "^25.5.3",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
||||
Generated
+89
-36
@@ -31,7 +31,7 @@ importers:
|
||||
version: 5.2.2(react-hook-form@7.64.0(react@19.2.0))
|
||||
'@lobehub/icons':
|
||||
specifier: ^2.38.0
|
||||
version: 2.38.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
version: 2.38.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.12
|
||||
version: 1.2.12(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
@@ -68,6 +68,12 @@ importers:
|
||||
'@radix-ui/react-tabs':
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@radix-ui/react-toggle':
|
||||
specifier: ^1.1.10
|
||||
version: 1.1.10(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@radix-ui/react-toggle-group':
|
||||
specifier: ^1.1.11
|
||||
version: 1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
@@ -113,9 +119,9 @@ importers:
|
||||
cmdk:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
date-fns:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
dayjs:
|
||||
specifier: ^1.11.19
|
||||
version: 1.11.19
|
||||
i18next:
|
||||
specifier: ^25.5.3
|
||||
version: 25.5.3(typescript@5.8.3)
|
||||
@@ -1569,6 +1575,32 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.11':
|
||||
resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toggle@1.1.10':
|
||||
resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tooltip@1.0.5':
|
||||
resolution: {integrity: sha512-cDKVcfzyO6PpckZekODJZDe5ZxZ2fCZlzKzTmPhe4mX9qTHRfLcKgqb0OKf22xLwDequ2tVleim+ZYx3rabD5w==}
|
||||
peerDependencies:
|
||||
@@ -2890,11 +2922,8 @@ packages:
|
||||
date-fns@3.6.0:
|
||||
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
|
||||
|
||||
date-fns@4.1.0:
|
||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||
|
||||
dayjs@1.11.18:
|
||||
resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
|
||||
dayjs@1.11.19:
|
||||
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
@@ -6527,12 +6556,12 @@ snapshots:
|
||||
|
||||
'@lobehub/emojilib@1.0.0': {}
|
||||
|
||||
'@lobehub/fluent-emoji@2.0.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
'@lobehub/fluent-emoji@2.0.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
'@lobehub/emojilib': 1.0.0
|
||||
'@lobehub/ui': 2.13.2(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd: 5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd-style: 3.7.1(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@lobehub/ui': 2.13.2(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd: 5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd-style: 3.7.1(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
emoji-regex: 10.5.0
|
||||
lodash-es: 4.17.21
|
||||
lucide-react: 0.469.0(react@19.2.0)
|
||||
@@ -6549,11 +6578,11 @@ snapshots:
|
||||
- micromark-util-types
|
||||
- supports-color
|
||||
|
||||
'@lobehub/icons@2.38.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
'@lobehub/icons@2.38.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
'@lobehub/ui': 2.13.2(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd: 5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd-style: 3.7.1(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@lobehub/ui': 2.13.2(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd: 5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd-style: 3.7.1(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
lucide-react: 0.469.0(react@19.2.0)
|
||||
polished: 4.3.1
|
||||
react: 19.2.0
|
||||
@@ -6568,7 +6597,7 @@ snapshots:
|
||||
- micromark-util-types
|
||||
- supports-color
|
||||
|
||||
'@lobehub/ui@2.13.2(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
'@lobehub/ui@2.13.2(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
'@ant-design/cssinjs': 1.24.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@dnd-kit/core': 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
@@ -6579,19 +6608,19 @@ snapshots:
|
||||
'@emoji-mart/react': 1.1.1(emoji-mart@5.6.0)(react@19.2.0)
|
||||
'@floating-ui/react': 0.27.16(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@giscus/react': 3.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@lobehub/fluent-emoji': 2.0.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@lobehub/icons': 2.38.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@lobehub/fluent-emoji': 2.0.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@lobehub/icons': 2.38.0(@babel/core@7.28.4)(@types/mdast@4.0.4)(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@mdx-js/mdx': 3.1.1
|
||||
'@mdx-js/react': 3.1.1(@types/react@19.2.0)(react@19.2.0)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.0)(react@19.2.0)
|
||||
'@shikijs/transformers': 3.13.0
|
||||
'@splinetool/runtime': 0.9.526
|
||||
ahooks: 3.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd: 5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd-style: 3.7.1(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd: 5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd-style: 3.7.1(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
chroma-js: 3.1.2
|
||||
class-variance-authority: 0.7.1
|
||||
dayjs: 1.11.18
|
||||
dayjs: 1.11.19
|
||||
emoji-mart: 5.6.0
|
||||
fast-deep-equal: 3.1.3
|
||||
framer-motion: 12.23.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
@@ -7207,6 +7236,32 @@ snapshots:
|
||||
'@types/react': 19.2.0
|
||||
'@types/react-dom': 19.2.0(@types/react@19.2.0)
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.0)(react@19.2.0)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.0)(react@19.2.0)
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.0
|
||||
'@types/react-dom': 19.2.0(@types/react@19.2.0)
|
||||
|
||||
'@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.0)(react@19.2.0)
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.0
|
||||
'@types/react-dom': 19.2.0(@types/react@19.2.0)
|
||||
|
||||
'@radix-ui/react-tooltip@1.0.5(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
@@ -8061,7 +8116,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@types/js-cookie': 3.0.6
|
||||
dayjs: 1.11.18
|
||||
dayjs: 1.11.19
|
||||
intersection-observer: 0.12.2
|
||||
js-cookie: 3.0.5
|
||||
lodash: 4.17.21
|
||||
@@ -8089,7 +8144,7 @@ snapshots:
|
||||
|
||||
ansis@4.2.0: {}
|
||||
|
||||
antd-style@3.7.1(@types/react@19.2.0)(antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
antd-style@3.7.1(@types/react@19.2.0)(antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
'@ant-design/cssinjs': 1.24.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
'@babel/runtime': 7.28.4
|
||||
@@ -8098,7 +8153,7 @@ snapshots:
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.0)(react@19.2.0)
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/utils': 1.4.2
|
||||
antd: 5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
antd: 5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
react: 19.2.0
|
||||
use-merge-value: 1.2.0(react@19.2.0)
|
||||
transitivePeerDependencies:
|
||||
@@ -8106,7 +8161,7 @@ snapshots:
|
||||
- react-dom
|
||||
- supports-color
|
||||
|
||||
antd@5.27.4(date-fns@4.1.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
antd@5.27.4(date-fns@3.6.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
'@ant-design/colors': 7.2.1
|
||||
'@ant-design/cssinjs': 1.24.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
@@ -8122,7 +8177,7 @@ snapshots:
|
||||
'@rc-component/trigger': 2.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
classnames: 2.5.1
|
||||
copy-to-clipboard: 3.3.3
|
||||
dayjs: 1.11.18
|
||||
dayjs: 1.11.19
|
||||
rc-cascader: 3.34.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
rc-checkbox: 3.5.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
rc-collapse: 3.9.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
@@ -8138,7 +8193,7 @@ snapshots:
|
||||
rc-motion: 2.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
rc-notification: 5.6.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
rc-pagination: 5.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
rc-picker: 4.11.3(date-fns@4.1.0)(dayjs@1.11.18)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
rc-picker: 4.11.3(date-fns@3.6.0)(dayjs@1.11.19)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
rc-progress: 4.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
rc-rate: 2.13.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
rc-resize-observer: 1.4.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
@@ -8616,9 +8671,7 @@ snapshots:
|
||||
|
||||
date-fns@3.6.0: {}
|
||||
|
||||
date-fns@4.1.0: {}
|
||||
|
||||
dayjs@1.11.18: {}
|
||||
dayjs@1.11.19: {}
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
@@ -9705,7 +9758,7 @@ snapshots:
|
||||
d3: 7.9.0
|
||||
d3-sankey: 0.12.3
|
||||
dagre-d3-es: 7.0.11
|
||||
dayjs: 1.11.18
|
||||
dayjs: 1.11.19
|
||||
dompurify: 3.2.7
|
||||
katex: 0.16.23
|
||||
khroma: 2.1.0
|
||||
@@ -10464,7 +10517,7 @@ snapshots:
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
|
||||
rc-picker@4.11.3(date-fns@4.1.0)(dayjs@1.11.18)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
rc-picker@4.11.3(date-fns@3.6.0)(dayjs@1.11.19)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@rc-component/trigger': 2.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
@@ -10475,8 +10528,8 @@ snapshots:
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
optionalDependencies:
|
||||
date-fns: 4.1.0
|
||||
dayjs: 1.11.18
|
||||
date-fns: 3.6.0
|
||||
dayjs: 1.11.19
|
||||
|
||||
rc-progress@4.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type ProjectUsageRecord } from "@/lib/query";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
interface ActivityGridProps {
|
||||
data: ProjectUsageRecord[];
|
||||
onFilteredDataChange?: (data: ProjectUsageRecord[]) => void;
|
||||
}
|
||||
|
||||
interface DayData {
|
||||
date: Date;
|
||||
dateKey: string;
|
||||
totalTokens: number;
|
||||
count: number;
|
||||
records: ProjectUsageRecord[];
|
||||
}
|
||||
|
||||
interface ActivityLevel {
|
||||
level: number;
|
||||
color: string;
|
||||
darkColor: string;
|
||||
}
|
||||
|
||||
export function ActivityGrid({ data }: ActivityGridProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Activity utilities included in the component
|
||||
const getActivityData = useMemo(() => {
|
||||
const today = dayjs();
|
||||
const startDate = today.startOf('year');
|
||||
|
||||
// Create a map of all dates in the time range
|
||||
const dateMap = new Map<string, DayData>();
|
||||
|
||||
// Initialize all dates with zero values
|
||||
let currentDate = startDate;
|
||||
while (currentDate.isBefore(today) || currentDate.isSame(today, 'day')) {
|
||||
const dateKey = currentDate.format('YYYY-MM-DD');
|
||||
const date = currentDate.toDate();
|
||||
|
||||
dateMap.set(dateKey, {
|
||||
date,
|
||||
dateKey,
|
||||
totalTokens: 0,
|
||||
count: 0,
|
||||
records: []
|
||||
});
|
||||
|
||||
currentDate = currentDate.add(1, 'day');
|
||||
}
|
||||
|
||||
// Process usage data
|
||||
data.forEach(record => {
|
||||
try {
|
||||
const recordDate = dayjs(record.timestamp);
|
||||
const dateKey = recordDate.format('YYYY-MM-DD');
|
||||
|
||||
if (dateMap.has(dateKey)) {
|
||||
const existing = dateMap.get(dateKey)!;
|
||||
const tokens = (record.usage?.input_tokens || 0) +
|
||||
(record.usage?.output_tokens || 0) +
|
||||
(record.usage?.cache_read_input_tokens || 0);
|
||||
|
||||
existing.totalTokens += tokens;
|
||||
existing.count += 1;
|
||||
existing.records.push(record);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Invalid date in record:', record.timestamp);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(dateMap.values());
|
||||
}, [data]);
|
||||
|
||||
const getIntensityLevel = (totalTokens: number): ActivityLevel => {
|
||||
// Calculate intensity levels based on token usage percentiles
|
||||
const allValues = getActivityData.map(d => d.totalTokens).filter(v => v > 0);
|
||||
|
||||
if (totalTokens === 0 || allValues.length === 0) {
|
||||
return { level: 0, color: '#ebedf0', darkColor: '#161b22' };
|
||||
}
|
||||
|
||||
// Simple intensity calculation using quartiles
|
||||
const sorted = [...allValues].sort((a, b) => a - b);
|
||||
const len = sorted.length;
|
||||
|
||||
if (totalTokens >= sorted[Math.floor(len * 0.9)]) {
|
||||
return { level: 4, color: '#216e39', darkColor: '#0e4429' };
|
||||
} else if (totalTokens >= sorted[Math.floor(len * 0.7)]) {
|
||||
return { level: 3, color: '#30a14e', darkColor: '#006d32' };
|
||||
} else if (totalTokens >= sorted[Math.floor(len * 0.5)]) {
|
||||
return { level: 2, color: '#40c463', darkColor: '#26a641' };
|
||||
} else if (totalTokens >= sorted[Math.floor(len * 0.3)]) {
|
||||
return { level: 1, color: '#9be9a8', darkColor: '#39d353' };
|
||||
} else {
|
||||
return { level: 0, color: '#ebedf0', darkColor: '#161b22' };
|
||||
}
|
||||
};
|
||||
|
||||
const createGrid = () => {
|
||||
const today = dayjs();
|
||||
const startDate = today.startOf('year');
|
||||
|
||||
const weeks: DayData[][] = [];
|
||||
|
||||
// Start from January 1st and create weeks
|
||||
let currentDate = startDate;
|
||||
|
||||
// Generate weeks until we pass today
|
||||
while (currentDate.isBefore(today) || currentDate.isSame(today, 'day')) {
|
||||
const currentWeek: DayData[] = [];
|
||||
|
||||
// Create a week (7 days), but only include days that are within our date range
|
||||
for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
|
||||
const dateKey = currentDate.format('YYYY-MM-DD');
|
||||
const dayData = getActivityData.find(d => d.dateKey === dateKey);
|
||||
|
||||
currentWeek.push(dayData || {
|
||||
date: currentDate.toDate(),
|
||||
totalTokens: 0,
|
||||
count: 0,
|
||||
records: [],
|
||||
dateKey
|
||||
});
|
||||
|
||||
currentDate = currentDate.add(1, 'day');
|
||||
|
||||
// Stop if we've passed today
|
||||
if (currentDate.isAfter(today)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Only add the week if it contains at least one day from our target period
|
||||
if (currentWeek.some(day => {
|
||||
const dayJs = dayjs(day.date);
|
||||
return dayJs.isAfter(startDate.subtract(1, 'day')) && dayJs.isBefore(today.add(1, 'day'));
|
||||
})) {
|
||||
weeks.push(currentWeek);
|
||||
}
|
||||
}
|
||||
|
||||
return weeks;
|
||||
};
|
||||
|
||||
const grid = createGrid();
|
||||
|
||||
const monthLabels = useMemo(() => {
|
||||
if (grid.length === 0) return [];
|
||||
|
||||
const result: Array<{ month: string; weekIndex: number }> = [];
|
||||
|
||||
grid.forEach((week, weekIndex) => {
|
||||
// Safety check for week data
|
||||
if (!week || week.length === 0 || !week[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstDay = dayjs(week[0].date);
|
||||
|
||||
// Safety check for date
|
||||
if (!firstDay || !firstDay.isValid()) {
|
||||
console.warn(`Invalid date at week ${weekIndex}:`, week[0].date);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use dayjs to format month name directly
|
||||
const monthName = firstDay.format('MMM');
|
||||
|
||||
// Only show label if this is the first week of the month or first week overall
|
||||
const isFirstWeek = weekIndex === 0;
|
||||
const previousWeekMonth = weekIndex > 0 && grid[weekIndex - 1]?.[0]?.date && dayjs(grid[weekIndex - 1][0].date).format('MMM');
|
||||
const isFirstWeekOfMonth = !isFirstWeek && previousWeekMonth !== monthName;
|
||||
|
||||
if (isFirstWeek || isFirstWeekOfMonth) {
|
||||
result.push({ month: monthName, weekIndex });
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}, [grid]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
|
||||
<div className="overflow-x-auto p-3">
|
||||
<div className="inline-block min-w-full">
|
||||
<div className="flex gap-3">
|
||||
{/* Grid */}
|
||||
<div className="flex-1">
|
||||
{/* Month labels */}
|
||||
<div className="flex mb-2 gap-1">
|
||||
{grid.map((_, weekIndex) => {
|
||||
const monthLabel = monthLabels.find(m => m.weekIndex === weekIndex);
|
||||
const displayText = monthLabel?.month || '';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`week-label-week-${weekIndex}`}
|
||||
className="w-3 flex justify-center"
|
||||
title={`Week ${weekIndex}: ${displayText || 'No label'}`}
|
||||
>
|
||||
{displayText && (
|
||||
<div className="text-xs text-muted-foreground whitespace-nowrap">
|
||||
{displayText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Activity grid */}
|
||||
<div className="flex gap-1">
|
||||
{grid.map((week, weekIndex) => (
|
||||
<div key={`activity-week-${weekIndex}`} className="flex flex-col gap-1">
|
||||
{week.map((dayData, dayIndex) => {
|
||||
const intensity = getIntensityLevel(dayData.totalTokens);
|
||||
|
||||
return (
|
||||
<TooltipProvider key={`activity-day-${weekIndex}-${dayIndex}`}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className="w-3 h-3 rounded-sm cursor-pointer border"
|
||||
style={{
|
||||
backgroundColor: intensity.color,
|
||||
borderColor: 'rgba(27, 31, 36, 0.06)'
|
||||
}}
|
||||
data-date={dayjs(dayData.date).format('YYYY-MM-DD')}
|
||||
data-level={intensity.level}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="text-sm space-y-1">
|
||||
<div className="font-medium">
|
||||
{dayjs(dayData.date).format('MMM D, YYYY')}
|
||||
</div>
|
||||
{dayData.totalTokens > 0 ? (
|
||||
<>
|
||||
<div>
|
||||
{formatLargeNumber(dayData.totalTokens)} {t("usage.tokens", "tokens")}
|
||||
</div>
|
||||
<div>
|
||||
{dayData.count} {t("usage.requests", "requests")}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-muted-foreground">
|
||||
{t("usage.noActivity", "No activity")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Legend - Fixed position, not scrollable */}
|
||||
<div className="flex items-center px-3 gap-2 mt-3 text-xs text-muted-foreground">
|
||||
<span>{t("usage.activityGrid.less", "Less")}</span>
|
||||
<div className="flex gap-1">
|
||||
{[0, 1, 2, 3, 4].map((level) => (
|
||||
<div
|
||||
key={level}
|
||||
className="w-3 h-3 rounded-sm border"
|
||||
style={{
|
||||
backgroundColor: level === 0 ? '#ebedf0' :
|
||||
level === 1 ? '#9be9a8' :
|
||||
level === 2 ? '#40c463' :
|
||||
level === 3 ? '#30a14e' : '#216e39',
|
||||
borderColor: 'rgba(27, 31, 36, 0.06)'
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<span>{t("usage.activityGrid.more", "More")}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function for formatting large numbers
|
||||
function formatLargeNumber(num: number): string {
|
||||
if (num >= 1000000) {
|
||||
return `${(num / 1000000).toFixed(1)}M`;
|
||||
} else if (num >= 1000) {
|
||||
return `${(num / 1000).toFixed(1)}K`;
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
format,
|
||||
startOfDay,
|
||||
startOfMonth,
|
||||
startOfWeek,
|
||||
subDays,
|
||||
subHours,
|
||||
} from "date-fns";
|
||||
import dayjs from 'dayjs';
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AreaChart } from "@/components/ui/area-chart";
|
||||
@@ -59,44 +52,44 @@ export function TokenUsageChart({
|
||||
}
|
||||
|
||||
// Filter by time range
|
||||
const now = new Date();
|
||||
let startTime: Date;
|
||||
const now = dayjs();
|
||||
let startTime: dayjs.Dayjs;
|
||||
|
||||
switch (timeRange) {
|
||||
case "5h":
|
||||
startTime = subHours(now, 5);
|
||||
startTime = now.subtract(5, 'hour');
|
||||
break;
|
||||
case "today":
|
||||
startTime = startOfDay(now);
|
||||
startTime = now.startOf('day');
|
||||
break;
|
||||
case "7d":
|
||||
startTime = subDays(now, 6);
|
||||
startTime = now.subtract(6, 'day');
|
||||
break;
|
||||
case "week":
|
||||
startTime = startOfWeek(now);
|
||||
startTime = now.day(0); // Sunday (start of week)
|
||||
break;
|
||||
case "month":
|
||||
startTime = startOfMonth(now);
|
||||
startTime = now.startOf('month');
|
||||
break;
|
||||
case "all":
|
||||
// For "all time", find the earliest timestamp in the data
|
||||
if (filtered.length > 0) {
|
||||
const earliestTime = new Date(
|
||||
const earliestTime = dayjs(
|
||||
Math.min(
|
||||
...filtered.map((record) => new Date(record.timestamp).getTime()),
|
||||
...filtered.map((record) => dayjs(record.timestamp).valueOf()),
|
||||
),
|
||||
);
|
||||
startTime = earliestTime;
|
||||
} else {
|
||||
startTime = new Date(0);
|
||||
startTime = dayjs(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
startTime = subHours(now, 5);
|
||||
startTime = now.subtract(5, 'hour');
|
||||
}
|
||||
|
||||
filtered = filtered.filter(
|
||||
(record) => new Date(record.timestamp) >= startTime,
|
||||
(record) => dayjs(record.timestamp).isAfter(startTime.subtract(1, 'millisecond')),
|
||||
);
|
||||
|
||||
return filtered;
|
||||
@@ -131,43 +124,41 @@ export function TokenUsageChart({
|
||||
const intervals: {
|
||||
[key: string]: { input: number; output: number; cache: number };
|
||||
} = {};
|
||||
const now = new Date();
|
||||
const now = dayjs();
|
||||
|
||||
if (timeRange === "all") {
|
||||
// For all time, group by week
|
||||
const earliestTime =
|
||||
records.length > 0
|
||||
? new Date(
|
||||
? dayjs(
|
||||
Math.min(
|
||||
...records.map((record) =>
|
||||
new Date(record.timestamp).getTime(),
|
||||
dayjs(record.timestamp).valueOf(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: new Date();
|
||||
: dayjs();
|
||||
|
||||
// Get start of the week for earliest time
|
||||
let currentWeekStart = startOfWeek(earliestTime);
|
||||
const nowWeekStart = startOfWeek(now);
|
||||
let currentWeekStart = earliestTime.day(0); // Sunday
|
||||
const nowWeekStart = now.day(0); // Sunday
|
||||
|
||||
// Generate weekly intervals from earliest week to current week
|
||||
while (currentWeekStart <= nowWeekStart) {
|
||||
intervals[currentWeekStart.getTime()] = {
|
||||
while (currentWeekStart.isBefore(nowWeekStart) || currentWeekStart.isSame(nowWeekStart)) {
|
||||
intervals[currentWeekStart.valueOf()] = {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cache: 0,
|
||||
};
|
||||
// Move to next week (7 days)
|
||||
currentWeekStart = new Date(
|
||||
currentWeekStart.getTime() + 7 * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
currentWeekStart = currentWeekStart.add(1, 'week');
|
||||
}
|
||||
|
||||
// Group records into weekly intervals
|
||||
records.forEach((record) => {
|
||||
const recordTime = new Date(record.timestamp);
|
||||
const weekStart = startOfWeek(recordTime);
|
||||
const weekKey = weekStart.getTime();
|
||||
const recordTime = dayjs(record.timestamp);
|
||||
const weekStart = recordTime.day(0); // Sunday
|
||||
const weekKey = weekStart.valueOf();
|
||||
|
||||
if (intervals[weekKey]) {
|
||||
intervals[weekKey].input += record.usage?.input_tokens || 0;
|
||||
@@ -182,7 +173,7 @@ export function TokenUsageChart({
|
||||
|
||||
// Round current time down to nearest 30-minute boundary (epoch-based)
|
||||
const currentIntervalKey =
|
||||
Math.floor(now.getTime() / intervalMs) * intervalMs;
|
||||
Math.floor(now.valueOf() / intervalMs) * intervalMs;
|
||||
|
||||
// Generate intervals (10 intervals for 5 hours)
|
||||
for (let i = 0; i < 10; i++) {
|
||||
@@ -192,9 +183,9 @@ export function TokenUsageChart({
|
||||
|
||||
// Group records into 30-minute intervals
|
||||
records.forEach((record) => {
|
||||
const recordTime = new Date(record.timestamp);
|
||||
const recordTime = dayjs(record.timestamp);
|
||||
const recordIntervalKey =
|
||||
Math.floor(recordTime.getTime() / intervalMs) * intervalMs;
|
||||
Math.floor(recordTime.valueOf() / intervalMs) * intervalMs;
|
||||
|
||||
if (intervals[recordIntervalKey]) {
|
||||
intervals[recordIntervalKey].input += record.usage?.input_tokens || 0;
|
||||
@@ -206,19 +197,18 @@ export function TokenUsageChart({
|
||||
});
|
||||
} else if (timeRange === "today") {
|
||||
// Group by hour for today
|
||||
const startOfToday = startOfDay(now);
|
||||
const currentHour = now.getHours();
|
||||
const startOfToday = now.startOf('day');
|
||||
const currentHour = now.hour();
|
||||
|
||||
for (let i = 0; i <= currentHour; i++) {
|
||||
const hourTime = new Date(startOfToday.getTime() + i * 60 * 60 * 1000);
|
||||
intervals[hourTime.getTime()] = { input: 0, output: 0, cache: 0 };
|
||||
const hourTime = startOfToday.add(i, 'hour');
|
||||
intervals[hourTime.valueOf()] = { input: 0, output: 0, cache: 0 };
|
||||
}
|
||||
|
||||
records.forEach((record) => {
|
||||
const recordTime = new Date(record.timestamp);
|
||||
const hourStart = new Date(recordTime);
|
||||
hourStart.setMinutes(0, 0, 0);
|
||||
const hourKey = hourStart.getTime();
|
||||
const recordTime = dayjs(record.timestamp);
|
||||
const hourStart = recordTime.startOf('hour');
|
||||
const hourKey = hourStart.valueOf();
|
||||
|
||||
if (intervals[hourKey]) {
|
||||
intervals[hourKey].input += record.usage?.input_tokens || 0;
|
||||
@@ -229,45 +219,34 @@ export function TokenUsageChart({
|
||||
});
|
||||
} else {
|
||||
// Group by day for longer periods (7d, week, month)
|
||||
let startDate: Date;
|
||||
let startDate: dayjs.Dayjs;
|
||||
let days: number;
|
||||
|
||||
if (timeRange === "week") {
|
||||
startDate = startOfWeek(now);
|
||||
startDate = now.day(0); // Sunday
|
||||
// Calculate actual days in the current week so far (from start of week to today)
|
||||
const todayStart = startOfDay(now);
|
||||
days =
|
||||
Math.floor(
|
||||
(todayStart.getTime() - startDate.getTime()) /
|
||||
(24 * 60 * 60 * 1000),
|
||||
) + 1;
|
||||
const todayStart = now.startOf('day');
|
||||
days = todayStart.diff(startDate, 'day') + 1;
|
||||
} else if (timeRange === "month") {
|
||||
startDate = startOfMonth(now);
|
||||
startDate = now.startOf('month');
|
||||
// Calculate actual days in the current month so far (from start of month to today)
|
||||
const todayStart = startOfDay(now);
|
||||
days =
|
||||
Math.floor(
|
||||
(todayStart.getTime() - startDate.getTime()) /
|
||||
(24 * 60 * 60 * 1000),
|
||||
) + 1;
|
||||
const todayStart = now.startOf('day');
|
||||
days = todayStart.diff(startDate, 'day') + 1;
|
||||
} else {
|
||||
// For 7d, start from (days-1) days ago to include today
|
||||
days = 7;
|
||||
startDate = startOfDay(subDays(now, days - 1));
|
||||
startDate = now.subtract(days - 1, 'day').startOf('day');
|
||||
}
|
||||
|
||||
for (let i = 0; i < days; i++) {
|
||||
const dayTime = startOfDay(
|
||||
new Date(startDate.getTime() + i * 24 * 60 * 60 * 1000),
|
||||
);
|
||||
intervals[dayTime.getTime()] = { input: 0, output: 0, cache: 0 };
|
||||
const dayTime = startDate.add(i, 'day');
|
||||
intervals[dayTime.valueOf()] = { input: 0, output: 0, cache: 0 };
|
||||
}
|
||||
|
||||
records.forEach((record) => {
|
||||
const recordTime = new Date(record.timestamp);
|
||||
const dayStart = new Date(recordTime);
|
||||
dayStart.setHours(0, 0, 0, 0);
|
||||
const dayKey = dayStart.getTime();
|
||||
const recordTime = dayjs(record.timestamp);
|
||||
const dayStart = recordTime.startOf('day');
|
||||
const dayKey = dayStart.valueOf();
|
||||
|
||||
if (intervals[dayKey]) {
|
||||
intervals[dayKey].input += record.usage?.input_tokens || 0;
|
||||
@@ -287,16 +266,16 @@ export function TokenUsageChart({
|
||||
.map(Number)
|
||||
.sort((a, b) => a - b)
|
||||
.map((timestamp) => {
|
||||
const date = new Date(timestamp);
|
||||
const date = dayjs(timestamp);
|
||||
let label: string;
|
||||
if (timeRange === "all") {
|
||||
label = format(date, "MMM dd, yyyy");
|
||||
label = date.format("MMM DD, YYYY");
|
||||
} else if (timeRange === "today") {
|
||||
label = format(date, "HH:mm");
|
||||
label = date.format("HH:mm");
|
||||
} else if (timeRange === "5h") {
|
||||
label = format(date, "HH:mm");
|
||||
label = date.format("HH:mm");
|
||||
} else {
|
||||
label = format(date, "MMM dd");
|
||||
label = date.format("MMM DD");
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as React from "react"
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-3 min-w-10",
|
||||
sm: "h-9 px-2.5 min-w-9",
|
||||
lg: "h-11 px-5 min-w-11",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Toggle = React.forwardRef<
|
||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, ...props }, ref) => (
|
||||
<TogglePrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
Toggle.displayName = TogglePrimitive.Root.displayName
|
||||
|
||||
export { Toggle, toggleVariants }
|
||||
@@ -227,15 +227,32 @@
|
||||
"updateButton.newVersionAvailable": "New version available",
|
||||
"usage.cacheReadTokens": "Cache Read Tokens",
|
||||
"usage.cost": "Cost",
|
||||
"usage.days": "days",
|
||||
"usage.description": "Monitor your token usage",
|
||||
"usage.error": "Error loading usage data: {{error}}",
|
||||
"usage.inputTokens": "Input Tokens",
|
||||
"usage.loading": "Loading usage data...",
|
||||
"usage.noActivity": "No activity",
|
||||
"usage.noData": "No usage data found.",
|
||||
"usage.outputTokens": "Output Tokens",
|
||||
"usage.refresh": "Refresh",
|
||||
"usage.refreshing": "Refreshing",
|
||||
"usage.requests": "requests",
|
||||
"usage.title": "Usage",
|
||||
"usage.tokens": "tokens",
|
||||
"usage.totalTokens": "total tokens",
|
||||
"usage.view.chart": "Chart view",
|
||||
"usage.view.grid": "Grid view",
|
||||
"usage.activityGrid.title": "Activity Grid",
|
||||
"usage.activityGrid.description": "Daily token usage over the past year",
|
||||
"usage.activityGrid.description3Months": "Daily token usage over the past 3 months",
|
||||
"usage.activityGrid.description6Months": "Daily token usage over the past 6 months",
|
||||
"usage.activityGrid.descriptionThisYear": "Daily token usage this year",
|
||||
"usage.activityGrid.3Months": "3 months",
|
||||
"usage.activityGrid.6Months": "6 months",
|
||||
"usage.activityGrid.thisYear": "This year",
|
||||
"usage.activityGrid.less": "Less",
|
||||
"usage.activityGrid.more": "More",
|
||||
"usageChart.allModels": "All models",
|
||||
"usageChart.allTime": "All time",
|
||||
"usageChart.last5Hours": "Last 5 hours",
|
||||
|
||||
@@ -246,5 +246,22 @@
|
||||
"kimi.apiKeyPlaceholder": "Entrez votre clé API Kimi",
|
||||
"kimi.creating": "Création...",
|
||||
"kimi.createConfig": "Créer la configuration",
|
||||
"kimi.useKimi": "Utiliser Kimi"
|
||||
"kimi.useKimi": "Utiliser Kimi",
|
||||
"usage.days": "jours",
|
||||
"usage.noActivity": "Aucune activité",
|
||||
"usage.requests": "requêtes",
|
||||
"usage.tokens": "jetons",
|
||||
"usage.totalTokens": "jetons au total",
|
||||
"usage.view.chart": "Vue graphique",
|
||||
"usage.view.grid": "Vue grille",
|
||||
"usage.activityGrid.title": "Grille d'activité",
|
||||
"usage.activityGrid.description": "Utilisation quotidienne des jetons au cours de l'année écoulée",
|
||||
"usage.activityGrid.description3Months": "Utilisation quotidienne des jetons au cours des 3 derniers mois",
|
||||
"usage.activityGrid.description6Months": "Utilisation quotidienne des jetons au cours des 6 derniers mois",
|
||||
"usage.activityGrid.descriptionThisYear": "Utilisation quotidienne des jetons cette année",
|
||||
"usage.activityGrid.3Months": "3 mois",
|
||||
"usage.activityGrid.6Months": "6 mois",
|
||||
"usage.activityGrid.thisYear": "Cette année",
|
||||
"usage.activityGrid.less": "Moins",
|
||||
"usage.activityGrid.more": "Plus"
|
||||
}
|
||||
|
||||
@@ -246,5 +246,22 @@
|
||||
"kimi.apiKeyPlaceholder": "Kimi API キーを入力",
|
||||
"kimi.creating": "作成中...",
|
||||
"kimi.createConfig": "設定を作成",
|
||||
"kimi.useKimi": "Kimi を使用"
|
||||
"kimi.useKimi": "Kimi を使用",
|
||||
"usage.days": "日間",
|
||||
"usage.noActivity": "アクティビティなし",
|
||||
"usage.requests": "リクエスト",
|
||||
"usage.tokens": "トークン",
|
||||
"usage.totalTokens": "合計トークン",
|
||||
"usage.view.chart": "チャート表示",
|
||||
"usage.view.grid": "グリッド表示",
|
||||
"usage.activityGrid.title": "アクティビティグリッド",
|
||||
"usage.activityGrid.description": "過去1年間の毎日のトークン使用量",
|
||||
"usage.activityGrid.description3Months": "過去3ヶ月間の毎日のトークン使用量",
|
||||
"usage.activityGrid.description6Months": "過去6ヶ月間の毎日のトークン使用量",
|
||||
"usage.activityGrid.descriptionThisYear": "今年の毎日のトークン使用量",
|
||||
"usage.activityGrid.3Months": "3ヶ月",
|
||||
"usage.activityGrid.6Months": "6ヶ月",
|
||||
"usage.activityGrid.thisYear": "今年",
|
||||
"usage.activityGrid.less": "少ない",
|
||||
"usage.activityGrid.more": "多い"
|
||||
}
|
||||
|
||||
@@ -246,5 +246,22 @@
|
||||
"usageChart.startOfMonth": "本月开始",
|
||||
"usageChart.startOfToday": "今天开始",
|
||||
"usageChart.startOfWeek": "本周开始",
|
||||
"usageChart.timeRange": "时间范围:"
|
||||
"usageChart.timeRange": "时间范围:",
|
||||
"usage.days": "天",
|
||||
"usage.noActivity": "无活动",
|
||||
"usage.requests": "请求",
|
||||
"usage.tokens": "令牌",
|
||||
"usage.totalTokens": "总令牌",
|
||||
"usage.view.chart": "图表视图",
|
||||
"usage.view.grid": "网格视图",
|
||||
"usage.activityGrid.title": "活动网格",
|
||||
"usage.activityGrid.description": "过去一年的每日令牌使用情况",
|
||||
"usage.activityGrid.description3Months": "过去3个月的每日令牌使用情况",
|
||||
"usage.activityGrid.description6Months": "过去6个月的每日令牌使用情况",
|
||||
"usage.activityGrid.descriptionThisYear": "今年的每日令牌使用情况",
|
||||
"usage.activityGrid.3Months": "3个月",
|
||||
"usage.activityGrid.6Months": "6个月",
|
||||
"usage.activityGrid.thisYear": "今年",
|
||||
"usage.activityGrid.less": "较少",
|
||||
"usage.activityGrid.more": "较多"
|
||||
}
|
||||
|
||||
+55
-33
@@ -6,8 +6,10 @@ import {
|
||||
} from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActivityGrid } from "@/components/ActivityGrid";
|
||||
import { TokenUsageChart } from "@/components/TokenUsageChart";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { type ProjectUsageRecord, useProjectUsageFiles } from "@/lib/query";
|
||||
import { cn, formatLargeNumber } from "@/lib/utils";
|
||||
|
||||
@@ -32,45 +34,63 @@ export function UsagePage() {
|
||||
}, [usageData]);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<TooltipProvider>
|
||||
<div
|
||||
className="flex items-center p-3 border-b px-3 justify-between sticky top-0 bg-background z-10 mb-4"
|
||||
className="flex flex-col p-3 border-b px-3 sticky top-0 bg-background z-10 mb-4"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div data-tauri-drag-region>
|
||||
<h3 className="font-bold" data-tauri-drag-region>
|
||||
{t("usage.title")}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground" data-tauri-drag-region>
|
||||
{t("usage.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center justify-between"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div data-tauri-drag-region>
|
||||
<h3 className="font-bold" data-tauri-drag-region>
|
||||
{t("usage.title")}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground" data-tauri-drag-region>
|
||||
{t("usage.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
disabled={isRefetching || isLoading}
|
||||
onClick={(_) => {
|
||||
refetch();
|
||||
}}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
<RefreshCwIcon
|
||||
className={cn({
|
||||
"animate-spin": isRefetching || isLoading,
|
||||
})}
|
||||
/>
|
||||
{isRefetching || isLoading
|
||||
? t("usage.refreshing")
|
||||
: t("usage.refresh")}
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
disabled={isRefetching || isLoading}
|
||||
onClick={(_) => {
|
||||
refetch();
|
||||
}}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-muted-foreground h-8 px-3"
|
||||
>
|
||||
<RefreshCwIcon
|
||||
className={cn({
|
||||
"animate-spin": isRefetching || isLoading,
|
||||
})}
|
||||
/>
|
||||
{isRefetching || isLoading
|
||||
? t("usage.refreshing")
|
||||
: t("usage.refresh")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 space-y-4">
|
||||
{isLoading ? (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="p-1 border rounded-lg pb-5">
|
||||
<div className="space-y-4">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="bg-card p-4 rounded-lg space-y-2 border"
|
||||
>
|
||||
<div className="h-4 rounded animate-pulse"></div>
|
||||
<div className="h-8 rounded animate-pulse"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
@@ -89,6 +109,9 @@ export function UsagePage() {
|
||||
<p>{t("usage.error", { error: error.message })}</p>
|
||||
) : usageData && usageData.length > 0 ? (
|
||||
<>
|
||||
<div className=" rounded-lg pb-5">
|
||||
<ActivityGrid data={usageData} />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="bg-blue-50 border-blue-100 text-blue-700 border-2 p-4 rounded-lg space-y-2 dark:bg-blue-950/20 dark:border-blue-900/30 dark:text-blue-300">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -146,8 +169,7 @@ export function UsagePage() {
|
||||
</p>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
<div className=" bg-card p-6 rounded-lg w-full min-w-0 border">
|
||||
<div className="mt-6 bg-card p-6 rounded-lg w-full min-w-0 border">
|
||||
<TokenUsageChart
|
||||
data={usageData}
|
||||
onFilteredDataChange={setFilteredUsageData}
|
||||
@@ -158,6 +180,6 @@ export function UsagePage() {
|
||||
<p>{t("usage.noData")}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user