Compare commits

..
8 Commits
Author SHA1 Message Date
jialin 3247793d11 Revert "fix: normalize embedding data"
This reverts commit 2d46809b78.
2025-06-06 21:12:57 +08:00
jialin 2d46809b78 fix: normalize embedding data 2025-06-06 18:32:25 +08:00
jialin 57f4ddad26 chore: remove --port backend parameter 2025-06-05 11:29:54 +08:00
jialin de34d0ae00 fix: instances distribute info trigger by click 2025-06-05 11:24:09 +08:00
jialin 4f7037baa0 chore: reset changes on catalog 2025-06-03 18:10:39 +08:00
jialin bd15e9aa6c fix: tips for css files load failed 2025-06-03 16:28:03 +08:00
jialin 907105a492 fix: set default spec to form 2025-06-03 15:35:38 +08:00
gitlawr 0d3da04356 ci: publish v*-dev branches 2025-05-30 13:20:40 +08:00
239 changed files with 3900 additions and 8546 deletions
-1
View File
@@ -1,4 +1,3 @@
PORT=9000
UMI_DEV_SERVER_COMPRESS=none
DID_YOU_KNOW=none
+5 -3
View File
@@ -28,9 +28,6 @@ export default defineConfig({
logLevel: 'info',
defaultSizes: 'parsed' // stat // gzip
},
mfsu: {
exclude: ['lodash', 'ml-pca']
},
base: process.env.npm_config_base || '/',
...(isProduction
? {
@@ -53,6 +50,11 @@ export default defineConfig({
chunkFilename: `css/[name].${t}.chunk.css`
}
]);
config.module
.rule('worker')
.test(/\.worker\.js$/)
.use('worker-loader')
.loader('worker-loader');
config.output
.filename(`js/[name].${t}.js`)
.chunkFilename(`js/[name].${t}.chunk.js`);
+6
View File
@@ -20,6 +20,12 @@ export default function createProxyTable(target?: string) {
ws: true,
log: 'debug',
pathRewrite: (pth: string) => pth.replace(`/^/${api}`, `/${api}`),
// onProxyRes: (proxyRes: any, req: any, res: any) => {
// console.log('headers=========', {
// res: proxyRes.headers,
// req: req.headers
// });
// },
headers: {
origin: newTarget,
Connection: 'keep-alive'
+46 -124
View File
@@ -5,18 +5,13 @@ export default [
name: 'dashboard',
path: '/dashboard',
key: 'dashboard',
icon: 'icon-dashboard',
selectedIcon: 'icon-dashboard-filled',
defaultIcon: 'icon-dashboard',
icon: 'AppstoreOutlined',
access: 'canSeeAdmin',
component: './dashboard',
routes: []
component: './dashboard'
},
{
name: 'playground',
icon: 'icon-experiment',
selectedIcon: 'icon-experiment-filled',
defaultIcon: 'icon-experiment',
icon: 'ExperimentOutlined',
path: '/playground',
key: 'playground',
routes: [
@@ -29,39 +24,15 @@ export default [
title: 'Chat',
path: '/playground/chat',
key: 'chat',
icon: 'icon-chat',
selectedIcon: 'icon-chat-filled',
defaultIcon: 'icon-chat',
icon: 'Comment',
component: './playground/index'
},
{
name: 'embedding',
title: 'embedding',
path: '/playground/embedding',
key: 'embedding',
icon: 'icon-embedding',
selectedIcon: 'icon-embedding-filled',
defaultIcon: 'icon-embedding',
component: './playground/embedding'
},
{
name: 'rerank',
title: 'Rerank',
path: '/playground/rerank',
key: 'rerank',
icon: 'icon-reranker',
selectedIcon: 'icon-reranker-filled',
defaultIcon: 'icon-reranker',
component: './playground/rerank'
},
{
name: 'text2images',
title: 'Text2Images',
path: keepAliveRoutes.text2images,
key: 'text2images',
icon: 'icon-image1',
selectedIcon: 'icon-image-filled',
defaultIcon: 'icon-image1',
icon: 'Comment',
component: './playground/images'
},
{
@@ -69,115 +40,66 @@ export default [
title: 'Speech',
path: keepAliveRoutes.speech,
key: 'speech',
icon: 'icon-audio1',
selectedIcon: 'icon-audio-filled',
defaultIcon: 'icon-audio1',
icon: 'Comment',
component: './playground/speech'
},
{
name: 'embedding',
title: 'embedding',
path: '/playground/embedding',
key: 'embedding',
icon: 'Comment',
component: './playground/embedding'
},
{
name: 'rerank',
title: 'Rerank',
path: '/playground/rerank',
key: 'rerank',
icon: 'Comment',
component: './playground/rerank'
}
]
},
{
name: 'models',
path: '/models',
key: 'models',
name: 'modelCatalog',
path: '/models/catalog',
key: 'modelsCatalog',
icon: 'icon-catalog',
access: 'canSeeAdmin',
routes: [
{
path: '/models',
redirect: '/models/deployments'
},
{
name: 'modelCatalog',
path: '/models/catalog',
key: 'modelsCatalog',
icon: 'icon-layers',
selectedIcon: 'icon-layers-filled',
defaultIcon: 'icon-layers',
access: 'canSeeAdmin',
component: './llmodels/catalog'
},
{
name: 'deployment',
path: '/models/deployments',
key: 'modelDeployments',
icon: 'icon-rocket-launch1',
selectedIcon: 'icon-rocket-launch-fill',
defaultIcon: 'icon-rocket-launch1',
access: 'canSeeAdmin',
component: './llmodels/index'
}
]
component: './llmodels/catalog'
},
{
name: 'models',
path: '/models/list',
key: 'models',
icon: 'Block',
access: 'canSeeAdmin',
component: './llmodels/index'
},
{
name: 'resources',
path: '/resources',
key: 'resources',
icon: 'CloudServer',
access: 'canSeeAdmin',
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'
}
]
component: './resources'
},
{
name: 'apikeys',
path: '/api-keys',
key: 'apikeys',
hideInMenu: true,
selectedIcon: 'icon-key-filled',
icon: 'icon-key',
defaultIcon: 'icon-key',
icon: 'KeyOutlined',
component: './api-keys'
},
{
name: 'users',
path: '/users',
key: 'users',
icon: 'Team',
access: 'canSeeAdmin',
component: './users'
},
{
name: 'profile',
path: '/profile',
+1 -10
View File
@@ -33,16 +33,14 @@
"ansi-to-html": "^0.7.2",
"antd": "^5.21.6",
"antd-style": "^3.6.2",
"axios": "^1.8.2",
"axios": "^1.7.2",
"classnames": "^2.5.1",
"clipboard": "^2.0.11",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.11",
"dompurify": "^3.2.6",
"driver.js": "^1.3.1",
"echarts": "^5.5.1",
"epubjs": "^0.3.93",
"file-saver": "^2.0.5",
"has-ansi": "^5.0.1",
"highlight.js": "^11.10.0",
"jotai": "^2.8.4",
@@ -68,7 +66,6 @@
"react-hotkeys-hook": "^4.5.0",
"react-markdown": "^9.0.3",
"rehype-katex": "^7.0.1",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"simplebar-react": "^3.2.6",
@@ -82,7 +79,6 @@
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@umijs/case-sensitive-paths-webpack-plugin": "^1.0.1",
"@umijs/plugins": "^4.4.11",
"babel-plugin-named-asset-import": "^0.3.8",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"compression-webpack-plugin": "^11.1.0",
@@ -104,10 +100,5 @@
"url-loader": "^4.1.1",
"webpack-bundle-analyzer": "^4.10.2",
"worker-loader": "^3.0.8"
},
"pnpm": {
"overrides": {
"elliptic": "^6.6.1"
}
}
}
+692 -193
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

+1 -1
View File
@@ -23,7 +23,7 @@ const checkDefaultPage = async (userInfo: any) => {
if (isFirstLogin === null && isOnline()) {
writeState(IS_FIRST_LOGIN, true);
if (userInfo && userInfo?.is_admin) {
history.push('/models/deployments');
history.push('/models/list');
}
}
};
-1
View File
@@ -1 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-1
View File
@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 934 B

-8
View File
@@ -1,8 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.3 KiB

-4
View File
@@ -204,10 +204,6 @@
font-size: 20px;
}
.font-size-18 {
font-size: 18px;
}
.font-size-24 {
font-size: 24px;
}
+1 -15
View File
@@ -1,18 +1,4 @@
import { atom, getDefaultStore } from 'jotai';
import { atom } from 'jotai';
// models expand keys: create, update , delete,
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);
};
+5 -6
View File
@@ -7,15 +7,13 @@ type UserSettings = {
mode: 'light' | 'realDark' | 'auto';
colorPrimary: string;
isDarkTheme: boolean;
collapsed: boolean;
};
const defaultSettings: UserSettings = {
theme: 'light',
mode: 'auto',
isDarkTheme: false,
colorPrimary: colorPrimary,
collapsed: false
colorPrimary: colorPrimary
};
export const getStorageUserSettings = () => {
@@ -33,9 +31,10 @@ export const getStorageUserSettings = () => {
}
};
export const userSettingsAtom = atomWithStorage<UserSettings>('userSettings', {
...getStorageUserSettings()
});
export const userSettingsAtom = atomWithStorage<UserSettings>(
'userSettings',
defaultSettings
);
export const userSettingsHelperAtom = atom(
(get) => get(userSettingsAtom),
+72
View File
@@ -0,0 +1,72 @@
.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);
}
}
}
+6 -88
View File
@@ -1,10 +1,10 @@
import { WarningFilled } from '@ant-design/icons';
import { Typography } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import React from 'react';
import styled from 'styled-components';
import OverlayScroller, { OverlayScrollerOptions } from '../overlay-scroller';
import OverlayScroller from '../overlay-scroller';
import './block.less';
interface AlertInfoProps {
type: Global.MessageType;
message: React.ReactNode;
@@ -15,84 +15,8 @@ interface AlertInfoProps {
contentStyle?: React.CSSProperties;
title?: React.ReactNode;
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`
font-weight: 700;
color: var(--ant-color-text);
@@ -105,7 +29,6 @@ const ContentWrapper = styled.div<{ $hasTitle: boolean }>`
? 'var(--ant-color-text-secondary)'
: 'var(--ant-color-text)'};
font-weight: var(--font-weight-500);
white-space: pre-line;
`;
const AlertInfo: React.FC<AlertInfoProps> = (props) => {
@@ -118,15 +41,14 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
title,
contentStyle,
icon,
maxHeight = 86,
overlayScrollerProps = {}
maxHeight = 86
} = props;
const { styles } = useStyles();
return (
<>
{message ? (
<div
className={classNames(styles.alertBlockInfo, type)}
className={classNames('alert-info-block', type)}
style={{ ...style }}
>
<Typography.Paragraph
@@ -145,11 +67,7 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
{title && (
<TitleWrapper className="title-text">{title}</TitleWrapper>
)}
<OverlayScroller
maxHeight={maxHeight}
style={{ ...contentStyle }}
{...overlayScrollerProps}
>
<OverlayScroller maxHeight={maxHeight} style={{ ...contentStyle }}>
<ContentWrapper
$hasTitle={!!title}
className={classNames('content', type)}
@@ -1,22 +0,0 @@
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;
@@ -1,406 +0,0 @@
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);
+1 -1
View File
@@ -87,7 +87,7 @@
}
.single-image {
// height: 100%;
height: 100%;
width: inherit;
display: flex;
justify-content: center;
+4 -18
View File
@@ -8,7 +8,6 @@ import React, {
useRef,
useState
} from 'react';
import styled from 'styled-components';
import { TooltipOverlayScroller } from '../overlay-scroller';
// type TagProps = React.ComponentProps<typeof Tag>;
@@ -23,18 +22,9 @@ interface AutoTooltipProps extends Omit<TagProps, 'title'> {
title?: React.ReactNode;
showTitle?: boolean;
closable?: boolean;
radius?: number | string;
filled?: boolean;
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> = ({
children,
maxWidth = '100%',
@@ -43,8 +33,6 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
title,
showTitle = false,
tooltipProps,
radius = 12,
filled = false,
...tagProps
}) => {
const contentRef = useRef<HTMLDivElement>(null);
@@ -115,14 +103,13 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
{children}
</div>
) : (
<StyledTag
<Tag
{...tagProps}
className={`${tagProps.className || ''} ${filled ? 'tag-filled' : ''}`}
ref={contentRef}
style={{
...tagStyle,
paddingInline: tagProps.closable ? '8px 22px' : 8,
borderRadius: radius
borderRadius: 12
}}
closeIcon={
tagProps.closable ? (
@@ -130,8 +117,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
style={{
position: 'absolute',
right: 8,
top: '50%',
transform: 'translateY(-50%)'
top: 6
}}
/>
) : (
@@ -140,7 +126,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
}
>
{children}
</StyledTag>
</Tag>
)}
</TooltipOverlayScroller>
);
-109
View File
@@ -1,109 +0,0 @@
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>
);
};
+3 -3
View File
@@ -15,7 +15,7 @@ const fadeOut = keyframes`
}
`;
const Mask = styled.div<{ $isHiding: boolean }>`
const Mask = styled.div<{ isHiding: boolean }>`
display: flex;
justify-content: center;
align-items: center;
@@ -26,7 +26,7 @@ const Mask = styled.div<{ $isHiding: boolean }>`
height: 100vh;
background-color: rgba(59, 59, 59, 1);
z-index: 9999;
animation: ${({ $isHiding }) => ($isHiding ? fadeOut : 'none')} ${duration}s
animation: ${({ isHiding }) => (isHiding ? fadeOut : 'none')} ${duration}s
ease forwards;
`;
@@ -79,7 +79,7 @@ const DarkModeMask = () => {
return () => el.removeEventListener('animationend', handleAnimationEnd);
}, [visible]);
return visible ? <Mask ref={maskRef} $isHiding={isHiding}></Mask> : null;
return visible ? <Mask ref={maskRef} isHiding={isHiding}></Mask> : null;
};
export default DarkModeMask;
+22
View File
@@ -0,0 +1,22 @@
: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;
}
+41 -85
View File
@@ -9,38 +9,9 @@ import {
message,
type ModalFuncProps
} from 'antd';
import { createStyles } from 'antd-style';
import { forwardRef, useImperativeHandle, useState } from 'react';
import { FC, forwardRef, useImperativeHandle, useState } from 'react';
import styled from 'styled-components';
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;
}
`
}));
import Styles from './index.less';
const CheckboxWrapper = styled.div`
margin-top: 20px;
@@ -54,8 +25,12 @@ const CheckboxWrapper = styled.div`
}
`;
interface DeleteModalProps {
ref: any;
}
interface DataOptions {
content?: string;
content: string;
selection?: boolean;
name?: string;
okText?: string;
@@ -68,33 +43,28 @@ interface DataOptions {
};
}
interface Configuration {
checked: boolean;
}
const DeleteModal = forwardRef((props, ref) => {
const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
const intl = useIntl();
const { styles } = useStyles();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [visible, setVisible] = useState(false);
const [configuration, setConfiguration] = useState<Configuration>({
checked: false
});
const [checked, setChecked] = useState(false);
const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any);
const show = (data: ModalFuncProps & DataOptions) => {
saveScrollHeight();
setConfig(data);
setConfiguration({
checked: data.checkConfig?.defautlChecked || false
});
setVisible(true);
};
const hide = () => {
setVisible(false);
restoreScrollHeight();
};
useImperativeHandle(ref, () => ({
show: (data: ModalFuncProps & DataOptions) => {
saveScrollHeight();
setConfig(data);
setChecked(data.checkConfig?.defautlChecked || false);
setVisible(true);
},
hide: () => {
setVisible(false);
restoreScrollHeight();
},
configuration: {
checked: checked
}
}));
const handleCancel = () => {
setVisible(false);
@@ -124,12 +94,6 @@ const DeleteModal = forwardRef((props, ref) => {
}
};
useImperativeHandle(ref, () => ({
show,
hide,
configuration
}));
return (
<Modal
style={{
@@ -138,16 +102,12 @@ const DeleteModal = forwardRef((props, ref) => {
open={visible}
onOk={handleOk}
onCancel={handleCancel}
destroyOnClose={false}
destroyOnClose={true}
closeIcon={false}
maskClosable={false}
keyboard={false}
width={460}
styles={{
footer: {
marginTop: '20px'
}
}}
styles={{}}
footer={
<Space size={20}>
<Button onClick={handleCancel} size="middle">
@@ -163,7 +123,7 @@ const DeleteModal = forwardRef((props, ref) => {
</Space>
}
>
<div className={styles['delete-modal-content']}>
<div className={Styles['delete-modal-content']}>
<span className="title">
<ExclamationCircleFilled />
<span>
@@ -174,30 +134,26 @@ const DeleteModal = forwardRef((props, ref) => {
</span>
</div>
<div
className={styles['content']}
className={Styles['content']}
dangerouslySetInnerHTML={{
__html: config.content
? intl.formatMessage(
{
id: config.operation || ''
},
{
type: intl.formatMessage({ id: config.content }),
name: config.name
}
)
: ''
__html:
config.content &&
intl.formatMessage(
{
id: config.operation || ''
},
{
type: intl.formatMessage({ id: config.content }),
name: config.name
}
)
}}
></div>
{config.checkConfig && (
<CheckboxWrapper>
<Checkbox
checked={configuration.checked}
onChange={(e) =>
setConfiguration({
checked: e.target.checked
})
}
checked={checked}
onChange={(e) => setChecked(e.target.checked)}
>
<span className="check-text">
{intl.formatMessage({ id: config.checkConfig?.checkText })}
+20 -76
View File
@@ -1,6 +1,7 @@
import _, { throttle } from 'lodash';
import { throttle } from 'lodash';
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef
@@ -17,7 +18,6 @@ const Chart: React.FC<{
const chart = useRef<echarts.EChartsType>();
const resizeable = useRef(false);
const resizeObserver = useRef<ResizeObserver>();
const finished = useRef(false);
useImperativeHandle(ref, () => {
return {
@@ -25,96 +25,40 @@ const Chart: React.FC<{
};
});
const init = () => {
const init = useCallback(() => {
if (container.current) {
chart.current?.clear();
chart.current = echarts.init(container.current);
}
};
}, []);
const setOption = (options: ECOption) => {
console.log('setOption', options);
chart.current?.clear();
chart.current?.setOption(options, {
notMerge: true,
lazyUpdate: true
});
if (Array.isArray(options.yAxis) && options.yAxis.length > 1) {
chart.current?.resize();
}
};
const resize = useCallback(() => {
chart.current?.resize();
}, []);
const setOption = useCallback(
(options: ECOption) => {
chart.current?.clear();
chart.current?.setOption(options, {
notMerge: true,
lazyUpdate: true
});
},
[options]
);
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) {
init();
chart.current?.on('finished', handleOnFinished);
}
return () => {
chart.current?.dispose();
chart.current?.off('finished', handleOnFinished);
};
}, []);
}, [init]);
useEffect(() => {
resizeable.current = false;
finished.current = false;
resize();
setOption(options);
resizeable.current = true;
}, [options]);
+24 -12
View File
@@ -1,12 +1,27 @@
import useUserSettings from '@/hooks/use-user-settings';
import { formatLargeNumber } from '@/utils';
import { theme } from 'antd';
import { isFunction } from 'lodash';
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 = {
left: 0,
right: 0,
right: 20,
bottom: 20,
containLabel: true
};
@@ -41,23 +56,18 @@ export default function useChartConfig() {
borderColor: 'transparent',
formatter(params: any, callback?: (val: any) => any) {
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
params.forEach((item: any) => {
let value = isFunction(callback)
? callback?.(item.data.value)
: item.data.value;
const borderRadius = item.seriesType === 'bar' ? '2px' : '8px';
result += `<span class="tooltip-item">
<span class="tooltip-item-name">
<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>
<span class="tooltip-value">${value}</span>
<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 class="tooltip-title">${item.seriesName}</span>:
</span>
<span class="tooltip-value">${value}</span>
</span>`;
});
return `<div class="tooltip-wrapper">${result}</div>`;
}
};
@@ -88,6 +98,8 @@ export default function useChartConfig() {
};
const yAxis = {
// max: 100,
// min: 0,
nameTextStyle: {
padding: [0, 0, 0, -20]
},
+4 -25
View File
@@ -5,7 +5,7 @@ import _ from 'lodash';
import React, { memo, useMemo } from 'react';
import { ChartProps } from './types';
const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
const BarChart: React.FC<ChartProps> = (props) => {
const {
seriesData,
xAxisData,
@@ -13,7 +13,6 @@ const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
width,
labelFormatter,
legendData,
maxItems,
title
} = props;
const {
@@ -31,13 +30,7 @@ const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
...titleConfig,
left: 'start'
},
grid: {
...grid,
top: 0,
bottom: maxItems
? `${(1 / maxItems) * (maxItems - xAxisData.length) * 100}%`
: 0
},
grid,
tooltip: {
...tooltip
},
@@ -48,15 +41,7 @@ const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
formatter: labelFormatter
}
},
yAxis: {
...yAxis,
axisLabel: {
...yAxis.axisLabel,
overflow: 'truncate',
width: 75,
ellipsis: '...'
}
},
yAxis,
legend: {
...legend,
data: []
@@ -129,15 +114,9 @@ const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
legend
]);
const isEmpty = useMemo(() => {
return seriesData?.every?.((item: any) => {
return !item?.data?.length;
});
}, [seriesData]);
return (
<>
{isEmpty ? (
{!seriesData.length ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
+1 -11
View File
@@ -28,16 +28,6 @@ const LineChart: React.FC<ChartProps> = (props) => {
yAxis
} = useChartConfig();
const axisLabelFormatter = (value: string, index: number) => {
if (labelFormatter) {
return labelFormatter(value, index);
}
if (index === xAxisData.length - 1) {
return '';
}
return value;
};
const options = {
title: {
text: ''
@@ -55,7 +45,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
...xAxis,
axisLabel: {
...xAxis.axisLabel,
formatter: axisLabelFormatter
formatter: labelFormatter
}
},
yAxis,
-152
View File
@@ -1,152 +0,0 @@
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;
+1 -1
View File
@@ -181,7 +181,7 @@ const Scatter: React.FC<ChartProps> = (props) => {
<span class="tooltip-value">${item.text}</span>
</span>`;
});
return `<div class="tooltip-wrapper scatter">${result}</div>`;
return `<div class="tooltip-wrapper">${result}</div>`;
}
},
title: {
+2 -3
View File
@@ -1,10 +1,9 @@
import type { LegendComponentOption } from 'echarts/components';
export interface ChartProps {
seriesData: any[];
showEmpty?: boolean;
xAxisData: string[];
legendData?: LegendComponentOption['data'];
labelFormatter?: (val?: any, index?: number) => string;
legendData?: string[];
labelFormatter?: (val?: any) => string;
tooltipValueFormatter?: (val: any) => string;
height: string | number;
width?: string | number;
+1 -1
View File
@@ -56,4 +56,4 @@ const HighlightCode: React.FC<{
);
};
export default HighlightCode;
export default React.memo(HighlightCode);
+11 -251
View File
@@ -1,8 +1,8 @@
@font-face {
font-family: iconfont; /* Project id 4613488 */
src: url('iconfont.woff2?t=1755160439217') format('woff2'),
url('iconfont.woff?t=1755160439217') format('woff'),
url('iconfont.ttf?t=1755160439217') format('truetype');
src: url('iconfont.woff2?t=1747231573342') format('woff2'),
url('iconfont.woff?t=1747231573342') format('woff'),
url('iconfont.ttf?t=1747231573342') format('truetype');
}
.iconfont {
@@ -13,254 +13,6 @@
-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 {
content: "\e646";
}
@@ -485,6 +237,14 @@
content: "\e62c";
}
.icon-stopcircle-fill::before {
content: "\e7cc";
}
.icon-playcircle-fill::before {
content: "\e7cd";
}
.icon-SpeakerSlash::before {
content: "\e661";
}
File diff suppressed because one or more lines are too long
+14 -434
View File
@@ -5,440 +5,6 @@
"css_prefix_text": "icon-",
"description": "",
"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",
"name": "dark_theme",
@@ -831,6 +397,20 @@
"unicode": "e62c",
"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",
"name": "SpeakerSlash",
Binary file not shown.
Binary file not shown.
Binary file not shown.
-32
View File
@@ -1,32 +0,0 @@
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,6 +107,7 @@ export default function useZoom(props: {
if (isLoadingMaskRef.current) {
return;
}
event.preventDefault();
// stop
handleZoom(event);
updateCursorSize();
+4 -13
View File
@@ -433,6 +433,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
fitView();
setActiveScale(autoScale.current);
updateCursorSize();
redrawStrokes(strokesRef.current);
};
const handleBrushSizeChange = (value: number) => {
@@ -453,22 +454,11 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
useEffect(() => {
const handleUndoShortcut = (e: KeyboardEvent) => {
if (
(e.ctrlKey || e.metaKey) &&
e.key === 'z' &&
!negativeMaskRef.current
) {
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
undo();
}
};
window.addEventListener('keydown', handleUndoShortcut);
return () => {
window.removeEventListener('keydown', handleUndoShortcut);
};
}, []);
useEffect(() => {
const handleMouseDown = (e: MouseEvent) => {
mouseDownState.current = true;
};
@@ -477,6 +467,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
mouseDownState.current = false;
};
window.addEventListener('keydown', handleUndoShortcut);
// mouse down
window.addEventListener('mousedown', handleMouseDown);
@@ -484,7 +475,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
window.addEventListener('mouseup', handleMouseUp);
return () => {
clearTimeout(timer.current);
window.removeEventListener('keydown', handleUndoShortcut);
window.removeEventListener('mousedown', handleMouseDown);
window.removeEventListener('mouseup', handleMouseUp);
};
+1 -19
View File
@@ -2,27 +2,9 @@ import langConfigMap from '@/locales/lang-config-map';
import { GlobalOutlined } from '@ant-design/icons';
import { getAllLocales, setLocale } from '@umijs/max';
import { Dropdown } from 'antd';
import { createStyles } from 'antd-style';
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 { styles } = useStyles();
const allLocals = getAllLocales();
const items = allLocals.map((key) => {
return {
@@ -43,7 +25,7 @@ const LangSelect = () => {
return (
<Dropdown menu={{ items }}>
<span className={styles.button}>
<span style={{ padding: '0 12px', cursor: 'pointer' }}>
<GlobalOutlined />
</span>
</Dropdown>
+1 -1
View File
@@ -1,7 +1,7 @@
export const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g;
export const replaceLineRegex = /\r\n/g;
export const PageSize = 1000;
export const PageSize = 500;
export const throttle = <T extends (...args: any[]) => void>(
func: T,
+1 -1
View File
@@ -72,7 +72,7 @@ const LogsList: React.FC<LogsListProps> = forwardRef((props, ref) => {
const isBottom = scrollTop + clientHeight + 150 >= scrollHeight;
// is scroll to top
if (scrollTop <= 10) {
if (scrollTop === 0) {
onScroll?.({
isTop: true,
isBottom: false
@@ -106,4 +106,4 @@ const LogsPagination: React.FC<LogsPaginationProps> = (props) => {
);
};
export default LogsPagination;
export default React.memo(LogsPagination);
+10 -62
View File
@@ -16,7 +16,6 @@ interface MessageProps {
chunked?: boolean;
progress?: number;
percent?: number;
isDownloading?: boolean;
}
class AnsiParser {
private cursorRow: number = 0;
@@ -31,9 +30,6 @@ class AnsiParser {
private percent: number = 0;
private isComplete: boolean = false;
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 colorMap = {
'30': 'black',
@@ -56,8 +52,6 @@ class AnsiParser {
this.screen = [['']];
this.rawDataRows = 0;
this.uid = this.uid + 1;
this.lines = [];
this.reminder = '';
this.page = 1;
}
@@ -81,10 +75,6 @@ class AnsiParser {
this.chunked = chunked ?? true;
}
public setIsDownloading(isDownloading: boolean) {
this.isDownloading = isDownloading;
}
private setId() {
this.uid += 1;
return this.uid;
@@ -119,6 +109,7 @@ class AnsiParser {
const n = parseInt(match[1] || '1', 10);
const m = parseInt(match[2] || '1', 10);
const command = match[3];
switch (command) {
case 'A':
this.cursorRow = Math.max(0, this.cursorRow - n);
@@ -174,18 +165,14 @@ class AnsiParser {
const remainingText = input.slice(lastIndex);
this.handleText(remainingText);
// const result = this.screen.map((row, index) => ({
// content: removeBracketsFromLine(row.join('')),
// uid: `${this.page}-${index}`
// }));
const result = this.screen.map((row, index) =>
removeBracketsFromLine(row.join(''))
);
const result = this.screen.map((row, index) => ({
content: removeBracketsFromLine(row.join('')),
uid: `${this.page}-${index}`
}));
return {
data: result,
lines: this.rawDataRows,
remainder: ''
lines: this.rawDataRows
};
}
@@ -196,31 +183,6 @@ class AnsiParser {
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> {
if (this.isProcessing) {
return;
@@ -229,23 +191,11 @@ class AnsiParser {
this.isProcessing = true;
while (this.taskQueue.length > 0) {
let input = '';
if (this.isDownloading) {
input = this.taskQueue.shift() || '';
} else {
input = this.reminder + this.taskQueue.shift();
}
const input = this.taskQueue.shift();
if (input) {
try {
const result = this.isDownloading
? this.processInput(input)
: this.processInputByLine(input);
if (!this.isDownloading) {
this.reminder = result.remainder;
}
const result = this.processInput(input);
if (this.chunked) {
self.postMessage({ result: result.data, lines: result.lines });
} else if (!this.isComplete) {
@@ -270,7 +220,7 @@ class AnsiParser {
this.processQueue();
} else if (this.isComplete && !this.chunked) {
self.postMessage({
result: this.getAllLines(),
result: this.getScreenText(),
percent: this.percent,
isComplete: true
});
@@ -294,11 +244,9 @@ self.onmessage = function (event: MessageEvent<MessageProps>) {
page,
isComplete = false,
chunked = true,
percent = 0,
isDownloading = false
percent = 0
} = event.data;
parser.setIsDownloading(isDownloading);
parser.setPage(page);
parser.setIsCompelete(isComplete);
parser.setChunked(chunked);
+1 -1
View File
@@ -17,7 +17,7 @@
top: 0;
bottom: 0;
right: -18px;
// width: 80px;
width: 80px;
height: 185px;
&:hover {
@@ -15,7 +15,6 @@
.text {
min-height: 22px;
padding-inline-end: 80px;
&.numable {
position: relative;
@@ -12,7 +12,7 @@
}
.ant-btn {
background-color: rgba(71, 71, 71, 70%) !important;
background-color: rgba(71, 71, 71, 100%) !important;
}
.pages {
@@ -4,6 +4,7 @@ import classNames from 'classnames';
import _ from 'lodash';
import React, {
forwardRef,
memo,
useCallback,
useEffect,
useImperativeHandle,
@@ -24,17 +25,9 @@ interface LogsViewerProps {
tail?: number;
enableScorllLoad?: boolean;
diffHeight?: number;
isDownloading?: boolean;
}
const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
const {
diffHeight,
url,
tail: defaultTail,
enableScorllLoad = true,
isDownloading
} = props;
const { diffHeight, url, tail: defaultTail, enableScorllLoad = true } = props;
const { pageSize, page, setPage, setTotalPage, totalPage } =
useLogsPagination();
const { setChunkFetch } = useSetChunkFetch();
@@ -50,7 +43,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
const pageRef = useRef<any>(page);
const totalPageRef = useRef<any>(totalPage);
const isLoadingMoreRef = useRef(false);
const [currentData, setCurrentPageData] = useState<any[]>([]);
const [currentData, setCurrentData] = useState<any[]>([]);
const scrollPosRef = useRef<any>({
pos: 'bottom',
page: 1
@@ -65,22 +58,6 @@ 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(() => {
setLoading(false);
isLoadingMoreRef.current = false;
@@ -157,8 +134,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
logParseWorker.current.postMessage({
inputStr: data,
page: pageRef.current,
reset: clearScreen.current,
isDownloading: isDownloading
reset: clearScreen.current
});
clearScreen.current = false;
};
@@ -255,7 +231,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
return () => {
chunkRequedtRef.current?.current?.abort?.();
};
}, [url, isDownloading]);
}, [url]);
useEffect(() => {
debouncedScroll();
@@ -337,7 +313,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
<div className="pg">
<div
className={classNames('pg-inner', {
'at-top': true
'at-top': isAtTop
})}
>
<LogsPagination
@@ -357,4 +333,4 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
);
});
export default LogsViewer;
export default memo(LogsViewer);
@@ -3,7 +3,6 @@ import 'katex/dist/katex.min.css';
import React, { useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import rehypeKatex from 'rehype-katex';
import remarkBreaks from 'remark-breaks';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import styled from 'styled-components';
@@ -18,7 +17,9 @@ interface FullMarkdownProps {
const Wrapper = styled.div.attrs(() => ({
className: 'markdown-viewer'
}))``;
}))`
line-height: 2;
`;
const CodeViewer = (props: any) => {
const { children, className, node, theme, ...rest } = props;
@@ -40,7 +41,7 @@ const FullMarkdown: React.FC<FullMarkdownProps> = ({ content, theme }) => {
return (
<Wrapper>
<ReactMarkdown
remarkPlugins={[[remarkMath], remarkGfm, remarkBreaks]}
remarkPlugins={[[remarkMath], remarkGfm]}
rehypePlugins={[rehypeKatex]}
components={{
code(props) {
+9 -5
View File
@@ -1,6 +1,5 @@
.markdown-viewer {
word-break: break-word;
line-height: 2;
font-size: var(--font-size-base);
.hr {
@@ -33,6 +32,7 @@
h2,
h3,
h4 {
font-weight: var(--font-weight-bold);
margin-top: 1em;
}
@@ -58,14 +58,20 @@
margin-top: 16px;
}
strong {
font-weight: var(--font-weight-bold);
}
ul {
margin-bottom: 0;
padding-left: 20px;
line-height: 2;
}
ol {
margin-bottom: 0;
padding-left: 20px;
line-height: 2;
}
a * {
@@ -81,7 +87,7 @@
font-weight: var(--font-weight-bold);
line-height: 1.5;
padding-inline: 6px;
border: 1px solid var(--ant-color-split);
border-bottom: 1px solid var(--ant-color-split);
word-break: break-word;
text-align: left !important;
}
@@ -89,7 +95,7 @@
td {
line-height: 1.5;
padding-inline: 6px;
border: 1px solid var(--ant-color-split);
border-bottom: 1px solid var(--ant-color-split);
word-break: break-word;
text-align: left !important;
}
@@ -112,8 +118,6 @@
img[src^="https://img.shields.io/"], img[src*="badge.svg"]
{
margin-bottom: 6px;
width: unset;
max-width: 100%;
}
video {
+3 -23
View File
@@ -1,7 +1,6 @@
import { EyeOutlined } from '@ant-design/icons';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { Checkbox, Image, Typography } from 'antd';
import DOMPurify from 'dompurify';
import { unescape } from 'lodash';
import { TokensList, marked } from 'marked';
import React, { Fragment, useCallback, useEffect } from 'react';
@@ -17,15 +16,6 @@ interface MarkdownViewerProps {
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> = ({
content,
generateImgLink,
@@ -78,15 +68,11 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
const renderItem = useCallback(
(token: any, render: any) => {
if (token.type === 'script' || token.type === 'style') {
return null;
}
if (!reDefineTypes.includes(token.type)) {
return (
<span
dangerouslySetInnerHTML={{
__html: cleanHtml(marked.parser([token], { renderer }))
__html: marked.parser([token], { renderer })
}}
></span>
);
@@ -103,13 +89,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
}
if (token.type === 'html') {
htmlstr = (
<div
dangerouslySetInnerHTML={{
__html: cleanHtml(token.text)
}}
/>
);
htmlstr = <div dangerouslySetInnerHTML={{ __html: token.text }} />;
}
if (token.type === 'list') {
htmlstr = token.order ? (
@@ -178,7 +158,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
if (token.type === 'link') {
htmlstr = (
<Link
href={sanitizeUrl(token.href || '')}
href={token.href}
title={token.title || ''}
target="_blank"
rel="noopener noreferrer"
@@ -12,9 +12,6 @@ const Wrapper = styled.div<{ $maxHeight?: number }>`
width: 100%;
`;
// export OverlayScrollerOptions
export type { OverlayScrollerOptions };
export const OverlayScroller: React.FC<
OverlayScrollerOptions & {
maxHeight?: number;
+3 -9
View File
@@ -1,9 +1,9 @@
import { Progress, Tooltip } from 'antd';
import React, { memo, useEffect, useMemo } from 'react';
import React, { memo, useMemo } from 'react';
const RenderProgress = memo(
(props: {
defaultOpen?: boolean;
open?: boolean;
percent: number;
steps?: number;
download?: boolean;
@@ -11,8 +11,7 @@ const RenderProgress = memo(
successPercent?: number;
successColor?: string;
}) => {
const { defaultOpen, percent, download, label, successPercent } = props;
const [open, setOpen] = React.useState(false);
const { open, percent, download, label, successPercent } = props;
const strokeColor = useMemo(() => {
if (download) {
@@ -28,10 +27,6 @@ const RenderProgress = memo(
return 'var(--ant-color-error)';
}, [percent]);
useEffect(() => {
setOpen(defaultOpen || false);
}, [defaultOpen]);
const renderProgress = useMemo(() => {
return (
<Progress
@@ -64,7 +59,6 @@ const RenderProgress = memo(
<Tooltip
title={label}
open={open}
onOpenChange={setOpen}
overlayInnerStyle={{ paddingInline: 12 }}
>
{renderProgress}
@@ -1,19 +0,0 @@
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;
@@ -1,38 +0,0 @@
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
};
}
+1 -1
View File
@@ -148,4 +148,4 @@ const RowTextarea: React.FC<SystemMessageProps> = (props) => {
);
};
export default RowTextarea;
export default React.memo(RowTextarea);
+1 -2
View File
@@ -11,7 +11,7 @@ import SelectWrapper from './wrapper/select';
const tag = (props: any) => {
if (props.isMaxTag) {
return props.label?.slice(0, -3);
return props.label;
}
const parent = _.split(props.value, '__RC_CASCADER_SPLIT__')?.[0];
return `${parent} / ${props?.label}`;
@@ -24,7 +24,6 @@ const renderTag = (props: any) => {
closable={props.closable}
onClose={props.onClose}
maxWidth={240}
filled
>
{tag(props)}
</AutoTooltip>
-259
View File
@@ -1,259 +0,0 @@
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,6 +1,5 @@
import IconFont from '@/components/icon-font';
import { RightOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Checkbox } from 'antd';
import _ from 'lodash';
import React from 'react';
@@ -30,20 +29,10 @@ const HeaderPrefix: React.FC<HeaderPrefixProps> = (props) => {
disabled
} = props;
const intl = useIntl();
const handleToggleExpand = () => {
onExpandAll?.(!expandAll);
};
const handleUnCheckAll = () => {
onSelectAll?.({
target: {
checked: false
}
});
};
if (!hasColumns) {
return null;
}
@@ -109,4 +98,4 @@ const HeaderPrefix: React.FC<HeaderPrefixProps> = (props) => {
return null;
};
export default HeaderPrefix;
export default React.memo(HeaderPrefix);
@@ -45,4 +45,4 @@ const Header: React.FC<HeaderProps> = (props) => {
);
};
export default Header;
export default React.memo(Header);
@@ -1,7 +1,8 @@
import { Pagination, type PaginationProps } from 'antd';
import { memo } from 'react';
const PaginationComponent: React.FC<PaginationProps> = (props) => {
return <Pagination {...props} />;
};
export default PaginationComponent;
export default memo(PaginationComponent);
@@ -60,4 +60,4 @@ const TableHeader: React.FC<TableHeaderProps> = (props) => {
);
};
export default TableHeader;
export default React.memo(TableHeader);
@@ -27,7 +27,7 @@ const TableRow: React.FC<
rowIndex,
expandable,
rowSelection,
expandedRowKeys = [],
expandedRowKeys,
rowKey,
childParentKey,
columns,
@@ -41,6 +41,7 @@ const TableRow: React.FC<
} = props;
const tableContext: any = React.useContext<{
allChildren?: any[];
allSubChildren?: any[];
setDisableExpand?: (record: any) => boolean;
}>(TableContext);
const { setChunkRequest } = useSetChunkRequest();
@@ -79,12 +80,6 @@ const TableRow: React.FC<
return rowSelection?.selectedRowKeys?.includes(record[rowKey]);
}, [rowSelection?.selectedRowKeys, record, rowKey]);
useEffect(() => {
if (expandedRowKeys?.length === 0) {
setCurrentExpand(false);
}
}, [expandedRowKeys.length]);
const renderChildrenData = () => {
if (childrenData.length === 0) {
// return (
@@ -136,6 +131,9 @@ const TableRow: React.FC<
}, [record, loadChildren]);
const filterUpdateChildrenHandler = () => {
if (!expandedRowKeys?.includes(record[rowKey])) {
return;
}
const dataList = _.filter(tableContext.allChildren, (data: any) => {
return _.get(data, [childParentKey]) === _.get(record, [rowKey]);
});
@@ -271,7 +269,7 @@ const TableRow: React.FC<
})}
</Row>
</div>
{expanded && !disableExpand && (
{expanded && (
<div className="expanded-row">
<Spin spinning={loading}>{renderChildrenData()}</Spin>
</div>
@@ -281,4 +279,4 @@ const TableRow: React.FC<
);
};
export default TableRow;
export default React.memo(TableRow);
+2 -2
View File
@@ -22,7 +22,7 @@ const InfoColumn: React.FC<InfoColumnProps> = (props) => {
<span className="flex" style={style}>
{fieldList.map((item, index) => {
return (
<span key={item.key || index} className="flex-center">
<>
<span className="flex-column flex-center">
<span>
{' '}
@@ -46,7 +46,7 @@ const InfoColumn: React.FC<InfoColumnProps> = (props) => {
}}
></Divider>
) : null}
</span>
</>
);
})}
</span>
-26
View File
@@ -1,26 +0,0 @@
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;
+11 -18
View File
@@ -29,7 +29,6 @@ type StatusTagProps = {
download?: {
percent: number;
};
maxTooltipWidth?: number;
extra?: React.ReactNode;
actions?: {
label: string;
@@ -45,7 +44,6 @@ const StatusTag: React.FC<StatusTagProps> = ({
download,
extra,
actions = [],
maxTooltipWidth = 250,
type = 'tag'
}) => {
const { text, status } = statusValue;
@@ -58,21 +56,16 @@ const StatusTag: React.FC<StatusTagProps> = ({
return StatusColorMap[status];
}, [status]);
const hasLink = useMemo(() => {
if (!statusValue.message) return false;
return linkReg.test(statusValue.message || '');
const statusMessage = useMemo(() => {
return statusValue.message?.replace(linkReg, '');
}, [statusValue.message]);
const statusMessage = useMemo<string>(() => {
if (!statusValue.message) return '';
const messageLink = useMemo(() => {
const link = statusValue.message?.match(linkReg);
if (link) {
return statusValue.message?.replace(
linkReg,
'<a $1 target="_blank">$2</a>'
);
return link?.[0].replace(linkReg, '<a $1 target="_blank">$2</a>');
}
return statusValue.message;
return null;
}, [statusValue.message]);
const renderContent = () => {
@@ -125,17 +118,17 @@ const StatusTag: React.FC<StatusTagProps> = ({
<div
style={{
width: 'max-content',
maxWidth: maxTooltipWidth,
maxWidth: 250,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word'
}}
>
{hasLink ? (
<span dangerouslySetInnerHTML={{ __html: statusMessage }}></span>
) : (
statusMessage
{statusMessage}
{statusMessage && <span className="m-r-5"></span>}
{extra}
{messageLink && (
<span dangerouslySetInnerHTML={{ __html: messageLink }}></span>
)}
{extra && <span className="m-l-5">{extra}</span>}
</div>
</div>
);
@@ -15,13 +15,9 @@ const useStyles = createStyles(({ token, css }) => ({
padding: 0 8px;
&:hover {
color: ${token.colorTextTertiary};
.anticon {
color: ${token.colorTextTertiary};
}
}
.anticon {
font-size: 16px;
color: ${token.colorText};
}
.icon-auto {
font-size: 20px;
+7 -26
View File
@@ -1,8 +1,7 @@
import { UploadOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Tooltip, Upload, message } from 'antd';
import { Button, Tooltip, Upload } from 'antd';
import React from 'react';
import { convertFileSize } from '../../utils';
interface UploadAudioProps {
accept?: string;
@@ -11,39 +10,22 @@ interface UploadAudioProps {
icon?: React.ReactNode;
size?: 'small' | 'middle' | 'large';
shape?: 'circle' | 'round' | 'default';
maxFileSize?: number; // in bytes
onChange?: (data: { file: any; fileList: any[] }) => void;
}
const UploadAudio: React.FC<UploadAudioProps> = (props) => {
const [messageApi, contextHolder] = message.useMessage();
const { icon, accept, type, size = 'large', shape = 'circle' } = props;
const intl = useIntl();
const beforeUpload = (file: any) => {
return false;
};
const isFileSizeValid = (file: File) => {
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);
};
const handleOnChange = React.useCallback(
(data: { file: any; fileList: any }) => {
props.onChange?.(data);
},
[]
);
return (
<Tooltip
overlayInnerStyle={{ maxWidth: 290, width: 'max-content' }}
@@ -77,7 +59,6 @@ const UploadAudio: React.FC<UploadAudioProps> = (props) => {
></Button>
</div>
</Upload>
{contextHolder}
</Tooltip>
);
};
+1 -3
View File
@@ -20,8 +20,6 @@ declare namespace Global {
full_name: string;
require_password_change: boolean;
id: number;
source: string;
avatar_url?: string;
}
interface BaseListItem<T> {
@@ -40,7 +38,7 @@ declare namespace Global {
interface HintOptions {
label: string;
value: string;
opts?: Array<BaseOption<string | number>>;
opts?: Array<BaseOption<string>>;
}
type SearchParams = Pagination & { search?: string };
+1 -2
View File
@@ -27,8 +27,7 @@ const KeybindingsMap = {
NEW3: ['Ctrl+3', 'Meta+3'],
NEW4: ['Ctrl+4', 'Meta+4'],
FOCUS: ['/', '/'],
ADD: ['Alt+Ctrl+Enter', 'Alt+Meta+Enter'],
ESC: ['Esc', 'Esc']
ADD: ['Alt+Ctrl+Enter', 'Alt+Meta+Enter']
};
type KeyBindingType = keyof typeof KeybindingsMap;
+2 -5
View File
@@ -24,15 +24,13 @@ export default {
},
Tabs: {
titleFontSizeLG: 14
},
DatePicker: {
fontSizeLG: 14
// cardBg: '#1D1E20'
},
Menu: {
iconSize: 16,
iconMarginInlineEnd: 12,
itemBorderRadius: 4,
itemHeight: 32,
itemHeight: 44,
itemSelectedColor: '#007BFF',
darkItemSelectedBg: '#141414',
darkItemHoverBg: 'rgba(255, 255, 255, 0.03)',
@@ -71,7 +69,6 @@ export default {
}
},
token: {
darkMode: true,
fontFamily:
"Helvetica Neue, -apple-system, BlinkMacSystemFont, Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
colorText: '#ccc',
+1 -1
View File
@@ -6,5 +6,5 @@ export const colorPrimary = '#007BFF';
export default {
light,
dark,
colorPrimary: colorPrimary
colorPrimary: '#007BFF'
};
+1 -5
View File
@@ -25,15 +25,12 @@ export default {
Tabs: {
titleFontSizeLG: 14
},
DatePicker: {
fontSizeLG: 14
},
Menu: {
iconSize: 16,
iconMarginInlineEnd: 12,
itemBorderRadius: 4,
itemSelectedColor: '#007BFF',
itemHeight: 32,
itemHeight: 44,
groupTitleColor: 'rgba(0,0,0,1)',
itemHoverColor: 'rgba(0,0,0,1)',
itemColor: 'rgba(0,0,0,1)',
@@ -69,7 +66,6 @@ export default {
}
},
token: {
darkMode: false,
fontFamily:
"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)',
+13 -40
View File
@@ -72,7 +72,7 @@ html {
--color-green-fill-light: rgb(243 251 248);
--ant-rate-star-color: #fadb14;
--color-fill-spin-bg: rgba(255, 255, 255, 15%);
--width-tooltip-max: 420px;
--width-tooltip-max: 300px;
--color-bg-tooltip: '#fff';
--color-modal-content-bg: rgba(255, 255, 255, 90%);
--color-modal-box-shadow: 0 4px 16px rgba(0, 0, 0, 10%);
@@ -84,7 +84,6 @@ html {
--ant-color-border: #d9d9d9;
--ant-line-type: solid;
--ant-line-width: 1px;
--color-esc-hint-bg: rgba(0, 0, 0, 75%);
}
html[data-theme='realDark'] {
@@ -100,7 +99,6 @@ html[data-theme='realDark'] {
--color-modal-content-bg: #1f1f1f;
--color-modal-box-shadow: none;
--color-spotlight-bg: #3e3e3e;
--color-esc-hint-bg: rgba(150, 150, 150, 75%);
background: #141414;
@@ -252,8 +250,7 @@ body {
}
.ant-pro-sider .ant-layout-sider-children {
// background-color: var(--color-fill-sider);
border-right: 1px solid var(--ant-color-split);
background-color: var(--color-fill-sider);
}
}
@@ -278,12 +275,6 @@ body {
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 {
height: 100vh;
@@ -293,10 +284,6 @@ body {
}
}
.ant-pro-sider-actions-list-collapsed {
margin-block-end: 0;
}
.ant-pro-page-container-children-container {
padding-block: var(--layout-content-blockpadding);
padding-inline: var(--layout-content-inlinepadding);
@@ -324,37 +311,31 @@ body {
.ant-menu {
.ant-menu-item {
// color: var(--ant-color-text) !important;
color: var(--ant-color-text) !important;
&: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) {
// color: var(--ant-color-text);
color: var(--ant-color-text);
}
.ant-menu-item.ant-menu-item-selected {
// color: var(--ant-color-primary) !important;
font-weight: 400;
}
.ant-menu-item-group-title.hide-submenu + .ant-menu-item-group-list {
display: none;
color: var(--ant-color-primary) !important;
}
}
}
}
// ======== menu style end ============
.ant-menu-submenu-popup {
.ant-menu-sub {
.ant-menu-item-only-child {
height: 40px;
line-height: 40px;
// color: var(--ant-color-text);
color: var(--ant-color-text);
.anticon {
font-size: var(--font-size-middle);
@@ -385,7 +366,7 @@ body {
.ant-menu {
.ant-menu-submenu-title {
// color: var(--ant-color-text) !important;
color: var(--ant-color-text) !important;
}
}
@@ -393,7 +374,7 @@ body {
.user-avatar.ant-menu-submenu {
.ant-menu-submenu-title {
padding-left: 10px;
// color: var(--ant-color-text) !important;
color: var(--ant-color-text) !important;
}
}
@@ -402,7 +383,7 @@ body {
display: flex;
justify-content: center;
align-items: center;
// color: var(--ant-color-text) !important;
color: var(--ant-color-text) !important;
padding-inline: unset !important;
width: 100%;
margin-inline: 0 !important;
@@ -429,7 +410,7 @@ body {
display: flex;
justify-content: flex-start;
align-items: center;
// color: var(--ant-color-text) !important;
color: var(--ant-color-text) !important;
}
}
@@ -570,6 +551,7 @@ body {
.ant-message-notice-content {
border-radius: var(--border-radius-base) !important;
line-height: 2;
max-width: 300px;
text-align: left;
word-break: break-word;
@@ -578,7 +560,7 @@ body {
overflow-y: auto;
.anticon {
padding-top: 3px;
padding-top: 5px;
}
}
@@ -764,18 +746,9 @@ body {
min-width: 100px;
max-width: 360px;
&.scatter {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(48%, 1fr));
gap: 8px;
}
.tooltip-x-name {
font-size: var(--font-size-small);
color: var(--ant-color-text-tertiary);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.tooltip-item {
+20 -54
View File
@@ -1,4 +1,3 @@
import { convertFileSize } from '@/utils';
import { throttle } from 'lodash';
import qs from 'query-string';
import { useEffect, useRef } from 'react';
@@ -43,19 +42,13 @@ const useSetChunkFetch = () => {
const readTextEventStreamData = async (
response: Response,
callback: HandlerFunction,
delay = 100
delay = 200
) => {
class BufferManager {
private buffer: any[] = [];
private contentLength: number | null = null;
private progress: 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 }) {
this.contentLength = options.contentLength
@@ -70,49 +63,24 @@ 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) {
this.buffer.push(data);
this.updateProgress(data);
}
public async flush(done?: boolean) {
public flush(done?: boolean) {
if (this.buffer.length > 0) {
while (this.buffer.length > 0) {
const item = this.buffer.shift()!;
const isComplete = this.buffer.length === 0 && done;
await new Promise<void>((resolve) => {
callback(item, {
isComplete: isComplete || this.percent === 100,
percent: this.percent,
progress: this.progress,
contentLength: this.contentLength
});
resolve();
const currentBuffer = [...this.buffer];
this.buffer = [];
currentBuffer.forEach((item, i) => {
const isComplete = i === currentBuffer.length - 1 && done;
callback(item, {
isComplete: isComplete || this.percent === 100,
percent: this.percent,
progress: this.progress,
contentLength: this.contentLength
});
}
});
}
}
@@ -130,8 +98,8 @@ const useSetChunkFetch = () => {
contentLength: contentLength
});
const throttledCallback = throttle(async () => {
await bufferManager.flush();
const throttledCallback = throttle(() => {
bufferManager.flush();
}, delay);
let isReading = true;
@@ -139,6 +107,12 @@ const useSetChunkFetch = () => {
while (isReading) {
const { done, value } = await reader.read();
if (done) {
isReading = false;
bufferManager.flush(done);
break;
}
try {
const chunk = decoder.decode(value, { stream: true });
bufferManager.add(chunk);
@@ -146,14 +120,6 @@ const useSetChunkFetch = () => {
} catch (error) {
// handle error
}
if (done) {
isReading = false;
await bufferManager.flush(done);
throttledCallback.cancel();
reader.releaseLock();
break;
}
}
};
-18
View File
@@ -1,18 +0,0 @@
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
}
);
}
+26
View File
@@ -0,0 +1,26 @@
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 };
}
-106
View File
@@ -1,106 +0,0 @@
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 };
}
+3 -16
View File
@@ -6,18 +6,8 @@ import {
import React, { useEffect } from 'react';
import useUserSettings from './use-user-settings';
type OverflowBehavior =
| 'hidden'
| 'scroll'
| 'visible'
| 'visible-hidden'
| 'visible-scroll';
export interface OverlayScrollerOptions {
oppositeTheme?: boolean;
overflow?: {
x?: OverflowBehavior;
y?: OverflowBehavior;
};
scrollbars?: {
theme?: 'os-theme-light' | 'os-theme-dark';
autoHide?: 'never' | 'scroll' | 'leave' | 'move';
@@ -44,8 +34,6 @@ export const overlaySollerOptions: UseOverlayScrollbarsParams = {
defer: true
};
const RESETSCROLLDELAY = 5000;
/**
*
* @param options.theme: if set theme, it will fix the theme
@@ -58,7 +46,7 @@ export default function useOverlayScroller(data?: {
}) {
const { userSettings } = useUserSettings();
const { options, events, defer = true } = data || {};
const { scrollbars, overflow, oppositeTheme } = options || {};
const { scrollbars, oppositeTheme } = options || {};
const scrollEventElement = React.useRef<any>(null);
const instanceRef = React.useRef<any>(null);
const initialized = React.useRef(false);
@@ -71,8 +59,7 @@ export default function useOverlayScroller(data?: {
debounce: 0
},
overflow: {
x: 'hidden',
...overflow
x: 'hidden'
},
scrollbars: {
autoHide: 'scroll',
@@ -164,7 +151,7 @@ export default function useOverlayScroller(data?: {
}
timerRef.current = setTimeout(() => {
stopUpdatePosition.current = false;
}, RESETSCROLLDELAY);
}, 1500);
}, []);
// add wheel event
+7 -35
View File
@@ -11,17 +11,19 @@ export function useCancelToken() {
const { source } = axiso.CancelToken;
const requestToken = useRef<any>(null);
const updateCancelToken = () => {
if (requestToken.current) {
requestToken.current.cancel();
}
requestToken.current = source();
};
const cancelRequest = () => {
if (requestToken.current) {
requestToken.current.cancel();
}
};
const updateCancelToken = () => {
cancelRequest();
requestToken.current = source();
};
const getCanceltToken = () => {
return requestToken.current.token;
};
@@ -36,33 +38,3 @@ export function useCancelToken() {
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 };
}
+17 -69
View File
@@ -7,42 +7,28 @@ import _ from 'lodash';
import qs from 'query-string';
import { useEffect, useRef, useState } from 'react';
type EventsType = 'CREATE' | 'UPDATE' | 'DELETE' | 'INSERT';
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>>;
deleteAPI?: (id: number, params?: any) => Promise<any>;
contentForDelete?: string;
defaultData?: any[];
events?: EventsType[];
} & WatchConfig
) {
export default function useTableFetch<ListItem>(options: {
API?: string;
watch?: boolean;
fetchAPI: (params: any) => Promise<Global.PageResponse<ListItem>>;
deleteAPI?: (id: number, params?: any) => Promise<any>;
contentForDelete?: string;
defaultData?: any[];
}) {
const {
fetchAPI,
deleteAPI,
contentForDelete,
API,
polling = false,
watch,
defaultData = [],
events = ['UPDATE', 'DELETE']
defaultData = []
} = options;
const pollingRef = useRef<any>(null);
const chunkRequedtRef = useRef<any>(null);
const modalRef = useRef<any>(null);
const rowSelection = useTableRowSelection();
const { sortOrder, setSortOrder } = useTableSort({
defaultSortOrder: 'descend'
});
const [extraStatus, setExtraStatus] = useState<Record<string, any>>({
firstLoad: true
});
const [dataSource, setDataSource] = useState<{
dataList: ListItem[];
@@ -65,7 +51,7 @@ export default function useTableFetch<ListItem>(
const { setChunkRequest } = useSetChunkRequest();
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
events: events,
events: ['UPDATE', 'DELETE'],
dataList: dataSource.dataList,
setDataList(list, opts?: any) {
setDataSource((pre) => {
@@ -81,8 +67,6 @@ export default function useTableFetch<ListItem>(
}
});
const debounceSetExtraStatus = _.debounce(setExtraStatus, 3000);
const updateHandler = (list: any) => {
_.each(list, (data: any) => {
updateChunkedList(data);
@@ -104,14 +88,12 @@ export default function useTableFetch<ListItem>(
}
};
const fetchData = async (params?: { query: any }, polling = false) => {
if (!polling) {
setDataSource((pre) => {
pre.loading = true;
return { ...pre };
});
}
const fetchData = async (params?: { query: any }) => {
const { query } = params || {};
setDataSource((pre) => {
pre.loading = true;
return { ...pre };
});
try {
const params = {
..._.pickBy(query || queryParams, (val: any) => !!val)
@@ -154,31 +136,9 @@ export default function useTableFetch<ListItem>(
total: dataSource.total,
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) => {
setQueryParams({
...queryParams,
@@ -216,7 +176,7 @@ export default function useTableFetch<ListItem>(
row: ListItem & { name: string; id: number },
options?: any
) => {
modalRef.current?.show({
modalRef.current.show({
content: contentForDelete,
operation: 'common.delete.single.confirm',
name: row.name,
@@ -232,7 +192,7 @@ export default function useTableFetch<ListItem>(
};
const handleDeleteBatch = (options = {}) => {
modalRef.current?.show({
modalRef.current.show({
content: contentForDelete,
operation: 'common.delete.confirm',
selection: true,
@@ -256,17 +216,6 @@ export default function useTableFetch<ListItem>(
});
};
useEffect(() => {
if (dataSource.loadend) {
fetchAPIWithPolling(queryParams);
}
return () => {
if (pollingRef.current) {
clearInterval(pollingRef.current);
}
};
}, [dataSource.loadend, queryParams]);
useEffect(() => {
const init = async () => {
await fetchData();
@@ -287,7 +236,6 @@ export default function useTableFetch<ListItem>(
sortOrder,
queryParams,
modalRef,
extraStatus,
setQueryParams,
handleDelete,
handleDeleteBatch,
+5 -17
View File
@@ -9,8 +9,6 @@ export default function useUserSettings() {
const { light, dark, colorPrimary } = themeConfig;
const [userSettings, setUserSettings] = useAtom(userSettingsHelperAtom);
console.log('userSettings===', userSettings);
const getCurrentTheme = (mode: Theme): 'light' | 'realDark' => {
if (mode === 'auto') {
return window.matchMedia('(prefers-color-scheme: dark)').matches
@@ -48,32 +46,22 @@ export default function useUserSettings() {
});
};
const initTheme = () => {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
.matches
? 'realDark'
: 'light';
if (userSettings.mode === 'auto' && systemTheme !== userSettings.theme) {
setTheme('auto');
}
};
useEffect(() => {
setHtmlThemeAttr(userSettings.theme);
}, [userSettings.theme]);
useEffect(() => {
initTheme();
}, [userSettings.theme, userSettings.mode]);
if (userSettings.mode !== 'auto') return;
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = () => {
// The theme follows the system preference, when the mode is set to 'auto'
if (userSettings.mode !== 'auto') return;
setTheme('auto');
};
mediaQuery.addEventListener('change', handleChange);
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}, [userSettings.mode, userSettings.theme]);
}, [userSettings.mode]);
return {
userSettings,
+1 -1
View File
@@ -26,7 +26,7 @@ const ErrorResult: React.FC<ErrorResultProps> = ({ extra }) => {
const intl = useIntl();
return (
<Result
status="warning"
status="error"
title={
isChunkLoadError(extra)
? intl.formatMessage({ id: 'common.page.refresh.tips' })
+110 -174
View File
@@ -3,7 +3,6 @@
import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache';
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
import DarkMask from '@/components/dark-mask';
import IconFont from '@/components/icon-font';
import ShortCuts, {
modalConfig as ShortCutsConfig
} from '@/components/short-cuts';
@@ -15,6 +14,7 @@ import useUserSettings from '@/hooks/use-user-settings';
import { logout } from '@/pages/login/apis';
import { useAccessMarkedRoutes } from '@@/plugin-access';
import { useModel } from '@@/plugin-model';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import { ProLayout } from '@ant-design/pro-components';
import {
Link,
@@ -28,18 +28,17 @@ import {
useNavigate,
type IRoute
} from '@umijs/max';
import { Button, ConfigProvider, Modal, Tooltip, theme } from 'antd';
import { Button, ConfigProvider, Modal, theme } from 'antd';
import 'driver.js/dist/driver.css';
import { useAtom } from 'jotai';
import 'overlayscrollbars/overlayscrollbars.css';
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import Exception from './Exception';
import './Layout.css';
import { LogoIcon, SLogoIcon } from './Logo';
import ErrorBoundary from './error-boundary';
import { getRightRenderContent } from './rightRender';
import { patchRoutes } from './runtime';
import SiderMenu from './sider-menu';
const loginPath = '/login';
@@ -103,8 +102,7 @@ export default (props: any) => {
defer: false
});
const [modal, contextHolder] = Modal.useModal();
const { themeData, setTheme, setUserSettings, userSettings, isDarkTheme } =
useUserSettings();
const { themeData, setTheme, userSettings, isDarkTheme } = useUserSettings();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const { initialize: initializeMenu } = useOverlayScroller();
const [userInfo] = useAtom(userAtom);
@@ -115,9 +113,8 @@ export default (props: any) => {
const navigate = useNavigate();
const intl = useIntl();
const { clientRoutes, pluginManager } = useAppData();
// const [collapsed, setCollapsed] = useState(userSettings.collapsed || false);
const [collapsed, setCollapsed] = useState(false);
const [collapseValue, setCollapseValue] = useState(false);
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
const initialInfo = (useModel && useModel('@@initialState')) || {
initialState: undefined,
@@ -190,11 +187,7 @@ export default (props: any) => {
const handleToggleCollapse = (e: any) => {
e.stopPropagation();
// setCollapsed(!collapsed);
setUserSettings({
...userSettings,
collapsed: !userSettings.collapsed
});
setCollapsed(!collapsed);
};
const newRoutes = filterRoutes(
@@ -210,8 +203,6 @@ export default (props: any) => {
const role = initialState?.currentUser?.is_admin ? 'admin' : 'user';
const [route] = useAccessMarkedRoutes(mapRoutes(newRoutes, role));
console.log('route============', route);
patchRoutes({
routes: route.children,
initialState: initialInfo.initialState
@@ -222,24 +213,6 @@ export default (props: any) => {
[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(() => {
return (
initialState?.currentUser?.is_admin &&
@@ -283,159 +256,122 @@ export default (props: any) => {
return () => clearTimeout(timeout);
}, [initializeMenu, matchedRoute, location]);
const collapsed = useMemo(() => {
return userSettings.collapsed || false;
}, [userSettings.collapsed]);
const renderMenuHeader = (logo, title) => {
return (
<>
{logo}
<div className="collapse-wrap" onClick={handleToggleCollapse}>
<Button
style={{ marginRight: collapsed ? 0 : -14, border: 'none' }}
size="small"
type={collapsed ? 'default' : 'text'}
>
<>
<IconFont
type="icon-expand-left"
className="font-size-18 text-secondary"
style={{ display: collapsed ? 'block' : 'none' }}
/>
<IconFont
type="icon-expand-right"
className="font-size-18 text-secondary"
style={{ display: !collapsed ? 'block' : 'none' }}
/>
</>
</Button>
</div>
</>
);
};
const handleToggleGroup = (menuItemProps, e) => {
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({
runtimeConfig,
loading,
initialState,
setInitialState,
intl,
isDarkTheme: userSettings.isDarkTheme,
siderWidth: layoutProps.siderWidth,
collapsed: layoutProps.collapsed,
showUpgrade
});
return dom;
};
const menuItemRender = (menuItemProps, defaultDom) => {
console.log('defaultdom==========', menuItemProps, defaultDom);
if (menuItemProps.isUrl || menuItemProps.children) {
return defaultDom;
}
if (menuItemProps.path && location.pathname !== menuItemProps.path) {
const renderMenuHeader = useCallback(
(logo, title) => {
return (
<Tooltip
title={collapsed ? menuItemProps.name : false}
placement="right"
>
<>
{logo}
<div className="collapse-wrap" onClick={handleToggleCollapse}>
<Button
style={{ marginRight: collapsed ? 0 : -14 }}
size="small"
type={collapsed ? 'default' : 'text'}
>
<>
<MenuUnfoldOutlined
style={{ display: collapsed ? 'block' : 'none' }}
/>
<MenuFoldOutlined
style={{ display: !collapsed ? 'block' : 'none' }}
/>
</>
</Button>
</div>
</>
);
},
[collapsed]
);
const actionRender = useCallback(
(layoutProps) => {
const dom = getRightRenderContent({
runtimeConfig,
loading,
initialState,
setInitialState,
intl,
isDarkTheme: userSettings.isDarkTheme,
siderWidth: layoutProps.siderWidth,
collapsed: layoutProps.collapsed,
showUpgrade
});
return dom;
},
[intl, showUpgrade, userSettings.theme, userSettings.isDarkTheme]
);
const itemRender = useCallback((route, _, routes) => {
const { breadcrumbName, title, path } = route;
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) {
return defaultDom;
}
if (menuItemProps.path && location.pathname !== menuItemProps.path) {
return (
<Link
to={menuItemProps.path.replace('/*', '')}
target={menuItemProps.target}
>
{defaultDom}
</Link>
</Tooltip>
);
}
return (
<Tooltip title={collapsed ? menuItemProps.name : false} placement="right">
{defaultDom}
</Tooltip>
);
};
const menuDataRender = (menuData) => {
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 <>{defaultDom}</>;
},
[location.pathname]
);
const onPageChange = useCallback(
(route) => {
const { location } = history;
const { pathname } = location;
initRouteCacheValue(pathname);
dropRouteCache(pathname);
// if user is not change password, redirect to change password page
if (
location.pathname !== loginPath &&
userInfo?.require_password_change
) {
history.push(loginPath);
return;
}
return newItem;
});
return result;
};
// if user is not logged in, redirect to login page
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
} else if (location.pathname === '/') {
const pathname = initialState?.currentUser?.is_admin
? '/dashboard'
: '/playground';
history.push(pathname);
}
},
[userInfo?.require_password_change, initialState?.currentUser]
);
const onPageChange = (route) => {
const { location } = history;
const { pathname } = location;
console.log('onPageChange', pathname, route);
initRouteCacheValue(pathname);
dropRouteCache(pathname);
// if user is not change password, redirect to change password page
if (location.pathname !== loginPath && userInfo?.require_password_change) {
history.push(loginPath);
return;
}
// if user is not logged in, redirect to login page
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
} else if (location.pathname === '/') {
const pathname = initialState?.currentUser?.is_admin
? '/dashboard'
: '/playground';
history.push(pathname);
}
};
const onMenuHeaderClick = (e) => {
const onMenuHeaderClick = useCallback((e) => {
e.stopPropagation();
e.preventDefault();
navigate('/dashboard');
};
}, []);
const onCollapse = (value) => {
// setCollapsed(value);
setUserSettings({
...userSettings,
collapsed: value
});
setCollapsed(value);
};
useEffect(() => {
@@ -481,6 +417,7 @@ export default (props: any) => {
: theme.defaultAlgorithm,
...themeData
};
console.log('currentTheme====', data);
return data;
}, [userSettings.isDarkTheme, themeData]);
@@ -515,16 +452,15 @@ export default (props: any) => {
onCollapse={onCollapse}
onMenuHeaderClick={onMenuHeaderClick}
menuHeaderRender={renderMenuHeader}
collapsed={userSettings.collapsed}
collapsed={collapsed}
onPageChange={onPageChange}
formatMessage={formatMessage}
menu={{
locale: true,
type: 'group'
locale: true
}}
splitMenus={true}
logo={collapsed ? SLogoIcon : LogoIcon}
menuContentRender={menuContentRender}
menuItemRender={menuItemRender}
itemRender={itemRender}
disableContentMargin
fixSiderbar
fixedHeader
+37 -41
View File
@@ -6,11 +6,12 @@ import langConfigMap from '@/locales/lang-config-map';
import {
DiscordOutlined,
GithubOutlined,
GlobalOutlined,
HomeOutlined,
InfoCircleOutlined,
LogoutOutlined,
MoonOutlined,
MoreOutlined,
QuestionCircleOutlined,
ReadOutlined,
SettingOutlined,
SunOutlined
@@ -20,14 +21,23 @@ import { Avatar, Menu, Spin } from 'antd';
import _ from 'lodash';
import React from 'react';
const getMenuStyle = (
collapsed: boolean,
siderWidth: number,
extraStyle: React.CSSProperties = {}
) => ({
width: collapsed ? 40 : `calc(${siderWidth}px - 16px)`,
...extraStyle
});
const themeConfig = [
{
key: 'realDark',
label: 'common.appearance.dark',
icon: <MoonOutlined />
},
{
key: 'light',
label: 'common.appearance.light',
icon: <SunOutlined />
}
// {
// key: 'auto',
// label: 'common.appearance.system',
// icon: <IconFont type="icon-theme-auto" />
// }
];
export const getRightRenderContent = (opts: {
runtimeConfig: any;
@@ -130,7 +140,7 @@ export const getRightRenderContent = (opts: {
items: [
{
key: 'help',
icon: <IconFont type="icon-help" />,
icon: <QuestionCircleOutlined />,
label: (
<span className="sub-title ">
<span className="flex-center">
@@ -203,7 +213,7 @@ export const getRightRenderContent = (opts: {
items: [
{
key: 'lang',
icon: <IconFont type="icon-language" />,
icon: <GlobalOutlined />,
label: (
<span className="sub-title">
{intl?.formatMessage?.({ id: 'common.settings.language' })}
@@ -255,7 +265,7 @@ export const getRightRenderContent = (opts: {
? 'user-menu-container user-menu-collapsed'
: 'user-menu-container',
mode: 'vertical',
expandIcon: collapsed ? false : <MoreOutlined />,
expandIcon: false,
// inlineCollapsed: collapsed,
triggerSubMenuAction: 'hover',
items: [
@@ -264,10 +274,9 @@ export const getRightRenderContent = (opts: {
key: 'user',
className: 'user-avatar',
icon: (
<span className="avatar-container">
<span>
<Avatar
size={28}
src={opts.initialState?.currentUser?.avatar_url}
style={{
color: 'var(--ant-color-text)',
fontSize: 16
@@ -278,13 +287,7 @@ export const getRightRenderContent = (opts: {
{!collapsed && (
<span
className="m-l-8 font-size-14"
style={{
fontWeight: 'var(--font-weight-normal)',
maxWidth: 100,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
style={{ fontWeight: 'var(--font-weight-normal)' }}
>
{opts.initialState?.currentUser?.username}
</span>
@@ -292,20 +295,6 @@ export const getRightRenderContent = (opts: {
</span>
),
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',
label: (
@@ -339,16 +328,23 @@ export const getRightRenderContent = (opts: {
]
};
const getMenuStyle = (
collapsed: boolean,
siderWidth: number,
extraStyle: React.CSSProperties = {}
) => ({
width: collapsed ? 40 : `calc(${siderWidth}px - 16px)`,
...extraStyle
});
return (
<>
<div>
<Menu {...helpMenu} style={getMenuStyle(collapsed, siderWidth)} />
<Menu {...langMenu} style={getMenuStyle(collapsed, siderWidth)} />
<Menu
{...userMenu}
style={getMenuStyle(collapsed, siderWidth, {
marginTop: 8,
marginBottom: 0
})}
style={getMenuStyle(collapsed, siderWidth, { marginTop: 20 })}
/>
</>
</div>
);
};
-250
View File
@@ -1,250 +0,0 @@
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;
+8 -18
View File
@@ -5,7 +5,7 @@ export default {
'common.button.editmode': 'Edit mode',
'common.button.shortcut': 'Keyboard Shortcut',
'common.button.add': 'Add',
'common.button.login': 'Log in',
'common.button.login': 'Log In',
'common.button.select': 'Select',
'common.button.selected': 'Selected',
'common.button.continue': 'Continue',
@@ -97,7 +97,7 @@ export default {
'common.form.field.input.required': 'required',
'common.form.field.select.required': 'required',
'common.select.option': 'All',
'common.checbox.all': 'Select all',
'common.checbox.all': 'All',
'common.select.all': 'All {type}',
'common.data.unkonwn': 'Unknown',
'common.data.none': 'No Data',
@@ -105,7 +105,7 @@ export default {
'common.button.addfile': 'Add File',
'common.logout.msg': 'Log out succeeded',
'common.button.logout': 'Log out',
'common.form.rule.select': 'Please select a {name}',
'common.form.rule.select': 'please select {name}',
'common.form.rule.input': '{name} is required',
'common.form.key': 'the same key exists',
'common.date.utc': 'UTC time',
@@ -194,13 +194,13 @@ export default {
'common.delete.confirm':
'Are you sure you want to delete the selected {type}?',
'common.delete.single.confirm':
'Are you sure you want to delete? \n <span style="font-size: 13px;font-weight: 700">{name}</span>',
'Are you sure you want to delete <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.single.confirm':
'Are you sure you want to stop? \n <span style="font-size: 13px;font-weight: 700">{name}</span>',
'Are you sure you want to stop <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.single.confirm':
'Are you sure you want to start? \n <span style="font-size: 13px;font-weight: 700">{name}</span>',
'Are you sure you want to start <span style="font-size: 13px;font-weight: 700">{name}</span>?',
'common.filter.name': 'Filter by name',
'common.form.password': 'Password',
'common.form.username': 'Username',
@@ -210,7 +210,7 @@ export default {
'common.button.feedback': 'Feedback',
'common.button.docs': 'Documentation',
'common.button.version': 'Version',
'common.title.delete.confirm': 'Confirm deletion',
'common.title.delete.confirm': 'Confirm delete',
'common.title.stop.confirm': 'Confirm stop',
'common.title.start.confirm': 'Confirm start',
'common.title.recreate.confirm': 'Confirm recreate',
@@ -247,15 +247,5 @@ export default {
'common.appearance.theme': 'Theme',
'common.page.wentwrong': 'Something went wrong.',
'common.page.refresh.tips':
'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.'
'Oops! Something went wrong. Try refreshing the page.'
};
+1 -10
View File
@@ -21,14 +21,5 @@ export default {
'dashboard.activeModels': 'Active Models',
'dashboard.runninginstances': 'Running Instances',
'dashboard.activeModels.name': 'Model Name',
'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'
'dashboard.allocatevram': 'Allocated VRAM / RAM'
};
-7
View File
@@ -11,17 +11,10 @@ export default {
'menu.models.modelList': 'Deploy & Manage',
'menu.models.modelCatalog': 'Catalog',
'menu.models.catalog': 'Model Catalog',
'menu.models.deployment': 'Deployments',
'menu.modelCatalog': 'Catalog',
'menu.resources': 'Resources',
'menu.apikeys': 'API Keys',
'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.login': 'Login',
'menu.usage': 'Usage',
+4 -6
View File
@@ -85,7 +85,7 @@ export default {
'models.form.backend.llamabox':
'For GGUF format models, supports Linux, macOS, and Windows.',
'models.form.backend.vllm':
'For non-GGUF format models, supported only on Linux.',
'For non-GGUF format models, supported only on Linux (amd64/x86_64).',
'models.form.backend.voxbox':
'For non-GGUF format audio models, supported only on NVIDIA GPUs and CPUs.',
'models.form.backend.mindie':
@@ -133,11 +133,9 @@ export default {
'models.form.check.params': 'Checking configuration...',
'models.form.check.passed': 'Compatibility Check Passed',
'models.form.check.claims':
'The model will consume approximately {vram} VRAM and {ram} RAM.',
'models.form.check.claims2':
'The model will consume approximately {vram} VRAM.',
'models.form.check.claims3':
'The model will consume approximately {ram} RAM.',
'The model requires approximately {vram} VRAM and {ram} RAM.',
'models.form.check.claims2': 'The model requires approximately {vram} VRAM.',
'models.form.check.claims3': 'The model requires approximately {ram} RAM.',
'models.form.update.tips':
'Changes will only apply after you delete and recreate the instance.',
'models.table.download.progress': 'Download Progress',
+3 -8
View File
@@ -21,7 +21,7 @@ export default {
'playground.params.temperature.tips':
'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.',
'playground.params.maxtokens.tips':
"The maximum number of tokens to generate. The total length of input tokens and generated tokens is limited by the model's context length.",
"The maximum number of tokens to generated. The total length of input tokens and generated tokens is limited by the model's context length.",
'playground.params.topp.tips':
'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered.',
'playground.params.seed.tips':
@@ -90,7 +90,7 @@ export default {
'playground.audio.stoprecord': 'Stop Recording',
'playground.audio.generating.tips': 'Generated text will appear here.',
'playground.audio.uploadfile.tips':
'Upload an audio file, supported formats: {formats}',
'Please upload an audio file, supported formats: {formats}',
'playground.input.multiplePaste': 'Batch Input Mode',
'playground.input.multiplePaste.tips':
'When enabled, pasted multi-line text will be automatically split by newline into separate entries in the form.',
@@ -151,10 +151,5 @@ export default {
'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.',
'playground.model.noavailable.tips2':
'If the expected model isnt showing up, make sure its running and correctly categorized. If the category is incorrect, you can manually adjust it in the models 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}.'
'If the expected model isnt showing up, make sure its running and correctly categorized. If the category is incorrect, you can manually adjust it in the models settings.'
};
+2 -21
View File
@@ -70,26 +70,7 @@ export default {
'Waiting for the download to complete...',
'resources.filter.worker': 'Filter by worker',
'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.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'
'resources.filter.path': 'Filter by path'
};
+1 -3
View File
@@ -6,7 +6,6 @@ export default {
'users.table.username': 'User Name',
'users.table.role': 'Role',
'users.form.fullname': 'Full Name',
'users.form.source': 'Source',
'users.table.user': 'users',
'users.form.admin': 'Admin',
'users.form.user': 'User',
@@ -28,6 +27,5 @@ export default {
'users.password.confirm.error': 'The two passwords entered do not match.',
'users.login.title': 'Log in to',
'users.version.islatest': 'GPUStack {version} is the latest version',
'users.version.update': 'GPUStack {version} is available',
'users.settings.title': 'User Settings'
'users.version.update': 'GPUStack {version} is available'
};
+1 -19
View File
@@ -247,17 +247,7 @@ export default {
'common.appearance.theme': 'Theme',
'common.page.wentwrong': 'Something went wrong.',
'common.page.refresh.tips':
'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.'
'Oops! Something went wrong. Try refreshing the page.'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -273,12 +263,4 @@ export default {
// 10. 'common.appearance.theme': 'Theme',
// 11. 'common.page.wentwrong': 'Something went wrong.',
// 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 ==========
+1 -22
View File
@@ -21,26 +21,5 @@ export default {
'dashboard.activeModels': 'アクティブなモデル',
'dashboard.runninginstances': '稼働中のインスタンス',
'dashboard.activeModels.name': 'モデル名',
'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'
'dashboard.allocatevram': '割り当て済みVRAM / メモリ'
};
// ========== 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 ==========
+1 -18
View File
@@ -11,7 +11,6 @@ export default {
'menu.models.modelList': 'デプロイと管理',
'menu.models.modelCatalog': 'カタログ',
'menu.models.catalog': 'モデルカタログ',
'menu.models.deployment': 'Deployment',
'menu.modelCatalog': 'カタログ',
'menu.resources': 'リソース',
'menu.apikeys': 'APIキー',
@@ -19,21 +18,5 @@ export default {
'menu.profile': 'プロフィール',
'menu.login': 'ログイン',
'menu.usage': '使用状況',
'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'
'menu.404': '404'
};
// ========== 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