Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9cf111a86 | ||
|
|
99f8914830 | ||
|
|
6da877709d | ||
|
|
c4948dc11f | ||
|
|
3f243129a4 | ||
|
|
eb2cae7a77 | ||
|
|
a2bbcd3939 | ||
|
|
0650c4bc26 | ||
|
|
7c32bf97ae | ||
|
|
70aecc1f0c | ||
|
|
5bd5c7d2c5 | ||
|
|
d6dce4021d | ||
|
|
ccfdb99348 | ||
|
|
2ca712435a | ||
|
|
0af8e34a8f | ||
|
|
f2583e152a | ||
|
|
cf70bd2dc1 | ||
|
|
8f50af917f | ||
|
|
4bdde365cf | ||
|
|
7f1bc33eed | ||
|
|
d0d24aba4e | ||
|
|
0d68c0d683 | ||
|
|
e1ab735823 | ||
|
|
bb67a8cbbb | ||
|
|
c8a7ac7dc6 | ||
|
|
788a294200 | ||
|
|
f34935a634 | ||
|
|
a7331256fc | ||
|
|
8fa3941db9 | ||
|
|
ada120c1b9 | ||
|
|
72f7168893 | ||
|
|
75b70a26bd | ||
|
|
4433e5d8e5 | ||
|
|
351b2e1417 | ||
|
|
3c7dc5a49e | ||
|
|
9895231696 | ||
|
|
3fa3281c51 | ||
|
|
bbf11d0374 | ||
|
|
04ee696f6a | ||
|
|
ddbbe92a85 | ||
|
|
32ba5e1802 | ||
|
|
a1c6eb177a | ||
|
|
14d60264c7 | ||
|
|
f4d6abace7 | ||
|
|
a5e1717138 | ||
|
|
472a4d400b | ||
|
|
fda4786737 | ||
|
|
c3686579b2 | ||
|
|
f2dfb2f32d | ||
|
|
60ee9f1a15 | ||
|
|
f43d1af6a7 | ||
|
|
b631b4c7b7 | ||
|
|
e3aba0529f | ||
|
|
cf682feae9 | ||
|
|
03d798760c | ||
|
|
51450c2b02 | ||
|
|
751ddbb3f3 | ||
|
|
36b3620d0c | ||
|
|
fd3eb513ff | ||
|
|
af0aaddbfd | ||
|
|
b167170aa9 | ||
|
|
642ac3ebc4 | ||
|
|
69cceb4fc7 | ||
|
|
ac32e23e87 | ||
|
|
04de4ff606 | ||
|
|
27761566c9 | ||
|
|
3d93eda92d | ||
|
|
6943571a6d | ||
|
|
28994cb9da | ||
|
|
5866634b3f | ||
|
|
e3d91f1cc7 | ||
|
|
8ac06259f5 | ||
|
|
c8933a6669 | ||
|
|
6c2da264db | ||
|
|
993f01a301 | ||
|
|
9e29e7283c | ||
|
|
7da106798d | ||
|
|
b2e9cbb44a | ||
|
|
143f6df2c1 | ||
|
|
a64505fcc1 | ||
|
|
0aaf919827 | ||
|
|
bf8e988488 | ||
|
|
d26ea28028 | ||
|
|
abdd5e08a6 | ||
|
|
47a5ef915b | ||
|
|
5861508153 | ||
|
|
85754ccfb5 | ||
|
|
cbeea23295 | ||
|
|
e2d79a6802 | ||
|
|
c68d85096b | ||
|
|
fcafa83111 | ||
|
|
47d7418eb1 | ||
|
|
8751400ae3 | ||
|
|
3889c1fa71 | ||
|
|
a4d1d3b16c | ||
|
|
adfdf41e85 | ||
|
|
a6825f7ee3 | ||
|
|
830ee45161 | ||
|
|
5f7df3f871 | ||
|
|
34af730934 | ||
|
|
645801fe86 | ||
|
|
547f49a978 | ||
|
|
fd3d7a6f29 | ||
|
|
6be7140a47 | ||
|
|
ca7dc02d88 | ||
|
|
1924c45358 | ||
|
|
be92f92991 | ||
|
|
a9b909cca1 | ||
|
|
175b83dd68 | ||
|
|
df85284a60 | ||
|
|
17ffa93393 | ||
|
|
73c525127f | ||
|
|
3e76d7c321 | ||
|
|
9295f9cef3 | ||
|
|
9d1e6df8f0 | ||
|
|
0805d70c07 | ||
|
|
0133524f09 | ||
|
|
2c5100b2ed | ||
|
|
51296cdc15 | ||
|
|
f261bec248 | ||
|
|
97c7087f6c | ||
|
|
109906d083 | ||
|
|
81833c4938 | ||
|
|
1b6cfdbdc4 | ||
|
|
281d8bddc1 | ||
|
|
0eaa3aeeb7 | ||
|
|
4dcb0bcfed | ||
|
|
e69b7dfd6a | ||
|
|
38d63c71ee | ||
|
|
6880fd3f5f | ||
|
|
1771ca363a | ||
|
|
c896c88c23 | ||
|
|
5ef733729b | ||
|
|
f73e29937c | ||
|
|
8383547943 | ||
|
|
8070d3f1e6 | ||
|
|
a161fa953e | ||
|
|
471e1ae96f | ||
|
|
ed28bcdfb0 | ||
|
|
31ea81c211 | ||
|
|
283189a17c | ||
|
|
40020df865 | ||
|
|
aa6b5ec24f | ||
|
|
f53fe042a9 | ||
|
|
034a54bf4b | ||
|
|
894dd1e353 | ||
|
|
ca99db5a97 | ||
|
|
61e506010c | ||
|
|
4b4343a9fd | ||
|
|
9efacefba3 | ||
|
|
f288c9a9ad | ||
|
|
ec45f23485 | ||
|
|
1c11e73ff8 |
@@ -1,3 +1,4 @@
|
|||||||
PORT=9000
|
PORT=9000
|
||||||
UMI_DEV_SERVER_COMPRESS=none
|
UMI_DEV_SERVER_COMPRESS=none
|
||||||
|
DID_YOU_KNOW=none
|
||||||
|
|
||||||
|
|||||||
+3
-5
@@ -28,6 +28,9 @@ export default defineConfig({
|
|||||||
logLevel: 'info',
|
logLevel: 'info',
|
||||||
defaultSizes: 'parsed' // stat // gzip
|
defaultSizes: 'parsed' // stat // gzip
|
||||||
},
|
},
|
||||||
|
mfsu: {
|
||||||
|
exclude: ['lodash', 'ml-pca']
|
||||||
|
},
|
||||||
base: process.env.npm_config_base || '/',
|
base: process.env.npm_config_base || '/',
|
||||||
...(isProduction
|
...(isProduction
|
||||||
? {
|
? {
|
||||||
@@ -50,11 +53,6 @@ export default defineConfig({
|
|||||||
chunkFilename: `css/[name].${t}.chunk.css`
|
chunkFilename: `css/[name].${t}.chunk.css`
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
config.module
|
|
||||||
.rule('worker')
|
|
||||||
.test(/\.worker\.js$/)
|
|
||||||
.use('worker-loader')
|
|
||||||
.loader('worker-loader');
|
|
||||||
config.output
|
config.output
|
||||||
.filename(`js/[name].${t}.js`)
|
.filename(`js/[name].${t}.js`)
|
||||||
.chunkFilename(`js/[name].${t}.chunk.js`);
|
.chunkFilename(`js/[name].${t}.chunk.js`);
|
||||||
|
|||||||
@@ -20,12 +20,6 @@ export default function createProxyTable(target?: string) {
|
|||||||
ws: true,
|
ws: true,
|
||||||
log: 'debug',
|
log: 'debug',
|
||||||
pathRewrite: (pth: string) => pth.replace(`/^/${api}`, `/${api}`),
|
pathRewrite: (pth: string) => pth.replace(`/^/${api}`, `/${api}`),
|
||||||
// onProxyRes: (proxyRes: any, req: any, res: any) => {
|
|
||||||
// console.log('headers=========', {
|
|
||||||
// res: proxyRes.headers,
|
|
||||||
// req: req.headers
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
headers: {
|
headers: {
|
||||||
origin: newTarget,
|
origin: newTarget,
|
||||||
Connection: 'keep-alive'
|
Connection: 'keep-alive'
|
||||||
|
|||||||
+116
-38
@@ -5,13 +5,18 @@ export default [
|
|||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
key: 'dashboard',
|
key: 'dashboard',
|
||||||
icon: 'AppstoreOutlined',
|
icon: 'icon-dashboard',
|
||||||
|
selectedIcon: 'icon-dashboard-filled',
|
||||||
|
defaultIcon: 'icon-dashboard',
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './dashboard'
|
component: './dashboard',
|
||||||
|
routes: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'playground',
|
name: 'playground',
|
||||||
icon: 'ExperimentOutlined',
|
icon: 'icon-experiment',
|
||||||
|
selectedIcon: 'icon-experiment-filled',
|
||||||
|
defaultIcon: 'icon-experiment',
|
||||||
path: '/playground',
|
path: '/playground',
|
||||||
key: 'playground',
|
key: 'playground',
|
||||||
routes: [
|
routes: [
|
||||||
@@ -24,31 +29,19 @@ export default [
|
|||||||
title: 'Chat',
|
title: 'Chat',
|
||||||
path: '/playground/chat',
|
path: '/playground/chat',
|
||||||
key: 'chat',
|
key: 'chat',
|
||||||
icon: 'Comment',
|
icon: 'icon-chat',
|
||||||
|
selectedIcon: 'icon-chat-filled',
|
||||||
|
defaultIcon: 'icon-chat',
|
||||||
component: './playground/index'
|
component: './playground/index'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'text2images',
|
|
||||||
title: 'Text2Images',
|
|
||||||
path: keepAliveRoutes.text2images,
|
|
||||||
key: 'text2images',
|
|
||||||
icon: 'Comment',
|
|
||||||
component: './playground/images'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'speech',
|
|
||||||
title: 'Speech',
|
|
||||||
path: keepAliveRoutes.speech,
|
|
||||||
key: 'speech',
|
|
||||||
icon: 'Comment',
|
|
||||||
component: './playground/speech'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'embedding',
|
name: 'embedding',
|
||||||
title: 'embedding',
|
title: 'embedding',
|
||||||
path: '/playground/embedding',
|
path: '/playground/embedding',
|
||||||
key: 'embedding',
|
key: 'embedding',
|
||||||
icon: 'Comment',
|
icon: 'icon-embedding',
|
||||||
|
selectedIcon: 'icon-embedding-filled',
|
||||||
|
defaultIcon: 'icon-embedding',
|
||||||
component: './playground/embedding'
|
component: './playground/embedding'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -56,50 +49,135 @@ export default [
|
|||||||
title: 'Rerank',
|
title: 'Rerank',
|
||||||
path: '/playground/rerank',
|
path: '/playground/rerank',
|
||||||
key: 'rerank',
|
key: 'rerank',
|
||||||
icon: 'Comment',
|
icon: 'icon-reranker',
|
||||||
|
selectedIcon: 'icon-reranker-filled',
|
||||||
|
defaultIcon: 'icon-reranker',
|
||||||
component: './playground/rerank'
|
component: './playground/rerank'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'text2images',
|
||||||
|
title: 'Text2Images',
|
||||||
|
path: keepAliveRoutes.text2images,
|
||||||
|
key: 'text2images',
|
||||||
|
icon: 'icon-image1',
|
||||||
|
selectedIcon: 'icon-image-filled',
|
||||||
|
defaultIcon: 'icon-image1',
|
||||||
|
component: './playground/images'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'speech',
|
||||||
|
title: 'Speech',
|
||||||
|
path: keepAliveRoutes.speech,
|
||||||
|
key: 'speech',
|
||||||
|
icon: 'icon-audio1',
|
||||||
|
selectedIcon: 'icon-audio-filled',
|
||||||
|
defaultIcon: 'icon-audio1',
|
||||||
|
component: './playground/speech'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'models',
|
||||||
|
path: '/models',
|
||||||
|
key: 'models',
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/models',
|
||||||
|
redirect: '/models/deployments'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'modelCatalog',
|
name: 'modelCatalog',
|
||||||
path: '/models/catalog',
|
path: '/models/catalog',
|
||||||
key: 'modelsCatalog',
|
key: 'modelsCatalog',
|
||||||
icon: 'icon-catalog',
|
icon: 'icon-layers',
|
||||||
|
selectedIcon: 'icon-layers-filled',
|
||||||
|
defaultIcon: 'icon-layers',
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './llmodels/catalog'
|
component: './llmodels/catalog'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'models',
|
name: 'deployment',
|
||||||
path: '/models/list',
|
path: '/models/deployments',
|
||||||
key: 'models',
|
key: 'modelDeployments',
|
||||||
icon: 'Block',
|
icon: 'icon-rocket-launch1',
|
||||||
|
selectedIcon: 'icon-rocket-launch-fill',
|
||||||
|
defaultIcon: 'icon-rocket-launch1',
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './llmodels/index'
|
component: './llmodels/index'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'resources',
|
name: 'resources',
|
||||||
path: '/resources',
|
path: '/resources',
|
||||||
key: 'resources',
|
key: 'resources',
|
||||||
icon: 'CloudServer',
|
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './resources'
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/resources',
|
||||||
|
redirect: '/resources/workers'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'workers',
|
||||||
|
path: '/resources/workers',
|
||||||
|
key: 'workers',
|
||||||
|
icon: 'icon-resources',
|
||||||
|
selectedIcon: 'icon-resources-filled',
|
||||||
|
defaultIcon: 'icon-resources',
|
||||||
|
component: './resources/components/workers'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gpus',
|
||||||
|
path: '/resources/gpus',
|
||||||
|
key: 'gpus',
|
||||||
|
icon: 'icon-gpu1',
|
||||||
|
selectedIcon: 'icon-gpu-filled',
|
||||||
|
defaultIcon: 'icon-gpu1',
|
||||||
|
component: './resources/components/gpus'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'modelfiles',
|
||||||
|
path: '/resources/modelfiles',
|
||||||
|
key: 'modelfiles',
|
||||||
|
icon: 'icon-files',
|
||||||
|
selectedIcon: 'icon-files-filled',
|
||||||
|
defaultIcon: 'icon-files',
|
||||||
|
component: './resources/components/model-files'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accessControl',
|
||||||
|
path: '/access-control',
|
||||||
|
key: 'accessControl',
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/access-control',
|
||||||
|
redirect: '/access-control/users'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'users',
|
||||||
|
path: '/access-control/users',
|
||||||
|
key: 'users',
|
||||||
|
icon: 'icon-users',
|
||||||
|
selectedIcon: 'icon-users-filled',
|
||||||
|
defaultIcon: 'icon-users',
|
||||||
|
component: './users'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'apikeys',
|
name: 'apikeys',
|
||||||
path: '/api-keys',
|
path: '/api-keys',
|
||||||
key: 'apikeys',
|
key: 'apikeys',
|
||||||
icon: 'KeyOutlined',
|
hideInMenu: true,
|
||||||
|
selectedIcon: 'icon-key-filled',
|
||||||
|
icon: 'icon-key',
|
||||||
|
defaultIcon: 'icon-key',
|
||||||
component: './api-keys'
|
component: './api-keys'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'users',
|
|
||||||
path: '/users',
|
|
||||||
key: 'users',
|
|
||||||
icon: 'Team',
|
|
||||||
access: 'canSeeAdmin',
|
|
||||||
component: './users'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'profile',
|
name: 'profile',
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
|
|||||||
+10
-1
@@ -33,14 +33,16 @@
|
|||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"antd": "^5.21.6",
|
"antd": "^5.21.6",
|
||||||
"antd-style": "^3.6.2",
|
"antd-style": "^3.6.2",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.8.2",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"dompurify": "^3.2.6",
|
||||||
"driver.js": "^1.3.1",
|
"driver.js": "^1.3.1",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"epubjs": "^0.3.93",
|
"epubjs": "^0.3.93",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"has-ansi": "^5.0.1",
|
"has-ansi": "^5.0.1",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
"jotai": "^2.8.4",
|
"jotai": "^2.8.4",
|
||||||
@@ -66,6 +68,7 @@
|
|||||||
"react-hotkeys-hook": "^4.5.0",
|
"react-hotkeys-hook": "^4.5.0",
|
||||||
"react-markdown": "^9.0.3",
|
"react-markdown": "^9.0.3",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
|
"remark-breaks": "^4.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"simplebar-react": "^3.2.6",
|
"simplebar-react": "^3.2.6",
|
||||||
@@ -79,6 +82,7 @@
|
|||||||
"@types/react": "^18.3.1",
|
"@types/react": "^18.3.1",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@umijs/case-sensitive-paths-webpack-plugin": "^1.0.1",
|
"@umijs/case-sensitive-paths-webpack-plugin": "^1.0.1",
|
||||||
|
"@umijs/plugins": "^4.4.11",
|
||||||
"babel-plugin-named-asset-import": "^0.3.8",
|
"babel-plugin-named-asset-import": "^0.3.8",
|
||||||
"case-sensitive-paths-webpack-plugin": "^2.4.0",
|
"case-sensitive-paths-webpack-plugin": "^2.4.0",
|
||||||
"compression-webpack-plugin": "^11.1.0",
|
"compression-webpack-plugin": "^11.1.0",
|
||||||
@@ -100,5 +104,10 @@
|
|||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"webpack-bundle-analyzer": "^4.10.2",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"worker-loader": "^3.0.8"
|
"worker-loader": "^3.0.8"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"elliptic": "^6.6.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+193
-692
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
+1
-1
@@ -23,7 +23,7 @@ const checkDefaultPage = async (userInfo: any) => {
|
|||||||
if (isFirstLogin === null && isOnline()) {
|
if (isFirstLogin === null && isOnline()) {
|
||||||
writeState(IS_FIRST_LOGIN, true);
|
writeState(IS_FIRST_LOGIN, true);
|
||||||
if (userInfo && userInfo?.is_admin) {
|
if (userInfo && userInfo?.is_admin) {
|
||||||
history.push('/models/list');
|
history.push('/models/deployments');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1755020187776" class="icon" viewBox="0 0 1105 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7392" xmlns:xlink="http://www.w3.org/1999/xlink" width="138.125" height="128"><path d="M507.2896 133.44768l130.98496-71.4496v820.9408l-130.97984 69.71392V133.44768z" fill="#F38019" p-id="7393"></path><path d="M479.39072 323.968a724.30592 724.30592 0 0 0-77.056 16.6144C118.35904 417.01376 37.77536 603.8016 67.95776 701.696c30.11584 97.8944 161.03424 263.2192 439.2704 250.19904 0.13824-27.41248 0.13824-57.73824 0-90.89536-200.49408-26.37824-302.67904-88.82688-306.55488-187.40736-5.74464-147.74272 134.1696-216.90368 217.87136-229.0176 16.82432-2.49344 37.66272-5.67808 61.19936-8.6528l-0.27648-111.9488h-0.07168z m185.12384 107.65312c52.1984 5.6832 103.57248 18.69312 147.0464 44.44672-10.65984 5.19168-34.26816 19.73248-70.82496 43.47904l290.49344 66.39104-16.54272-206.58176-78.37184 44.30336c-116.10112-57.8048-205.19936-91.0336-267.29984-99.6864l-4.50048 107.648z" fill="#ADAFB3" p-id="7394"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M140-180v-88.92q0-29.39 15.96-54.43 15.96-25.03 42.66-38.49 59.3-29.08 119.65-43.62Q378.62-420 440-420q21.06 0 42.12 1.89 21.05 1.88 42.11 5.65-1.69 51.46 20.81 96.81Q567.54-270.31 609-240v60H140ZM753.85-72.31l-53.35-53.14v-164.73q-39.11-11.51-63.85-43.97-24.73-32.46-24.73-74.7 0-51.46 36.38-87.84t87.85-36.38q51.46 0 87.66 36.39Q860-460.28 860-408.79q0 39.94-22.42 70.71-22.43 30.77-57.2 44.23l44.23 44.23-53.07 53.2 53.07 53.19-70.76 70.92ZM440-484.62q-57.75 0-98.87-41.12Q300-566.86 300-624.61q0-57.75 41.13-98.88 41.12-41.12 98.87-41.12 57.75 0 98.87 41.12Q580-682.36 580-624.61q0 57.75-41.13 98.87-41.12 41.12-98.87 41.12Zm296.15 93.47q14.7 0 25.04-10.54 10.35-10.54 10.35-25.23 0-14.69-10.35-25.04-10.34-10.35-25.04-10.35-14.69 0-25.23 10.35-10.54 10.35-10.54 25.04t10.54 25.23q10.54 10.54 25.23 10.54Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 934 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||||
|
␍<g fill="#C22E33">
|
||||||
|
␍<path d="M7.754 2l.463.41c.343.304.687.607 1.026.915C11.44 5.32 13.3 7.565 14.7 10.149c.072.132.137.268.202.403l.098.203-.108.057-.081-.115-.21-.299-.147-.214c-1.019-1.479-2.04-2.96-3.442-4.145a6.563 6.563 0 00-1.393-.904c-1.014-.485-1.916-.291-2.69.505-.736.757-1.118 1.697-1.463 2.653-.045.123-.092.245-.139.367l-.082.215-.172-.055c.1-.348.192-.698.284-1.049.21-.795.42-1.59.712-2.356.31-.816.702-1.603 1.093-2.39.169-.341.338-.682.5-1.025h.092z"/>
|
||||||
|
␍<path d="M8.448 11.822c-1.626.77-5.56 1.564-7.426 1.36C.717 11.576 3.71 4.05 5.18 2.91l-.095.218a4.638 4.638 0 01-.138.303l-.066.129c-.76 1.462-1.519 2.926-1.908 4.53a7.482 7.482 0 00-.228 1.689c-.01 1.34.824 2.252 2.217 2.309.67.027 1.347-.043 2.023-.114.294-.03.587-.061.88-.084.108-.008.214-.021.352-.039l.231-.028z"/>
|
||||||
|
␍<path d="M3.825 14.781c-.445.034-.89.068-1.333.108 4.097.39 8.03-.277 11.91-1.644-1.265-2.23-2.97-3.991-4.952-5.522.026.098.084.169.141.239l.048.06c.17.226.348.448.527.67.409.509.818 1.018 1.126 1.578.778 1.42.356 2.648-1.168 3.296-1.002.427-2.097.718-3.18.892-1.03.164-2.075.243-3.119.323z"/>
|
||||||
|
␍</g>
|
||||||
|
␍</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -204,6 +204,10 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.font-size-18 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.font-size-24 {
|
.font-size-24 {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-1
@@ -1,4 +1,18 @@
|
|||||||
import { atom } from 'jotai';
|
import { atom, getDefaultStore } from 'jotai';
|
||||||
|
|
||||||
// models expand keys: create, update , delete,
|
// models expand keys: create, update , delete,
|
||||||
export const modelsExpandKeysAtom = atom<string[]>([]);
|
export const modelsExpandKeysAtom = atom<string[]>([]);
|
||||||
|
|
||||||
|
export const requestIdAtom = atom<number>(0);
|
||||||
|
|
||||||
|
export const setRquestId = () => {
|
||||||
|
const store = getDefaultStore();
|
||||||
|
const id = Date.now();
|
||||||
|
store.set(requestIdAtom, id);
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRequestId = () => {
|
||||||
|
const store = getDefaultStore();
|
||||||
|
return store.get(requestIdAtom);
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ type UserSettings = {
|
|||||||
mode: 'light' | 'realDark' | 'auto';
|
mode: 'light' | 'realDark' | 'auto';
|
||||||
colorPrimary: string;
|
colorPrimary: string;
|
||||||
isDarkTheme: boolean;
|
isDarkTheme: boolean;
|
||||||
|
collapsed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultSettings: UserSettings = {
|
const defaultSettings: UserSettings = {
|
||||||
theme: 'light',
|
theme: 'light',
|
||||||
mode: 'auto',
|
mode: 'auto',
|
||||||
isDarkTheme: false,
|
isDarkTheme: false,
|
||||||
colorPrimary: colorPrimary
|
colorPrimary: colorPrimary,
|
||||||
|
collapsed: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStorageUserSettings = () => {
|
export const getStorageUserSettings = () => {
|
||||||
@@ -31,10 +33,9 @@ export const getStorageUserSettings = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userSettingsAtom = atomWithStorage<UserSettings>(
|
export const userSettingsAtom = atomWithStorage<UserSettings>('userSettings', {
|
||||||
'userSettings',
|
...getStorageUserSettings()
|
||||||
defaultSettings
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const userSettingsHelperAtom = atom(
|
export const userSettingsHelperAtom = atom(
|
||||||
(get) => get(userSettingsAtom),
|
(get) => get(userSettingsAtom),
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
.alert-info-block {
|
|
||||||
padding-block: 6px;
|
|
||||||
padding-inline: 10px 16px;
|
|
||||||
position: relative;
|
|
||||||
padding-left: 32px;
|
|
||||||
text-align: left;
|
|
||||||
border-radius: var(--border-radius-base);
|
|
||||||
margin: 0;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
|
|
||||||
.ant-typography {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.danger {
|
|
||||||
border-color: var(--ant-color-error-border);
|
|
||||||
background-color: var(--ant-color-error-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.warning {
|
|
||||||
border-color: var(--ant-color-warning-border);
|
|
||||||
background-color: var(--ant-color-warning-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.transition {
|
|
||||||
color: var(--ant-geekblue-7);
|
|
||||||
background: var(--ant-geekblue-1);
|
|
||||||
border-color: var(--ant-geekblue-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.success {
|
|
||||||
border: 1px solid var(--ant-color-success);
|
|
||||||
color: var(--ant-color-success);
|
|
||||||
background: var(--ant-color-success-bg);
|
|
||||||
|
|
||||||
.content.success {
|
|
||||||
font-weight: var(--font-weight-normal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
display: flex;
|
|
||||||
height: 32px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: var(--border-radius-base) var(--border-radius-base) 0 0;
|
|
||||||
|
|
||||||
.info-icon {
|
|
||||||
&.danger {
|
|
||||||
color: var(--ant-color-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.warning {
|
|
||||||
color: var(--ant-color-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.transition {
|
|
||||||
color: var(--ant-geekblue-7);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.success {
|
|
||||||
color: var(--ant-color-success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-weight: var(--font-weight-bold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { WarningFilled } from '@ant-design/icons';
|
import { WarningFilled } from '@ant-design/icons';
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
|
import { createStyles } from 'antd-style';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import OverlayScroller from '../overlay-scroller';
|
import OverlayScroller, { OverlayScrollerOptions } from '../overlay-scroller';
|
||||||
import './block.less';
|
|
||||||
interface AlertInfoProps {
|
interface AlertInfoProps {
|
||||||
type: Global.MessageType;
|
type: Global.MessageType;
|
||||||
message: React.ReactNode;
|
message: React.ReactNode;
|
||||||
@@ -15,8 +15,84 @@ interface AlertInfoProps {
|
|||||||
contentStyle?: React.CSSProperties;
|
contentStyle?: React.CSSProperties;
|
||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
maxHeight?: number;
|
maxHeight?: number;
|
||||||
|
overlayScrollerProps?: OverlayScrollerOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ token, css }) => {
|
||||||
|
return {
|
||||||
|
alertBlockInfo: css`
|
||||||
|
padding-block: 6px;
|
||||||
|
padding-inline: 10px 16px;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 32px;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: ${token.borderRadius}px;
|
||||||
|
margin: 0;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
.ant-typography {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
border-color: ${token.colorErrorBorder};
|
||||||
|
background-color: ${token.colorErrorBg};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
border-color: ${token.colorWarningBorder};
|
||||||
|
background-color: ${token.colorWarningBg};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.transition {
|
||||||
|
color: ${token.geekblue7};
|
||||||
|
background: ${token.geekblue1};
|
||||||
|
border-color: ${token.geekblue3};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
border: 1px solid ${token.colorSuccess};
|
||||||
|
color: ${token.colorSuccessText};
|
||||||
|
background: ${token.colorSuccessBg};
|
||||||
|
|
||||||
|
.content.success {
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
height: 32px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: ${token.borderRadius}px ${token.borderRadius}px 0 0;
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
&.danger {
|
||||||
|
color: ${token.colorErrorText};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
color: ${token.colorWarningText};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.transition {
|
||||||
|
color: ${token.geekblue7};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
color: ${token.colorSuccessText};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const TitleWrapper = styled.div`
|
const TitleWrapper = styled.div`
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--ant-color-text);
|
color: var(--ant-color-text);
|
||||||
@@ -29,6 +105,7 @@ const ContentWrapper = styled.div<{ $hasTitle: boolean }>`
|
|||||||
? 'var(--ant-color-text-secondary)'
|
? 'var(--ant-color-text-secondary)'
|
||||||
: 'var(--ant-color-text)'};
|
: 'var(--ant-color-text)'};
|
||||||
font-weight: var(--font-weight-500);
|
font-weight: var(--font-weight-500);
|
||||||
|
white-space: pre-line;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
||||||
@@ -41,14 +118,15 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
|||||||
title,
|
title,
|
||||||
contentStyle,
|
contentStyle,
|
||||||
icon,
|
icon,
|
||||||
maxHeight = 86
|
maxHeight = 86,
|
||||||
|
overlayScrollerProps = {}
|
||||||
} = props;
|
} = props;
|
||||||
|
const { styles } = useStyles();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{message ? (
|
{message ? (
|
||||||
<div
|
<div
|
||||||
className={classNames('alert-info-block', type)}
|
className={classNames(styles.alertBlockInfo, type)}
|
||||||
style={{ ...style }}
|
style={{ ...style }}
|
||||||
>
|
>
|
||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
@@ -67,7 +145,11 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
|||||||
{title && (
|
{title && (
|
||||||
<TitleWrapper className="title-text">{title}</TitleWrapper>
|
<TitleWrapper className="title-text">{title}</TitleWrapper>
|
||||||
)}
|
)}
|
||||||
<OverlayScroller maxHeight={maxHeight} style={{ ...contentStyle }}>
|
<OverlayScroller
|
||||||
|
maxHeight={maxHeight}
|
||||||
|
style={{ ...contentStyle }}
|
||||||
|
{...overlayScrollerProps}
|
||||||
|
>
|
||||||
<ContentWrapper
|
<ContentWrapper
|
||||||
$hasTitle={!!title}
|
$hasTitle={!!title}
|
||||||
className={classNames('content', type)}
|
className={classNames('content', type)}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const AudioWrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AudioElement: React.FC<any> = (props) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AudioWrapper>
|
||||||
|
<audio {...props} controls></audio>
|
||||||
|
</AudioWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AudioElement;
|
||||||
@@ -0,0 +1,406 @@
|
|||||||
|
import { formatTime } from '@/utils/index';
|
||||||
|
import { DeleteOutlined, DownloadOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button, Dropdown, Slider, type MenuProps } from 'antd';
|
||||||
|
import { createStyles } from 'antd-style';
|
||||||
|
import { round } from 'lodash';
|
||||||
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useMemo
|
||||||
|
} from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import AutoTooltip from '../auto-tooltip';
|
||||||
|
import IconFont from '../icon-font';
|
||||||
|
|
||||||
|
type ActionItem = 'download' | 'delete' | 'speed';
|
||||||
|
|
||||||
|
interface AudioPlayerProps {
|
||||||
|
autoplay?: boolean;
|
||||||
|
url: string;
|
||||||
|
speed?: number;
|
||||||
|
ref?: any;
|
||||||
|
name: string;
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
|
duration?: number;
|
||||||
|
actions?: ActionItem[];
|
||||||
|
onDelete?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SliderWrapper = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
.ant-slider {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
color: var(--ant-color-text-tertiary);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ css, token }) => {
|
||||||
|
// @ts-ignore
|
||||||
|
const isDarkMode = token.darkMode as boolean;
|
||||||
|
return {
|
||||||
|
wrapper: css`
|
||||||
|
position: relative;
|
||||||
|
min-width: 360px;
|
||||||
|
height: 54px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background-color: ${isDarkMode
|
||||||
|
? 'var(--ant-color-fill-secondary)'
|
||||||
|
: '#F1F3F4'};
|
||||||
|
border-radius: 28px;
|
||||||
|
.inner {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex: 1;
|
||||||
|
gap: 8px;
|
||||||
|
.slider {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
.ant-slider {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.ant-slider-handle {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:focus-within {
|
||||||
|
.ant-slider-handle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-slider-handle {
|
||||||
|
opacity: 0;
|
||||||
|
&::before {
|
||||||
|
background-color: var(--ant-color-bg-spotlight);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const sliderStyles = {
|
||||||
|
rail: {
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: 'var(--ant-color-fill-secondary)'
|
||||||
|
},
|
||||||
|
track: {
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: 'var(--ant-color-bg-spotlight)'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const speedOptions = [
|
||||||
|
{ label: '1x', value: 1 },
|
||||||
|
{ label: '2x', value: 2 },
|
||||||
|
{ label: '3x', value: 3 },
|
||||||
|
{ label: '4x', value: 4 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const speedConfig = {
|
||||||
|
min: 0.5,
|
||||||
|
max: 2,
|
||||||
|
step: 0.25
|
||||||
|
};
|
||||||
|
|
||||||
|
const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { styles } = useStyles();
|
||||||
|
const {
|
||||||
|
autoplay = false,
|
||||||
|
speed: defaultSpeed = 1,
|
||||||
|
actions = ['delete'],
|
||||||
|
name,
|
||||||
|
onDelete
|
||||||
|
} = props;
|
||||||
|
const audioRef = React.useRef<HTMLAudioElement>(null);
|
||||||
|
const [audioState, setAudioState] = React.useState<{
|
||||||
|
currentTime: number;
|
||||||
|
duration: number;
|
||||||
|
}>({
|
||||||
|
currentTime: 0,
|
||||||
|
duration: 0
|
||||||
|
});
|
||||||
|
console.log('audioState', name);
|
||||||
|
const [playOn, setPlayOn] = React.useState<boolean>(false);
|
||||||
|
const [speakerOn, setSpeakerOn] = React.useState<boolean>(false);
|
||||||
|
const [volume, setVolume] = React.useState<number>(1);
|
||||||
|
const [speed, setSpeed] = React.useState<number>(defaultSpeed);
|
||||||
|
const timer = React.useRef<any>(null);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
play: () => {
|
||||||
|
audioRef.current?.play();
|
||||||
|
},
|
||||||
|
pause: () => {
|
||||||
|
audioRef.current?.pause();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
const handleShowVolume = useCallback(() => {
|
||||||
|
setSpeakerOn(!speakerOn);
|
||||||
|
}, [speakerOn]);
|
||||||
|
|
||||||
|
const handleSeepdChange = useCallback((value: number | string) => {
|
||||||
|
setSpeed(value as number);
|
||||||
|
audioRef.current!.playbackRate = value as number;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAudioOnPlay = useCallback(() => {
|
||||||
|
timer.current = setInterval(() => {
|
||||||
|
setAudioState((prestate) => {
|
||||||
|
return {
|
||||||
|
currentTime: Math.ceil(audioRef.current?.currentTime || 0),
|
||||||
|
duration:
|
||||||
|
prestate.duration || Math.ceil(audioRef.current?.duration || 0)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (audioRef.current?.paused || audioRef.current?.ended) {
|
||||||
|
clearInterval(timer.current);
|
||||||
|
setPlayOn(false);
|
||||||
|
setAudioState((prestate: any) => {
|
||||||
|
return {
|
||||||
|
currentTime: audioRef.current?.ended ? 0 : prestate.currentTime,
|
||||||
|
duration: prestate.duration
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handlePlay = useCallback(() => {
|
||||||
|
setPlayOn(!playOn);
|
||||||
|
if (playOn) {
|
||||||
|
audioRef.current?.pause();
|
||||||
|
} else {
|
||||||
|
audioRef.current?.play();
|
||||||
|
}
|
||||||
|
}, [playOn]);
|
||||||
|
|
||||||
|
const handleFormatVolume = (val?: number) => {
|
||||||
|
if (val === undefined) {
|
||||||
|
return `${round(volume * 100)}%`;
|
||||||
|
}
|
||||||
|
return `${round(val * 100)}%`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVolumeChange = useCallback((value: number) => {
|
||||||
|
audioRef.current!.volume = round(value, 2);
|
||||||
|
setVolume(round(value, 2));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const initPlayerConfig = () => {
|
||||||
|
if (audioRef.current) {
|
||||||
|
audioRef.current!.volume = volume;
|
||||||
|
audioRef.current!.playbackRate = speed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoadedMetadata = useCallback(
|
||||||
|
(data: any) => {
|
||||||
|
const duration = Math.ceil(audioRef.current?.duration || 0);
|
||||||
|
setAudioState({
|
||||||
|
currentTime: 0,
|
||||||
|
duration:
|
||||||
|
duration && duration !== Infinity ? duration : props.duration || 0
|
||||||
|
});
|
||||||
|
setPlayOn(autoplay);
|
||||||
|
},
|
||||||
|
[autoplay, props.duration]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCurrentChange = useCallback((val: number) => {
|
||||||
|
audioRef.current!.currentTime = val;
|
||||||
|
setAudioState((prestate) => {
|
||||||
|
return {
|
||||||
|
currentTime: val,
|
||||||
|
duration: prestate.duration
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleReduceSpeed = () => {
|
||||||
|
setSpeed((pre) => {
|
||||||
|
if (pre - speedConfig.step < speedConfig.min) {
|
||||||
|
return speedConfig.min;
|
||||||
|
}
|
||||||
|
const next = pre - speedConfig.step;
|
||||||
|
audioRef.current!.playbackRate = next;
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddSpeed = () => {
|
||||||
|
setSpeed((pre) => {
|
||||||
|
if (pre + speedConfig.step > speedConfig.max) {
|
||||||
|
return speedConfig.max;
|
||||||
|
}
|
||||||
|
const next = pre + speedConfig.step;
|
||||||
|
audioRef.current!.playbackRate = next;
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnLoad = (e: any) => {
|
||||||
|
console.log('onload', e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDownload = useCallback(() => {
|
||||||
|
const url = props.url || '';
|
||||||
|
const filename = props.name;
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = filename || 'audio.mp3'; // Default filename
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}, [props.url, props.name]);
|
||||||
|
|
||||||
|
const items: MenuProps['items'] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'download',
|
||||||
|
label: intl.formatMessage({ id: 'common.button.download' }),
|
||||||
|
icon: <DownloadOutlined />,
|
||||||
|
onClick: onDownload
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'speed',
|
||||||
|
label: intl.formatMessage({ id: 'playground.params.speed' }),
|
||||||
|
icon: <IconFont type="icon-play-speed"></IconFont>,
|
||||||
|
children: speedOptions.map((item) => ({
|
||||||
|
key: item.value,
|
||||||
|
label: item.label,
|
||||||
|
onClick: () => handleSeepdChange(item.value)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
label: intl.formatMessage({ id: 'common.button.delete' }),
|
||||||
|
icon: <DeleteOutlined />,
|
||||||
|
danger: true,
|
||||||
|
onClick: () => {
|
||||||
|
if (audioRef.current) {
|
||||||
|
audioRef.current.pause();
|
||||||
|
audioRef.current.src = '';
|
||||||
|
audioRef.current.load();
|
||||||
|
}
|
||||||
|
setAudioState({ currentTime: 0, duration: 0 });
|
||||||
|
setPlayOn(false);
|
||||||
|
onDelete?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
].filter((item) => actions.includes(item.key as ActionItem));
|
||||||
|
}, [actions, intl, onDownload, onDelete, handleSeepdChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (audioRef.current) {
|
||||||
|
initPlayerConfig();
|
||||||
|
}
|
||||||
|
}, [audioRef.current]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer.current);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.wrapper}
|
||||||
|
style={{
|
||||||
|
width: props.width || '100%',
|
||||||
|
height: props.height || '60px',
|
||||||
|
position: 'relative'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="inner">
|
||||||
|
<Button
|
||||||
|
size="middle"
|
||||||
|
type="text"
|
||||||
|
onClick={handlePlay}
|
||||||
|
shape="circle"
|
||||||
|
disabled={!audioState?.duration}
|
||||||
|
icon={
|
||||||
|
!playOn ? (
|
||||||
|
<IconFont
|
||||||
|
type="icon-playcircle-fill"
|
||||||
|
style={{ fontSize: '24px' }}
|
||||||
|
></IconFont>
|
||||||
|
) : (
|
||||||
|
<IconFont
|
||||||
|
type="icon-stopcircle-fill"
|
||||||
|
style={{ fontSize: '24px' }}
|
||||||
|
></IconFont>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></Button>
|
||||||
|
|
||||||
|
<div className="slider">
|
||||||
|
<div className="flex-center flex-between file-name">
|
||||||
|
<AutoTooltip ghost maxWidth={200}>
|
||||||
|
<span>{name}</span>
|
||||||
|
</AutoTooltip>
|
||||||
|
</div>
|
||||||
|
<SliderWrapper>
|
||||||
|
<span className="time">{formatTime(audioState.currentTime)}</span>
|
||||||
|
<Slider
|
||||||
|
tooltip={{ open: false }}
|
||||||
|
min={0}
|
||||||
|
step={1}
|
||||||
|
styles={sliderStyles}
|
||||||
|
max={audioState.duration}
|
||||||
|
value={audioState.currentTime}
|
||||||
|
onChange={handleCurrentChange}
|
||||||
|
/>
|
||||||
|
</SliderWrapper>
|
||||||
|
</div>
|
||||||
|
<Dropdown menu={{ items }} trigger={['click']}>
|
||||||
|
<Button
|
||||||
|
icon={<IconFont type="icon-more"></IconFont>}
|
||||||
|
type="text"
|
||||||
|
size="middle"
|
||||||
|
shape="circle"
|
||||||
|
></Button>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
<audio
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
autoPlay={autoplay}
|
||||||
|
src={props.url}
|
||||||
|
ref={audioRef}
|
||||||
|
preload="metadata"
|
||||||
|
style={{ opacity: 0, position: 'absolute', left: '-9999px' }}
|
||||||
|
onPlay={handleAudioOnPlay}
|
||||||
|
onLoadedMetadata={handleLoadedMetadata}
|
||||||
|
></audio>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default React.memo(AudioPlayer);
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.single-image {
|
.single-image {
|
||||||
height: 100%;
|
// height: 100%;
|
||||||
width: inherit;
|
width: inherit;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { TooltipOverlayScroller } from '../overlay-scroller';
|
import { TooltipOverlayScroller } from '../overlay-scroller';
|
||||||
|
|
||||||
// type TagProps = React.ComponentProps<typeof Tag>;
|
// type TagProps = React.ComponentProps<typeof Tag>;
|
||||||
@@ -22,9 +23,18 @@ interface AutoTooltipProps extends Omit<TagProps, 'title'> {
|
|||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
showTitle?: boolean;
|
showTitle?: boolean;
|
||||||
closable?: boolean;
|
closable?: boolean;
|
||||||
|
radius?: number | string;
|
||||||
|
filled?: boolean;
|
||||||
tooltipProps?: React.ComponentProps<typeof Tooltip>;
|
tooltipProps?: React.ComponentProps<typeof Tooltip>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledTag = styled(Tag)`
|
||||||
|
&.tag-filled {
|
||||||
|
border: none;
|
||||||
|
background-color: var(--ant-color-fill-secondary);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
||||||
children,
|
children,
|
||||||
maxWidth = '100%',
|
maxWidth = '100%',
|
||||||
@@ -33,6 +43,8 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
title,
|
title,
|
||||||
showTitle = false,
|
showTitle = false,
|
||||||
tooltipProps,
|
tooltipProps,
|
||||||
|
radius = 12,
|
||||||
|
filled = false,
|
||||||
...tagProps
|
...tagProps
|
||||||
}) => {
|
}) => {
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -103,13 +115,14 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Tag
|
<StyledTag
|
||||||
{...tagProps}
|
{...tagProps}
|
||||||
|
className={`${tagProps.className || ''} ${filled ? 'tag-filled' : ''}`}
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
style={{
|
style={{
|
||||||
...tagStyle,
|
...tagStyle,
|
||||||
paddingInline: tagProps.closable ? '8px 22px' : 8,
|
paddingInline: tagProps.closable ? '8px 22px' : 8,
|
||||||
borderRadius: 12
|
borderRadius: radius
|
||||||
}}
|
}}
|
||||||
closeIcon={
|
closeIcon={
|
||||||
tagProps.closable ? (
|
tagProps.closable ? (
|
||||||
@@ -117,7 +130,8 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 8,
|
right: 8,
|
||||||
top: 6
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -126,7 +140,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Tag>
|
</StyledTag>
|
||||||
)}
|
)}
|
||||||
</TooltipOverlayScroller>
|
</TooltipOverlayScroller>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { createStyles } from 'antd-style';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const SimpleCardItemWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
gap: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ css, token }) => ({
|
||||||
|
wrapper: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: ${token.colorBgContainer};
|
||||||
|
border-radius: ${token.borderRadius}px;
|
||||||
|
padding: ${token.padding}px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: ${token.padding}px;
|
||||||
|
&.bordered {
|
||||||
|
border: 1px solid ${token.colorBorder};
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: ${token.fontSize}px;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: ${token.fontSize}px;
|
||||||
|
color: ${token.colorTextSecondary};
|
||||||
|
gap: 8px;
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
gap: 10px;
|
||||||
|
&.roundRect {
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
&.circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}));
|
||||||
|
export const SimpleCardItem: React.FC<{
|
||||||
|
title?: string;
|
||||||
|
content?: React.ReactNode;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
bordered?: boolean;
|
||||||
|
color?: string;
|
||||||
|
iconType?: string;
|
||||||
|
}> = (props) => {
|
||||||
|
const { styles, cx } = useStyles();
|
||||||
|
|
||||||
|
const { title, content, style, bordered, iconType, color } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx({ bordered: bordered }, styles.wrapper)} style={style}>
|
||||||
|
<div className="title">{title}</div>
|
||||||
|
<div className="content">
|
||||||
|
<span
|
||||||
|
className={cx([iconType], 'icon')}
|
||||||
|
style={{
|
||||||
|
backgroundColor: color || 'transparent'
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
<span>{content}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SimpleCard: React.FC<{
|
||||||
|
dataList: {
|
||||||
|
label: string;
|
||||||
|
value: React.ReactNode;
|
||||||
|
color: string;
|
||||||
|
iconType: string;
|
||||||
|
}[];
|
||||||
|
height?: string | number;
|
||||||
|
bordered?: boolean;
|
||||||
|
}> = (props) => {
|
||||||
|
const { dataList, bordered } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleCardItemWrapper style={{ height: props.height || '100%' }}>
|
||||||
|
{dataList.map((item, index) => (
|
||||||
|
<SimpleCardItem
|
||||||
|
key={index}
|
||||||
|
title={item.label}
|
||||||
|
content={item.value}
|
||||||
|
bordered={bordered}
|
||||||
|
color={item.color}
|
||||||
|
iconType={item.iconType}
|
||||||
|
></SimpleCardItem>
|
||||||
|
))}
|
||||||
|
</SimpleCardItemWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -15,7 +15,7 @@ const fadeOut = keyframes`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Mask = styled.div<{ isHiding: boolean }>`
|
const Mask = styled.div<{ $isHiding: boolean }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -26,7 +26,7 @@ const Mask = styled.div<{ isHiding: boolean }>`
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: rgba(59, 59, 59, 1);
|
background-color: rgba(59, 59, 59, 1);
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
animation: ${({ isHiding }) => (isHiding ? fadeOut : 'none')} ${duration}s
|
animation: ${({ $isHiding }) => ($isHiding ? fadeOut : 'none')} ${duration}s
|
||||||
ease forwards;
|
ease forwards;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ const DarkModeMask = () => {
|
|||||||
return () => el.removeEventListener('animationend', handleAnimationEnd);
|
return () => el.removeEventListener('animationend', handleAnimationEnd);
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
return visible ? <Mask ref={maskRef} isHiding={isHiding}></Mask> : null;
|
return visible ? <Mask ref={maskRef} $isHiding={isHiding}></Mask> : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DarkModeMask;
|
export default DarkModeMask;
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
:local(.delete-modal-content) {
|
|
||||||
display: flex;
|
|
||||||
font-size: var(--font-size-middle);
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.anticon {
|
|
||||||
font-size: 20px;
|
|
||||||
margin-right: 10px;
|
|
||||||
color: var(--ant-color-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:local(.content) {
|
|
||||||
padding-top: 15px;
|
|
||||||
padding-left: 30px;
|
|
||||||
}
|
|
||||||
@@ -9,9 +9,38 @@ import {
|
|||||||
message,
|
message,
|
||||||
type ModalFuncProps
|
type ModalFuncProps
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { FC, forwardRef, useImperativeHandle, useState } from 'react';
|
import { createStyles } from 'antd-style';
|
||||||
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Styles from './index.less';
|
|
||||||
|
const useStyles = createStyles(({ css }) => ({
|
||||||
|
'delete-modal-content': css`
|
||||||
|
display: flex;
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
.anticon {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
color: var(--ant-color-warning);
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: var(--font-weight-500);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
content: css`
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-left: 30px;
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
white-space: pre-line;
|
||||||
|
word-break: break-all;
|
||||||
|
span {
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
display: flex;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}));
|
||||||
|
|
||||||
const CheckboxWrapper = styled.div`
|
const CheckboxWrapper = styled.div`
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
@@ -25,12 +54,8 @@ const CheckboxWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface DeleteModalProps {
|
|
||||||
ref: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DataOptions {
|
interface DataOptions {
|
||||||
content: string;
|
content?: string;
|
||||||
selection?: boolean;
|
selection?: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
okText?: string;
|
okText?: string;
|
||||||
@@ -43,28 +68,33 @@ interface DataOptions {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
interface Configuration {
|
||||||
|
checked: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteModal = forwardRef((props, ref) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { styles } = useStyles();
|
||||||
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [checked, setChecked] = useState(false);
|
const [configuration, setConfiguration] = useState<Configuration>({
|
||||||
|
checked: false
|
||||||
|
});
|
||||||
const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any);
|
const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
const show = (data: ModalFuncProps & DataOptions) => {
|
||||||
show: (data: ModalFuncProps & DataOptions) => {
|
|
||||||
saveScrollHeight();
|
saveScrollHeight();
|
||||||
setConfig(data);
|
setConfig(data);
|
||||||
setChecked(data.checkConfig?.defautlChecked || false);
|
setConfiguration({
|
||||||
|
checked: data.checkConfig?.defautlChecked || false
|
||||||
|
});
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
},
|
};
|
||||||
hide: () => {
|
|
||||||
|
const hide = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
restoreScrollHeight();
|
restoreScrollHeight();
|
||||||
},
|
};
|
||||||
configuration: {
|
|
||||||
checked: checked
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
@@ -94,6 +124,12 @@ const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
configuration
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
style={{
|
style={{
|
||||||
@@ -102,12 +138,16 @@ const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
|||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
destroyOnClose={true}
|
destroyOnClose={false}
|
||||||
closeIcon={false}
|
closeIcon={false}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
keyboard={false}
|
keyboard={false}
|
||||||
width={460}
|
width={460}
|
||||||
styles={{}}
|
styles={{
|
||||||
|
footer: {
|
||||||
|
marginTop: '20px'
|
||||||
|
}
|
||||||
|
}}
|
||||||
footer={
|
footer={
|
||||||
<Space size={20}>
|
<Space size={20}>
|
||||||
<Button onClick={handleCancel} size="middle">
|
<Button onClick={handleCancel} size="middle">
|
||||||
@@ -123,7 +163,7 @@ const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={Styles['delete-modal-content']}>
|
<div className={styles['delete-modal-content']}>
|
||||||
<span className="title">
|
<span className="title">
|
||||||
<ExclamationCircleFilled />
|
<ExclamationCircleFilled />
|
||||||
<span>
|
<span>
|
||||||
@@ -134,11 +174,10 @@ const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={Styles['content']}
|
className={styles['content']}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html:
|
__html: config.content
|
||||||
config.content &&
|
? intl.formatMessage(
|
||||||
intl.formatMessage(
|
|
||||||
{
|
{
|
||||||
id: config.operation || ''
|
id: config.operation || ''
|
||||||
},
|
},
|
||||||
@@ -147,13 +186,18 @@ const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
|||||||
name: config.name
|
name: config.name
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
: ''
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
{config.checkConfig && (
|
{config.checkConfig && (
|
||||||
<CheckboxWrapper>
|
<CheckboxWrapper>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={checked}
|
checked={configuration.checked}
|
||||||
onChange={(e) => setChecked(e.target.checked)}
|
onChange={(e) =>
|
||||||
|
setConfiguration({
|
||||||
|
checked: e.target.checked
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<span className="check-text">
|
<span className="check-text">
|
||||||
{intl.formatMessage({ id: config.checkConfig?.checkText })}
|
{intl.formatMessage({ id: config.checkConfig?.checkText })}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { throttle } from 'lodash';
|
import _, { throttle } from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useCallback,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useRef
|
useRef
|
||||||
@@ -18,6 +17,7 @@ const Chart: React.FC<{
|
|||||||
const chart = useRef<echarts.EChartsType>();
|
const chart = useRef<echarts.EChartsType>();
|
||||||
const resizeable = useRef(false);
|
const resizeable = useRef(false);
|
||||||
const resizeObserver = useRef<ResizeObserver>();
|
const resizeObserver = useRef<ResizeObserver>();
|
||||||
|
const finished = useRef(false);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
return {
|
return {
|
||||||
@@ -25,40 +25,96 @@ const Chart: React.FC<{
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const init = useCallback(() => {
|
const init = () => {
|
||||||
if (container.current) {
|
if (container.current) {
|
||||||
chart.current?.clear();
|
chart.current?.clear();
|
||||||
chart.current = echarts.init(container.current);
|
chart.current = echarts.init(container.current);
|
||||||
}
|
}
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
const resize = useCallback(() => {
|
const setOption = (options: ECOption) => {
|
||||||
chart.current?.resize();
|
console.log('setOption', options);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setOption = useCallback(
|
|
||||||
(options: ECOption) => {
|
|
||||||
chart.current?.clear();
|
chart.current?.clear();
|
||||||
chart.current?.setOption(options, {
|
chart.current?.setOption(options, {
|
||||||
notMerge: true,
|
notMerge: true,
|
||||||
lazyUpdate: true
|
lazyUpdate: true
|
||||||
});
|
});
|
||||||
},
|
if (Array.isArray(options.yAxis) && options.yAxis.length > 1) {
|
||||||
[options]
|
chart.current?.resize();
|
||||||
);
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const handleOnFinished = () => {
|
||||||
|
if (!chart.current || finished.current) return;
|
||||||
|
|
||||||
|
const currentChart = chart.current;
|
||||||
|
const optionsYAxis = currentChart.getOption()?.yAxis;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!optionsYAxis ||
|
||||||
|
!Array.isArray(optionsYAxis) ||
|
||||||
|
optionsYAxis.length < 2
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
// @ts-ignore
|
||||||
|
const model = currentChart.getModel();
|
||||||
|
|
||||||
|
const yAxisModels = [
|
||||||
|
model.getComponent('yAxis', 0),
|
||||||
|
model.getComponent('yAxis', 1)
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!yAxisModels[0] || !yAxisModels[1]) return;
|
||||||
|
|
||||||
|
const axes = yAxisModels.map((m) => m.axis);
|
||||||
|
|
||||||
|
const intervals = axes.map((axis) => axis.scale.getInterval());
|
||||||
|
const ticksList = axes.map((axis) => axis.scale.getTicks());
|
||||||
|
const counts = ticksList.map((t) => t.length);
|
||||||
|
|
||||||
|
const unifiedCount = Math.max(counts[0], counts[1]);
|
||||||
|
|
||||||
|
const newMax0 = intervals[0] * (unifiedCount - 1);
|
||||||
|
const newMax1 = intervals[1] * (unifiedCount - 1);
|
||||||
|
|
||||||
|
// if newMax0 equal to maxValue0, and newMax1 equal to maxValue1, do not update yAxis
|
||||||
|
if (counts[0] === counts[1]) return;
|
||||||
|
|
||||||
|
const yAxis: any[] = [{}, {}];
|
||||||
|
|
||||||
|
if (counts[0] < unifiedCount) {
|
||||||
|
yAxis[0].max = _.round(newMax0, 2);
|
||||||
|
yAxis[0].interval = intervals[0];
|
||||||
|
yAxis[0].splitNumber = unifiedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counts[1] < unifiedCount) {
|
||||||
|
yAxis[1].max = _.round(newMax1, 2);
|
||||||
|
yAxis[1].interval = intervals[1];
|
||||||
|
yAxis[1].splitNumber = unifiedCount;
|
||||||
|
}
|
||||||
|
finished.current = true;
|
||||||
|
|
||||||
|
currentChart.setOption({
|
||||||
|
yAxis: yAxis
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (container.current) {
|
if (container.current) {
|
||||||
init();
|
init();
|
||||||
|
chart.current?.on('finished', handleOnFinished);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
chart.current?.dispose();
|
chart.current?.dispose();
|
||||||
|
chart.current?.off('finished', handleOnFinished);
|
||||||
};
|
};
|
||||||
}, [init]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resizeable.current = false;
|
resizeable.current = false;
|
||||||
resize();
|
finished.current = false;
|
||||||
setOption(options);
|
setOption(options);
|
||||||
resizeable.current = true;
|
resizeable.current = true;
|
||||||
}, [options]);
|
}, [options]);
|
||||||
|
|||||||
@@ -1,27 +1,12 @@
|
|||||||
import useUserSettings from '@/hooks/use-user-settings';
|
import useUserSettings from '@/hooks/use-user-settings';
|
||||||
|
import { formatLargeNumber } from '@/utils';
|
||||||
import { theme } from 'antd';
|
import { theme } from 'antd';
|
||||||
import { isFunction } from 'lodash';
|
import { isFunction } from 'lodash';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
const formatLargeNumber = (value: number) => {
|
|
||||||
if (typeof value !== 'number' || isNaN(value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value >= 1e9) {
|
|
||||||
return (value / 1e9).toFixed(1).replace(/\.0$/, '') + 'B';
|
|
||||||
} else if (value >= 1e6) {
|
|
||||||
return (value / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
|
|
||||||
} else if (value >= 1e3) {
|
|
||||||
return (value / 1e3).toFixed(1).replace(/\.0$/, '') + 'K';
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const grid = {
|
export const grid = {
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 20,
|
right: 0,
|
||||||
bottom: 20,
|
bottom: 20,
|
||||||
containLabel: true
|
containLabel: true
|
||||||
};
|
};
|
||||||
@@ -56,18 +41,23 @@ export default function useChartConfig() {
|
|||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
formatter(params: any, callback?: (val: any) => any) {
|
formatter(params: any, callback?: (val: any) => any) {
|
||||||
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
|
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
|
||||||
|
|
||||||
params.forEach((item: any) => {
|
params.forEach((item: any) => {
|
||||||
let value = isFunction(callback)
|
let value = isFunction(callback)
|
||||||
? callback?.(item.data.value)
|
? callback?.(item.data.value)
|
||||||
: item.data.value;
|
: item.data.value;
|
||||||
|
|
||||||
|
const borderRadius = item.seriesType === 'bar' ? '2px' : '8px';
|
||||||
|
|
||||||
result += `<span class="tooltip-item">
|
result += `<span class="tooltip-item">
|
||||||
<span class="tooltip-item-name">
|
<span class="tooltip-item-name">
|
||||||
<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:${item.color};"></span>
|
<span style="display:inline-block;margin-right:5px;border-radius:${borderRadius};width:8px;height:8px;background-color:${item.color};"></span>
|
||||||
<span class="tooltip-title">${item.seriesName}</span>:
|
<span class="tooltip-title">${item.seriesName}</span>:
|
||||||
</span>
|
</span>
|
||||||
<span class="tooltip-value">${value}</span>
|
<span class="tooltip-value">${value}</span>
|
||||||
</span>`;
|
</span>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return `<div class="tooltip-wrapper">${result}</div>`;
|
return `<div class="tooltip-wrapper">${result}</div>`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -98,8 +88,6 @@ export default function useChartConfig() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const yAxis = {
|
const yAxis = {
|
||||||
// max: 100,
|
|
||||||
// min: 0,
|
|
||||||
nameTextStyle: {
|
nameTextStyle: {
|
||||||
padding: [0, 0, 0, -20]
|
padding: [0, 0, 0, -20]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import _ from 'lodash';
|
|||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { ChartProps } from './types';
|
import { ChartProps } from './types';
|
||||||
|
|
||||||
const BarChart: React.FC<ChartProps> = (props) => {
|
const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
|
||||||
const {
|
const {
|
||||||
seriesData,
|
seriesData,
|
||||||
xAxisData,
|
xAxisData,
|
||||||
@@ -13,6 +13,7 @@ const BarChart: React.FC<ChartProps> = (props) => {
|
|||||||
width,
|
width,
|
||||||
labelFormatter,
|
labelFormatter,
|
||||||
legendData,
|
legendData,
|
||||||
|
maxItems,
|
||||||
title
|
title
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
@@ -30,7 +31,13 @@ const BarChart: React.FC<ChartProps> = (props) => {
|
|||||||
...titleConfig,
|
...titleConfig,
|
||||||
left: 'start'
|
left: 'start'
|
||||||
},
|
},
|
||||||
grid,
|
grid: {
|
||||||
|
...grid,
|
||||||
|
top: 0,
|
||||||
|
bottom: maxItems
|
||||||
|
? `${(1 / maxItems) * (maxItems - xAxisData.length) * 100}%`
|
||||||
|
: 0
|
||||||
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
...tooltip
|
...tooltip
|
||||||
},
|
},
|
||||||
@@ -41,7 +48,15 @@ const BarChart: React.FC<ChartProps> = (props) => {
|
|||||||
formatter: labelFormatter
|
formatter: labelFormatter
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
yAxis,
|
yAxis: {
|
||||||
|
...yAxis,
|
||||||
|
axisLabel: {
|
||||||
|
...yAxis.axisLabel,
|
||||||
|
overflow: 'truncate',
|
||||||
|
width: 75,
|
||||||
|
ellipsis: '...'
|
||||||
|
}
|
||||||
|
},
|
||||||
legend: {
|
legend: {
|
||||||
...legend,
|
...legend,
|
||||||
data: []
|
data: []
|
||||||
@@ -114,9 +129,15 @@ const BarChart: React.FC<ChartProps> = (props) => {
|
|||||||
legend
|
legend
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const isEmpty = useMemo(() => {
|
||||||
|
return seriesData?.every?.((item: any) => {
|
||||||
|
return !item?.data?.length;
|
||||||
|
});
|
||||||
|
}, [seriesData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!seriesData.length ? (
|
{isEmpty ? (
|
||||||
<EmptyData height={height} title={title}></EmptyData>
|
<EmptyData height={height} title={title}></EmptyData>
|
||||||
) : (
|
) : (
|
||||||
<Chart
|
<Chart
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ const LineChart: React.FC<ChartProps> = (props) => {
|
|||||||
yAxis
|
yAxis
|
||||||
} = useChartConfig();
|
} = useChartConfig();
|
||||||
|
|
||||||
|
const axisLabelFormatter = (value: string, index: number) => {
|
||||||
|
if (labelFormatter) {
|
||||||
|
return labelFormatter(value, index);
|
||||||
|
}
|
||||||
|
if (index === xAxisData.length - 1) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
title: {
|
title: {
|
||||||
text: ''
|
text: ''
|
||||||
@@ -45,7 +55,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
|
|||||||
...xAxis,
|
...xAxis,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
...xAxis.axisLabel,
|
...xAxis.axisLabel,
|
||||||
formatter: labelFormatter
|
formatter: axisLabelFormatter
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
yAxis,
|
yAxis,
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import Chart from '@/components/echarts/chart';
|
||||||
|
import useChartConfig from '@/components/echarts/config';
|
||||||
|
import EmptyData from '@/components/empty-data';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { ChartProps } from './types';
|
||||||
|
|
||||||
|
const MixLineBarChart: React.FC<
|
||||||
|
ChartProps & {
|
||||||
|
chartData: {
|
||||||
|
line: any[];
|
||||||
|
bar: any[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
> = (props) => {
|
||||||
|
const {
|
||||||
|
seriesData,
|
||||||
|
xAxisData,
|
||||||
|
yAxisName,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
labelFormatter,
|
||||||
|
tooltipValueFormatter = null,
|
||||||
|
legendData = [],
|
||||||
|
smooth,
|
||||||
|
title,
|
||||||
|
chartData
|
||||||
|
} = props;
|
||||||
|
const {
|
||||||
|
grid,
|
||||||
|
legend,
|
||||||
|
lineItemConfig,
|
||||||
|
barItemConfig,
|
||||||
|
title: titleConfig,
|
||||||
|
tooltip,
|
||||||
|
xAxis,
|
||||||
|
yAxis
|
||||||
|
} = useChartConfig();
|
||||||
|
|
||||||
|
const { line: lineSeriesData, bar: barSeriesData } = chartData;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
title: {
|
||||||
|
text: ''
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
...grid,
|
||||||
|
right: 0,
|
||||||
|
top: 20,
|
||||||
|
bottom: 10
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
...tooltip,
|
||||||
|
formatter(params: any) {
|
||||||
|
return tooltipValueFormatter
|
||||||
|
? tooltip.formatter(params, tooltipValueFormatter)
|
||||||
|
: tooltip.formatter(params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
...xAxis,
|
||||||
|
axisLabel: {
|
||||||
|
...xAxis.axisLabel,
|
||||||
|
formatter: labelFormatter
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis,
|
||||||
|
legend: {
|
||||||
|
...legend,
|
||||||
|
data: legendData,
|
||||||
|
itemGap: 20,
|
||||||
|
bottom: 5,
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataOptions = useMemo((): any => {
|
||||||
|
const linedata = _.map(lineSeriesData, (item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
...lineItemConfig,
|
||||||
|
smooth: smooth,
|
||||||
|
itemStyle: {
|
||||||
|
...lineItemConfig.itemStyle,
|
||||||
|
color: item.color
|
||||||
|
},
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: {
|
||||||
|
...lineItemConfig.lineStyle,
|
||||||
|
color: item.color
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const barData = _.map(barSeriesData, (item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
...barItemConfig,
|
||||||
|
stack: 'total',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
itemStyle: {
|
||||||
|
...item.itemStyle,
|
||||||
|
color: item.color
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
animation: false,
|
||||||
|
title: {
|
||||||
|
...titleConfig,
|
||||||
|
text: title
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
...options.yAxis
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...options.yAxis,
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
align: 'right'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
xAxis: {
|
||||||
|
...options.xAxis,
|
||||||
|
data: xAxisData
|
||||||
|
},
|
||||||
|
series: [...barData, ...linedata]
|
||||||
|
};
|
||||||
|
}, [seriesData, xAxisData, yAxisName, title, smooth, legendData, options]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!lineSeriesData.length && !barSeriesData.length ? (
|
||||||
|
<EmptyData height={height} title={title}></EmptyData>
|
||||||
|
) : (
|
||||||
|
<Chart
|
||||||
|
height={height}
|
||||||
|
options={dataOptions}
|
||||||
|
width={width || '100%'}
|
||||||
|
></Chart>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MixLineBarChart;
|
||||||
@@ -181,7 +181,7 @@ const Scatter: React.FC<ChartProps> = (props) => {
|
|||||||
<span class="tooltip-value">${item.text}</span>
|
<span class="tooltip-value">${item.text}</span>
|
||||||
</span>`;
|
</span>`;
|
||||||
});
|
});
|
||||||
return `<div class="tooltip-wrapper">${result}</div>`;
|
return `<div class="tooltip-wrapper scatter">${result}</div>`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import type { LegendComponentOption } from 'echarts/components';
|
||||||
export interface ChartProps {
|
export interface ChartProps {
|
||||||
seriesData: any[];
|
seriesData: any[];
|
||||||
showEmpty?: boolean;
|
showEmpty?: boolean;
|
||||||
xAxisData: string[];
|
xAxisData: string[];
|
||||||
legendData?: string[];
|
legendData?: LegendComponentOption['data'];
|
||||||
labelFormatter?: (val?: any) => string;
|
labelFormatter?: (val?: any, index?: number) => string;
|
||||||
tooltipValueFormatter?: (val: any) => string;
|
tooltipValueFormatter?: (val: any) => string;
|
||||||
height: string | number;
|
height: string | number;
|
||||||
width?: string | number;
|
width?: string | number;
|
||||||
|
|||||||
@@ -56,4 +56,4 @@ const HighlightCode: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(HighlightCode);
|
export default HighlightCode;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: iconfont; /* Project id 4613488 */
|
font-family: iconfont; /* Project id 4613488 */
|
||||||
src: url('iconfont.woff2?t=1747231573342') format('woff2'),
|
src: url('iconfont.woff2?t=1755160439217') format('woff2'),
|
||||||
url('iconfont.woff?t=1747231573342') format('woff'),
|
url('iconfont.woff?t=1755160439217') format('woff'),
|
||||||
url('iconfont.ttf?t=1747231573342') format('truetype');
|
url('iconfont.ttf?t=1755160439217') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,254 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-admin-user::before {
|
||||||
|
content: "\e69d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-user::before {
|
||||||
|
content: "\e69e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-quick_reference_24dp_1F1F1F_FILL0_wght300_GRAD0_op::before {
|
||||||
|
content: "\e69b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-docker::before {
|
||||||
|
content: "\e69c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-digitalocean::before {
|
||||||
|
content: "\eb79";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rocket-launch1::before {
|
||||||
|
content: "\e699";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rocket-launch-fill::before {
|
||||||
|
content: "\e69a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-credential-filled::before {
|
||||||
|
content: "\e697";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-credential-outline::before {
|
||||||
|
content: "\e698";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-k8s-filled::before {
|
||||||
|
content: "\e63e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-k8s-outline::before {
|
||||||
|
content: "\e64b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-huggingface1::before {
|
||||||
|
content: "\e605";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-modelscope_light::before {
|
||||||
|
content: "\e696";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-catalog1::before {
|
||||||
|
content: "\e691";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-chat-filled::before {
|
||||||
|
content: "\e692";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-chat::before {
|
||||||
|
content: "\e693";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-files-filled::before {
|
||||||
|
content: "\e694";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-files::before {
|
||||||
|
content: "\e695";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-models-filled::before {
|
||||||
|
content: "\e682";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-image-filled::before {
|
||||||
|
content: "\e683";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-audio1::before {
|
||||||
|
content: "\e684";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-image1::before {
|
||||||
|
content: "\e685";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-audio-filled::before {
|
||||||
|
content: "\e686";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-reranker-filled::before {
|
||||||
|
content: "\e687";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-embedding-filled::before {
|
||||||
|
content: "\e68a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-models::before {
|
||||||
|
content: "\e68b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-reranker::before {
|
||||||
|
content: "\e68c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-embedding::before {
|
||||||
|
content: "\e68d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-gpu-filled::before {
|
||||||
|
content: "\e68e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-catalog-filled::before {
|
||||||
|
content: "\e68f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-gpu1::before {
|
||||||
|
content: "\e690";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-language::before {
|
||||||
|
content: "\e679";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-help::before {
|
||||||
|
content: "\e67a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-key-filled::before {
|
||||||
|
content: "\e67b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-key::before {
|
||||||
|
content: "\e67d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-resources::before {
|
||||||
|
content: "\e67e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-users::before {
|
||||||
|
content: "\e67f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-resources-filled::before {
|
||||||
|
content: "\e680";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-users-filled::before {
|
||||||
|
content: "\e681";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-model::before {
|
||||||
|
content: "\e676";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-model-filled::before {
|
||||||
|
content: "\e678";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-layers-filled::before {
|
||||||
|
content: "\e671";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-layers::before {
|
||||||
|
content: "\e673";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-experiment::before {
|
||||||
|
content: "\e674";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-experiment-filled::before {
|
||||||
|
content: "\e675";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-dashboard::before {
|
||||||
|
content: "\e66d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-dashboard-filled::before {
|
||||||
|
content: "\e66e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-expand-left::before {
|
||||||
|
content: "\ea48";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-expand-right::before {
|
||||||
|
content: "\ea49";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-users-fill::before {
|
||||||
|
content: "\e677";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-layers-fill::before {
|
||||||
|
content: "\e89d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-key-fill::before {
|
||||||
|
content: "\e80e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-server-fill::before {
|
||||||
|
content: "\e7a3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-model-fill::before {
|
||||||
|
content: "\e7b7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-left_panel_close::before {
|
||||||
|
content: "\e668";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-left_panel_open::before {
|
||||||
|
content: "\e669";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-playcircle-fill::before {
|
||||||
|
content: "\e665";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-stopcircle-fill::before {
|
||||||
|
content: "\e666";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-play-speed::before {
|
||||||
|
content: "\e856";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-more::before {
|
||||||
|
content: "\e62e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-play::before {
|
||||||
|
content: "\e9f9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pause::before {
|
||||||
|
content: "\e713";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-dark_theme::before {
|
.icon-dark_theme::before {
|
||||||
content: "\e646";
|
content: "\e646";
|
||||||
}
|
}
|
||||||
@@ -237,14 +485,6 @@
|
|||||||
content: "\e62c";
|
content: "\e62c";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-stopcircle-fill::before {
|
|
||||||
content: "\e7cc";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-playcircle-fill::before {
|
|
||||||
content: "\e7cd";
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-SpeakerSlash::before {
|
.icon-SpeakerSlash::before {
|
||||||
content: "\e661";
|
content: "\e661";
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,440 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "45240470",
|
||||||
|
"name": "admin-user",
|
||||||
|
"font_class": "admin-user",
|
||||||
|
"unicode": "e69d",
|
||||||
|
"unicode_decimal": 59037
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "45240469",
|
||||||
|
"name": "user",
|
||||||
|
"font_class": "user",
|
||||||
|
"unicode": "e69e",
|
||||||
|
"unicode_decimal": 59038
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "45221182",
|
||||||
|
"name": "quick_reference_24dp_1F1F1F_FILL0_wght300_GRAD0_op",
|
||||||
|
"font_class": "quick_reference_24dp_1F1F1F_FILL0_wght300_GRAD0_op",
|
||||||
|
"unicode": "e69b",
|
||||||
|
"unicode_decimal": 59035
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "45102077",
|
||||||
|
"name": "Docker",
|
||||||
|
"font_class": "docker",
|
||||||
|
"unicode": "e69c",
|
||||||
|
"unicode_decimal": 59036
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "15378290",
|
||||||
|
"name": "digitalocean",
|
||||||
|
"font_class": "digitalocean",
|
||||||
|
"unicode": "eb79",
|
||||||
|
"unicode_decimal": 60281
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "45040302",
|
||||||
|
"name": "rocket-launch",
|
||||||
|
"font_class": "rocket-launch1",
|
||||||
|
"unicode": "e699",
|
||||||
|
"unicode_decimal": 59033
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "45040301",
|
||||||
|
"name": "rocket-launch-fill",
|
||||||
|
"font_class": "rocket-launch-fill",
|
||||||
|
"unicode": "e69a",
|
||||||
|
"unicode_decimal": 59034
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "45028316",
|
||||||
|
"name": "credential",
|
||||||
|
"font_class": "credential-filled",
|
||||||
|
"unicode": "e697",
|
||||||
|
"unicode_decimal": 59031
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "45028315",
|
||||||
|
"name": "credential",
|
||||||
|
"font_class": "credential-outline",
|
||||||
|
"unicode": "e698",
|
||||||
|
"unicode_decimal": 59032
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "37601405",
|
||||||
|
"name": "k8s",
|
||||||
|
"font_class": "k8s-filled",
|
||||||
|
"unicode": "e63e",
|
||||||
|
"unicode_decimal": 58942
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41673174",
|
||||||
|
"name": "k8s",
|
||||||
|
"font_class": "k8s-outline",
|
||||||
|
"unicode": "e64b",
|
||||||
|
"unicode_decimal": 58955
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40865292",
|
||||||
|
"name": "huggingface",
|
||||||
|
"font_class": "huggingface1",
|
||||||
|
"unicode": "e605",
|
||||||
|
"unicode_decimal": 58885
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "43616582",
|
||||||
|
"name": "modelscope_light",
|
||||||
|
"font_class": "modelscope_light",
|
||||||
|
"unicode": "e696",
|
||||||
|
"unicode_decimal": 59030
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776845",
|
||||||
|
"name": "catalog",
|
||||||
|
"font_class": "catalog1",
|
||||||
|
"unicode": "e691",
|
||||||
|
"unicode_decimal": 59025
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776848",
|
||||||
|
"name": "chat-filled",
|
||||||
|
"font_class": "chat-filled",
|
||||||
|
"unicode": "e692",
|
||||||
|
"unicode_decimal": 59026
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776846",
|
||||||
|
"name": "chat",
|
||||||
|
"font_class": "chat",
|
||||||
|
"unicode": "e693",
|
||||||
|
"unicode_decimal": 59027
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776841",
|
||||||
|
"name": "files-filled",
|
||||||
|
"font_class": "files-filled",
|
||||||
|
"unicode": "e694",
|
||||||
|
"unicode_decimal": 59028
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776840",
|
||||||
|
"name": "files",
|
||||||
|
"font_class": "files",
|
||||||
|
"unicode": "e695",
|
||||||
|
"unicode_decimal": 59029
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776851",
|
||||||
|
"name": "models-filled",
|
||||||
|
"font_class": "models-filled",
|
||||||
|
"unicode": "e682",
|
||||||
|
"unicode_decimal": 59010
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776857",
|
||||||
|
"name": "image-filled",
|
||||||
|
"font_class": "image-filled",
|
||||||
|
"unicode": "e683",
|
||||||
|
"unicode_decimal": 59011
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776856",
|
||||||
|
"name": "audio",
|
||||||
|
"font_class": "audio1",
|
||||||
|
"unicode": "e684",
|
||||||
|
"unicode_decimal": 59012
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776855",
|
||||||
|
"name": "image",
|
||||||
|
"font_class": "image1",
|
||||||
|
"unicode": "e685",
|
||||||
|
"unicode_decimal": 59013
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776850",
|
||||||
|
"name": "audio-filled",
|
||||||
|
"font_class": "audio-filled",
|
||||||
|
"unicode": "e686",
|
||||||
|
"unicode_decimal": 59014
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776849",
|
||||||
|
"name": "reranker-filled",
|
||||||
|
"font_class": "reranker-filled",
|
||||||
|
"unicode": "e687",
|
||||||
|
"unicode_decimal": 59015
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776852",
|
||||||
|
"name": "embedding-filled",
|
||||||
|
"font_class": "embedding-filled",
|
||||||
|
"unicode": "e68a",
|
||||||
|
"unicode_decimal": 59018
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776853",
|
||||||
|
"name": "models",
|
||||||
|
"font_class": "models",
|
||||||
|
"unicode": "e68b",
|
||||||
|
"unicode_decimal": 59019
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776844",
|
||||||
|
"name": "reranker",
|
||||||
|
"font_class": "reranker",
|
||||||
|
"unicode": "e68c",
|
||||||
|
"unicode_decimal": 59020
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776854",
|
||||||
|
"name": "embedding",
|
||||||
|
"font_class": "embedding",
|
||||||
|
"unicode": "e68d",
|
||||||
|
"unicode_decimal": 59021
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776843",
|
||||||
|
"name": "gpu-filled",
|
||||||
|
"font_class": "gpu-filled",
|
||||||
|
"unicode": "e68e",
|
||||||
|
"unicode_decimal": 59022
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776847",
|
||||||
|
"name": "catalog-filled",
|
||||||
|
"font_class": "catalog-filled",
|
||||||
|
"unicode": "e68f",
|
||||||
|
"unicode_decimal": 59023
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44776842",
|
||||||
|
"name": "gpu",
|
||||||
|
"font_class": "gpu1",
|
||||||
|
"unicode": "e690",
|
||||||
|
"unicode_decimal": 59024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44685190",
|
||||||
|
"name": "language",
|
||||||
|
"font_class": "language",
|
||||||
|
"unicode": "e679",
|
||||||
|
"unicode_decimal": 59001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44685189",
|
||||||
|
"name": "help",
|
||||||
|
"font_class": "help",
|
||||||
|
"unicode": "e67a",
|
||||||
|
"unicode_decimal": 59002
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684306",
|
||||||
|
"name": "key-filled",
|
||||||
|
"font_class": "key-filled",
|
||||||
|
"unicode": "e67b",
|
||||||
|
"unicode_decimal": 59003
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684302",
|
||||||
|
"name": "key",
|
||||||
|
"font_class": "key",
|
||||||
|
"unicode": "e67d",
|
||||||
|
"unicode_decimal": 59005
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684304",
|
||||||
|
"name": "resources",
|
||||||
|
"font_class": "resources",
|
||||||
|
"unicode": "e67e",
|
||||||
|
"unicode_decimal": 59006
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684305",
|
||||||
|
"name": "users",
|
||||||
|
"font_class": "users",
|
||||||
|
"unicode": "e67f",
|
||||||
|
"unicode_decimal": 59007
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684303",
|
||||||
|
"name": "resources-filled",
|
||||||
|
"font_class": "resources-filled",
|
||||||
|
"unicode": "e680",
|
||||||
|
"unicode_decimal": 59008
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684301",
|
||||||
|
"name": "users-filled",
|
||||||
|
"font_class": "users-filled",
|
||||||
|
"unicode": "e681",
|
||||||
|
"unicode_decimal": 59009
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684150",
|
||||||
|
"name": "model",
|
||||||
|
"font_class": "model",
|
||||||
|
"unicode": "e676",
|
||||||
|
"unicode_decimal": 58998
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684149",
|
||||||
|
"name": "model-filled",
|
||||||
|
"font_class": "model-filled",
|
||||||
|
"unicode": "e678",
|
||||||
|
"unicode_decimal": 59000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684090",
|
||||||
|
"name": "layers-filled",
|
||||||
|
"font_class": "layers-filled",
|
||||||
|
"unicode": "e671",
|
||||||
|
"unicode_decimal": 58993
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684091",
|
||||||
|
"name": "layers",
|
||||||
|
"font_class": "layers",
|
||||||
|
"unicode": "e673",
|
||||||
|
"unicode_decimal": 58995
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684086",
|
||||||
|
"name": "experiment",
|
||||||
|
"font_class": "experiment",
|
||||||
|
"unicode": "e674",
|
||||||
|
"unicode_decimal": 58996
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684085",
|
||||||
|
"name": "experiment",
|
||||||
|
"font_class": "experiment-filled",
|
||||||
|
"unicode": "e675",
|
||||||
|
"unicode_decimal": 58997
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684021",
|
||||||
|
"name": "dashboard",
|
||||||
|
"font_class": "dashboard",
|
||||||
|
"unicode": "e66d",
|
||||||
|
"unicode_decimal": 58989
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44684020",
|
||||||
|
"name": "dashboard-filled",
|
||||||
|
"font_class": "dashboard-filled",
|
||||||
|
"unicode": "e66e",
|
||||||
|
"unicode_decimal": 58990
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40068272",
|
||||||
|
"name": "expand-left",
|
||||||
|
"font_class": "expand-left",
|
||||||
|
"unicode": "ea48",
|
||||||
|
"unicode_decimal": 59976
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40068278",
|
||||||
|
"name": "expand-right",
|
||||||
|
"font_class": "expand-right",
|
||||||
|
"unicode": "ea49",
|
||||||
|
"unicode_decimal": 59977
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1350497",
|
||||||
|
"name": "sq-users",
|
||||||
|
"font_class": "users-fill",
|
||||||
|
"unicode": "e677",
|
||||||
|
"unicode_decimal": 58999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "13691715",
|
||||||
|
"name": "layers",
|
||||||
|
"font_class": "layers-fill",
|
||||||
|
"unicode": "e89d",
|
||||||
|
"unicode_decimal": 59549
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "14267903",
|
||||||
|
"name": "key-skeleton-alt",
|
||||||
|
"font_class": "key-fill",
|
||||||
|
"unicode": "e80e",
|
||||||
|
"unicode_decimal": 59406
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "14341823",
|
||||||
|
"name": "cloud-server-solid",
|
||||||
|
"font_class": "server-fill",
|
||||||
|
"unicode": "e7a3",
|
||||||
|
"unicode_decimal": 59299
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "40764011",
|
||||||
|
"name": "icon-model-selected",
|
||||||
|
"font_class": "model-fill",
|
||||||
|
"unicode": "e7b7",
|
||||||
|
"unicode_decimal": 59319
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44668359",
|
||||||
|
"name": "left_panel_close_36dp_1F1F1F_FILL0_wght400_GRAD0_o",
|
||||||
|
"font_class": "left_panel_close",
|
||||||
|
"unicode": "e668",
|
||||||
|
"unicode_decimal": 58984
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44668360",
|
||||||
|
"name": "left_panel_open_36dp_1F1F1F_FILL0_wght400_GRAD0_op",
|
||||||
|
"font_class": "left_panel_open",
|
||||||
|
"unicode": "e669",
|
||||||
|
"unicode_decimal": 58985
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44650419",
|
||||||
|
"name": "play circle-fill",
|
||||||
|
"font_class": "playcircle-fill",
|
||||||
|
"unicode": "e665",
|
||||||
|
"unicode_decimal": 58981
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "44650418",
|
||||||
|
"name": "stop circle-fill",
|
||||||
|
"font_class": "stopcircle-fill",
|
||||||
|
"unicode": "e666",
|
||||||
|
"unicode_decimal": 58982
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "8592899",
|
||||||
|
"name": "play-speed",
|
||||||
|
"font_class": "play-speed",
|
||||||
|
"unicode": "e856",
|
||||||
|
"unicode_decimal": 59478
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10941162",
|
||||||
|
"name": "more",
|
||||||
|
"font_class": "more",
|
||||||
|
"unicode": "e62e",
|
||||||
|
"unicode_decimal": 58926
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "703373",
|
||||||
|
"name": "play",
|
||||||
|
"font_class": "play",
|
||||||
|
"unicode": "e9f9",
|
||||||
|
"unicode_decimal": 59897
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "736882",
|
||||||
|
"name": "pause",
|
||||||
|
"font_class": "pause",
|
||||||
|
"unicode": "e713",
|
||||||
|
"unicode_decimal": 59155
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "44028047",
|
"icon_id": "44028047",
|
||||||
"name": "dark_theme",
|
"name": "dark_theme",
|
||||||
@@ -397,20 +831,6 @@
|
|||||||
"unicode": "e62c",
|
"unicode": "e62c",
|
||||||
"unicode_decimal": 58924
|
"unicode_decimal": 58924
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"icon_id": "6151140",
|
|
||||||
"name": "stop circle-fill",
|
|
||||||
"font_class": "stopcircle-fill",
|
|
||||||
"unicode": "e7cc",
|
|
||||||
"unicode_decimal": 59340
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon_id": "6151141",
|
|
||||||
"name": "play circle-fill",
|
|
||||||
"font_class": "playcircle-fill",
|
|
||||||
"unicode": "e7cd",
|
|
||||||
"unicode_decimal": 59341
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"icon_id": "23563264",
|
"icon_id": "23563264",
|
||||||
"name": "SpeakerSlash",
|
"name": "SpeakerSlash",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,32 @@
|
|||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import {
|
||||||
|
ApiOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownloadOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
ExperimentOutlined,
|
||||||
|
RetweetOutlined,
|
||||||
|
ThunderboltOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
EditOutlined: React.createElement(EditOutlined),
|
||||||
|
ExperimentOutlined: React.createElement(ExperimentOutlined),
|
||||||
|
DeleteOutlined: React.createElement(DeleteOutlined),
|
||||||
|
ThunderboltOutlined: React.createElement(ThunderboltOutlined),
|
||||||
|
RetweetOutlined: React.createElement(RetweetOutlined),
|
||||||
|
DownloadOutlined: React.createElement(DownloadOutlined),
|
||||||
|
ApiOutlined: React.createElement(ApiOutlined),
|
||||||
|
Stop: React.createElement(IconFont, { type: 'icon-stop1' }),
|
||||||
|
Play: React.createElement(IconFont, { type: 'icon-outline-play' }),
|
||||||
|
Catalog: React.createElement(IconFont, { type: 'icon-catalog' }),
|
||||||
|
HF: React.createElement(IconFont, { type: 'icon-huggingface' }),
|
||||||
|
Ollama: React.createElement(IconFont, { type: 'icon-ollama' }),
|
||||||
|
ModelScope: React.createElement(IconFont, { type: 'icon-tu2' }),
|
||||||
|
LocalPath: React.createElement(IconFont, { type: 'icon-hard-disk' }),
|
||||||
|
Launch: React.createElement(IconFont, { type: 'icon-rocket-launch' }),
|
||||||
|
Deployment: React.createElement(IconFont, { type: 'icon-rocket-launch1' })
|
||||||
|
};
|
||||||
|
|
||||||
|
export default icons;
|
||||||
@@ -107,7 +107,6 @@ export default function useZoom(props: {
|
|||||||
if (isLoadingMaskRef.current) {
|
if (isLoadingMaskRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
|
||||||
// stop
|
// stop
|
||||||
handleZoom(event);
|
handleZoom(event);
|
||||||
updateCursorSize();
|
updateCursorSize();
|
||||||
|
|||||||
@@ -433,7 +433,6 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
|
|||||||
fitView();
|
fitView();
|
||||||
setActiveScale(autoScale.current);
|
setActiveScale(autoScale.current);
|
||||||
updateCursorSize();
|
updateCursorSize();
|
||||||
redrawStrokes(strokesRef.current);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBrushSizeChange = (value: number) => {
|
const handleBrushSizeChange = (value: number) => {
|
||||||
@@ -454,11 +453,22 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleUndoShortcut = (e: KeyboardEvent) => {
|
const handleUndoShortcut = (e: KeyboardEvent) => {
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
|
if (
|
||||||
|
(e.ctrlKey || e.metaKey) &&
|
||||||
|
e.key === 'z' &&
|
||||||
|
!negativeMaskRef.current
|
||||||
|
) {
|
||||||
undo();
|
undo();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
window.addEventListener('keydown', handleUndoShortcut);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleUndoShortcut);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
mouseDownState.current = true;
|
mouseDownState.current = true;
|
||||||
};
|
};
|
||||||
@@ -467,7 +477,6 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
|
|||||||
mouseDownState.current = false;
|
mouseDownState.current = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', handleUndoShortcut);
|
|
||||||
// mouse down
|
// mouse down
|
||||||
window.addEventListener('mousedown', handleMouseDown);
|
window.addEventListener('mousedown', handleMouseDown);
|
||||||
|
|
||||||
@@ -475,7 +484,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
|
|||||||
window.addEventListener('mouseup', handleMouseUp);
|
window.addEventListener('mouseup', handleMouseUp);
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(timer.current);
|
clearTimeout(timer.current);
|
||||||
window.removeEventListener('keydown', handleUndoShortcut);
|
|
||||||
window.removeEventListener('mousedown', handleMouseDown);
|
window.removeEventListener('mousedown', handleMouseDown);
|
||||||
window.removeEventListener('mouseup', handleMouseUp);
|
window.removeEventListener('mouseup', handleMouseUp);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,27 @@ import langConfigMap from '@/locales/lang-config-map';
|
|||||||
import { GlobalOutlined } from '@ant-design/icons';
|
import { GlobalOutlined } from '@ant-design/icons';
|
||||||
import { getAllLocales, setLocale } from '@umijs/max';
|
import { getAllLocales, setLocale } from '@umijs/max';
|
||||||
import { Dropdown } from 'antd';
|
import { Dropdown } from 'antd';
|
||||||
|
import { createStyles } from 'antd-style';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ token, css }) => ({
|
||||||
|
button: css`
|
||||||
|
color: ${token.colorText};
|
||||||
|
padding: 0 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
.anticon {
|
||||||
|
color: ${token.colorText};
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.anticon {
|
||||||
|
color: ${token.colorTextTertiary};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}));
|
||||||
|
|
||||||
const LangSelect = () => {
|
const LangSelect = () => {
|
||||||
|
const { styles } = useStyles();
|
||||||
const allLocals = getAllLocales();
|
const allLocals = getAllLocales();
|
||||||
const items = allLocals.map((key) => {
|
const items = allLocals.map((key) => {
|
||||||
return {
|
return {
|
||||||
@@ -25,7 +43,7 @@ const LangSelect = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown menu={{ items }}>
|
<Dropdown menu={{ items }}>
|
||||||
<span style={{ padding: '0 12px', cursor: 'pointer' }}>
|
<span className={styles.button}>
|
||||||
<GlobalOutlined />
|
<GlobalOutlined />
|
||||||
</span>
|
</span>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g;
|
export const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g;
|
||||||
export const replaceLineRegex = /\r\n/g;
|
export const replaceLineRegex = /\r\n/g;
|
||||||
|
|
||||||
export const PageSize = 500;
|
export const PageSize = 1000;
|
||||||
|
|
||||||
export const throttle = <T extends (...args: any[]) => void>(
|
export const throttle = <T extends (...args: any[]) => void>(
|
||||||
func: T,
|
func: T,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const LogsList: React.FC<LogsListProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
const isBottom = scrollTop + clientHeight + 150 >= scrollHeight;
|
const isBottom = scrollTop + clientHeight + 150 >= scrollHeight;
|
||||||
// is scroll to top
|
// is scroll to top
|
||||||
if (scrollTop === 0) {
|
if (scrollTop <= 10) {
|
||||||
onScroll?.({
|
onScroll?.({
|
||||||
isTop: true,
|
isTop: true,
|
||||||
isBottom: false
|
isBottom: false
|
||||||
|
|||||||
@@ -106,4 +106,4 @@ const LogsPagination: React.FC<LogsPaginationProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(LogsPagination);
|
export default LogsPagination;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ interface MessageProps {
|
|||||||
chunked?: boolean;
|
chunked?: boolean;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
percent?: number;
|
percent?: number;
|
||||||
|
isDownloading?: boolean;
|
||||||
}
|
}
|
||||||
class AnsiParser {
|
class AnsiParser {
|
||||||
private cursorRow: number = 0;
|
private cursorRow: number = 0;
|
||||||
@@ -30,6 +31,9 @@ class AnsiParser {
|
|||||||
private percent: number = 0;
|
private percent: number = 0;
|
||||||
private isComplete: boolean = false;
|
private isComplete: boolean = false;
|
||||||
private chunked: boolean = true; // true: send data in chunks, false: send all data at once
|
private chunked: boolean = true; // true: send data in chunks, false: send all data at once
|
||||||
|
private reminder: string = '';
|
||||||
|
private lines: string[] = [];
|
||||||
|
isDownloading: boolean = false;
|
||||||
private pageSize: number = 500;
|
private pageSize: number = 500;
|
||||||
private colorMap = {
|
private colorMap = {
|
||||||
'30': 'black',
|
'30': 'black',
|
||||||
@@ -52,6 +56,8 @@ class AnsiParser {
|
|||||||
this.screen = [['']];
|
this.screen = [['']];
|
||||||
this.rawDataRows = 0;
|
this.rawDataRows = 0;
|
||||||
this.uid = this.uid + 1;
|
this.uid = this.uid + 1;
|
||||||
|
this.lines = [];
|
||||||
|
this.reminder = '';
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +81,10 @@ class AnsiParser {
|
|||||||
this.chunked = chunked ?? true;
|
this.chunked = chunked ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setIsDownloading(isDownloading: boolean) {
|
||||||
|
this.isDownloading = isDownloading;
|
||||||
|
}
|
||||||
|
|
||||||
private setId() {
|
private setId() {
|
||||||
this.uid += 1;
|
this.uid += 1;
|
||||||
return this.uid;
|
return this.uid;
|
||||||
@@ -109,7 +119,6 @@ class AnsiParser {
|
|||||||
const n = parseInt(match[1] || '1', 10);
|
const n = parseInt(match[1] || '1', 10);
|
||||||
const m = parseInt(match[2] || '1', 10);
|
const m = parseInt(match[2] || '1', 10);
|
||||||
const command = match[3];
|
const command = match[3];
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'A':
|
case 'A':
|
||||||
this.cursorRow = Math.max(0, this.cursorRow - n);
|
this.cursorRow = Math.max(0, this.cursorRow - n);
|
||||||
@@ -165,14 +174,18 @@ class AnsiParser {
|
|||||||
const remainingText = input.slice(lastIndex);
|
const remainingText = input.slice(lastIndex);
|
||||||
this.handleText(remainingText);
|
this.handleText(remainingText);
|
||||||
|
|
||||||
const result = this.screen.map((row, index) => ({
|
// const result = this.screen.map((row, index) => ({
|
||||||
content: removeBracketsFromLine(row.join('')),
|
// content: removeBracketsFromLine(row.join('')),
|
||||||
uid: `${this.page}-${index}`
|
// uid: `${this.page}-${index}`
|
||||||
}));
|
// }));
|
||||||
|
const result = this.screen.map((row, index) =>
|
||||||
|
removeBracketsFromLine(row.join(''))
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: result,
|
data: result,
|
||||||
lines: this.rawDataRows
|
lines: this.rawDataRows,
|
||||||
|
remainder: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +196,31 @@ class AnsiParser {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private processInputByLine(input: string): {
|
||||||
|
data: string[];
|
||||||
|
lines: number;
|
||||||
|
remainder: string;
|
||||||
|
} {
|
||||||
|
const lines = input?.split(/\r?\n/) || [];
|
||||||
|
const remainder = lines.pop() || '';
|
||||||
|
|
||||||
|
// const data = lines.join('\n');
|
||||||
|
this.rawDataRows += lines.length;
|
||||||
|
lines.forEach((line) => {
|
||||||
|
this.lines.push(line);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: this.lines,
|
||||||
|
lines: this.rawDataRows,
|
||||||
|
remainder
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAllLines() {
|
||||||
|
return this.lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
private async processQueue(): Promise<void> {
|
private async processQueue(): Promise<void> {
|
||||||
if (this.isProcessing) {
|
if (this.isProcessing) {
|
||||||
return;
|
return;
|
||||||
@@ -191,11 +229,23 @@ class AnsiParser {
|
|||||||
this.isProcessing = true;
|
this.isProcessing = true;
|
||||||
|
|
||||||
while (this.taskQueue.length > 0) {
|
while (this.taskQueue.length > 0) {
|
||||||
const input = this.taskQueue.shift();
|
let input = '';
|
||||||
|
|
||||||
|
if (this.isDownloading) {
|
||||||
|
input = this.taskQueue.shift() || '';
|
||||||
|
} else {
|
||||||
|
input = this.reminder + this.taskQueue.shift();
|
||||||
|
}
|
||||||
|
|
||||||
if (input) {
|
if (input) {
|
||||||
try {
|
try {
|
||||||
const result = this.processInput(input);
|
const result = this.isDownloading
|
||||||
|
? this.processInput(input)
|
||||||
|
: this.processInputByLine(input);
|
||||||
|
if (!this.isDownloading) {
|
||||||
|
this.reminder = result.remainder;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.chunked) {
|
if (this.chunked) {
|
||||||
self.postMessage({ result: result.data, lines: result.lines });
|
self.postMessage({ result: result.data, lines: result.lines });
|
||||||
} else if (!this.isComplete) {
|
} else if (!this.isComplete) {
|
||||||
@@ -220,7 +270,7 @@ class AnsiParser {
|
|||||||
this.processQueue();
|
this.processQueue();
|
||||||
} else if (this.isComplete && !this.chunked) {
|
} else if (this.isComplete && !this.chunked) {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
result: this.getScreenText(),
|
result: this.getAllLines(),
|
||||||
percent: this.percent,
|
percent: this.percent,
|
||||||
isComplete: true
|
isComplete: true
|
||||||
});
|
});
|
||||||
@@ -244,9 +294,11 @@ self.onmessage = function (event: MessageEvent<MessageProps>) {
|
|||||||
page,
|
page,
|
||||||
isComplete = false,
|
isComplete = false,
|
||||||
chunked = true,
|
chunked = true,
|
||||||
percent = 0
|
percent = 0,
|
||||||
|
isDownloading = false
|
||||||
} = event.data;
|
} = event.data;
|
||||||
|
|
||||||
|
parser.setIsDownloading(isDownloading);
|
||||||
parser.setPage(page);
|
parser.setPage(page);
|
||||||
parser.setIsCompelete(isComplete);
|
parser.setIsCompelete(isComplete);
|
||||||
parser.setChunked(chunked);
|
parser.setChunked(chunked);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: -18px;
|
right: -18px;
|
||||||
width: 80px;
|
// width: 80px;
|
||||||
height: 185px;
|
height: 185px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
.text {
|
.text {
|
||||||
min-height: 22px;
|
min-height: 22px;
|
||||||
|
padding-inline-end: 80px;
|
||||||
|
|
||||||
&.numable {
|
&.numable {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn {
|
.ant-btn {
|
||||||
background-color: rgba(71, 71, 71, 100%) !important;
|
background-color: rgba(71, 71, 71, 70%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pages {
|
.pages {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import classNames from 'classnames';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
memo,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
@@ -25,9 +24,17 @@ interface LogsViewerProps {
|
|||||||
tail?: number;
|
tail?: number;
|
||||||
enableScorllLoad?: boolean;
|
enableScorllLoad?: boolean;
|
||||||
diffHeight?: number;
|
diffHeight?: number;
|
||||||
|
isDownloading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
||||||
const { diffHeight, url, tail: defaultTail, enableScorllLoad = true } = props;
|
const {
|
||||||
|
diffHeight,
|
||||||
|
url,
|
||||||
|
tail: defaultTail,
|
||||||
|
enableScorllLoad = true,
|
||||||
|
isDownloading
|
||||||
|
} = props;
|
||||||
const { pageSize, page, setPage, setTotalPage, totalPage } =
|
const { pageSize, page, setPage, setTotalPage, totalPage } =
|
||||||
useLogsPagination();
|
useLogsPagination();
|
||||||
const { setChunkFetch } = useSetChunkFetch();
|
const { setChunkFetch } = useSetChunkFetch();
|
||||||
@@ -43,7 +50,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
const pageRef = useRef<any>(page);
|
const pageRef = useRef<any>(page);
|
||||||
const totalPageRef = useRef<any>(totalPage);
|
const totalPageRef = useRef<any>(totalPage);
|
||||||
const isLoadingMoreRef = useRef(false);
|
const isLoadingMoreRef = useRef(false);
|
||||||
const [currentData, setCurrentData] = useState<any[]>([]);
|
const [currentData, setCurrentPageData] = useState<any[]>([]);
|
||||||
const scrollPosRef = useRef<any>({
|
const scrollPosRef = useRef<any>({
|
||||||
pos: 'bottom',
|
pos: 'bottom',
|
||||||
page: 1
|
page: 1
|
||||||
@@ -58,6 +65,22 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const removeBracketsFromLine = (row: string) => {
|
||||||
|
return row.startsWith('(…)') ? row.slice(3) : row;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCurrentData = (lines: string[]) => {
|
||||||
|
console.log('setCurrentData', lines);
|
||||||
|
const dataList = lines.map((line, index) => {
|
||||||
|
return {
|
||||||
|
content: line,
|
||||||
|
uid: `${pageRef.current}-${index}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setCurrentPageData(dataList);
|
||||||
|
};
|
||||||
|
|
||||||
const debounceLoading = _.debounce(() => {
|
const debounceLoading = _.debounce(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
isLoadingMoreRef.current = false;
|
isLoadingMoreRef.current = false;
|
||||||
@@ -134,7 +157,8 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
logParseWorker.current.postMessage({
|
logParseWorker.current.postMessage({
|
||||||
inputStr: data,
|
inputStr: data,
|
||||||
page: pageRef.current,
|
page: pageRef.current,
|
||||||
reset: clearScreen.current
|
reset: clearScreen.current,
|
||||||
|
isDownloading: isDownloading
|
||||||
});
|
});
|
||||||
clearScreen.current = false;
|
clearScreen.current = false;
|
||||||
};
|
};
|
||||||
@@ -231,7 +255,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
return () => {
|
return () => {
|
||||||
chunkRequedtRef.current?.current?.abort?.();
|
chunkRequedtRef.current?.current?.abort?.();
|
||||||
};
|
};
|
||||||
}, [url]);
|
}, [url, isDownloading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
debouncedScroll();
|
debouncedScroll();
|
||||||
@@ -313,7 +337,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
<div className="pg">
|
<div className="pg">
|
||||||
<div
|
<div
|
||||||
className={classNames('pg-inner', {
|
className={classNames('pg-inner', {
|
||||||
'at-top': isAtTop
|
'at-top': true
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<LogsPagination
|
<LogsPagination
|
||||||
@@ -333,4 +357,4 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default memo(LogsViewer);
|
export default LogsViewer;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'katex/dist/katex.min.css';
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import rehypeKatex from 'rehype-katex';
|
import rehypeKatex from 'rehype-katex';
|
||||||
|
import remarkBreaks from 'remark-breaks';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import remarkMath from 'remark-math';
|
import remarkMath from 'remark-math';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -17,9 +18,7 @@ interface FullMarkdownProps {
|
|||||||
|
|
||||||
const Wrapper = styled.div.attrs(() => ({
|
const Wrapper = styled.div.attrs(() => ({
|
||||||
className: 'markdown-viewer'
|
className: 'markdown-viewer'
|
||||||
}))`
|
}))``;
|
||||||
line-height: 2;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CodeViewer = (props: any) => {
|
const CodeViewer = (props: any) => {
|
||||||
const { children, className, node, theme, ...rest } = props;
|
const { children, className, node, theme, ...rest } = props;
|
||||||
@@ -41,7 +40,7 @@ const FullMarkdown: React.FC<FullMarkdownProps> = ({ content, theme }) => {
|
|||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[[remarkMath], remarkGfm]}
|
remarkPlugins={[[remarkMath], remarkGfm, remarkBreaks]}
|
||||||
rehypePlugins={[rehypeKatex]}
|
rehypePlugins={[rehypeKatex]}
|
||||||
components={{
|
components={{
|
||||||
code(props) {
|
code(props) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.markdown-viewer {
|
.markdown-viewer {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
line-height: 2;
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
|
|
||||||
.hr {
|
.hr {
|
||||||
@@ -32,7 +33,6 @@
|
|||||||
h2,
|
h2,
|
||||||
h3,
|
h3,
|
||||||
h4 {
|
h4 {
|
||||||
font-weight: var(--font-weight-bold);
|
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,20 +58,14 @@
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
|
||||||
font-weight: var(--font-weight-bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
line-height: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
line-height: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a * {
|
a * {
|
||||||
@@ -87,7 +81,7 @@
|
|||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding-inline: 6px;
|
padding-inline: 6px;
|
||||||
border-bottom: 1px solid var(--ant-color-split);
|
border: 1px solid var(--ant-color-split);
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
}
|
}
|
||||||
@@ -95,7 +89,7 @@
|
|||||||
td {
|
td {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding-inline: 6px;
|
padding-inline: 6px;
|
||||||
border-bottom: 1px solid var(--ant-color-split);
|
border: 1px solid var(--ant-color-split);
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
}
|
}
|
||||||
@@ -118,6 +112,8 @@
|
|||||||
img[src^="https://img.shields.io/"], img[src*="badge.svg"]
|
img[src^="https://img.shields.io/"], img[src*="badge.svg"]
|
||||||
{
|
{
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
|
width: unset;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
video {
|
video {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { EyeOutlined } from '@ant-design/icons';
|
import { EyeOutlined } from '@ant-design/icons';
|
||||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||||
import { Checkbox, Image, Typography } from 'antd';
|
import { Checkbox, Image, Typography } from 'antd';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
import { unescape } from 'lodash';
|
import { unescape } from 'lodash';
|
||||||
import { TokensList, marked } from 'marked';
|
import { TokensList, marked } from 'marked';
|
||||||
import React, { Fragment, useCallback, useEffect } from 'react';
|
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||||
@@ -16,6 +17,15 @@ interface MarkdownViewerProps {
|
|||||||
generateImgLink?: (src: string) => string;
|
generateImgLink?: (src: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dompurifyOptions = {
|
||||||
|
FORBID_TAGS: ['meta', 'style', 'script', 'iframe'],
|
||||||
|
FORBID_ATTR: ['onerror', 'onclick', 'onload', 'style', 'autoplay']
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanHtml = (html: string): string => {
|
||||||
|
return DOMPurify?.sanitize?.(html, dompurifyOptions);
|
||||||
|
};
|
||||||
|
|
||||||
const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
||||||
content,
|
content,
|
||||||
generateImgLink,
|
generateImgLink,
|
||||||
@@ -68,11 +78,15 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(token: any, render: any) => {
|
(token: any, render: any) => {
|
||||||
|
if (token.type === 'script' || token.type === 'style') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!reDefineTypes.includes(token.type)) {
|
if (!reDefineTypes.includes(token.type)) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: marked.parser([token], { renderer })
|
__html: cleanHtml(marked.parser([token], { renderer }))
|
||||||
}}
|
}}
|
||||||
></span>
|
></span>
|
||||||
);
|
);
|
||||||
@@ -89,7 +103,13 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token.type === 'html') {
|
if (token.type === 'html') {
|
||||||
htmlstr = <div dangerouslySetInnerHTML={{ __html: token.text }} />;
|
htmlstr = (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: cleanHtml(token.text)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (token.type === 'list') {
|
if (token.type === 'list') {
|
||||||
htmlstr = token.order ? (
|
htmlstr = token.order ? (
|
||||||
@@ -158,7 +178,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
if (token.type === 'link') {
|
if (token.type === 'link') {
|
||||||
htmlstr = (
|
htmlstr = (
|
||||||
<Link
|
<Link
|
||||||
href={token.href}
|
href={sanitizeUrl(token.href || '')}
|
||||||
title={token.title || ''}
|
title={token.title || ''}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ const Wrapper = styled.div<{ $maxHeight?: number }>`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// export OverlayScrollerOptions
|
||||||
|
export type { OverlayScrollerOptions };
|
||||||
|
|
||||||
export const OverlayScroller: React.FC<
|
export const OverlayScroller: React.FC<
|
||||||
OverlayScrollerOptions & {
|
OverlayScrollerOptions & {
|
||||||
maxHeight?: number;
|
maxHeight?: number;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Progress, Tooltip } from 'antd';
|
import { Progress, Tooltip } from 'antd';
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
const RenderProgress = memo(
|
const RenderProgress = memo(
|
||||||
(props: {
|
(props: {
|
||||||
open?: boolean;
|
defaultOpen?: boolean;
|
||||||
percent: number;
|
percent: number;
|
||||||
steps?: number;
|
steps?: number;
|
||||||
download?: boolean;
|
download?: boolean;
|
||||||
@@ -11,7 +11,8 @@ const RenderProgress = memo(
|
|||||||
successPercent?: number;
|
successPercent?: number;
|
||||||
successColor?: string;
|
successColor?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { open, percent, download, label, successPercent } = props;
|
const { defaultOpen, percent, download, label, successPercent } = props;
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
const strokeColor = useMemo(() => {
|
const strokeColor = useMemo(() => {
|
||||||
if (download) {
|
if (download) {
|
||||||
@@ -27,6 +28,10 @@ const RenderProgress = memo(
|
|||||||
return 'var(--ant-color-error)';
|
return 'var(--ant-color-error)';
|
||||||
}, [percent]);
|
}, [percent]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpen(defaultOpen || false);
|
||||||
|
}, [defaultOpen]);
|
||||||
|
|
||||||
const renderProgress = useMemo(() => {
|
const renderProgress = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Progress
|
<Progress
|
||||||
@@ -59,6 +64,7 @@ const RenderProgress = memo(
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={label}
|
title={label}
|
||||||
open={open}
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
overlayInnerStyle={{ paddingInline: 12 }}
|
overlayInnerStyle={{ paddingInline: 12 }}
|
||||||
>
|
>
|
||||||
{renderProgress}
|
{renderProgress}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { useEscHint } from '@/hooks/use-esc-hint';
|
||||||
|
import { Drawer, type DrawerProps } from 'antd';
|
||||||
|
|
||||||
|
const ScrollerModal = (props: DrawerProps) => {
|
||||||
|
const { EscHint } = useEscHint({
|
||||||
|
enabled: !props.keyboard && props.open
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Drawer {...props}>
|
||||||
|
{props.children}
|
||||||
|
{EscHint}
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScrollerModal;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
interface SelectRenderProps {
|
||||||
|
maxTagWidth?: number;
|
||||||
|
radius?: number | string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
filled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useSelectRender(config?: SelectRenderProps) {
|
||||||
|
const { maxTagWidth = 100, radius, filled, style } = config || {};
|
||||||
|
const TagRender = (props: any) => {
|
||||||
|
const { label } = props;
|
||||||
|
const labelText = useMemo(() => {
|
||||||
|
if (!props.isMaxTag) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
return label.slice(0, -3);
|
||||||
|
}, [label, props.isMaxTag]);
|
||||||
|
return (
|
||||||
|
<AutoTooltip
|
||||||
|
maxWidth={maxTagWidth}
|
||||||
|
closable={props.closable}
|
||||||
|
onClose={props.onClose}
|
||||||
|
radius={radius}
|
||||||
|
filled={filled}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{labelText}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
TagRender
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -148,4 +148,4 @@ const RowTextarea: React.FC<SystemMessageProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(RowTextarea);
|
export default RowTextarea;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import SelectWrapper from './wrapper/select';
|
|||||||
|
|
||||||
const tag = (props: any) => {
|
const tag = (props: any) => {
|
||||||
if (props.isMaxTag) {
|
if (props.isMaxTag) {
|
||||||
return props.label;
|
return props.label?.slice(0, -3);
|
||||||
}
|
}
|
||||||
const parent = _.split(props.value, '__RC_CASCADER_SPLIT__')?.[0];
|
const parent = _.split(props.value, '__RC_CASCADER_SPLIT__')?.[0];
|
||||||
return `${parent} / ${props?.label}`;
|
return `${parent} / ${props?.label}`;
|
||||||
@@ -24,6 +24,7 @@ const renderTag = (props: any) => {
|
|||||||
closable={props.closable}
|
closable={props.closable}
|
||||||
onClose={props.onClose}
|
onClose={props.onClose}
|
||||||
maxWidth={240}
|
maxWidth={240}
|
||||||
|
filled
|
||||||
>
|
>
|
||||||
{tag(props)}
|
{tag(props)}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import type { SelectProps } from 'antd';
|
||||||
|
import { Checkbox, Select, Tag } from 'antd';
|
||||||
|
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import AutoTooltip from '../auto-tooltip';
|
||||||
|
|
||||||
|
const OptionWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DropdownWrapper = styled.div`
|
||||||
|
.ant-select-item {
|
||||||
|
padding-inline-start: 8px;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--ant-select-option-active-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||||
|
background-color: unset;
|
||||||
|
font-weight: unset;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--ant-select-option-active-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelectAllWrapper = styled.div`
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-bottom: 1px solid var(--ant-color-split);
|
||||||
|
.ant-checkbox-wrapper {
|
||||||
|
color: var(--ant-color-text-tertiary);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TagWrapper = styled(Tag)`
|
||||||
|
border-radius: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SimpleSelect: React.FC<SelectProps> = (props) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { options = [], ...restProps } = props;
|
||||||
|
|
||||||
|
const [allSelection, setAllSelection] = React.useState<{
|
||||||
|
checked: boolean;
|
||||||
|
indeterminate: boolean;
|
||||||
|
}>({
|
||||||
|
checked: false,
|
||||||
|
indeterminate: false
|
||||||
|
});
|
||||||
|
const [optionsList, setOptionsList] = React.useState<any[]>(options || []);
|
||||||
|
const selectRef = React.useRef<any>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOptionsList(options || []);
|
||||||
|
}, [options]);
|
||||||
|
|
||||||
|
const optionRender = (option: any, info: any) => {
|
||||||
|
const { value, label } = option;
|
||||||
|
return (
|
||||||
|
<OptionWrapper>
|
||||||
|
{restProps.value?.includes(value) ? (
|
||||||
|
<Checkbox checked></Checkbox>
|
||||||
|
) : (
|
||||||
|
<Checkbox></Checkbox>
|
||||||
|
)}
|
||||||
|
<AutoTooltip ghost>{label}</AutoTooltip>
|
||||||
|
</OptionWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnCheckboxChange = (e: CheckboxChangeEvent) => {
|
||||||
|
const isChecked = e.target.checked;
|
||||||
|
const allValues = optionsList?.map((opt: any) => opt.value) || [];
|
||||||
|
|
||||||
|
setAllSelection({
|
||||||
|
checked: isChecked,
|
||||||
|
indeterminate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
let allSelectedValues = [...(restProps.value || [])];
|
||||||
|
|
||||||
|
if (isChecked) {
|
||||||
|
// Select all options
|
||||||
|
allSelectedValues = Array.from(
|
||||||
|
new Set([...allSelectedValues, ...allValues])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Deselect all options
|
||||||
|
allSelectedValues = allSelectedValues.filter(
|
||||||
|
(value) => !allValues.includes(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
restProps.onChange?.(allSelectedValues, optionsList || []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dropdownRender = (originPanel: React.ReactNode) => {
|
||||||
|
return (
|
||||||
|
<DropdownWrapper>
|
||||||
|
{restProps.mode === 'multiple' && (
|
||||||
|
<SelectAllWrapper>
|
||||||
|
<Checkbox
|
||||||
|
checked={allSelection.checked}
|
||||||
|
indeterminate={allSelection.indeterminate}
|
||||||
|
onChange={handleOnCheckboxChange}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'common.checbox.all' })}
|
||||||
|
</Checkbox>
|
||||||
|
</SelectAllWrapper>
|
||||||
|
)}
|
||||||
|
{originPanel}
|
||||||
|
</DropdownWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnChange = (value: any, option: any) => {
|
||||||
|
const selectedValues = Array.isArray(value) ? value : [value];
|
||||||
|
const allSelected = optionsList?.map((opt: any) => opt.value) || [];
|
||||||
|
const isAllSelected = selectedValues.length === allSelected?.length;
|
||||||
|
|
||||||
|
setAllSelection({
|
||||||
|
checked: isAllSelected,
|
||||||
|
indeterminate: !isAllSelected && selectedValues.length > 0
|
||||||
|
});
|
||||||
|
|
||||||
|
restProps.onChange?.(selectedValues, option);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterOption = (inputValue: string, option: any) => {
|
||||||
|
if (!option || !option.label) return false;
|
||||||
|
return option.label.toLowerCase().includes(inputValue.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkAllSelection = (list: Global.BaseOption<string | number>[]) => {
|
||||||
|
if (
|
||||||
|
!restProps.value ||
|
||||||
|
!Array.isArray(restProps.value) ||
|
||||||
|
list.length === 0
|
||||||
|
) {
|
||||||
|
setAllSelection({
|
||||||
|
checked: false,
|
||||||
|
indeterminate: false
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedValues = new Set(restProps.value);
|
||||||
|
const allValues = list?.map((opt: any) => opt.value) || [];
|
||||||
|
|
||||||
|
const isAllSelected = allValues.every((val: any) =>
|
||||||
|
selectedValues.has(val)
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSomeSelected = allValues.some((val: any) =>
|
||||||
|
selectedValues.has(val)
|
||||||
|
);
|
||||||
|
|
||||||
|
setAllSelection({
|
||||||
|
checked: isAllSelected,
|
||||||
|
indeterminate: isSomeSelected && !isAllSelected
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const TagRender = (props: any) => {
|
||||||
|
const { label } = props;
|
||||||
|
const count = props.isMaxTag ? label.slice(0, -3).slice(1) : label;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TagWrapper
|
||||||
|
bordered={false}
|
||||||
|
style={{
|
||||||
|
height: 24,
|
||||||
|
backgroundColor: 'var(--ant-color-fill-tertiary)',
|
||||||
|
fontSize: 'var(--ant-font-size)'
|
||||||
|
}}
|
||||||
|
className="flex-center"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'common.select.count' }, { count: count })}
|
||||||
|
</TagWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnSearch = (value: string) => {
|
||||||
|
if (restProps.onSearch) {
|
||||||
|
restProps.onSearch(value);
|
||||||
|
} else {
|
||||||
|
const filteredOptions = options?.filter((option: any) =>
|
||||||
|
option.label.toLowerCase().includes(value.toLowerCase())
|
||||||
|
) as Global.BaseOption<string | number>[];
|
||||||
|
setOptionsList(filteredOptions || []);
|
||||||
|
checkAllSelection(filteredOptions || []);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnBlur = (e: any) => {
|
||||||
|
restProps.onBlur?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnFocus = (e: any) => {
|
||||||
|
restProps.onFocus?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnOpenChange = (open: boolean) => {
|
||||||
|
if (!open) {
|
||||||
|
checkAllSelection(options as Global.BaseOption<string | number>[]);
|
||||||
|
setOptionsList(options || []);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const input = selectRef.current?.querySelector?.('input');
|
||||||
|
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
const handler = (event: KeyboardEvent) => {
|
||||||
|
if (
|
||||||
|
event.key === 'Backspace' &&
|
||||||
|
(input as HTMLInputElement).value === ''
|
||||||
|
) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
input.addEventListener('keydown', handler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
input.removeEventListener('keydown', handler);
|
||||||
|
};
|
||||||
|
}, [selectRef.current]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={selectRef}>
|
||||||
|
<Select
|
||||||
|
{...restProps}
|
||||||
|
options={optionsList}
|
||||||
|
maxTagCount={0}
|
||||||
|
defaultActiveFirstOption={false}
|
||||||
|
dropdownRender={dropdownRender}
|
||||||
|
optionRender={optionRender}
|
||||||
|
menuItemSelectedIcon={false}
|
||||||
|
onChange={handleOnChange}
|
||||||
|
tagRender={TagRender}
|
||||||
|
onBlur={handleOnBlur}
|
||||||
|
onFocus={handleOnFocus}
|
||||||
|
onSearch={handleOnSearch}
|
||||||
|
filterOption={filterOption}
|
||||||
|
onDropdownVisibleChange={handleOnOpenChange}
|
||||||
|
></Select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SimpleSelect;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import { RightOutlined } from '@ant-design/icons';
|
import { RightOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Checkbox } from 'antd';
|
import { Button, Checkbox } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -29,10 +30,20 @@ const HeaderPrefix: React.FC<HeaderPrefixProps> = (props) => {
|
|||||||
disabled
|
disabled
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleToggleExpand = () => {
|
const handleToggleExpand = () => {
|
||||||
onExpandAll?.(!expandAll);
|
onExpandAll?.(!expandAll);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUnCheckAll = () => {
|
||||||
|
onSelectAll?.({
|
||||||
|
target: {
|
||||||
|
checked: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (!hasColumns) {
|
if (!hasColumns) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -98,4 +109,4 @@ const HeaderPrefix: React.FC<HeaderPrefixProps> = (props) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(HeaderPrefix);
|
export default HeaderPrefix;
|
||||||
|
|||||||
@@ -45,4 +45,4 @@ const Header: React.FC<HeaderProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(Header);
|
export default Header;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Pagination, type PaginationProps } from 'antd';
|
import { Pagination, type PaginationProps } from 'antd';
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const PaginationComponent: React.FC<PaginationProps> = (props) => {
|
const PaginationComponent: React.FC<PaginationProps> = (props) => {
|
||||||
return <Pagination {...props} />;
|
return <Pagination {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(PaginationComponent);
|
export default PaginationComponent;
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ const TableHeader: React.FC<TableHeaderProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(TableHeader);
|
export default TableHeader;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const TableRow: React.FC<
|
|||||||
rowIndex,
|
rowIndex,
|
||||||
expandable,
|
expandable,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
expandedRowKeys,
|
expandedRowKeys = [],
|
||||||
rowKey,
|
rowKey,
|
||||||
childParentKey,
|
childParentKey,
|
||||||
columns,
|
columns,
|
||||||
@@ -41,7 +41,6 @@ const TableRow: React.FC<
|
|||||||
} = props;
|
} = props;
|
||||||
const tableContext: any = React.useContext<{
|
const tableContext: any = React.useContext<{
|
||||||
allChildren?: any[];
|
allChildren?: any[];
|
||||||
allSubChildren?: any[];
|
|
||||||
setDisableExpand?: (record: any) => boolean;
|
setDisableExpand?: (record: any) => boolean;
|
||||||
}>(TableContext);
|
}>(TableContext);
|
||||||
const { setChunkRequest } = useSetChunkRequest();
|
const { setChunkRequest } = useSetChunkRequest();
|
||||||
@@ -80,6 +79,12 @@ const TableRow: React.FC<
|
|||||||
return rowSelection?.selectedRowKeys?.includes(record[rowKey]);
|
return rowSelection?.selectedRowKeys?.includes(record[rowKey]);
|
||||||
}, [rowSelection?.selectedRowKeys, record, rowKey]);
|
}, [rowSelection?.selectedRowKeys, record, rowKey]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (expandedRowKeys?.length === 0) {
|
||||||
|
setCurrentExpand(false);
|
||||||
|
}
|
||||||
|
}, [expandedRowKeys.length]);
|
||||||
|
|
||||||
const renderChildrenData = () => {
|
const renderChildrenData = () => {
|
||||||
if (childrenData.length === 0) {
|
if (childrenData.length === 0) {
|
||||||
// return (
|
// return (
|
||||||
@@ -131,9 +136,6 @@ const TableRow: React.FC<
|
|||||||
}, [record, loadChildren]);
|
}, [record, loadChildren]);
|
||||||
|
|
||||||
const filterUpdateChildrenHandler = () => {
|
const filterUpdateChildrenHandler = () => {
|
||||||
if (!expandedRowKeys?.includes(record[rowKey])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const dataList = _.filter(tableContext.allChildren, (data: any) => {
|
const dataList = _.filter(tableContext.allChildren, (data: any) => {
|
||||||
return _.get(data, [childParentKey]) === _.get(record, [rowKey]);
|
return _.get(data, [childParentKey]) === _.get(record, [rowKey]);
|
||||||
});
|
});
|
||||||
@@ -269,7 +271,7 @@ const TableRow: React.FC<
|
|||||||
})}
|
})}
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
{expanded && (
|
{expanded && !disableExpand && (
|
||||||
<div className="expanded-row">
|
<div className="expanded-row">
|
||||||
<Spin spinning={loading}>{renderChildrenData()}</Spin>
|
<Spin spinning={loading}>{renderChildrenData()}</Spin>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,4 +281,4 @@ const TableRow: React.FC<
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(TableRow);
|
export default TableRow;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const InfoColumn: React.FC<InfoColumnProps> = (props) => {
|
|||||||
<span className="flex" style={style}>
|
<span className="flex" style={style}>
|
||||||
{fieldList.map((item, index) => {
|
{fieldList.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<span key={item.key || index} className="flex-center">
|
||||||
<span className="flex-column flex-center">
|
<span className="flex-column flex-center">
|
||||||
<span>
|
<span>
|
||||||
{' '}
|
{' '}
|
||||||
@@ -46,7 +46,7 @@ const InfoColumn: React.FC<InfoColumnProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
></Divider>
|
></Divider>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</span>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
|
import { createStyles } from 'antd-style/lib/functions';
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ token, css }) => ({
|
||||||
|
errorIcon: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: ${token.colorErrorBgActive};
|
||||||
|
color: ${token.colorError};
|
||||||
|
`
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ErrorIcon: React.FC<{ size?: number }> = ({ size }) => {
|
||||||
|
const { styles } = useStyles();
|
||||||
|
return (
|
||||||
|
<div className={styles.errorIcon}>
|
||||||
|
<CloseOutlined style={{ fontSize: size || 30 }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ErrorIcon;
|
||||||
@@ -29,6 +29,7 @@ type StatusTagProps = {
|
|||||||
download?: {
|
download?: {
|
||||||
percent: number;
|
percent: number;
|
||||||
};
|
};
|
||||||
|
maxTooltipWidth?: number;
|
||||||
extra?: React.ReactNode;
|
extra?: React.ReactNode;
|
||||||
actions?: {
|
actions?: {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -44,6 +45,7 @@ const StatusTag: React.FC<StatusTagProps> = ({
|
|||||||
download,
|
download,
|
||||||
extra,
|
extra,
|
||||||
actions = [],
|
actions = [],
|
||||||
|
maxTooltipWidth = 250,
|
||||||
type = 'tag'
|
type = 'tag'
|
||||||
}) => {
|
}) => {
|
||||||
const { text, status } = statusValue;
|
const { text, status } = statusValue;
|
||||||
@@ -56,16 +58,21 @@ const StatusTag: React.FC<StatusTagProps> = ({
|
|||||||
return StatusColorMap[status];
|
return StatusColorMap[status];
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
const statusMessage = useMemo(() => {
|
const hasLink = useMemo(() => {
|
||||||
return statusValue.message?.replace(linkReg, '');
|
if (!statusValue.message) return false;
|
||||||
|
return linkReg.test(statusValue.message || '');
|
||||||
}, [statusValue.message]);
|
}, [statusValue.message]);
|
||||||
|
|
||||||
const messageLink = useMemo(() => {
|
const statusMessage = useMemo<string>(() => {
|
||||||
|
if (!statusValue.message) return '';
|
||||||
const link = statusValue.message?.match(linkReg);
|
const link = statusValue.message?.match(linkReg);
|
||||||
if (link) {
|
if (link) {
|
||||||
return link?.[0].replace(linkReg, '<a $1 target="_blank">$2</a>');
|
return statusValue.message?.replace(
|
||||||
|
linkReg,
|
||||||
|
'<a $1 target="_blank">$2</a>'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return statusValue.message;
|
||||||
}, [statusValue.message]);
|
}, [statusValue.message]);
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
@@ -118,17 +125,17 @@ const StatusTag: React.FC<StatusTagProps> = ({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: 'max-content',
|
width: 'max-content',
|
||||||
maxWidth: 250,
|
maxWidth: maxTooltipWidth,
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
wordBreak: 'break-word'
|
wordBreak: 'break-word'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{statusMessage}
|
{hasLink ? (
|
||||||
{statusMessage && <span className="m-r-5"></span>}
|
<span dangerouslySetInnerHTML={{ __html: statusMessage }}></span>
|
||||||
{extra}
|
) : (
|
||||||
{messageLink && (
|
statusMessage
|
||||||
<span dangerouslySetInnerHTML={{ __html: messageLink }}></span>
|
|
||||||
)}
|
)}
|
||||||
|
{extra && <span className="m-l-5">{extra}</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,9 +15,13 @@ const useStyles = createStyles(({ token, css }) => ({
|
|||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${token.colorTextTertiary};
|
color: ${token.colorTextTertiary};
|
||||||
|
.anticon {
|
||||||
|
color: ${token.colorTextTertiary};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.anticon {
|
.anticon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
color: ${token.colorText};
|
||||||
}
|
}
|
||||||
.icon-auto {
|
.icon-auto {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { UploadOutlined } from '@ant-design/icons';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Tooltip, Upload } from 'antd';
|
import { Button, Tooltip, Upload, message } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { convertFileSize } from '../../utils';
|
||||||
|
|
||||||
interface UploadAudioProps {
|
interface UploadAudioProps {
|
||||||
accept?: string;
|
accept?: string;
|
||||||
@@ -10,22 +11,39 @@ interface UploadAudioProps {
|
|||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
size?: 'small' | 'middle' | 'large';
|
size?: 'small' | 'middle' | 'large';
|
||||||
shape?: 'circle' | 'round' | 'default';
|
shape?: 'circle' | 'round' | 'default';
|
||||||
|
maxFileSize?: number; // in bytes
|
||||||
onChange?: (data: { file: any; fileList: any[] }) => void;
|
onChange?: (data: { file: any; fileList: any[] }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UploadAudio: React.FC<UploadAudioProps> = (props) => {
|
const UploadAudio: React.FC<UploadAudioProps> = (props) => {
|
||||||
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
const { icon, accept, type, size = 'large', shape = 'circle' } = props;
|
const { icon, accept, type, size = 'large', shape = 'circle' } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const beforeUpload = (file: any) => {
|
const beforeUpload = (file: any) => {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnChange = React.useCallback(
|
const isFileSizeValid = (file: File) => {
|
||||||
(data: { file: any; fileList: any }) => {
|
if (props.maxFileSize && file.size > props.maxFileSize) {
|
||||||
|
messageApi.open({
|
||||||
|
type: 'warning',
|
||||||
|
content: intl.formatMessage(
|
||||||
|
{ id: 'playground.uploadfile.sizeError' },
|
||||||
|
{ size: `${convertFileSize(props.maxFileSize)}` } // Convert bytes to MB
|
||||||
|
)
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnChange = (data: { file: any; fileList: any }) => {
|
||||||
|
if (!isFileSizeValid(data.file)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
props.onChange?.(data);
|
props.onChange?.(data);
|
||||||
},
|
};
|
||||||
[]
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
overlayInnerStyle={{ maxWidth: 290, width: 'max-content' }}
|
overlayInnerStyle={{ maxWidth: 290, width: 'max-content' }}
|
||||||
@@ -59,6 +77,7 @@ const UploadAudio: React.FC<UploadAudioProps> = (props) => {
|
|||||||
></Button>
|
></Button>
|
||||||
</div>
|
</div>
|
||||||
</Upload>
|
</Upload>
|
||||||
|
{contextHolder}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+3
-1
@@ -20,6 +20,8 @@ declare namespace Global {
|
|||||||
full_name: string;
|
full_name: string;
|
||||||
require_password_change: boolean;
|
require_password_change: boolean;
|
||||||
id: number;
|
id: number;
|
||||||
|
source: string;
|
||||||
|
avatar_url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BaseListItem<T> {
|
interface BaseListItem<T> {
|
||||||
@@ -38,7 +40,7 @@ declare namespace Global {
|
|||||||
interface HintOptions {
|
interface HintOptions {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
opts?: Array<BaseOption<string>>;
|
opts?: Array<BaseOption<string | number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchParams = Pagination & { search?: string };
|
type SearchParams = Pagination & { search?: string };
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ const KeybindingsMap = {
|
|||||||
NEW3: ['Ctrl+3', 'Meta+3'],
|
NEW3: ['Ctrl+3', 'Meta+3'],
|
||||||
NEW4: ['Ctrl+4', 'Meta+4'],
|
NEW4: ['Ctrl+4', 'Meta+4'],
|
||||||
FOCUS: ['/', '/'],
|
FOCUS: ['/', '/'],
|
||||||
ADD: ['Alt+Ctrl+Enter', 'Alt+Meta+Enter']
|
ADD: ['Alt+Ctrl+Enter', 'Alt+Meta+Enter'],
|
||||||
|
ESC: ['Esc', 'Esc']
|
||||||
};
|
};
|
||||||
|
|
||||||
type KeyBindingType = keyof typeof KeybindingsMap;
|
type KeyBindingType = keyof typeof KeybindingsMap;
|
||||||
|
|||||||
@@ -24,13 +24,15 @@ export default {
|
|||||||
},
|
},
|
||||||
Tabs: {
|
Tabs: {
|
||||||
titleFontSizeLG: 14
|
titleFontSizeLG: 14
|
||||||
// cardBg: '#1D1E20'
|
},
|
||||||
|
DatePicker: {
|
||||||
|
fontSizeLG: 14
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
iconMarginInlineEnd: 12,
|
iconMarginInlineEnd: 12,
|
||||||
itemBorderRadius: 4,
|
itemBorderRadius: 4,
|
||||||
itemHeight: 44,
|
itemHeight: 32,
|
||||||
itemSelectedColor: '#007BFF',
|
itemSelectedColor: '#007BFF',
|
||||||
darkItemSelectedBg: '#141414',
|
darkItemSelectedBg: '#141414',
|
||||||
darkItemHoverBg: 'rgba(255, 255, 255, 0.03)',
|
darkItemHoverBg: 'rgba(255, 255, 255, 0.03)',
|
||||||
@@ -69,6 +71,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
|
darkMode: true,
|
||||||
fontFamily:
|
fontFamily:
|
||||||
"Helvetica Neue, -apple-system, BlinkMacSystemFont, Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
|
"Helvetica Neue, -apple-system, BlinkMacSystemFont, Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
|
||||||
colorText: '#ccc',
|
colorText: '#ccc',
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ export const colorPrimary = '#007BFF';
|
|||||||
export default {
|
export default {
|
||||||
light,
|
light,
|
||||||
dark,
|
dark,
|
||||||
colorPrimary: '#007BFF'
|
colorPrimary: colorPrimary
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,12 +25,15 @@ export default {
|
|||||||
Tabs: {
|
Tabs: {
|
||||||
titleFontSizeLG: 14
|
titleFontSizeLG: 14
|
||||||
},
|
},
|
||||||
|
DatePicker: {
|
||||||
|
fontSizeLG: 14
|
||||||
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
iconMarginInlineEnd: 12,
|
iconMarginInlineEnd: 12,
|
||||||
itemBorderRadius: 4,
|
itemBorderRadius: 4,
|
||||||
itemSelectedColor: '#007BFF',
|
itemSelectedColor: '#007BFF',
|
||||||
itemHeight: 44,
|
itemHeight: 32,
|
||||||
groupTitleColor: 'rgba(0,0,0,1)',
|
groupTitleColor: 'rgba(0,0,0,1)',
|
||||||
itemHoverColor: 'rgba(0,0,0,1)',
|
itemHoverColor: 'rgba(0,0,0,1)',
|
||||||
itemColor: 'rgba(0,0,0,1)',
|
itemColor: 'rgba(0,0,0,1)',
|
||||||
@@ -66,6 +69,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
|
darkMode: false,
|
||||||
fontFamily:
|
fontFamily:
|
||||||
"Helvetica Neue, -apple-system, BlinkMacSystemFont, Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
|
"Helvetica Neue, -apple-system, BlinkMacSystemFont, Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
|
||||||
colorText: 'rgba(0,0,0,1)',
|
colorText: 'rgba(0,0,0,1)',
|
||||||
|
|||||||
+40
-13
@@ -72,7 +72,7 @@ html {
|
|||||||
--color-green-fill-light: rgb(243 251 248);
|
--color-green-fill-light: rgb(243 251 248);
|
||||||
--ant-rate-star-color: #fadb14;
|
--ant-rate-star-color: #fadb14;
|
||||||
--color-fill-spin-bg: rgba(255, 255, 255, 15%);
|
--color-fill-spin-bg: rgba(255, 255, 255, 15%);
|
||||||
--width-tooltip-max: 300px;
|
--width-tooltip-max: 420px;
|
||||||
--color-bg-tooltip: '#fff';
|
--color-bg-tooltip: '#fff';
|
||||||
--color-modal-content-bg: rgba(255, 255, 255, 90%);
|
--color-modal-content-bg: rgba(255, 255, 255, 90%);
|
||||||
--color-modal-box-shadow: 0 4px 16px rgba(0, 0, 0, 10%);
|
--color-modal-box-shadow: 0 4px 16px rgba(0, 0, 0, 10%);
|
||||||
@@ -84,6 +84,7 @@ html {
|
|||||||
--ant-color-border: #d9d9d9;
|
--ant-color-border: #d9d9d9;
|
||||||
--ant-line-type: solid;
|
--ant-line-type: solid;
|
||||||
--ant-line-width: 1px;
|
--ant-line-width: 1px;
|
||||||
|
--color-esc-hint-bg: rgba(0, 0, 0, 75%);
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme='realDark'] {
|
html[data-theme='realDark'] {
|
||||||
@@ -99,6 +100,7 @@ html[data-theme='realDark'] {
|
|||||||
--color-modal-content-bg: #1f1f1f;
|
--color-modal-content-bg: #1f1f1f;
|
||||||
--color-modal-box-shadow: none;
|
--color-modal-box-shadow: none;
|
||||||
--color-spotlight-bg: #3e3e3e;
|
--color-spotlight-bg: #3e3e3e;
|
||||||
|
--color-esc-hint-bg: rgba(150, 150, 150, 75%);
|
||||||
|
|
||||||
background: #141414;
|
background: #141414;
|
||||||
|
|
||||||
@@ -250,7 +252,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider .ant-layout-sider-children {
|
.ant-pro-sider .ant-layout-sider-children {
|
||||||
background-color: var(--color-fill-sider);
|
// background-color: var(--color-fill-sider);
|
||||||
|
border-right: 1px solid var(--ant-color-split);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,6 +278,12 @@ body {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-pro-base-menu-vertical-item-title,
|
||||||
|
.ant-pro-base-menu-vertical-item-title-collapsed {
|
||||||
|
height: var(--ant-menu-item-height) !important;
|
||||||
|
line-height: var(--ant-menu-item-height) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-pro-layout {
|
.ant-pro-layout {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
||||||
@@ -284,6 +293,10 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-pro-sider-actions-list-collapsed {
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-pro-page-container-children-container {
|
.ant-pro-page-container-children-container {
|
||||||
padding-block: var(--layout-content-blockpadding);
|
padding-block: var(--layout-content-blockpadding);
|
||||||
padding-inline: var(--layout-content-inlinepadding);
|
padding-inline: var(--layout-content-inlinepadding);
|
||||||
@@ -311,31 +324,37 @@ body {
|
|||||||
|
|
||||||
.ant-menu {
|
.ant-menu {
|
||||||
.ant-menu-item {
|
.ant-menu-item {
|
||||||
color: var(--ant-color-text) !important;
|
// color: var(--ant-color-text) !important;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--ant-menu-item-selected-bg);
|
// background-color: var(--ant-menu-item-selected-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-item:not(.ant-menu-item-selected) {
|
.ant-menu-item:not(.ant-menu-item-selected) {
|
||||||
color: var(--ant-color-text);
|
// color: var(--ant-color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-item.ant-menu-item-selected {
|
.ant-menu-item.ant-menu-item-selected {
|
||||||
color: var(--ant-color-primary) !important;
|
// color: var(--ant-color-primary) !important;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-menu-item-group-title.hide-submenu + .ant-menu-item-group-list {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== menu style end ============
|
// ======== menu style end ============
|
||||||
|
|
||||||
.ant-menu-submenu-popup {
|
.ant-menu-submenu-popup {
|
||||||
.ant-menu-sub {
|
.ant-menu-sub {
|
||||||
.ant-menu-item-only-child {
|
.ant-menu-item-only-child {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
color: var(--ant-color-text);
|
// color: var(--ant-color-text);
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
font-size: var(--font-size-middle);
|
font-size: var(--font-size-middle);
|
||||||
@@ -366,7 +385,7 @@ body {
|
|||||||
|
|
||||||
.ant-menu {
|
.ant-menu {
|
||||||
.ant-menu-submenu-title {
|
.ant-menu-submenu-title {
|
||||||
color: var(--ant-color-text) !important;
|
// color: var(--ant-color-text) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +393,7 @@ body {
|
|||||||
.user-avatar.ant-menu-submenu {
|
.user-avatar.ant-menu-submenu {
|
||||||
.ant-menu-submenu-title {
|
.ant-menu-submenu-title {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
color: var(--ant-color-text) !important;
|
// color: var(--ant-color-text) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +402,7 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--ant-color-text) !important;
|
// color: var(--ant-color-text) !important;
|
||||||
padding-inline: unset !important;
|
padding-inline: unset !important;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-inline: 0 !important;
|
margin-inline: 0 !important;
|
||||||
@@ -410,7 +429,7 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--ant-color-text) !important;
|
// color: var(--ant-color-text) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,7 +570,6 @@ body {
|
|||||||
|
|
||||||
.ant-message-notice-content {
|
.ant-message-notice-content {
|
||||||
border-radius: var(--border-radius-base) !important;
|
border-radius: var(--border-radius-base) !important;
|
||||||
line-height: 2;
|
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
@@ -560,7 +578,7 @@ body {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
padding-top: 5px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,9 +764,18 @@ body {
|
|||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
|
|
||||||
|
&.scatter {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(48%, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.tooltip-x-name {
|
.tooltip-x-name {
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
color: var(--ant-color-text-tertiary);
|
color: var(--ant-color-text-tertiary);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-item {
|
.tooltip-item {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { convertFileSize } from '@/utils';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import qs from 'query-string';
|
import qs from 'query-string';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
@@ -42,13 +43,19 @@ const useSetChunkFetch = () => {
|
|||||||
const readTextEventStreamData = async (
|
const readTextEventStreamData = async (
|
||||||
response: Response,
|
response: Response,
|
||||||
callback: HandlerFunction,
|
callback: HandlerFunction,
|
||||||
delay = 200
|
delay = 100
|
||||||
) => {
|
) => {
|
||||||
class BufferManager {
|
class BufferManager {
|
||||||
private buffer: any[] = [];
|
private buffer: any[] = [];
|
||||||
private contentLength: number | null = null;
|
private contentLength: number | null = null;
|
||||||
private progress: number = 0;
|
private progress: number = 0;
|
||||||
private percent: number = 0;
|
private percent: number = 0;
|
||||||
|
private speedHistory: number[] = [];
|
||||||
|
private maxHistory = 5;
|
||||||
|
private lastTime: number = performance.now();
|
||||||
|
private lastBytes: number = 0;
|
||||||
|
private totalBytes: number = 0;
|
||||||
|
private avgSpeed: number = 0;
|
||||||
|
|
||||||
constructor(private options: { contentLength?: string | null }) {
|
constructor(private options: { contentLength?: string | null }) {
|
||||||
this.contentLength = options.contentLength
|
this.contentLength = options.contentLength
|
||||||
@@ -63,26 +70,51 @@ const useSetChunkFetch = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private logSpeed(speedBps: number) {
|
||||||
|
this.speedHistory.push(speedBps);
|
||||||
|
if (this.speedHistory.length > this.maxHistory) {
|
||||||
|
this.speedHistory.shift();
|
||||||
|
}
|
||||||
|
this.avgSpeed =
|
||||||
|
this.speedHistory.reduce((a, b) => a + b, 0) /
|
||||||
|
this.speedHistory.length;
|
||||||
|
console.log(`瞬时均值: ${convertFileSize(this.avgSpeed)}/s`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateSpeed(bytes: number) {
|
||||||
|
const now = performance.now();
|
||||||
|
const elapsed = (now - this.lastTime) / 1000;
|
||||||
|
if (elapsed > 0.3) {
|
||||||
|
const speed = (this.totalBytes + bytes - this.lastBytes) / elapsed;
|
||||||
|
this.logSpeed(speed);
|
||||||
|
this.lastTime = now;
|
||||||
|
this.lastBytes = this.totalBytes + bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public add(data: any) {
|
public add(data: any) {
|
||||||
this.buffer.push(data);
|
this.buffer.push(data);
|
||||||
this.updateProgress(data);
|
this.updateProgress(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public flush(done?: boolean) {
|
public async flush(done?: boolean) {
|
||||||
if (this.buffer.length > 0) {
|
if (this.buffer.length > 0) {
|
||||||
const currentBuffer = [...this.buffer];
|
while (this.buffer.length > 0) {
|
||||||
this.buffer = [];
|
const item = this.buffer.shift()!;
|
||||||
currentBuffer.forEach((item, i) => {
|
const isComplete = this.buffer.length === 0 && done;
|
||||||
const isComplete = i === currentBuffer.length - 1 && done;
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
callback(item, {
|
callback(item, {
|
||||||
isComplete: isComplete || this.percent === 100,
|
isComplete: isComplete || this.percent === 100,
|
||||||
percent: this.percent,
|
percent: this.percent,
|
||||||
progress: this.progress,
|
progress: this.progress,
|
||||||
contentLength: this.contentLength
|
contentLength: this.contentLength
|
||||||
});
|
});
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getBuffer() {
|
public getBuffer() {
|
||||||
return this.buffer;
|
return this.buffer;
|
||||||
@@ -98,8 +130,8 @@ const useSetChunkFetch = () => {
|
|||||||
contentLength: contentLength
|
contentLength: contentLength
|
||||||
});
|
});
|
||||||
|
|
||||||
const throttledCallback = throttle(() => {
|
const throttledCallback = throttle(async () => {
|
||||||
bufferManager.flush();
|
await bufferManager.flush();
|
||||||
}, delay);
|
}, delay);
|
||||||
|
|
||||||
let isReading = true;
|
let isReading = true;
|
||||||
@@ -107,12 +139,6 @@ const useSetChunkFetch = () => {
|
|||||||
while (isReading) {
|
while (isReading) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
|
|
||||||
if (done) {
|
|
||||||
isReading = false;
|
|
||||||
bufferManager.flush(done);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const chunk = decoder.decode(value, { stream: true });
|
const chunk = decoder.decode(value, { stream: true });
|
||||||
bufferManager.add(chunk);
|
bufferManager.add(chunk);
|
||||||
@@ -120,6 +146,14 @@ const useSetChunkFetch = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// handle error
|
// handle error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
isReading = false;
|
||||||
|
await bufferManager.flush(done);
|
||||||
|
throttledCallback.cancel();
|
||||||
|
reader.releaseLock();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { useRequest } from 'ahooks';
|
||||||
|
|
||||||
|
export default function useDeferredRequest<P extends any[], R>(
|
||||||
|
requestFn: (...args: P) => Promise<R>,
|
||||||
|
delay = 100
|
||||||
|
) {
|
||||||
|
return useRequest(
|
||||||
|
async (...args: P) => {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, delay);
|
||||||
|
});
|
||||||
|
return await requestFn(...args);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import { Modal, message, type ModalFuncProps } from 'antd';
|
|
||||||
import { useRef } from 'react';
|
|
||||||
|
|
||||||
export default function useDeleteModal() {
|
|
||||||
const intl = useIntl();
|
|
||||||
const modalRef = useRef<any>();
|
|
||||||
const showDeleteModal = (config: ModalFuncProps = {}) => {
|
|
||||||
modalRef.current = Modal.confirm({
|
|
||||||
...config,
|
|
||||||
okText: intl.formatMessage({
|
|
||||||
id: 'common.button.delete'
|
|
||||||
}),
|
|
||||||
onCancel: () => {
|
|
||||||
config.onCancel?.();
|
|
||||||
modalRef.current.destroy?.();
|
|
||||||
},
|
|
||||||
onOk: async () => {
|
|
||||||
await config.onOk?.();
|
|
||||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return { showDeleteModal };
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import HotKeys from '@/config/hotkeys';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { createStyles } from 'antd-style';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ css, token }) => ({
|
||||||
|
hintOverlay: css`
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: var(--color-esc-hint-bg);
|
||||||
|
color: ${token.colorTextLightSolid};
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 2000;
|
||||||
|
font-size: 14px;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: fadeInOut 2s ease-in-out;
|
||||||
|
@keyframes fadeInOut {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function useEscHint(options?: {
|
||||||
|
enabled?: boolean;
|
||||||
|
message?: string;
|
||||||
|
throttleDelay?: number;
|
||||||
|
}) {
|
||||||
|
const { enabled = true, message, throttleDelay = 3000 } = options || {};
|
||||||
|
const intl = useIntl();
|
||||||
|
const { styles } = useStyles();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const timeoutRef = useRef<any>(null);
|
||||||
|
const isHintActiveRef = useRef(false);
|
||||||
|
|
||||||
|
const showHintThrottled = useMemo(
|
||||||
|
() =>
|
||||||
|
throttle(
|
||||||
|
() => {
|
||||||
|
if (isHintActiveRef.current) return;
|
||||||
|
|
||||||
|
isHintActiveRef.current = true;
|
||||||
|
|
||||||
|
setVisible(true);
|
||||||
|
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
setVisible(false);
|
||||||
|
isHintActiveRef.current = false;
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
throttleDelay,
|
||||||
|
{
|
||||||
|
leading: true,
|
||||||
|
trailing: false
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[throttleDelay]
|
||||||
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
HotKeys.ESC,
|
||||||
|
() => {
|
||||||
|
if (!enabled) return;
|
||||||
|
showHintThrottled();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: enabled
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
showHintThrottled.cancel();
|
||||||
|
};
|
||||||
|
}, [showHintThrottled]);
|
||||||
|
|
||||||
|
const EscHint = visible ? (
|
||||||
|
<div className={styles.hintOverlay}>
|
||||||
|
{message || intl.formatMessage({ id: 'common.tips.escape.disable' })}
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return { EscHint };
|
||||||
|
}
|
||||||
@@ -6,8 +6,18 @@ import {
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import useUserSettings from './use-user-settings';
|
import useUserSettings from './use-user-settings';
|
||||||
|
|
||||||
|
type OverflowBehavior =
|
||||||
|
| 'hidden'
|
||||||
|
| 'scroll'
|
||||||
|
| 'visible'
|
||||||
|
| 'visible-hidden'
|
||||||
|
| 'visible-scroll';
|
||||||
export interface OverlayScrollerOptions {
|
export interface OverlayScrollerOptions {
|
||||||
oppositeTheme?: boolean;
|
oppositeTheme?: boolean;
|
||||||
|
overflow?: {
|
||||||
|
x?: OverflowBehavior;
|
||||||
|
y?: OverflowBehavior;
|
||||||
|
};
|
||||||
scrollbars?: {
|
scrollbars?: {
|
||||||
theme?: 'os-theme-light' | 'os-theme-dark';
|
theme?: 'os-theme-light' | 'os-theme-dark';
|
||||||
autoHide?: 'never' | 'scroll' | 'leave' | 'move';
|
autoHide?: 'never' | 'scroll' | 'leave' | 'move';
|
||||||
@@ -34,6 +44,8 @@ export const overlaySollerOptions: UseOverlayScrollbarsParams = {
|
|||||||
defer: true
|
defer: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RESETSCROLLDELAY = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param options.theme: if set theme, it will fix the theme
|
* @param options.theme: if set theme, it will fix the theme
|
||||||
@@ -46,7 +58,7 @@ export default function useOverlayScroller(data?: {
|
|||||||
}) {
|
}) {
|
||||||
const { userSettings } = useUserSettings();
|
const { userSettings } = useUserSettings();
|
||||||
const { options, events, defer = true } = data || {};
|
const { options, events, defer = true } = data || {};
|
||||||
const { scrollbars, oppositeTheme } = options || {};
|
const { scrollbars, overflow, oppositeTheme } = options || {};
|
||||||
const scrollEventElement = React.useRef<any>(null);
|
const scrollEventElement = React.useRef<any>(null);
|
||||||
const instanceRef = React.useRef<any>(null);
|
const instanceRef = React.useRef<any>(null);
|
||||||
const initialized = React.useRef(false);
|
const initialized = React.useRef(false);
|
||||||
@@ -59,7 +71,8 @@ export default function useOverlayScroller(data?: {
|
|||||||
debounce: 0
|
debounce: 0
|
||||||
},
|
},
|
||||||
overflow: {
|
overflow: {
|
||||||
x: 'hidden'
|
x: 'hidden',
|
||||||
|
...overflow
|
||||||
},
|
},
|
||||||
scrollbars: {
|
scrollbars: {
|
||||||
autoHide: 'scroll',
|
autoHide: 'scroll',
|
||||||
@@ -151,7 +164,7 @@ export default function useOverlayScroller(data?: {
|
|||||||
}
|
}
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
stopUpdatePosition.current = false;
|
stopUpdatePosition.current = false;
|
||||||
}, 1500);
|
}, RESETSCROLLDELAY);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// add wheel event
|
// add wheel event
|
||||||
|
|||||||
@@ -11,19 +11,17 @@ export function useCancelToken() {
|
|||||||
const { source } = axiso.CancelToken;
|
const { source } = axiso.CancelToken;
|
||||||
const requestToken = useRef<any>(null);
|
const requestToken = useRef<any>(null);
|
||||||
|
|
||||||
const updateCancelToken = () => {
|
|
||||||
if (requestToken.current) {
|
|
||||||
requestToken.current.cancel();
|
|
||||||
}
|
|
||||||
requestToken.current = source();
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelRequest = () => {
|
const cancelRequest = () => {
|
||||||
if (requestToken.current) {
|
if (requestToken.current) {
|
||||||
requestToken.current.cancel();
|
requestToken.current.cancel();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateCancelToken = () => {
|
||||||
|
cancelRequest();
|
||||||
|
requestToken.current = source();
|
||||||
|
};
|
||||||
|
|
||||||
const getCanceltToken = () => {
|
const getCanceltToken = () => {
|
||||||
return requestToken.current.token;
|
return requestToken.current.token;
|
||||||
};
|
};
|
||||||
@@ -38,3 +36,33 @@ export function useCancelToken() {
|
|||||||
|
|
||||||
return { updateCancelToken, cancelRequest, getCanceltToken, source };
|
return { updateCancelToken, cancelRequest, getCanceltToken, source };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useAbortController() {
|
||||||
|
const controller = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
|
const abortController = () => {
|
||||||
|
if (controller.current) {
|
||||||
|
controller.current.abort();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getController = () => {
|
||||||
|
if (!controller.current) {
|
||||||
|
controller.current = new AbortController();
|
||||||
|
}
|
||||||
|
return controller.current;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateController = () => {
|
||||||
|
abortController();
|
||||||
|
controller.current = new AbortController();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
abortController();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { getController, updateController, abortController, controller };
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,28 +7,42 @@ import _ from 'lodash';
|
|||||||
import qs from 'query-string';
|
import qs from 'query-string';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
export default function useTableFetch<ListItem>(options: {
|
type EventsType = 'CREATE' | 'UPDATE' | 'DELETE' | 'INSERT';
|
||||||
API?: string;
|
|
||||||
watch?: boolean;
|
type WatchConfig =
|
||||||
|
| { watch?: false | undefined; API?: string; polling?: boolean }
|
||||||
|
| { watch: true; API: string; polling?: false | undefined }
|
||||||
|
| { polling: true; watch: false | undefined; API?: string };
|
||||||
|
|
||||||
|
export default function useTableFetch<ListItem>(
|
||||||
|
options: {
|
||||||
fetchAPI: (params: any) => Promise<Global.PageResponse<ListItem>>;
|
fetchAPI: (params: any) => Promise<Global.PageResponse<ListItem>>;
|
||||||
deleteAPI?: (id: number, params?: any) => Promise<any>;
|
deleteAPI?: (id: number, params?: any) => Promise<any>;
|
||||||
contentForDelete?: string;
|
contentForDelete?: string;
|
||||||
defaultData?: any[];
|
defaultData?: any[];
|
||||||
}) {
|
events?: EventsType[];
|
||||||
|
} & WatchConfig
|
||||||
|
) {
|
||||||
const {
|
const {
|
||||||
fetchAPI,
|
fetchAPI,
|
||||||
deleteAPI,
|
deleteAPI,
|
||||||
contentForDelete,
|
contentForDelete,
|
||||||
API,
|
API,
|
||||||
|
polling = false,
|
||||||
watch,
|
watch,
|
||||||
defaultData = []
|
defaultData = [],
|
||||||
|
events = ['UPDATE', 'DELETE']
|
||||||
} = options;
|
} = options;
|
||||||
|
const pollingRef = useRef<any>(null);
|
||||||
const chunkRequedtRef = useRef<any>(null);
|
const chunkRequedtRef = useRef<any>(null);
|
||||||
const modalRef = useRef<any>(null);
|
const modalRef = useRef<any>(null);
|
||||||
const rowSelection = useTableRowSelection();
|
const rowSelection = useTableRowSelection();
|
||||||
const { sortOrder, setSortOrder } = useTableSort({
|
const { sortOrder, setSortOrder } = useTableSort({
|
||||||
defaultSortOrder: 'descend'
|
defaultSortOrder: 'descend'
|
||||||
});
|
});
|
||||||
|
const [extraStatus, setExtraStatus] = useState<Record<string, any>>({
|
||||||
|
firstLoad: true
|
||||||
|
});
|
||||||
|
|
||||||
const [dataSource, setDataSource] = useState<{
|
const [dataSource, setDataSource] = useState<{
|
||||||
dataList: ListItem[];
|
dataList: ListItem[];
|
||||||
@@ -51,7 +65,7 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
|
|
||||||
const { setChunkRequest } = useSetChunkRequest();
|
const { setChunkRequest } = useSetChunkRequest();
|
||||||
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
|
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
|
||||||
events: ['UPDATE', 'DELETE'],
|
events: events,
|
||||||
dataList: dataSource.dataList,
|
dataList: dataSource.dataList,
|
||||||
setDataList(list, opts?: any) {
|
setDataList(list, opts?: any) {
|
||||||
setDataSource((pre) => {
|
setDataSource((pre) => {
|
||||||
@@ -67,6 +81,8 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const debounceSetExtraStatus = _.debounce(setExtraStatus, 3000);
|
||||||
|
|
||||||
const updateHandler = (list: any) => {
|
const updateHandler = (list: any) => {
|
||||||
_.each(list, (data: any) => {
|
_.each(list, (data: any) => {
|
||||||
updateChunkedList(data);
|
updateChunkedList(data);
|
||||||
@@ -88,12 +104,14 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchData = async (params?: { query: any }) => {
|
const fetchData = async (params?: { query: any }, polling = false) => {
|
||||||
const { query } = params || {};
|
if (!polling) {
|
||||||
setDataSource((pre) => {
|
setDataSource((pre) => {
|
||||||
pre.loading = true;
|
pre.loading = true;
|
||||||
return { ...pre };
|
return { ...pre };
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
const { query } = params || {};
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
..._.pickBy(query || queryParams, (val: any) => !!val)
|
..._.pickBy(query || queryParams, (val: any) => !!val)
|
||||||
@@ -136,9 +154,31 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
total: dataSource.total,
|
total: dataSource.total,
|
||||||
totalPage: dataSource.totalPage
|
totalPage: dataSource.totalPage
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
debounceSetExtraStatus({
|
||||||
|
firstLoad: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchAPIWithPolling = async (params: any) => {
|
||||||
|
if (!polling || watch || !fetchAPI) return;
|
||||||
|
|
||||||
|
if (pollingRef.current) {
|
||||||
|
clearInterval(pollingRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch data with polling, 1s interval
|
||||||
|
pollingRef.current = setInterval(async () => {
|
||||||
|
fetchData(
|
||||||
|
{
|
||||||
|
query: params
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
const handleQueryChange = (params: any) => {
|
const handleQueryChange = (params: any) => {
|
||||||
setQueryParams({
|
setQueryParams({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
@@ -176,7 +216,7 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
row: ListItem & { name: string; id: number },
|
row: ListItem & { name: string; id: number },
|
||||||
options?: any
|
options?: any
|
||||||
) => {
|
) => {
|
||||||
modalRef.current.show({
|
modalRef.current?.show({
|
||||||
content: contentForDelete,
|
content: contentForDelete,
|
||||||
operation: 'common.delete.single.confirm',
|
operation: 'common.delete.single.confirm',
|
||||||
name: row.name,
|
name: row.name,
|
||||||
@@ -192,7 +232,7 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteBatch = (options = {}) => {
|
const handleDeleteBatch = (options = {}) => {
|
||||||
modalRef.current.show({
|
modalRef.current?.show({
|
||||||
content: contentForDelete,
|
content: contentForDelete,
|
||||||
operation: 'common.delete.confirm',
|
operation: 'common.delete.confirm',
|
||||||
selection: true,
|
selection: true,
|
||||||
@@ -216,6 +256,17 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dataSource.loadend) {
|
||||||
|
fetchAPIWithPolling(queryParams);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (pollingRef.current) {
|
||||||
|
clearInterval(pollingRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [dataSource.loadend, queryParams]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
await fetchData();
|
await fetchData();
|
||||||
@@ -236,6 +287,7 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
sortOrder,
|
sortOrder,
|
||||||
queryParams,
|
queryParams,
|
||||||
modalRef,
|
modalRef,
|
||||||
|
extraStatus,
|
||||||
setQueryParams,
|
setQueryParams,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
handleDeleteBatch,
|
handleDeleteBatch,
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default function useUserSettings() {
|
|||||||
const { light, dark, colorPrimary } = themeConfig;
|
const { light, dark, colorPrimary } = themeConfig;
|
||||||
const [userSettings, setUserSettings] = useAtom(userSettingsHelperAtom);
|
const [userSettings, setUserSettings] = useAtom(userSettingsHelperAtom);
|
||||||
|
|
||||||
|
console.log('userSettings===', userSettings);
|
||||||
|
|
||||||
const getCurrentTheme = (mode: Theme): 'light' | 'realDark' => {
|
const getCurrentTheme = (mode: Theme): 'light' | 'realDark' => {
|
||||||
if (mode === 'auto') {
|
if (mode === 'auto') {
|
||||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
@@ -46,22 +48,32 @@ export default function useUserSettings() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const initTheme = () => {
|
||||||
setHtmlThemeAttr(userSettings.theme);
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
}, [userSettings.theme]);
|
.matches
|
||||||
|
? 'realDark'
|
||||||
|
: 'light';
|
||||||
|
if (userSettings.mode === 'auto' && systemTheme !== userSettings.theme) {
|
||||||
|
setTheme('auto');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userSettings.mode !== 'auto') return;
|
initTheme();
|
||||||
|
}, [userSettings.theme, userSettings.mode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
const handleChange = () => {
|
const handleChange = () => {
|
||||||
|
// The theme follows the system preference, when the mode is set to 'auto'
|
||||||
|
if (userSettings.mode !== 'auto') return;
|
||||||
setTheme('auto');
|
setTheme('auto');
|
||||||
};
|
};
|
||||||
mediaQuery.addEventListener('change', handleChange);
|
mediaQuery.addEventListener('change', handleChange);
|
||||||
return () => {
|
return () => {
|
||||||
mediaQuery.removeEventListener('change', handleChange);
|
mediaQuery.removeEventListener('change', handleChange);
|
||||||
};
|
};
|
||||||
}, [userSettings.mode]);
|
}, [userSettings.mode, userSettings.theme]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userSettings,
|
userSettings,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const ErrorResult: React.FC<ErrorResultProps> = ({ extra }) => {
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
return (
|
return (
|
||||||
<Result
|
<Result
|
||||||
status="error"
|
status="warning"
|
||||||
title={
|
title={
|
||||||
isChunkLoadError(extra)
|
isChunkLoadError(extra)
|
||||||
? intl.formatMessage({ id: 'common.page.refresh.tips' })
|
? intl.formatMessage({ id: 'common.page.refresh.tips' })
|
||||||
|
|||||||
+117
-53
@@ -3,6 +3,7 @@
|
|||||||
import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache';
|
import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache';
|
||||||
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
|
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
|
||||||
import DarkMask from '@/components/dark-mask';
|
import DarkMask from '@/components/dark-mask';
|
||||||
|
import IconFont from '@/components/icon-font';
|
||||||
import ShortCuts, {
|
import ShortCuts, {
|
||||||
modalConfig as ShortCutsConfig
|
modalConfig as ShortCutsConfig
|
||||||
} from '@/components/short-cuts';
|
} from '@/components/short-cuts';
|
||||||
@@ -14,7 +15,6 @@ import useUserSettings from '@/hooks/use-user-settings';
|
|||||||
import { logout } from '@/pages/login/apis';
|
import { logout } from '@/pages/login/apis';
|
||||||
import { useAccessMarkedRoutes } from '@@/plugin-access';
|
import { useAccessMarkedRoutes } from '@@/plugin-access';
|
||||||
import { useModel } from '@@/plugin-model';
|
import { useModel } from '@@/plugin-model';
|
||||||
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
|
|
||||||
import { ProLayout } from '@ant-design/pro-components';
|
import { ProLayout } from '@ant-design/pro-components';
|
||||||
import {
|
import {
|
||||||
Link,
|
Link,
|
||||||
@@ -28,17 +28,18 @@ import {
|
|||||||
useNavigate,
|
useNavigate,
|
||||||
type IRoute
|
type IRoute
|
||||||
} from '@umijs/max';
|
} from '@umijs/max';
|
||||||
import { Button, ConfigProvider, Modal, theme } from 'antd';
|
import { Button, ConfigProvider, Modal, Tooltip, theme } from 'antd';
|
||||||
import 'driver.js/dist/driver.css';
|
import 'driver.js/dist/driver.css';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import Exception from './Exception';
|
import Exception from './Exception';
|
||||||
import './Layout.css';
|
import './Layout.css';
|
||||||
import { LogoIcon, SLogoIcon } from './Logo';
|
import { LogoIcon, SLogoIcon } from './Logo';
|
||||||
import ErrorBoundary from './error-boundary';
|
import ErrorBoundary from './error-boundary';
|
||||||
import { getRightRenderContent } from './rightRender';
|
import { getRightRenderContent } from './rightRender';
|
||||||
import { patchRoutes } from './runtime';
|
import { patchRoutes } from './runtime';
|
||||||
|
import SiderMenu from './sider-menu';
|
||||||
|
|
||||||
const loginPath = '/login';
|
const loginPath = '/login';
|
||||||
|
|
||||||
@@ -102,7 +103,8 @@ export default (props: any) => {
|
|||||||
defer: false
|
defer: false
|
||||||
});
|
});
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
const { themeData, setTheme, userSettings, isDarkTheme } = useUserSettings();
|
const { themeData, setTheme, setUserSettings, userSettings, isDarkTheme } =
|
||||||
|
useUserSettings();
|
||||||
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
||||||
const { initialize: initializeMenu } = useOverlayScroller();
|
const { initialize: initializeMenu } = useOverlayScroller();
|
||||||
const [userInfo] = useAtom(userAtom);
|
const [userInfo] = useAtom(userAtom);
|
||||||
@@ -113,8 +115,9 @@ export default (props: any) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { clientRoutes, pluginManager } = useAppData();
|
const { clientRoutes, pluginManager } = useAppData();
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
// const [collapsed, setCollapsed] = useState(userSettings.collapsed || false);
|
||||||
const [collapseValue, setCollapseValue] = useState(false);
|
const [collapseValue, setCollapseValue] = useState(false);
|
||||||
|
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const initialInfo = (useModel && useModel('@@initialState')) || {
|
const initialInfo = (useModel && useModel('@@initialState')) || {
|
||||||
initialState: undefined,
|
initialState: undefined,
|
||||||
@@ -187,7 +190,11 @@ export default (props: any) => {
|
|||||||
|
|
||||||
const handleToggleCollapse = (e: any) => {
|
const handleToggleCollapse = (e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setCollapsed(!collapsed);
|
// setCollapsed(!collapsed);
|
||||||
|
setUserSettings({
|
||||||
|
...userSettings,
|
||||||
|
collapsed: !userSettings.collapsed
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const newRoutes = filterRoutes(
|
const newRoutes = filterRoutes(
|
||||||
@@ -203,6 +210,8 @@ export default (props: any) => {
|
|||||||
const role = initialState?.currentUser?.is_admin ? 'admin' : 'user';
|
const role = initialState?.currentUser?.is_admin ? 'admin' : 'user';
|
||||||
const [route] = useAccessMarkedRoutes(mapRoutes(newRoutes, role));
|
const [route] = useAccessMarkedRoutes(mapRoutes(newRoutes, role));
|
||||||
|
|
||||||
|
console.log('route============', route);
|
||||||
|
|
||||||
patchRoutes({
|
patchRoutes({
|
||||||
routes: route.children,
|
routes: route.children,
|
||||||
initialState: initialInfo.initialState
|
initialState: initialInfo.initialState
|
||||||
@@ -213,6 +222,24 @@ export default (props: any) => {
|
|||||||
[location.pathname]
|
[location.pathname]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const allRouteKeys = useMemo(() => {
|
||||||
|
const keys = new Set<string>();
|
||||||
|
const childrenRoutes = route?.children || [];
|
||||||
|
const traverseRoutes = (routes) => {
|
||||||
|
routes.forEach((r) => {
|
||||||
|
if (r.path) {
|
||||||
|
keys.add(r.path);
|
||||||
|
}
|
||||||
|
if (r.children) {
|
||||||
|
traverseRoutes(r.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
traverseRoutes(childrenRoutes);
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}, [route?.children]);
|
||||||
|
|
||||||
const showUpgrade = useMemo(() => {
|
const showUpgrade = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
initialState?.currentUser?.is_admin &&
|
initialState?.currentUser?.is_admin &&
|
||||||
@@ -256,22 +283,29 @@ export default (props: any) => {
|
|||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}, [initializeMenu, matchedRoute, location]);
|
}, [initializeMenu, matchedRoute, location]);
|
||||||
|
|
||||||
const renderMenuHeader = useCallback(
|
const collapsed = useMemo(() => {
|
||||||
(logo, title) => {
|
return userSettings.collapsed || false;
|
||||||
|
}, [userSettings.collapsed]);
|
||||||
|
|
||||||
|
const renderMenuHeader = (logo, title) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{logo}
|
{logo}
|
||||||
<div className="collapse-wrap" onClick={handleToggleCollapse}>
|
<div className="collapse-wrap" onClick={handleToggleCollapse}>
|
||||||
<Button
|
<Button
|
||||||
style={{ marginRight: collapsed ? 0 : -14 }}
|
style={{ marginRight: collapsed ? 0 : -14, border: 'none' }}
|
||||||
size="small"
|
size="small"
|
||||||
type={collapsed ? 'default' : 'text'}
|
type={collapsed ? 'default' : 'text'}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<MenuUnfoldOutlined
|
<IconFont
|
||||||
|
type="icon-expand-left"
|
||||||
|
className="font-size-18 text-secondary"
|
||||||
style={{ display: collapsed ? 'block' : 'none' }}
|
style={{ display: collapsed ? 'block' : 'none' }}
|
||||||
/>
|
/>
|
||||||
<MenuFoldOutlined
|
<IconFont
|
||||||
|
type="icon-expand-right"
|
||||||
|
className="font-size-18 text-secondary"
|
||||||
style={{ display: !collapsed ? 'block' : 'none' }}
|
style={{ display: !collapsed ? 'block' : 'none' }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -279,12 +313,25 @@ export default (props: any) => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
[collapsed]
|
|
||||||
);
|
|
||||||
|
|
||||||
const actionRender = useCallback(
|
const handleToggleGroup = (menuItemProps, e) => {
|
||||||
(layoutProps) => {
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (collapseKeys.has(menuItemProps.key)) {
|
||||||
|
collapseKeys.delete(menuItemProps.key);
|
||||||
|
} else {
|
||||||
|
collapseKeys.add(menuItemProps.key);
|
||||||
|
}
|
||||||
|
setCollapseKeys(new Set(collapseKeys));
|
||||||
|
};
|
||||||
|
|
||||||
|
const menuContentRender = (menuProps, defaultDom) => {
|
||||||
|
return <SiderMenu {...menuProps}></SiderMenu>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionRender = (layoutProps) => {
|
||||||
|
console.log('actionRender', layoutProps);
|
||||||
const dom = getRightRenderContent({
|
const dom = getRightRenderContent({
|
||||||
runtimeConfig,
|
runtimeConfig,
|
||||||
loading,
|
loading,
|
||||||
@@ -298,55 +345,70 @@ export default (props: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return dom;
|
return dom;
|
||||||
},
|
};
|
||||||
[intl, showUpgrade, userSettings.theme, userSettings.isDarkTheme]
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemRender = useCallback((route, _, routes) => {
|
const menuItemRender = (menuItemProps, defaultDom) => {
|
||||||
const { breadcrumbName, title, path } = route;
|
console.log('defaultdom==========', menuItemProps, defaultDom);
|
||||||
const label = title || breadcrumbName;
|
|
||||||
const last = routes[routes.length - 1];
|
|
||||||
if (last) {
|
|
||||||
if (last.path === path || last.linkPath === path) {
|
|
||||||
return <span>{label}</span>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <Link to={path}>{label}</Link>;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const menuItemRender = useCallback(
|
|
||||||
(menuItemProps, defaultDom) => {
|
|
||||||
if (menuItemProps.isUrl || menuItemProps.children) {
|
if (menuItemProps.isUrl || menuItemProps.children) {
|
||||||
return defaultDom;
|
return defaultDom;
|
||||||
}
|
}
|
||||||
if (menuItemProps.path && location.pathname !== menuItemProps.path) {
|
if (menuItemProps.path && location.pathname !== menuItemProps.path) {
|
||||||
return (
|
return (
|
||||||
|
<Tooltip
|
||||||
|
title={collapsed ? menuItemProps.name : false}
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
<Link
|
<Link
|
||||||
to={menuItemProps.path.replace('/*', '')}
|
to={menuItemProps.path.replace('/*', '')}
|
||||||
target={menuItemProps.target}
|
target={menuItemProps.target}
|
||||||
>
|
>
|
||||||
{defaultDom}
|
{defaultDom}
|
||||||
</Link>
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <>{defaultDom}</>;
|
return (
|
||||||
},
|
<Tooltip title={collapsed ? menuItemProps.name : false} placement="right">
|
||||||
[location.pathname]
|
{defaultDom}
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const onPageChange = useCallback(
|
const menuDataRender = (menuData) => {
|
||||||
(route) => {
|
const currentItem = menuData.find((s) => location.pathname === s.path);
|
||||||
|
const result = menuData.map((item) => {
|
||||||
|
const newItem = { ...item };
|
||||||
|
|
||||||
|
const selected =
|
||||||
|
location.pathname === newItem.path ||
|
||||||
|
location.pathname.indexOf(newItem.path) > -1;
|
||||||
|
|
||||||
|
if (newItem.icon) {
|
||||||
|
newItem.icon = selected ? (
|
||||||
|
<IconFont type={newItem.selectedIcon} />
|
||||||
|
) : (
|
||||||
|
<IconFont type={newItem.defaultIcon} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (newItem.children) {
|
||||||
|
newItem.children = menuDataRender(newItem.children);
|
||||||
|
}
|
||||||
|
return newItem;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPageChange = (route) => {
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
|
console.log('onPageChange', pathname, route);
|
||||||
|
|
||||||
initRouteCacheValue(pathname);
|
initRouteCacheValue(pathname);
|
||||||
dropRouteCache(pathname);
|
dropRouteCache(pathname);
|
||||||
|
|
||||||
// if user is not change password, redirect to change password page
|
// if user is not change password, redirect to change password page
|
||||||
if (
|
if (location.pathname !== loginPath && userInfo?.require_password_change) {
|
||||||
location.pathname !== loginPath &&
|
|
||||||
userInfo?.require_password_change
|
|
||||||
) {
|
|
||||||
history.push(loginPath);
|
history.push(loginPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -360,18 +422,20 @@ export default (props: any) => {
|
|||||||
: '/playground';
|
: '/playground';
|
||||||
history.push(pathname);
|
history.push(pathname);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
[userInfo?.require_password_change, initialState?.currentUser]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onMenuHeaderClick = useCallback((e) => {
|
const onMenuHeaderClick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
const onCollapse = (value) => {
|
const onCollapse = (value) => {
|
||||||
setCollapsed(value);
|
// setCollapsed(value);
|
||||||
|
setUserSettings({
|
||||||
|
...userSettings,
|
||||||
|
collapsed: value
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -417,7 +481,6 @@ export default (props: any) => {
|
|||||||
: theme.defaultAlgorithm,
|
: theme.defaultAlgorithm,
|
||||||
...themeData
|
...themeData
|
||||||
};
|
};
|
||||||
console.log('currentTheme====', data);
|
|
||||||
return data;
|
return data;
|
||||||
}, [userSettings.isDarkTheme, themeData]);
|
}, [userSettings.isDarkTheme, themeData]);
|
||||||
|
|
||||||
@@ -452,15 +515,16 @@ export default (props: any) => {
|
|||||||
onCollapse={onCollapse}
|
onCollapse={onCollapse}
|
||||||
onMenuHeaderClick={onMenuHeaderClick}
|
onMenuHeaderClick={onMenuHeaderClick}
|
||||||
menuHeaderRender={renderMenuHeader}
|
menuHeaderRender={renderMenuHeader}
|
||||||
collapsed={collapsed}
|
collapsed={userSettings.collapsed}
|
||||||
onPageChange={onPageChange}
|
onPageChange={onPageChange}
|
||||||
formatMessage={formatMessage}
|
formatMessage={formatMessage}
|
||||||
menu={{
|
menu={{
|
||||||
locale: true
|
locale: true,
|
||||||
|
type: 'group'
|
||||||
}}
|
}}
|
||||||
|
splitMenus={true}
|
||||||
logo={collapsed ? SLogoIcon : LogoIcon}
|
logo={collapsed ? SLogoIcon : LogoIcon}
|
||||||
menuItemRender={menuItemRender}
|
menuContentRender={menuContentRender}
|
||||||
itemRender={itemRender}
|
|
||||||
disableContentMargin
|
disableContentMargin
|
||||||
fixSiderbar
|
fixSiderbar
|
||||||
fixedHeader
|
fixedHeader
|
||||||
|
|||||||
+41
-37
@@ -6,12 +6,11 @@ import langConfigMap from '@/locales/lang-config-map';
|
|||||||
import {
|
import {
|
||||||
DiscordOutlined,
|
DiscordOutlined,
|
||||||
GithubOutlined,
|
GithubOutlined,
|
||||||
GlobalOutlined,
|
|
||||||
HomeOutlined,
|
HomeOutlined,
|
||||||
InfoCircleOutlined,
|
InfoCircleOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
MoonOutlined,
|
MoonOutlined,
|
||||||
QuestionCircleOutlined,
|
MoreOutlined,
|
||||||
ReadOutlined,
|
ReadOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
SunOutlined
|
SunOutlined
|
||||||
@@ -21,23 +20,14 @@ import { Avatar, Menu, Spin } from 'antd';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const themeConfig = [
|
const getMenuStyle = (
|
||||||
{
|
collapsed: boolean,
|
||||||
key: 'realDark',
|
siderWidth: number,
|
||||||
label: 'common.appearance.dark',
|
extraStyle: React.CSSProperties = {}
|
||||||
icon: <MoonOutlined />
|
) => ({
|
||||||
},
|
width: collapsed ? 40 : `calc(${siderWidth}px - 16px)`,
|
||||||
{
|
...extraStyle
|
||||||
key: 'light',
|
});
|
||||||
label: 'common.appearance.light',
|
|
||||||
icon: <SunOutlined />
|
|
||||||
}
|
|
||||||
// {
|
|
||||||
// key: 'auto',
|
|
||||||
// label: 'common.appearance.system',
|
|
||||||
// icon: <IconFont type="icon-theme-auto" />
|
|
||||||
// }
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getRightRenderContent = (opts: {
|
export const getRightRenderContent = (opts: {
|
||||||
runtimeConfig: any;
|
runtimeConfig: any;
|
||||||
@@ -140,7 +130,7 @@ export const getRightRenderContent = (opts: {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'help',
|
key: 'help',
|
||||||
icon: <QuestionCircleOutlined />,
|
icon: <IconFont type="icon-help" />,
|
||||||
label: (
|
label: (
|
||||||
<span className="sub-title ">
|
<span className="sub-title ">
|
||||||
<span className="flex-center">
|
<span className="flex-center">
|
||||||
@@ -213,7 +203,7 @@ export const getRightRenderContent = (opts: {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'lang',
|
key: 'lang',
|
||||||
icon: <GlobalOutlined />,
|
icon: <IconFont type="icon-language" />,
|
||||||
label: (
|
label: (
|
||||||
<span className="sub-title">
|
<span className="sub-title">
|
||||||
{intl?.formatMessage?.({ id: 'common.settings.language' })}
|
{intl?.formatMessage?.({ id: 'common.settings.language' })}
|
||||||
@@ -265,7 +255,7 @@ export const getRightRenderContent = (opts: {
|
|||||||
? 'user-menu-container user-menu-collapsed'
|
? 'user-menu-container user-menu-collapsed'
|
||||||
: 'user-menu-container',
|
: 'user-menu-container',
|
||||||
mode: 'vertical',
|
mode: 'vertical',
|
||||||
expandIcon: false,
|
expandIcon: collapsed ? false : <MoreOutlined />,
|
||||||
// inlineCollapsed: collapsed,
|
// inlineCollapsed: collapsed,
|
||||||
triggerSubMenuAction: 'hover',
|
triggerSubMenuAction: 'hover',
|
||||||
items: [
|
items: [
|
||||||
@@ -274,9 +264,10 @@ export const getRightRenderContent = (opts: {
|
|||||||
key: 'user',
|
key: 'user',
|
||||||
className: 'user-avatar',
|
className: 'user-avatar',
|
||||||
icon: (
|
icon: (
|
||||||
<span>
|
<span className="avatar-container">
|
||||||
<Avatar
|
<Avatar
|
||||||
size={28}
|
size={28}
|
||||||
|
src={opts.initialState?.currentUser?.avatar_url}
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--ant-color-text)',
|
color: 'var(--ant-color-text)',
|
||||||
fontSize: 16
|
fontSize: 16
|
||||||
@@ -287,7 +278,13 @@ export const getRightRenderContent = (opts: {
|
|||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<span
|
<span
|
||||||
className="m-l-8 font-size-14"
|
className="m-l-8 font-size-14"
|
||||||
style={{ fontWeight: 'var(--font-weight-normal)' }}
|
style={{
|
||||||
|
fontWeight: 'var(--font-weight-normal)',
|
||||||
|
maxWidth: 100,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{opts.initialState?.currentUser?.username}
|
{opts.initialState?.currentUser?.username}
|
||||||
</span>
|
</span>
|
||||||
@@ -295,6 +292,20 @@ export const getRightRenderContent = (opts: {
|
|||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
key: 'apikeys',
|
||||||
|
label: (
|
||||||
|
<span className="flex flex-center">
|
||||||
|
<IconFont type="icon-key" />
|
||||||
|
<span className="m-l-8" style={{ marginLeft: 8 }}>
|
||||||
|
{intl?.formatMessage?.({ id: 'menu.apikeys' })}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
onClick: () => {
|
||||||
|
history.push('/api-keys');
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'settings',
|
key: 'settings',
|
||||||
label: (
|
label: (
|
||||||
@@ -328,23 +339,16 @@ export const getRightRenderContent = (opts: {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMenuStyle = (
|
|
||||||
collapsed: boolean,
|
|
||||||
siderWidth: number,
|
|
||||||
extraStyle: React.CSSProperties = {}
|
|
||||||
) => ({
|
|
||||||
width: collapsed ? 40 : `calc(${siderWidth}px - 16px)`,
|
|
||||||
...extraStyle
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Menu {...helpMenu} style={getMenuStyle(collapsed, siderWidth)} />
|
<Menu {...helpMenu} style={getMenuStyle(collapsed, siderWidth)} />
|
||||||
<Menu {...langMenu} style={getMenuStyle(collapsed, siderWidth)} />
|
|
||||||
<Menu
|
<Menu
|
||||||
{...userMenu}
|
{...userMenu}
|
||||||
style={getMenuStyle(collapsed, siderWidth, { marginTop: 20 })}
|
style={getMenuStyle(collapsed, siderWidth, {
|
||||||
|
marginTop: 8,
|
||||||
|
marginBottom: 0
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import { CaretDownOutlined } from '@ant-design/icons';
|
||||||
|
import { Link, useLocation } from '@umijs/max';
|
||||||
|
import { Divider, Tooltip } from 'antd';
|
||||||
|
import { createStyles } from 'antd-style';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
icon?: string;
|
||||||
|
selectedIcon?: string;
|
||||||
|
defaultIcon?: string;
|
||||||
|
children?: MenuItem[];
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SiderMenuProps {
|
||||||
|
menuData: MenuItem[];
|
||||||
|
collapsed?: boolean;
|
||||||
|
initialState: Global.InitialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ css, token }) => {
|
||||||
|
console.log('useStyles', token);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const { Menu } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
siderMenu: css`
|
||||||
|
&.sider-menu-collapsed {
|
||||||
|
.menu-item {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
groupTitle: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: var(--ant-padding-xs) var(--ant-padding);
|
||||||
|
font-size: 12px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 30px;
|
||||||
|
&:hover {
|
||||||
|
.group-title-text {
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.anticon {
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
.group-title-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ant-color-text-tertiary);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.menu-item-group-title-collapsed {
|
||||||
|
height: 1px;
|
||||||
|
padding-block: 0;
|
||||||
|
padding-inline: 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
menuItemContent: css`
|
||||||
|
margin: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
`,
|
||||||
|
menuItemWrapper: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
padding-inline: calc(var(--ant-font-size) * 2) var(--ant-padding);
|
||||||
|
padding-left: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
height: ${Menu.itemHeight}px;
|
||||||
|
line-height: ${Menu.itemHeight}px;
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
&:hover {
|
||||||
|
background-color: ${Menu.itemHoverBg};
|
||||||
|
color: ${Menu.itemHoverColor};
|
||||||
|
}
|
||||||
|
&.menu-item-selected {
|
||||||
|
background-color: ${Menu.itemSelectedBg};
|
||||||
|
color: ${Menu.itemSelectedColor};
|
||||||
|
.anticon {
|
||||||
|
color: ${Menu.itemSelectedColor};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: ${Menu.itemActiveBg};
|
||||||
|
color: ${Menu.itemActiveColor};
|
||||||
|
}
|
||||||
|
.anticon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.icon-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
menuItemGroup: css`
|
||||||
|
&.menu-item-group-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const SiderMenu: React.FC<SiderMenuProps> = (props) => {
|
||||||
|
const { menuData, collapsed, initialState } = props;
|
||||||
|
const is_admin = initialState?.currentUser?.is_admin || false;
|
||||||
|
const { styles, cx } = useStyles();
|
||||||
|
const location = useLocation();
|
||||||
|
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
|
||||||
|
console.log('SiderMenu', props);
|
||||||
|
|
||||||
|
const dividerStyles = useMemo(() => {
|
||||||
|
if (collapsed) {
|
||||||
|
return {
|
||||||
|
margin: '6px 0'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
margin: '6px 16px',
|
||||||
|
width: 'unset',
|
||||||
|
minWidth: 'unset',
|
||||||
|
maxWidth: 'unset'
|
||||||
|
};
|
||||||
|
}, [collapsed]);
|
||||||
|
|
||||||
|
const handleToggleGroup = (e: any, menuGroup: any) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
console.log('handleToggleGroup', menuGroup.key);
|
||||||
|
|
||||||
|
if (collapseKeys.has(menuGroup.key)) {
|
||||||
|
collapseKeys.delete(menuGroup.key);
|
||||||
|
} else {
|
||||||
|
collapseKeys.add(menuGroup.key);
|
||||||
|
}
|
||||||
|
setCollapseKeys(new Set(collapseKeys));
|
||||||
|
};
|
||||||
|
|
||||||
|
const menuItemRender = (menuItem: MenuItem, key: string) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(styles.menuItemContent, 'menu-item-content')}
|
||||||
|
key={key}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={menuItem.path.replace('/*', '')}
|
||||||
|
target={menuItem.target}
|
||||||
|
className={cx(styles.menuItemWrapper, 'menu-item', {
|
||||||
|
'menu-item-selected': location.pathname === menuItem.path
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{collapsed ? (
|
||||||
|
<Tooltip title={menuItem.name} placement="right">
|
||||||
|
<span className="icon-wrapper">
|
||||||
|
<IconFont
|
||||||
|
type={
|
||||||
|
location.pathname === menuItem.path
|
||||||
|
? menuItem.selectedIcon || ''
|
||||||
|
: menuItem.defaultIcon || ''
|
||||||
|
}
|
||||||
|
></IconFont>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<IconFont
|
||||||
|
type={
|
||||||
|
location.pathname === menuItem.path
|
||||||
|
? menuItem.selectedIcon || ''
|
||||||
|
: menuItem.defaultIcon || ''
|
||||||
|
}
|
||||||
|
></IconFont>
|
||||||
|
<span>{menuItem.name}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(styles.siderMenu, 'sider-menu', {
|
||||||
|
'sider-menu-collapsed': collapsed
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{menuData.map((item: MenuItem, index: number) => (
|
||||||
|
<div key={item.key}>
|
||||||
|
{item.children && item.children.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={cx(styles.groupTitle, {
|
||||||
|
'menu-item-group-title-collapsed': collapsed
|
||||||
|
})}
|
||||||
|
onClick={(e) => handleToggleGroup(e, item)}
|
||||||
|
>
|
||||||
|
{!collapsed ? (
|
||||||
|
<span className="group-title-text">
|
||||||
|
<span>{item.name}</span>
|
||||||
|
<CaretDownOutlined
|
||||||
|
rotate={collapseKeys.has(item.key) ? -90 : 0}
|
||||||
|
></CaretDownOutlined>
|
||||||
|
</span>
|
||||||
|
) : is_admin ? (
|
||||||
|
<Divider style={dividerStyles} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cx(styles.menuItemGroup, {
|
||||||
|
'menu-item-group-collapsed': collapsed,
|
||||||
|
'menu-item-group-hidden':
|
||||||
|
!collapsed && collapseKeys.has(item.key)
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{item.children?.map((child: MenuItem) =>
|
||||||
|
menuItemRender(child, child.key)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
menuItemRender(item, item.key)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SiderMenu;
|
||||||
@@ -5,7 +5,7 @@ export default {
|
|||||||
'common.button.editmode': 'Edit mode',
|
'common.button.editmode': 'Edit mode',
|
||||||
'common.button.shortcut': 'Keyboard Shortcut',
|
'common.button.shortcut': 'Keyboard Shortcut',
|
||||||
'common.button.add': 'Add',
|
'common.button.add': 'Add',
|
||||||
'common.button.login': 'Log In',
|
'common.button.login': 'Log in',
|
||||||
'common.button.select': 'Select',
|
'common.button.select': 'Select',
|
||||||
'common.button.selected': 'Selected',
|
'common.button.selected': 'Selected',
|
||||||
'common.button.continue': 'Continue',
|
'common.button.continue': 'Continue',
|
||||||
@@ -97,7 +97,7 @@ export default {
|
|||||||
'common.form.field.input.required': 'required',
|
'common.form.field.input.required': 'required',
|
||||||
'common.form.field.select.required': 'required',
|
'common.form.field.select.required': 'required',
|
||||||
'common.select.option': 'All',
|
'common.select.option': 'All',
|
||||||
'common.checbox.all': 'All',
|
'common.checbox.all': 'Select all',
|
||||||
'common.select.all': 'All {type}',
|
'common.select.all': 'All {type}',
|
||||||
'common.data.unkonwn': 'Unknown',
|
'common.data.unkonwn': 'Unknown',
|
||||||
'common.data.none': 'No Data',
|
'common.data.none': 'No Data',
|
||||||
@@ -105,7 +105,7 @@ export default {
|
|||||||
'common.button.addfile': 'Add File',
|
'common.button.addfile': 'Add File',
|
||||||
'common.logout.msg': 'Log out succeeded',
|
'common.logout.msg': 'Log out succeeded',
|
||||||
'common.button.logout': 'Log out',
|
'common.button.logout': 'Log out',
|
||||||
'common.form.rule.select': 'please select {name}',
|
'common.form.rule.select': 'Please select a {name}',
|
||||||
'common.form.rule.input': '{name} is required',
|
'common.form.rule.input': '{name} is required',
|
||||||
'common.form.key': 'the same key exists',
|
'common.form.key': 'the same key exists',
|
||||||
'common.date.utc': 'UTC time',
|
'common.date.utc': 'UTC time',
|
||||||
@@ -194,13 +194,13 @@ export default {
|
|||||||
'common.delete.confirm':
|
'common.delete.confirm':
|
||||||
'Are you sure you want to delete the selected {type}?',
|
'Are you sure you want to delete the selected {type}?',
|
||||||
'common.delete.single.confirm':
|
'common.delete.single.confirm':
|
||||||
'Are you sure you want to delete <span style="font-size: 13px;font-weight: 700">{name}</span>?',
|
'Are you sure you want to delete? \n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
||||||
'common.stop.confirm': 'Are you sure you want to stop the selected {type}?',
|
'common.stop.confirm': 'Are you sure you want to stop the selected {type}?',
|
||||||
'common.stop.single.confirm':
|
'common.stop.single.confirm':
|
||||||
'Are you sure you want to stop <span style="font-size: 13px;font-weight: 700">{name}</span>?',
|
'Are you sure you want to stop? \n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
||||||
'common.start.confirm': 'Are you sure you want to start the selected {type}?',
|
'common.start.confirm': 'Are you sure you want to start the selected {type}?',
|
||||||
'common.start.single.confirm':
|
'common.start.single.confirm':
|
||||||
'Are you sure you want to start <span style="font-size: 13px;font-weight: 700">{name}</span>?',
|
'Are you sure you want to start? \n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
||||||
'common.filter.name': 'Filter by name',
|
'common.filter.name': 'Filter by name',
|
||||||
'common.form.password': 'Password',
|
'common.form.password': 'Password',
|
||||||
'common.form.username': 'Username',
|
'common.form.username': 'Username',
|
||||||
@@ -210,7 +210,7 @@ export default {
|
|||||||
'common.button.feedback': 'Feedback',
|
'common.button.feedback': 'Feedback',
|
||||||
'common.button.docs': 'Documentation',
|
'common.button.docs': 'Documentation',
|
||||||
'common.button.version': 'Version',
|
'common.button.version': 'Version',
|
||||||
'common.title.delete.confirm': 'Confirm delete',
|
'common.title.delete.confirm': 'Confirm deletion',
|
||||||
'common.title.stop.confirm': 'Confirm stop',
|
'common.title.stop.confirm': 'Confirm stop',
|
||||||
'common.title.start.confirm': 'Confirm start',
|
'common.title.start.confirm': 'Confirm start',
|
||||||
'common.title.recreate.confirm': 'Confirm recreate',
|
'common.title.recreate.confirm': 'Confirm recreate',
|
||||||
@@ -247,5 +247,15 @@ export default {
|
|||||||
'common.appearance.theme': 'Theme',
|
'common.appearance.theme': 'Theme',
|
||||||
'common.page.wentwrong': 'Something went wrong.',
|
'common.page.wentwrong': 'Something went wrong.',
|
||||||
'common.page.refresh.tips':
|
'common.page.refresh.tips':
|
||||||
'Oops! Something went wrong. Try refreshing the page.'
|
'Oops! Something went wrong. Try refreshing the page.',
|
||||||
|
'common.tips.escape.disable':
|
||||||
|
'Click Cancel or the X at the top right to close.',
|
||||||
|
'common.button.clearSelection': 'Clear Selection',
|
||||||
|
'common.select.count': '{count} selected',
|
||||||
|
'common.login.auth': 'Authenticating...',
|
||||||
|
'common.login.auth.failed': 'Authentication failed',
|
||||||
|
'common.login.password': 'Log in with Password',
|
||||||
|
'common.external.login': 'Log in with {type}',
|
||||||
|
'common.sso.noConfig':
|
||||||
|
'Single sign-on is not enabled on this system. Please contact your administrator.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,5 +21,14 @@ export default {
|
|||||||
'dashboard.activeModels': 'Active Models',
|
'dashboard.activeModels': 'Active Models',
|
||||||
'dashboard.runninginstances': 'Running Instances',
|
'dashboard.runninginstances': 'Running Instances',
|
||||||
'dashboard.activeModels.name': 'Model Name',
|
'dashboard.activeModels.name': 'Model Name',
|
||||||
'dashboard.allocatevram': 'Allocated VRAM / RAM'
|
'dashboard.allocatevram': 'Allocated VRAM / RAM',
|
||||||
|
'dashboard.usage.selectuser': 'Select users',
|
||||||
|
'dashboard.usage.selectmodel': 'Select models',
|
||||||
|
'dashboard.usage.export': 'Export Data',
|
||||||
|
'dashboard.usage.export.user': 'User',
|
||||||
|
'dashboard.usage.export.model': 'Model',
|
||||||
|
'dashboard.usage.export.date': 'Date',
|
||||||
|
'dashboard.usage.datePicker.last7days': 'Last 7 Days',
|
||||||
|
'dashboard.usage.datePicker.last30days': 'Last 30 Days',
|
||||||
|
'dashboard.usage.datePicker.last60days': 'Last 60 Days'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,10 +11,17 @@ export default {
|
|||||||
'menu.models.modelList': 'Deploy & Manage',
|
'menu.models.modelList': 'Deploy & Manage',
|
||||||
'menu.models.modelCatalog': 'Catalog',
|
'menu.models.modelCatalog': 'Catalog',
|
||||||
'menu.models.catalog': 'Model Catalog',
|
'menu.models.catalog': 'Model Catalog',
|
||||||
|
'menu.models.deployment': 'Deployments',
|
||||||
'menu.modelCatalog': 'Catalog',
|
'menu.modelCatalog': 'Catalog',
|
||||||
'menu.resources': 'Resources',
|
'menu.resources': 'Resources',
|
||||||
'menu.apikeys': 'API Keys',
|
'menu.apikeys': 'API Keys',
|
||||||
'menu.users': 'Users',
|
'menu.users': 'Users',
|
||||||
|
'menu.resources.workers': 'Workers',
|
||||||
|
'menu.resources.gpus': 'GPUs',
|
||||||
|
'menu.resources.modelfiles': 'Model Files',
|
||||||
|
'menu.accessControl': 'Access Control',
|
||||||
|
'menu.accessControl.apikeys': 'API Keys',
|
||||||
|
'menu.accessControl.users': 'Users',
|
||||||
'menu.profile': 'Profile',
|
'menu.profile': 'Profile',
|
||||||
'menu.login': 'Login',
|
'menu.login': 'Login',
|
||||||
'menu.usage': 'Usage',
|
'menu.usage': 'Usage',
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default {
|
|||||||
'models.form.backend.llamabox':
|
'models.form.backend.llamabox':
|
||||||
'For GGUF format models, supports Linux, macOS, and Windows.',
|
'For GGUF format models, supports Linux, macOS, and Windows.',
|
||||||
'models.form.backend.vllm':
|
'models.form.backend.vllm':
|
||||||
'For non-GGUF format models, supported only on Linux (amd64/x86_64).',
|
'For non-GGUF format models, supported only on Linux.',
|
||||||
'models.form.backend.voxbox':
|
'models.form.backend.voxbox':
|
||||||
'For non-GGUF format audio models, supported only on NVIDIA GPUs and CPUs.',
|
'For non-GGUF format audio models, supported only on NVIDIA GPUs and CPUs.',
|
||||||
'models.form.backend.mindie':
|
'models.form.backend.mindie':
|
||||||
@@ -133,9 +133,11 @@ export default {
|
|||||||
'models.form.check.params': 'Checking configuration...',
|
'models.form.check.params': 'Checking configuration...',
|
||||||
'models.form.check.passed': 'Compatibility Check Passed',
|
'models.form.check.passed': 'Compatibility Check Passed',
|
||||||
'models.form.check.claims':
|
'models.form.check.claims':
|
||||||
'The model requires approximately {vram} VRAM and {ram} RAM.',
|
'The model will consume approximately {vram} VRAM and {ram} RAM.',
|
||||||
'models.form.check.claims2': 'The model requires approximately {vram} VRAM.',
|
'models.form.check.claims2':
|
||||||
'models.form.check.claims3': 'The model requires approximately {ram} RAM.',
|
'The model will consume approximately {vram} VRAM.',
|
||||||
|
'models.form.check.claims3':
|
||||||
|
'The model will consume approximately {ram} RAM.',
|
||||||
'models.form.update.tips':
|
'models.form.update.tips':
|
||||||
'Changes will only apply after you delete and recreate the instance.',
|
'Changes will only apply after you delete and recreate the instance.',
|
||||||
'models.table.download.progress': 'Download Progress',
|
'models.table.download.progress': 'Download Progress',
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default {
|
|||||||
'playground.params.temperature.tips':
|
'playground.params.temperature.tips':
|
||||||
'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.',
|
'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.',
|
||||||
'playground.params.maxtokens.tips':
|
'playground.params.maxtokens.tips':
|
||||||
"The maximum number of tokens to generated. The total length of input tokens and generated tokens is limited by the model's context length.",
|
"The maximum number of tokens to generate. The total length of input tokens and generated tokens is limited by the model's context length.",
|
||||||
'playground.params.topp.tips':
|
'playground.params.topp.tips':
|
||||||
'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered.',
|
'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered.',
|
||||||
'playground.params.seed.tips':
|
'playground.params.seed.tips':
|
||||||
@@ -90,7 +90,7 @@ export default {
|
|||||||
'playground.audio.stoprecord': 'Stop Recording',
|
'playground.audio.stoprecord': 'Stop Recording',
|
||||||
'playground.audio.generating.tips': 'Generated text will appear here.',
|
'playground.audio.generating.tips': 'Generated text will appear here.',
|
||||||
'playground.audio.uploadfile.tips':
|
'playground.audio.uploadfile.tips':
|
||||||
'Please upload an audio file, supported formats: {formats}',
|
'Upload an audio file, supported formats: {formats}',
|
||||||
'playground.input.multiplePaste': 'Batch Input Mode',
|
'playground.input.multiplePaste': 'Batch Input Mode',
|
||||||
'playground.input.multiplePaste.tips':
|
'playground.input.multiplePaste.tips':
|
||||||
'When enabled, pasted multi-line text will be automatically split by newline into separate entries in the form.',
|
'When enabled, pasted multi-line text will be automatically split by newline into separate entries in the form.',
|
||||||
@@ -151,5 +151,10 @@ export default {
|
|||||||
'playground.image.negativeMask.tips':
|
'playground.image.negativeMask.tips':
|
||||||
'1. After selection, no further masking can be drawn; therefore, you should draw the mask first and then check the option.\n 2. Once a mask image is uploaded, no further masks can be generated.',
|
'1. After selection, no further masking can be drawn; therefore, you should draw the mask first and then check the option.\n 2. Once a mask image is uploaded, no further masks can be generated.',
|
||||||
'playground.model.noavailable.tips2':
|
'playground.model.noavailable.tips2':
|
||||||
'If the expected model isn’t showing up, make sure it’s running and correctly categorized. If the category is incorrect, you can manually adjust it in the model’s settings.'
|
'If the expected model isn’t showing up, make sure it’s running and correctly categorized. If the category is incorrect, you can manually adjust it in the model’s settings.',
|
||||||
|
'playground.rerank.query.validate': 'The query is required.',
|
||||||
|
'playground.image.generate.error':
|
||||||
|
'Something went wrong. The image could not be generated.',
|
||||||
|
'playground.uploadfile.sizeError':
|
||||||
|
'File size exceeds the limit. Maximum allowed: {size}.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -70,7 +70,26 @@ export default {
|
|||||||
'Waiting for the download to complete...',
|
'Waiting for the download to complete...',
|
||||||
'resources.filter.worker': 'Filter by worker',
|
'resources.filter.worker': 'Filter by worker',
|
||||||
'resources.filter.source': 'Filter by Source',
|
'resources.filter.source': 'Filter by Source',
|
||||||
'resources.modelfiles.delete.tips': 'Also delete the file from disk!',
|
'resources.modelfiles.delete.tips': 'Also delete the file from disk',
|
||||||
'resources.modelfiles.copy.tips': 'Copy Full Path',
|
'resources.modelfiles.copy.tips': 'Copy Full Path',
|
||||||
'resources.filter.path': 'Filter by path'
|
'resources.filter.path': 'Filter by path',
|
||||||
|
'resources.register.worker.step1':
|
||||||
|
'Click the <span class="bold-text">Copy Token</span> menu in the application.',
|
||||||
|
'resources.register.worker.step2':
|
||||||
|
'Click the <span class="bold-text">Quick Config</span> menu in the application.',
|
||||||
|
'resources.register.worker.step3':
|
||||||
|
'Click the <span class="bold-text">General</span> tab.',
|
||||||
|
'resources.register.worker.step4':
|
||||||
|
'Select <span class="bold-text">Worker</span> as the service role.',
|
||||||
|
'resources.register.worker.step5':
|
||||||
|
'Enter the <span class="bold-text">Server URL</span>: {url}.',
|
||||||
|
'resources.register.worker.step6':
|
||||||
|
'Paste the <span class="bold-text">Token</span>.',
|
||||||
|
'resources.register.worker.step7':
|
||||||
|
'Click <span class="bold-text">Restart</span> to apply the settings.',
|
||||||
|
'resources.register.install.title': 'Install GPUStack on {os}',
|
||||||
|
'resources.register.download':
|
||||||
|
'Download and install the <a href={url} target="_blank">installer</a>. Only supported: {versions}.',
|
||||||
|
'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+',
|
||||||
|
'resource.register.windows.support': 'win 10, win 11'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export default {
|
|||||||
'users.table.username': 'User Name',
|
'users.table.username': 'User Name',
|
||||||
'users.table.role': 'Role',
|
'users.table.role': 'Role',
|
||||||
'users.form.fullname': 'Full Name',
|
'users.form.fullname': 'Full Name',
|
||||||
|
'users.form.source': 'Source',
|
||||||
'users.table.user': 'users',
|
'users.table.user': 'users',
|
||||||
'users.form.admin': 'Admin',
|
'users.form.admin': 'Admin',
|
||||||
'users.form.user': 'User',
|
'users.form.user': 'User',
|
||||||
@@ -27,5 +28,6 @@ export default {
|
|||||||
'users.password.confirm.error': 'The two passwords entered do not match.',
|
'users.password.confirm.error': 'The two passwords entered do not match.',
|
||||||
'users.login.title': 'Log in to',
|
'users.login.title': 'Log in to',
|
||||||
'users.version.islatest': 'GPUStack {version} is the latest version',
|
'users.version.islatest': 'GPUStack {version} is the latest version',
|
||||||
'users.version.update': 'GPUStack {version} is available'
|
'users.version.update': 'GPUStack {version} is available',
|
||||||
|
'users.settings.title': 'User Settings'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -247,7 +247,17 @@ export default {
|
|||||||
'common.appearance.theme': 'Theme',
|
'common.appearance.theme': 'Theme',
|
||||||
'common.page.wentwrong': 'Something went wrong.',
|
'common.page.wentwrong': 'Something went wrong.',
|
||||||
'common.page.refresh.tips':
|
'common.page.refresh.tips':
|
||||||
'Oops! Something went wrong. Try refreshing the page.'
|
'Oops! Something went wrong. Try refreshing the page.',
|
||||||
|
'common.tips.escape.disable':
|
||||||
|
'Click Cancel or the X at the top right to close.',
|
||||||
|
'common.button.clearSelection': 'Clear Selection',
|
||||||
|
'common.select.count': '{count} selected',
|
||||||
|
'common.login.auth': 'Authenticating...',
|
||||||
|
'common.login.auth.failed': 'Authentication failed',
|
||||||
|
'common.login.password': 'Log in with Password',
|
||||||
|
'common.external.login': 'Log in with {type}',
|
||||||
|
'common.sso.noConfig':
|
||||||
|
'Single sign-on is not enabled on this system. Please contact your administrator.'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -263,4 +273,12 @@ export default {
|
|||||||
// 10. 'common.appearance.theme': 'Theme',
|
// 10. 'common.appearance.theme': 'Theme',
|
||||||
// 11. 'common.page.wentwrong': 'Something went wrong.',
|
// 11. 'common.page.wentwrong': 'Something went wrong.',
|
||||||
// 12. 'common.page.refresh.tips': 'Oops! Something went wrong. Try refreshing the page.'
|
// 12. 'common.page.refresh.tips': 'Oops! Something went wrong. Try refreshing the page.'
|
||||||
|
// 13. 'common.tips.escape.disable': 'Click Cancel or the X at the top right to close.'
|
||||||
|
// 14. 'common.button.clearSelection': 'Clear Selection',
|
||||||
|
// 15. 'common.select.count': '{count} selected',
|
||||||
|
// 16. 'common.login.auth': 'Authenticating...',
|
||||||
|
// 17. 'common.login.auth.failed': 'Authentication failed',
|
||||||
|
// 18. 'common.external.login': 'Log in with {type}'
|
||||||
|
// 19. 'common.login.password': 'Log in with Password',
|
||||||
|
// 20. 'common.sso.noConfig': 'Single sign-on is not enabled on this system. Please contact your administrator.'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -21,5 +21,26 @@ export default {
|
|||||||
'dashboard.activeModels': 'アクティブなモデル',
|
'dashboard.activeModels': 'アクティブなモデル',
|
||||||
'dashboard.runninginstances': '稼働中のインスタンス',
|
'dashboard.runninginstances': '稼働中のインスタンス',
|
||||||
'dashboard.activeModels.name': 'モデル名',
|
'dashboard.activeModels.name': 'モデル名',
|
||||||
'dashboard.allocatevram': '割り当て済みVRAM / メモリ'
|
'dashboard.allocatevram': '割り当て済みVRAM / メモリ',
|
||||||
|
'dashboard.usage.selectuser': 'Select users',
|
||||||
|
'dashboard.usage.selectmodel': 'Select models',
|
||||||
|
'dashboard.usage.export': 'Export Data',
|
||||||
|
'dashboard.usage.export.user': 'User',
|
||||||
|
'dashboard.usage.export.model': 'Model',
|
||||||
|
'dashboard.usage.export.date': 'Date',
|
||||||
|
'dashboard.usage.datePicker.last7days': 'Last 7 Days',
|
||||||
|
'dashboard.usage.datePicker.last30days': 'Last 30 Days',
|
||||||
|
'dashboard.usage.datePicker.last60days': 'Last 60 Days'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'dashboard.usage.selectuser': 'Select users',
|
||||||
|
// 2. 'dashboard.usage.selectmodel': 'Select models',
|
||||||
|
// 3. 'dashboard.usage.export': 'Export Data',
|
||||||
|
// 4. 'dashboard.usage.export.user': 'User',
|
||||||
|
// 5. 'dashboard.usage.export.model': 'Model',
|
||||||
|
// 6. 'dashboard.usage.export.date': 'Date',
|
||||||
|
// 7. 'dashboard.usage.datePicker.last7days': 'Last 7 Days',
|
||||||
|
// 8.'dashboard.usage.datePicker.last30days': 'Last 30 Days',
|
||||||
|
// 9. 'dashboard.usage.datePicker.last60days': 'Last 60 Days'
|
||||||
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default {
|
|||||||
'menu.models.modelList': 'デプロイと管理',
|
'menu.models.modelList': 'デプロイと管理',
|
||||||
'menu.models.modelCatalog': 'カタログ',
|
'menu.models.modelCatalog': 'カタログ',
|
||||||
'menu.models.catalog': 'モデルカタログ',
|
'menu.models.catalog': 'モデルカタログ',
|
||||||
|
'menu.models.deployment': 'Deployment',
|
||||||
'menu.modelCatalog': 'カタログ',
|
'menu.modelCatalog': 'カタログ',
|
||||||
'menu.resources': 'リソース',
|
'menu.resources': 'リソース',
|
||||||
'menu.apikeys': 'APIキー',
|
'menu.apikeys': 'APIキー',
|
||||||
@@ -18,5 +19,21 @@ export default {
|
|||||||
'menu.profile': 'プロフィール',
|
'menu.profile': 'プロフィール',
|
||||||
'menu.login': 'ログイン',
|
'menu.login': 'ログイン',
|
||||||
'menu.usage': '使用状況',
|
'menu.usage': '使用状況',
|
||||||
'menu.404': '404'
|
'menu.404': '404',
|
||||||
|
'menu.resources.workers': 'Workers',
|
||||||
|
'menu.resources.gpus': 'GPUs',
|
||||||
|
'menu.resources.modelfiles': 'Model Files',
|
||||||
|
'menu.accessControl': 'Access Control',
|
||||||
|
'menu.accessControl.apikeys': 'API Keys',
|
||||||
|
'menu.accessControl.users': 'Users'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'menu.models.deployment': 'Deployment',
|
||||||
|
// 2. 'menu.resources.workers': 'Workers',
|
||||||
|
// 3. 'menu.resources.gpus': 'GPUs',
|
||||||
|
// 4. 'menu.resources.modelfiles': 'Model Files',
|
||||||
|
// 5. 'menu.accessControl': 'Access Control',
|
||||||
|
// 6. 'menu.accessControl.apikeys': 'API Keys',
|
||||||
|
// 7. 'menu.accessControl.users': 'Users',
|
||||||
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user