Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daa10617c0 | ||
|
|
c3b5c13cc5 | ||
|
|
1dd83645bd | ||
|
|
2df7c17bc1 | ||
|
|
a274349ace | ||
|
|
1c699fe307 | ||
|
|
f0a3e84e4f | ||
|
|
f87989d32e | ||
|
|
7355be9e3a | ||
|
|
d8f1e95e1a | ||
|
|
59eace702b | ||
|
|
8a24ba0252 | ||
|
|
03999a9d2c | ||
|
|
f335cb1775 | ||
|
|
b9c034469b | ||
|
|
ce3a82ddda | ||
|
|
5cbb38922c | ||
|
|
14f0afec08 | ||
|
|
769cc09906 | ||
|
|
306daf7591 | ||
|
|
d1c1132e6e | ||
|
|
962df859f1 | ||
|
|
97601280d1 | ||
|
|
ede42e99de | ||
|
|
47cae0134c | ||
|
|
33defab7df | ||
|
|
6b402e944b | ||
|
|
f8a6f4c5e5 | ||
|
|
adb999d3a9 |
+3
-1
@@ -4,7 +4,6 @@ import { compressionPluginConfig, monacoPluginConfig } from './plugins';
|
|||||||
import proxy from './proxy';
|
import proxy from './proxy';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { getBranchInfo } from './utils';
|
import { getBranchInfo } from './utils';
|
||||||
const CompressionWebpackPlugin = require('compression-webpack-plugin');
|
|
||||||
|
|
||||||
const versionInfo = getBranchInfo();
|
const versionInfo = getBranchInfo();
|
||||||
process.env.VERSION = JSON.stringify(versionInfo);
|
process.env.VERSION = JSON.stringify(versionInfo);
|
||||||
@@ -79,6 +78,9 @@ export default defineConfig({
|
|||||||
model: {},
|
model: {},
|
||||||
initialState: {},
|
initialState: {},
|
||||||
request: {},
|
request: {},
|
||||||
|
routePrefetch: {
|
||||||
|
defaultPrefetch: 'intent'
|
||||||
|
},
|
||||||
keepalive: keepAlive,
|
keepalive: keepAlive,
|
||||||
locale: {
|
locale: {
|
||||||
antd: true,
|
antd: true,
|
||||||
|
|||||||
+7
-2
@@ -79,10 +79,15 @@ export async function getInitialState(): Promise<{
|
|||||||
const getAppVersionInfo = async () => {
|
const getAppVersionInfo = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await queryVersionInfo();
|
const data = await queryVersionInfo();
|
||||||
const isProduction = data.version?.indexOf('0.0.0') === -1;
|
|
||||||
|
const isDev = data.version?.indexOf('0.0.0') > -1;
|
||||||
|
const isRc = data.version?.indexOf('rc') > -1;
|
||||||
|
|
||||||
setAtomStorage(GPUStackVersionAtom, {
|
setAtomStorage(GPUStackVersionAtom, {
|
||||||
...data,
|
...data,
|
||||||
isProduction
|
isProd: !isDev && !isRc,
|
||||||
|
isDev,
|
||||||
|
isRc
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('queryVersionInfo error', error);
|
console.error('queryVersionInfo error', error);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const regionOSImageListAtom = atom<
|
|||||||
{
|
{
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
os_image: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
vendor: string;
|
vendor: string;
|
||||||
@@ -60,4 +61,5 @@ export const fromClusterCreationAtom = atom(false);
|
|||||||
*/
|
*/
|
||||||
export const clusterSessionAtom = atom<{
|
export const clusterSessionAtom = atom<{
|
||||||
firstAddWorker: boolean;
|
firstAddWorker: boolean;
|
||||||
|
firstAddCluster: boolean;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|||||||
+6
-2
@@ -6,11 +6,15 @@ export const userAtom = atomWithStorage<any>('userInfo', null);
|
|||||||
export const GPUStackVersionAtom = atom<{
|
export const GPUStackVersionAtom = atom<{
|
||||||
version: string;
|
version: string;
|
||||||
git_commit: string;
|
git_commit: string;
|
||||||
isProduction: boolean;
|
isProd: boolean;
|
||||||
|
isDev?: boolean;
|
||||||
|
isRc?: boolean;
|
||||||
}>({
|
}>({
|
||||||
version: '',
|
version: '',
|
||||||
git_commit: '',
|
git_commit: '',
|
||||||
isProduction: false
|
isProd: false,
|
||||||
|
isDev: false,
|
||||||
|
isRc: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UpdateCheckAtom = atom<{
|
export const UpdateCheckAtom = atom<{
|
||||||
|
|||||||
@@ -70,8 +70,9 @@ const AutoImage: React.FC<
|
|||||||
setIsError(false);
|
setIsError(false);
|
||||||
}, [props.onLoad]);
|
}, [props.onLoad]);
|
||||||
|
|
||||||
const handleOnError = useCallback(() => {
|
const handleOnError = useCallback((e: any) => {
|
||||||
setIsError(true);
|
setIsError(true);
|
||||||
|
e.target.src = fallbackImg;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -127,4 +128,4 @@ const AutoImage: React.FC<
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(AutoImage);
|
export default AutoImage;
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ interface HintInputProps {
|
|||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
onBlur?: (e: any) => void;
|
onBlur?: (e: any) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
trim?: boolean;
|
||||||
sourceOptions?: Global.HintOptions[];
|
sourceOptions?: Global.HintOptions[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchReg = /[^=]+=[^=]*$/;
|
const matchReg = /[^=]+=[^=]*$/;
|
||||||
|
|
||||||
const HintInput: React.FC<HintInputProps> = (props) => {
|
const HintInput: React.FC<HintInputProps> = (props) => {
|
||||||
const { value, label, onChange, onBlur, sourceOptions } = props;
|
const { value, label, onChange, onBlur, sourceOptions, trim = true } = props;
|
||||||
const cursorPosRef = React.useRef(0);
|
const cursorPosRef = React.useRef(0);
|
||||||
const contextBeforeCursorRef = React.useRef('');
|
const contextBeforeCursorRef = React.useRef('');
|
||||||
const [options, setOptions] = React.useState<
|
const [options, setOptions] = React.useState<
|
||||||
@@ -70,11 +71,7 @@ const HintInput: React.FC<HintInputProps> = (props) => {
|
|||||||
|
|
||||||
const handleInput = (e: any) => {
|
const handleInput = (e: any) => {
|
||||||
getContextBeforeCursor(e);
|
getContextBeforeCursor(e);
|
||||||
onChange(e.target.value?.trim());
|
onChange(e.target.value);
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnChange = (value: string) => {
|
|
||||||
onChange(value?.trim());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnSelect = (value: string) => {
|
const handleOnSelect = (value: string) => {
|
||||||
@@ -93,9 +90,10 @@ const HintInput: React.FC<HintInputProps> = (props) => {
|
|||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
label={label}
|
label={label}
|
||||||
options={options}
|
options={options}
|
||||||
|
trim={trim}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(HintInput);
|
export default HintInput;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Wrapper from '../label-selector/wrapper';
|
import Wrapper from '../label-selector/wrapper';
|
||||||
@@ -12,13 +11,13 @@ interface ListInputProps {
|
|||||||
options?: Global.HintOptions[];
|
options?: Global.HintOptions[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
labelExtra?: React.ReactNode;
|
labelExtra?: React.ReactNode;
|
||||||
|
trim?: boolean;
|
||||||
onChange: (data: string[]) => void;
|
onChange: (data: string[]) => void;
|
||||||
onBlur?: (e: any, index: number) => void;
|
onBlur?: (e: any, index: number) => void;
|
||||||
onDelete?: (index: number) => void;
|
onDelete?: (index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListInput: React.FC<ListInputProps> = (props) => {
|
const ListInput: React.FC<ListInputProps> = (props) => {
|
||||||
const intl = useIntl();
|
|
||||||
const {
|
const {
|
||||||
dataList,
|
dataList,
|
||||||
label,
|
label,
|
||||||
@@ -28,7 +27,8 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
onDelete,
|
onDelete,
|
||||||
btnText,
|
btnText,
|
||||||
options,
|
options,
|
||||||
labelExtra
|
labelExtra,
|
||||||
|
trim = true
|
||||||
} = props;
|
} = props;
|
||||||
const [list, setList] = React.useState<{ value: string; uid: number }[]>([]);
|
const [list, setList] = React.useState<{ value: string; uid: number }[]>([]);
|
||||||
const countRef = React.useRef(0);
|
const countRef = React.useRef(0);
|
||||||
@@ -97,6 +97,7 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
onBlur={(e) => onBlur?.(e, index)}
|
onBlur={(e) => onBlur?.(e, index)}
|
||||||
onRemove={() => handleOnRemove(index)}
|
onRemove={() => handleOnRemove(index)}
|
||||||
onChange={(val) => handleOnChange(val, index)}
|
onChange={(val) => handleOnChange(val, index)}
|
||||||
|
trim={trim}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -13,10 +13,19 @@ interface LabelItemProps {
|
|||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
options?: Global.HintOptions[];
|
options?: Global.HintOptions[];
|
||||||
|
trim?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListItem: React.FC<LabelItemProps> = (props) => {
|
const ListItem: React.FC<LabelItemProps> = (props) => {
|
||||||
const { onRemove, onChange, onBlur, label, value, options } = props;
|
const {
|
||||||
|
onRemove,
|
||||||
|
onChange,
|
||||||
|
onBlur,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
options,
|
||||||
|
trim = true
|
||||||
|
} = props;
|
||||||
|
|
||||||
const handleOnChange = (value: any) => {
|
const handleOnChange = (value: any) => {
|
||||||
onChange(value);
|
onChange(value);
|
||||||
@@ -30,6 +39,7 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
|
|||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
label={label}
|
label={label}
|
||||||
sourceOptions={options}
|
sourceOptions={options}
|
||||||
|
trim={trim}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@@ -44,4 +54,4 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(ListItem);
|
export default ListItem;
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ const LogsList: React.FC<LogsListProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
const handleOnWheel = useCallback(
|
const handleOnWheel = useCallback(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
const scrollTop = scrollEventElement?.scrollTop;
|
const scrollTop = scrollEventElement?.current.scrollTop;
|
||||||
const scrollHeight = scrollEventElement?.scrollHeight;
|
const scrollHeight = scrollEventElement?.current.scrollHeight;
|
||||||
const clientHeight = scrollEventElement?.clientHeight;
|
const clientHeight = scrollEventElement?.current.clientHeight;
|
||||||
|
|
||||||
stopScroll.current = scrollTop + clientHeight <= scrollHeight;
|
stopScroll.current = scrollTop + clientHeight <= scrollHeight;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import useSetChunkFetch from '@/hooks/use-chunk-fetch';
|
import useSetChunkFetch from '@/hooks/use-chunk-fetch';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -177,7 +178,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnScroll = useCallback(
|
const handleOnScroll = useMemoizedFn(
|
||||||
async (data: { isTop: boolean; isBottom: boolean }) => {
|
async (data: { isTop: boolean; isBottom: boolean }) => {
|
||||||
const { isTop, isBottom } = data;
|
const { isTop, isBottom } = data;
|
||||||
setIsAtTop(isTop);
|
setIsAtTop(isTop);
|
||||||
@@ -225,17 +226,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
} else if (isBottom && page < totalPage) {
|
} else if (isBottom && page < totalPage) {
|
||||||
// getNextPage();
|
// getNextPage();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
[
|
|
||||||
loading,
|
|
||||||
logs.length,
|
|
||||||
pageSize,
|
|
||||||
enableScorllLoad,
|
|
||||||
page,
|
|
||||||
totalPage,
|
|
||||||
setScrollPos,
|
|
||||||
createChunkConnection
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const debouncedScroll = useCallback(
|
const debouncedScroll = useCallback(
|
||||||
|
|||||||
@@ -90,6 +90,14 @@ const SealAutoComplete: React.FC<
|
|||||||
const handleOnSelect = (value: any, option: any) => {
|
const handleOnSelect = (value: any, option: any) => {
|
||||||
onSelect?.(value, option);
|
onSelect?.(value, option);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnInput = (e: any) => {
|
||||||
|
if (trim) {
|
||||||
|
e.target.value = e.target.value?.trim();
|
||||||
|
}
|
||||||
|
props.onInput?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
const renderAfter = () => {
|
const renderAfter = () => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -123,6 +131,7 @@ const SealAutoComplete: React.FC<
|
|||||||
>
|
>
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
{...rest}
|
{...rest}
|
||||||
|
trim={trim}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
placeholder={
|
placeholder={
|
||||||
isFocus || !label ? (
|
isFocus || !label ? (
|
||||||
@@ -141,6 +150,7 @@ const SealAutoComplete: React.FC<
|
|||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
popupRender={popupRender}
|
popupRender={popupRender}
|
||||||
|
onInput={handleOnInput}
|
||||||
></AutoComplete>
|
></AutoComplete>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</SelectWrapper>
|
</SelectWrapper>
|
||||||
|
|||||||
@@ -2,52 +2,68 @@ import IconFont from '@/components/icon-font';
|
|||||||
import type { SelectProps } from 'antd';
|
import type { SelectProps } from 'antd';
|
||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import NotFoundContent from '../components/not-found-content';
|
import NotFoundContent from '../components/not-found-content';
|
||||||
|
|
||||||
const BaseSelect: React.FC<SelectProps & { ref?: any }> = forwardRef(
|
const Footer = styled.div`
|
||||||
(props, ref) => {
|
color: var(--ant-color-text-tertiary);
|
||||||
const { notFoundContent, loading, ...restProps } = props;
|
margin-top: 8px;
|
||||||
const [isFocus, setIsFocus] = React.useState(false);
|
margin-bottom: 0;
|
||||||
const inputRef = React.useRef<any>(null);
|
padding: 8px 12px;
|
||||||
|
border-top: 1px solid var(--ant-color-split);
|
||||||
|
`;
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
const BaseSelect: React.FC<
|
||||||
...(inputRef.current || ({} as any))
|
SelectProps & { ref?: any; footer?: React.ReactNode }
|
||||||
}));
|
> = forwardRef((props, ref) => {
|
||||||
|
const { notFoundContent, loading, ...restProps } = props;
|
||||||
|
const [isFocus, setIsFocus] = React.useState(false);
|
||||||
|
const inputRef = React.useRef<any>(null);
|
||||||
|
|
||||||
const handleFocus = (e: React.FocusEvent<HTMLDivElement>) => {
|
useImperativeHandle(ref, () => ({
|
||||||
setIsFocus(true);
|
...(inputRef.current || ({} as any))
|
||||||
props.onFocus?.(e);
|
}));
|
||||||
};
|
|
||||||
const handleBlur = (e: React.FocusEvent<HTMLDivElement>) => {
|
const handleFocus = (e: React.FocusEvent<HTMLDivElement>) => {
|
||||||
setIsFocus(false);
|
setIsFocus(true);
|
||||||
props.onBlur?.(e);
|
props.onFocus?.(e);
|
||||||
};
|
};
|
||||||
const renderSuffixIcon = () => {
|
const handleBlur = (e: React.FocusEvent<HTMLDivElement>) => {
|
||||||
if (props.suffixIcon) {
|
setIsFocus(false);
|
||||||
return props.suffixIcon;
|
props.onBlur?.(e);
|
||||||
|
};
|
||||||
|
const renderSuffixIcon = () => {
|
||||||
|
if (props.suffixIcon) {
|
||||||
|
return props.suffixIcon;
|
||||||
|
}
|
||||||
|
if (!props.showSearch) {
|
||||||
|
return <IconFont type="icon-down"></IconFont>;
|
||||||
|
}
|
||||||
|
return !isFocus ? <IconFont type="icon-down"></IconFont> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
{...restProps}
|
||||||
|
notFoundContent={
|
||||||
|
<NotFoundContent loading={loading} notFoundContent={notFoundContent} />
|
||||||
}
|
}
|
||||||
if (!props.showSearch) {
|
ref={inputRef}
|
||||||
return <IconFont type="icon-down"></IconFont>;
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
suffixIcon={renderSuffixIcon()}
|
||||||
|
popupRender={
|
||||||
|
props.footer
|
||||||
|
? (originNode) => (
|
||||||
|
<>
|
||||||
|
{originNode}
|
||||||
|
{props.footer && <Footer>{props.footer}</Footer>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
return !isFocus ? <IconFont type="icon-down"></IconFont> : undefined;
|
/>
|
||||||
};
|
);
|
||||||
|
});
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
{...restProps}
|
|
||||||
notFoundContent={
|
|
||||||
<NotFoundContent
|
|
||||||
loading={loading}
|
|
||||||
notFoundContent={notFoundContent}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
ref={inputRef}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
suffixIcon={renderSuffixIcon()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default BaseSelect;
|
export default BaseSelect;
|
||||||
|
|||||||
@@ -12,10 +12,11 @@
|
|||||||
|
|
||||||
.note-info {
|
.note-info {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.star {
|
.star {
|
||||||
position: relative;
|
font-size: 0.7rem;
|
||||||
top: 2px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const NoteInfo: React.FC<NoteInfoProps> = (props) => {
|
|||||||
if (!label) return null;
|
if (!label) return null;
|
||||||
const renderRequiredStar = required ? (
|
const renderRequiredStar = required ? (
|
||||||
<span className="star" style={{ color: 'red' }}>
|
<span className="star" style={{ color: 'red' }}>
|
||||||
*
|
﹡
|
||||||
</span>
|
</span>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ const SealPassword: React.FC<InputProps & SealFormItemProps> = (props) => {
|
|||||||
required={required}
|
required={required}
|
||||||
description={description}
|
description={description}
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
|
labelExtra={props.labelExtra}
|
||||||
hasPrefix={!!props.prefix}
|
hasPrefix={!!props.prefix}
|
||||||
onClick={handleClickWrapper}
|
onClick={handleClickWrapper}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import { SealFormItemProps } from './types';
|
|||||||
import Wrapper from './wrapper';
|
import Wrapper from './wrapper';
|
||||||
import SelectWrapper from './wrapper/select';
|
import SelectWrapper from './wrapper/select';
|
||||||
|
|
||||||
const SealSelect: React.FC<SelectProps & SealFormItemProps> = (props) => {
|
const SealSelect: React.FC<
|
||||||
|
SelectProps & SealFormItemProps & { footer?: React.ReactNode }
|
||||||
|
> = (props) => {
|
||||||
const {
|
const {
|
||||||
label,
|
label,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import breakpoints from '@/config/breakpoints';
|
import breakpoints from '@/config/breakpoints';
|
||||||
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
||||||
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
||||||
import { Col, FloatButton, Row, Spin } from 'antd';
|
import { Col, Row, Spin } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import ResizeObserver from 'rc-resize-observer';
|
import ResizeObserver from 'rc-resize-observer';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
@@ -122,7 +122,6 @@ const CardList: React.FC<CatalogListProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
</InfiniteScroller>
|
</InfiniteScroller>
|
||||||
</ResizeObserver>
|
</ResizeObserver>
|
||||||
<FloatButton.BackTop visibilityHeight={1000} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -87,4 +87,4 @@ const UploadAudio: React.FC<UploadAudioProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(UploadAudio);
|
export default UploadAudio;
|
||||||
|
|||||||
@@ -1,18 +1,29 @@
|
|||||||
import Logo from '@/assets/images/gpustack-logo.png';
|
import Logo from '@/assets/images/gpustack-logo.png';
|
||||||
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
|
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
|
||||||
import { getAtomStorage } from '@/atoms/utils';
|
|
||||||
import externalLinks from '@/constants/external-links';
|
import externalLinks from '@/constants/external-links';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
||||||
const latestVersion = getAtomStorage(UpdateCheckAtom).latest_version;
|
const [gpuStackVersionAtom] = useAtom(GPUStackVersionAtom);
|
||||||
const currentVersion = getAtomStorage(GPUStackVersionAtom)?.version;
|
const [userDataAtom] = useAtom(userAtom);
|
||||||
|
const [updateCheck] = useAtom(UpdateCheckAtom);
|
||||||
|
|
||||||
const isProd =
|
// current version info
|
||||||
currentVersion?.indexOf('rc') === -1 &&
|
const {
|
||||||
currentVersion?.indexOf('0.0.0') === -1;
|
version: currentVersion,
|
||||||
|
git_commit,
|
||||||
|
isProd,
|
||||||
|
isDev
|
||||||
|
} = gpuStackVersionAtom;
|
||||||
|
|
||||||
|
// user info
|
||||||
|
const { is_admin } = userDataAtom || {};
|
||||||
|
|
||||||
|
// update check latest version info
|
||||||
|
const { latest_version: latestVersion } = updateCheck;
|
||||||
|
|
||||||
const uiVersion = document.documentElement.getAttribute('data-version');
|
const uiVersion = document.documentElement.getAttribute('data-version');
|
||||||
|
|
||||||
@@ -29,10 +40,7 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{isProd ? (
|
{isProd ? (
|
||||||
<span className="val">
|
<span className="val">{currentVersion || git_commit}</span>
|
||||||
{getAtomStorage(GPUStackVersionAtom)?.version ||
|
|
||||||
getAtomStorage(GPUStackVersionAtom)?.git_commit}
|
|
||||||
</span>
|
|
||||||
) : (
|
) : (
|
||||||
<span className="val dev">
|
<span className="val dev">
|
||||||
<span className="item">
|
<span className="item">
|
||||||
@@ -40,9 +48,7 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
|||||||
{' '}
|
{' '}
|
||||||
{intl.formatMessage({ id: 'common.footer.version.server' })}
|
{intl.formatMessage({ id: 'common.footer.version.server' })}
|
||||||
</span>
|
</span>
|
||||||
{currentVersion.indexOf('0.0.0') > -1
|
{isDev ? git_commit : currentVersion}
|
||||||
? getAtomStorage(GPUStackVersionAtom)?.git_commit
|
|
||||||
: getAtomStorage(GPUStackVersionAtom)?.version}
|
|
||||||
</span>
|
</span>
|
||||||
<span className="item">
|
<span className="item">
|
||||||
<span className="tl">UI</span>
|
<span className="tl">UI</span>
|
||||||
@@ -51,12 +57,10 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{getAtomStorage(userAtom)?.is_admin && isProd && (
|
{is_admin && isProd && (
|
||||||
<div className="upgrade-text">
|
<div className="upgrade-text">
|
||||||
<span className="m-l-5">
|
<span className="m-l-5">
|
||||||
{latestVersion &&
|
{latestVersion && latestVersion !== currentVersion && !isDev
|
||||||
latestVersion !== currentVersion &&
|
|
||||||
latestVersion.indexOf('0.0.0') === -1
|
|
||||||
? intl.formatMessage(
|
? intl.formatMessage(
|
||||||
{ id: 'users.version.update' },
|
{ id: 'users.version.update' },
|
||||||
{ version: latestVersion }
|
{ version: latestVersion }
|
||||||
|
|||||||
Vendored
+1
-1
@@ -23,7 +23,7 @@ declare namespace Global {
|
|||||||
require_password_change: boolean;
|
require_password_change: boolean;
|
||||||
id: number;
|
id: number;
|
||||||
source: string;
|
source: string;
|
||||||
avatar: string;
|
avatar_url: string;
|
||||||
}
|
}
|
||||||
type EmptyObject = Record<never, never>;
|
type EmptyObject = Record<never, never>;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export const HEADER_HEIGHT = 56;
|
|||||||
|
|
||||||
export const DEFAULT_ENTER_PAGE = {
|
export const DEFAULT_ENTER_PAGE = {
|
||||||
adminForNormal: '/dashboard',
|
adminForNormal: '/dashboard',
|
||||||
adminForFirst: '/models/deployments',
|
adminForFirst: '/resources/workers',
|
||||||
user: '/models/user-models',
|
user: '/models/user-models',
|
||||||
login: '/login'
|
login: '/login'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -122,12 +122,12 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
|||||||
initialState?.currentUser?.is_admin &&
|
initialState?.currentUser?.is_admin &&
|
||||||
updateCheck.latest_version &&
|
updateCheck.latest_version &&
|
||||||
updateCheck.latest_version !== version?.version &&
|
updateCheck.latest_version !== version?.version &&
|
||||||
updateCheck.latest_version?.indexOf('0.0.0') === -1 &&
|
version?.isProd
|
||||||
updateCheck.latest_version?.indexOf('rc') === -1
|
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
updateCheck.latest_version,
|
updateCheck.latest_version,
|
||||||
version.version,
|
version.version,
|
||||||
|
version.isProd,
|
||||||
initialState?.currentUser?.is_admin
|
initialState?.currentUser?.is_admin
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
|||||||
<Avatar
|
<Avatar
|
||||||
size={24}
|
size={24}
|
||||||
style={{ ...avatarStyle }}
|
style={{ ...avatarStyle }}
|
||||||
src={initialState?.currentUser?.avatar}
|
src={initialState?.currentUser?.avatar_url}
|
||||||
icon={
|
icon={
|
||||||
<IconFont type="icon-user-filled" className="font-size-24" />
|
<IconFont type="icon-user-filled" className="font-size-24" />
|
||||||
}
|
}
|
||||||
@@ -322,7 +322,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
|||||||
<Avatar
|
<Avatar
|
||||||
size={24}
|
size={24}
|
||||||
style={{ ...avatarStyle }}
|
style={{ ...avatarStyle }}
|
||||||
src={initialState?.currentUser?.avatar}
|
src={initialState?.currentUser?.avatar_url}
|
||||||
icon={<IconFont type="icon-user-filled" className="font-size-24" />}
|
icon={<IconFont type="icon-user-filled" className="font-size-24" />}
|
||||||
/>
|
/>
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
|
|||||||
+18
-221
@@ -1,16 +1,11 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache';
|
import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache';
|
||||||
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
|
import { userAtom } from '@/atoms/user';
|
||||||
import DarkMask from '@/components/dark-mask';
|
import DarkMask from '@/components/dark-mask';
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import ShortCuts, {
|
|
||||||
modalConfig as ShortCutsConfig
|
|
||||||
} from '@/components/short-cuts';
|
|
||||||
import VersionInfo, { modalConfig } from '@/components/version-info';
|
|
||||||
import routeCachekey from '@/config/route-cachekey';
|
import routeCachekey from '@/config/route-cachekey';
|
||||||
import { DEFAULT_ENTER_PAGE } from '@/config/settings';
|
import { DEFAULT_ENTER_PAGE } from '@/config/settings';
|
||||||
import useBodyScroll from '@/hooks/use-body-scroll';
|
|
||||||
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||||
import useUserSettings from '@/hooks/use-user-settings';
|
import useUserSettings from '@/hooks/use-user-settings';
|
||||||
import { logout } from '@/pages/login/apis';
|
import { logout } from '@/pages/login/apis';
|
||||||
@@ -18,7 +13,6 @@ import { useAccessMarkedRoutes } from '@@/plugin-access';
|
|||||||
import { useModel } from '@@/plugin-model';
|
import { useModel } from '@@/plugin-model';
|
||||||
import { ProLayout } from '@ant-design/pro-components';
|
import { ProLayout } from '@ant-design/pro-components';
|
||||||
import {
|
import {
|
||||||
Link,
|
|
||||||
Outlet,
|
Outlet,
|
||||||
dropByCacheKey,
|
dropByCacheKey,
|
||||||
history,
|
history,
|
||||||
@@ -29,18 +23,17 @@ import {
|
|||||||
useNavigate,
|
useNavigate,
|
||||||
type IRoute
|
type IRoute
|
||||||
} from '@umijs/max';
|
} 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 'driver.js/dist/driver.css';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { PageContainerInner } from '../pages/_components/page-box';
|
import { PageContainerInner } from '../pages/_components/page-box';
|
||||||
import Exception from './Exception';
|
import Exception from './Exception';
|
||||||
import './Layout.css';
|
import './Layout.css';
|
||||||
import { LogoIcon, SLogoIcon } from './Logo';
|
import { LogoIcon, SLogoIcon } from './Logo';
|
||||||
import ErrorBoundary from './error-boundary';
|
import ErrorBoundary from './error-boundary';
|
||||||
import { ExtraContent } from './extraRender';
|
import { ExtraContent } from './extraRender';
|
||||||
import { getRightRenderContent } from './rightRender';
|
|
||||||
import { patchRoutes } from './runtime';
|
import { patchRoutes } from './runtime';
|
||||||
import SiderMenu from './sider-menu';
|
import SiderMenu from './sider-menu';
|
||||||
|
|
||||||
@@ -57,16 +50,11 @@ const NO_CONTAINER_PAGES = [
|
|||||||
|
|
||||||
const loginPath = DEFAULT_ENTER_PAGE.login;
|
const loginPath = DEFAULT_ENTER_PAGE.login;
|
||||||
|
|
||||||
type InitialStateType = {
|
|
||||||
fetchUserInfo: () => Promise<Global.UserInfo>;
|
|
||||||
currentUser?: Global.UserInfo;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter out the routes that need to be displayed, where filterFn indicates the levels that should not be shown
|
// Filter out the routes that need to be displayed, where filterFn indicates the levels that should not be shown
|
||||||
const filterRoutes = (
|
const filterRoutes = (
|
||||||
routes: IRoute[],
|
routes: IRoute[],
|
||||||
filterFn: (route: IRoute) => boolean
|
filterFn: (route: IRoute) => boolean
|
||||||
) => {
|
): any[] => {
|
||||||
if (routes.length === 0) {
|
if (routes.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -117,21 +105,13 @@ export default (props: any) => {
|
|||||||
defer: false
|
defer: false
|
||||||
});
|
});
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
const { themeData, setTheme, setUserSettings, userSettings, isDarkTheme } =
|
const { themeData, setUserSettings, userSettings } = useUserSettings();
|
||||||
useUserSettings();
|
|
||||||
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
|
||||||
const { initialize: initializeMenu } = useOverlayScroller();
|
|
||||||
const [userInfo] = useAtom(userAtom);
|
const [userInfo] = useAtom(userAtom);
|
||||||
const [routeCache] = useAtom(routeCacheAtom);
|
const [routeCache] = useAtom(routeCacheAtom);
|
||||||
const [version] = useAtom(GPUStackVersionAtom);
|
|
||||||
const [updateCheck] = useAtom(UpdateCheckAtom);
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { clientRoutes, pluginManager } = useAppData();
|
const { clientRoutes } = useAppData();
|
||||||
// const [collapsed, setCollapsed] = useState(userSettings.collapsed || false);
|
|
||||||
const [collapseValue, setCollapseValue] = useState(false);
|
|
||||||
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
|
|
||||||
|
|
||||||
const initialInfo = (useModel && useModel('@@initialState')) || {
|
const initialInfo = (useModel && useModel('@@initialState')) || {
|
||||||
initialState: undefined,
|
initialState: undefined,
|
||||||
@@ -150,23 +130,6 @@ export default (props: any) => {
|
|||||||
return intl.formatMessage({ id: args.id });
|
return intl.formatMessage({ id: args.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const showVersion = () => {
|
|
||||||
saveScrollHeight();
|
|
||||||
modal.info({
|
|
||||||
...modalConfig,
|
|
||||||
width: 460,
|
|
||||||
content: <VersionInfo intl={intl} />,
|
|
||||||
onCancel: restoreScrollHeight
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const showShortcuts = () => {
|
|
||||||
Modal.info({
|
|
||||||
...ShortCutsConfig,
|
|
||||||
content: <ShortCuts intl={intl} />
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const initRouteCacheValue = (pathname) => {
|
const initRouteCacheValue = (pathname) => {
|
||||||
if (routeCache.get(pathname) === undefined && routeCachekey[pathname]) {
|
if (routeCache.get(pathname) === undefined && routeCachekey[pathname]) {
|
||||||
setRouteCache(pathname, false);
|
setRouteCache(pathname, false);
|
||||||
@@ -184,22 +147,17 @@ export default (props: any) => {
|
|||||||
|
|
||||||
const runtimeConfig = {
|
const runtimeConfig = {
|
||||||
...initialInfo,
|
...initialInfo,
|
||||||
logout: async (userInfo) => {
|
logout: async () => {
|
||||||
await logout();
|
await logout();
|
||||||
navigate(loginPath);
|
navigate(loginPath);
|
||||||
},
|
},
|
||||||
showVersion: () => {
|
showVersion: () => {},
|
||||||
return showVersion();
|
showShortcuts: () => {},
|
||||||
},
|
|
||||||
showShortcuts: () => {
|
|
||||||
return showShortcuts();
|
|
||||||
},
|
|
||||||
notFound: <span>404 not found</span>
|
notFound: <span>404 not found</span>
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleCollapse = (e: any) => {
|
const handleToggleCollapse = (e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// setCollapsed(!collapsed);
|
|
||||||
setUserSettings({
|
setUserSettings({
|
||||||
...userSettings,
|
...userSettings,
|
||||||
collapsed: !userSettings.collapsed
|
collapsed: !userSettings.collapsed
|
||||||
@@ -235,38 +193,6 @@ export default (props: any) => {
|
|||||||
|
|
||||||
console.log('matchedRoute=========', matchedRoute, route);
|
console.log('matchedRoute=========', matchedRoute, route);
|
||||||
|
|
||||||
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 &&
|
|
||||||
updateCheck.latest_version &&
|
|
||||||
updateCheck.latest_version !== version?.version &&
|
|
||||||
updateCheck.latest_version?.indexOf('0.0.0') === -1 &&
|
|
||||||
updateCheck.latest_version?.indexOf('rc') === -1
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
updateCheck.latest_version,
|
|
||||||
version.version,
|
|
||||||
initialState?.currentUser?.is_admin
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const body = document.querySelector('body');
|
const body = document.querySelector('body');
|
||||||
if (body) {
|
if (body) {
|
||||||
@@ -306,92 +232,13 @@ export default (props: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleGroup = (menuItemProps, e) => {
|
const menuContentRender = (menuProps: any, defaultDom: React.ReactNode) => {
|
||||||
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>;
|
return <SiderMenu {...menuProps}></SiderMenu>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const actionRender = (layoutProps) => {
|
const onPageChange = (route: any) => {
|
||||||
const dom = getRightRenderContent({
|
|
||||||
runtimeConfig,
|
|
||||||
loading,
|
|
||||||
initialState,
|
|
||||||
setInitialState,
|
|
||||||
intl,
|
|
||||||
isDarkTheme: userSettings.isDarkTheme,
|
|
||||||
siderWidth: layoutProps?.siderWidth,
|
|
||||||
collapsed: layoutProps?.collapsed,
|
|
||||||
showUpgrade
|
|
||||||
});
|
|
||||||
|
|
||||||
return dom;
|
|
||||||
};
|
|
||||||
|
|
||||||
const menuItemRender = (menuItemProps, defaultDom) => {
|
|
||||||
if (menuItemProps.isUrl || menuItemProps.children) {
|
|
||||||
return defaultDom;
|
|
||||||
}
|
|
||||||
if (menuItemProps.path && location.pathname !== menuItemProps.path) {
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
title={collapsed ? menuItemProps.name : false}
|
|
||||||
placement="right"
|
|
||||||
>
|
|
||||||
<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 newItem;
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPageChange = (route) => {
|
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
console.log('onPageChange', pathname, route);
|
|
||||||
|
|
||||||
initRouteCacheValue(pathname);
|
initRouteCacheValue(pathname);
|
||||||
dropRouteCache(pathname);
|
dropRouteCache(pathname);
|
||||||
@@ -413,7 +260,7 @@ export default (props: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMenuHeaderClick = (e) => {
|
const onMenuHeaderClick = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const pagepath = initialState?.currentUser?.is_admin
|
const pagepath = initialState?.currentUser?.is_admin
|
||||||
@@ -423,65 +270,13 @@ export default (props: any) => {
|
|||||||
navigate(pagepath);
|
navigate(pagepath);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCollapse = (value) => {
|
const onCollapse = (value: boolean) => {
|
||||||
setUserSettings({
|
setUserSettings({
|
||||||
...userSettings,
|
...userSettings,
|
||||||
collapsed: value
|
collapsed: value
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// clear previous marks and measures
|
|
||||||
performance.clearMarks();
|
|
||||||
performance.clearMeasures();
|
|
||||||
|
|
||||||
// record start time
|
|
||||||
performance.mark('route-start');
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
// record end time
|
|
||||||
performance.mark('route-end');
|
|
||||||
|
|
||||||
// make sure `route-start` exists
|
|
||||||
if (performance.getEntriesByName('route-start').length > 0) {
|
|
||||||
performance.measure('route-change', 'route-start', 'route-end');
|
|
||||||
|
|
||||||
const measure = performance.getEntriesByName('route-change')[0];
|
|
||||||
console.log(
|
|
||||||
`[Performance] Route change to ${location.pathname} took ${measure.duration.toFixed(2)}ms`
|
|
||||||
);
|
|
||||||
|
|
||||||
// clear marks and measures
|
|
||||||
performance.clearMarks();
|
|
||||||
performance.clearMeasures();
|
|
||||||
} else {
|
|
||||||
console.warn('Missing performance mark: route-start');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [location.pathname]);
|
|
||||||
|
|
||||||
const onRenderCallback = (id, phase, actualDuration) => {
|
|
||||||
console.log(
|
|
||||||
`[Profiler] Route: ${id} - Phase: ${phase} - Render time: ${actualDuration.toFixed(2)}ms`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentTheme = useMemo(() => {
|
|
||||||
const data = {
|
|
||||||
algorithm: userSettings.isDarkTheme
|
|
||||||
? theme.darkAlgorithm
|
|
||||||
: theme.defaultAlgorithm,
|
|
||||||
...themeData
|
|
||||||
};
|
|
||||||
return data;
|
|
||||||
}, [userSettings.isDarkTheme, themeData]);
|
|
||||||
|
|
||||||
const outlet = runtimeConfig.childrenRender ? (
|
|
||||||
runtimeConfig.childrenRender(<Outlet />, props)
|
|
||||||
) : (
|
|
||||||
<Outlet />
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
componentSize="large"
|
componentSize="large"
|
||||||
@@ -530,7 +325,7 @@ export default (props: any) => {
|
|||||||
type: 'group'
|
type: 'group'
|
||||||
}}
|
}}
|
||||||
splitMenus={true}
|
splitMenus={true}
|
||||||
logo={collapsed ? SLogoIcon : LogoIcon}
|
logo={userSettings.collapsed ? <SLogoIcon /> : <LogoIcon />}
|
||||||
menuContentRender={menuContentRender}
|
menuContentRender={menuContentRender}
|
||||||
disableContentMargin
|
disableContentMargin
|
||||||
{...runtimeConfig}
|
{...runtimeConfig}
|
||||||
@@ -544,9 +339,11 @@ export default (props: any) => {
|
|||||||
noAccessible={runtimeConfig?.noAccessible}
|
noAccessible={runtimeConfig?.noAccessible}
|
||||||
>
|
>
|
||||||
{isNoContainerPage ? (
|
{isNoContainerPage ? (
|
||||||
outlet
|
<Outlet />
|
||||||
) : (
|
) : (
|
||||||
<PageContainerInner>{outlet}</PageContainerInner>
|
<PageContainerInner>
|
||||||
|
<Outlet />
|
||||||
|
</PageContainerInner>
|
||||||
)}
|
)}
|
||||||
</Exception>
|
</Exception>
|
||||||
</ProLayout>
|
</ProLayout>
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
|
|||||||
key={key}
|
key={key}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
|
prefetch="intent"
|
||||||
to={menuItem.path.replace('/*', '')}
|
to={menuItem.path.replace('/*', '')}
|
||||||
target={menuItem.target}
|
target={menuItem.target}
|
||||||
className={cx(styles.menuItemWrapper, 'menu-item', {
|
className={cx(styles.menuItemWrapper, 'menu-item', {
|
||||||
|
|||||||
@@ -80,5 +80,8 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
|||||||
'clusters.addworker.autoDetect': 'Auto-detect',
|
'clusters.addworker.autoDetect': 'Auto-detect',
|
||||||
'clusters.addworker.extraVolume.holder':
|
'clusters.addworker.extraVolume.holder':
|
||||||
'e.g. /data/models (path must start with /)',
|
'e.g. /data/models (path must start with /)',
|
||||||
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device'
|
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
||||||
|
'clusters.button.genToken':
|
||||||
|
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
|
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,11 +69,11 @@ export default {
|
|||||||
'models.form.ollamalink':
|
'models.form.ollamalink':
|
||||||
'Find More in <a href="https://www.ollama.com/library" target="_blank">Ollama Library</a>.',
|
'Find More in <a href="https://www.ollama.com/library" target="_blank">Ollama Library</a>.',
|
||||||
'models.form.backend_parameters.llamabox.placeholder':
|
'models.form.backend_parameters.llamabox.placeholder':
|
||||||
'e.g., --ctx-size=8192 (use = to separate name and value)',
|
'e.g., --ctx-size=8192 (use = or a space to separate name and value)',
|
||||||
'models.form.backend_parameters.vllm.placeholder':
|
'models.form.backend_parameters.vllm.placeholder':
|
||||||
'e.g., --max-model-len=8192 (use = to separate name and value)',
|
'e.g., --max-model-len=8192 (use = or a space to separate name and value)',
|
||||||
'models.form.backend_parameters.sglang.placeholder':
|
'models.form.backend_parameters.sglang.placeholder':
|
||||||
'e.g., --context-length=8192 (use = to separate name and value)',
|
'e.g., --context-length=8192 (use = or a space to separate name and value)',
|
||||||
'models.form.backend_parameters.vllm.tips':
|
'models.form.backend_parameters.vllm.tips':
|
||||||
'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
||||||
'models.logs.pagination.prev': 'Previous {lines} Lines',
|
'models.logs.pagination.prev': 'Previous {lines} Lines',
|
||||||
@@ -260,5 +260,13 @@ export default {
|
|||||||
'models.form.generic_proxy.button': 'Generic Proxy',
|
'models.form.generic_proxy.button': 'Generic Proxy',
|
||||||
'models.accessControlModal.includeusers': 'Include Users',
|
'models.accessControlModal.includeusers': 'Include Users',
|
||||||
'models.table.genericProxy':
|
'models.table.genericProxy':
|
||||||
'Use the following path prefix, and set the model name in either the <span class="bold-text">X-GPUStack-Model</span> request header or the model field in the request body. All requests under this path prefix will be forwarded to the inference backend.'
|
'Use the following path prefix, and set the model name in either the <span class="bold-text">X-GPUStack-Model</span> request header or the model field in the request body. All requests under this path prefix will be forwarded to the inference backend.',
|
||||||
|
'models.form.backendVersion.deprecated': 'Deprecated',
|
||||||
|
'models.accessSettings.public.desc':
|
||||||
|
'Accessible to anyone without authentication.',
|
||||||
|
'models.accessSettings.authed.tips':
|
||||||
|
'Accessible to all authenticated platform users.',
|
||||||
|
'models.accessSettings.allowedUsers.tips':
|
||||||
|
'Only designated users can access the model.',
|
||||||
|
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,5 +33,11 @@ export default {
|
|||||||
'noresult.keys.nofound': 'No matching API keys found.',
|
'noresult.keys.nofound': 'No matching API keys found.',
|
||||||
'noresult.catalog.title': 'No Models',
|
'noresult.catalog.title': 'No Models',
|
||||||
'noresult.catalog.subTitle': 'No models have been configured yet.',
|
'noresult.catalog.subTitle': 'No models have been configured yet.',
|
||||||
'noresult.catalog.nofound': 'No matching models found.'
|
'noresult.catalog.nofound': 'No matching models found.',
|
||||||
|
'noresult.resources.cluster':
|
||||||
|
'No clusters available. Add a cluster to get started.',
|
||||||
|
'noresult.resources.worker':
|
||||||
|
'No workers available. Add a worker to get started.',
|
||||||
|
'noresult.resources.gotocluster': 'Create Your First Cluster',
|
||||||
|
'noresult.resources.gotoworker': 'Add Worker'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -156,5 +156,9 @@ export default {
|
|||||||
'playground.image.generate.error':
|
'playground.image.generate.error':
|
||||||
'Something went wrong. The image could not be generated.',
|
'Something went wrong. The image could not be generated.',
|
||||||
'playground.uploadfile.sizeError':
|
'playground.uploadfile.sizeError':
|
||||||
'File size exceeds the limit. Maximum allowed: {size}.'
|
'File size exceeds the limit. Maximum allowed: {size}.',
|
||||||
|
'playground.uploadImage.url.invalid':
|
||||||
|
'Please enter a direct image URL (e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
'playground.uploadImage.url.holder': 'Enter an image URL',
|
||||||
|
'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -80,7 +80,10 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
|||||||
'clusters.addworker.autoDetect': 'Auto-detect',
|
'clusters.addworker.autoDetect': 'Auto-detect',
|
||||||
'clusters.addworker.extraVolume.holder':
|
'clusters.addworker.extraVolume.holder':
|
||||||
'e.g. /data/models (path must start with /)',
|
'e.g. /data/models (path must start with /)',
|
||||||
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device'
|
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
||||||
|
'clusters.button.genToken':
|
||||||
|
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
|
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -151,5 +154,7 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
|||||||
// 65. 'clusters.addworker.notSpecified': 'Not Specified',
|
// 65. 'clusters.addworker.notSpecified': 'Not Specified',
|
||||||
// 66. 'clusters.addworker.autoDetect': 'Auto-detect',
|
// 66. 'clusters.addworker.autoDetect': 'Auto-detect',
|
||||||
// 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)'
|
// 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)'
|
||||||
// 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device'
|
// 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
||||||
|
// 69. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
|
// 70. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -70,11 +70,11 @@ export default {
|
|||||||
'models.form.ollamalink':
|
'models.form.ollamalink':
|
||||||
'<a href="https://www.ollama.com/library" target="_blank">Ollamaライブラリ</a>でさらに探す',
|
'<a href="https://www.ollama.com/library" target="_blank">Ollamaライブラリ</a>でさらに探す',
|
||||||
'models.form.backend_parameters.llamabox.placeholder':
|
'models.form.backend_parameters.llamabox.placeholder':
|
||||||
'例: --ctx-size=8192(=で名前と値を分ける)',
|
'例: --ctx-size=8192(=または空白で名前と値を分ける)',
|
||||||
'models.form.backend_parameters.vllm.placeholder':
|
'models.form.backend_parameters.vllm.placeholder':
|
||||||
'例: --max-model-len=8192(=で名前と値を分ける)',
|
'例: --max-model-len=8192(=または空白で名前と値を分ける)',
|
||||||
'models.form.backend_parameters.sglang.placeholder':
|
'models.form.backend_parameters.sglang.placeholder':
|
||||||
'例: --context-length=8192(=で名前と値を分ける)',
|
'例: --context-length=8192(=または空白で名前と値を分ける)',
|
||||||
'models.form.backend_parameters.vllm.tips':
|
'models.form.backend_parameters.vllm.tips':
|
||||||
'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
||||||
'models.logs.pagination.prev': '前の{lines}行',
|
'models.logs.pagination.prev': '前の{lines}行',
|
||||||
@@ -260,7 +260,15 @@ export default {
|
|||||||
'models.form.generic_proxy.button': 'Generic Proxy',
|
'models.form.generic_proxy.button': 'Generic Proxy',
|
||||||
'models.accessControlModal.includeusers': 'Include Users',
|
'models.accessControlModal.includeusers': 'Include Users',
|
||||||
'models.table.genericProxy':
|
'models.table.genericProxy':
|
||||||
'Use the following path prefix, and set the model name in either the <span class="bold-text">X-GPUStack-Model</span> request header or the model field in the request body. All requests under this path prefix will be forwarded to the inference backend.'
|
'Use the following path prefix, and set the model name in either the <span class="bold-text">X-GPUStack-Model</span> request header or the model field in the request body. All requests under this path prefix will be forwarded to the inference backend.',
|
||||||
|
'models.form.backendVersion.deprecated': 'Deprecated',
|
||||||
|
'models.accessSettings.public.desc':
|
||||||
|
'Accessible to anyone without authentication.',
|
||||||
|
'models.accessSettings.authed.tips':
|
||||||
|
'Accessible to all authenticated platform users.',
|
||||||
|
'models.accessSettings.allowedUsers.tips':
|
||||||
|
'Only designated users can access the model.',
|
||||||
|
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -347,5 +355,10 @@ export default {
|
|||||||
// 64. 'models.table.userSelection.tips': 'Admin users can access all models by default.',
|
// 64. 'models.table.userSelection.tips': 'Admin users can access all models by default.',
|
||||||
// 65. 'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
|
// 65. 'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
|
||||||
// 66. 'models.form.backend.warning': 'The selected backend does not support GGUF models. Please add a backend with GGUF support in the Inference Backend.',
|
// 66. 'models.form.backend.warning': 'The selected backend does not support GGUF models. Please add a backend with GGUF support in the Inference Backend.',
|
||||||
// 67. 'models.form.backend.warning.gguf': 'Please ensure that the selected custom backend supports GGUF models.',
|
// 67. 'models.form.backend.warning.gguf': 'Please ensure that the selected custom backend supports GGUF models.',,
|
||||||
|
// 68. 'models.form.backendVersion.deprecated': 'Deprecated',
|
||||||
|
// 69. 'models.accessSettings.public.desc': 'Accessible to anyone without authentication.',
|
||||||
|
// 70. 'models.accessSettings.authed.tips': 'Accessible to all authenticated platform users.',
|
||||||
|
// 71.'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.',
|
||||||
|
// 72. 'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -33,5 +33,11 @@ export default {
|
|||||||
'noresult.keys.nofound': 'No matching API keys found.',
|
'noresult.keys.nofound': 'No matching API keys found.',
|
||||||
'noresult.catalog.title': 'No Models',
|
'noresult.catalog.title': 'No Models',
|
||||||
'noresult.catalog.subTitle': 'No models have been configured yet.',
|
'noresult.catalog.subTitle': 'No models have been configured yet.',
|
||||||
'noresult.catalog.nofound': 'No matching models found.'
|
'noresult.catalog.nofound': 'No matching models found.',
|
||||||
|
'noresult.resources.cluster':
|
||||||
|
'No clusters available. Add a cluster to get started.',
|
||||||
|
'noresult.resources.worker':
|
||||||
|
'No workers available. Add a worker to get started.',
|
||||||
|
'noresult.resources.gotocluster': 'Create Your First Cluster',
|
||||||
|
'noresult.resources.gotoworker': 'Add Worker'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -159,11 +159,18 @@ export default {
|
|||||||
'playground.image.generate.error':
|
'playground.image.generate.error':
|
||||||
'Something went wrong. The image could not be generated.',
|
'Something went wrong. The image could not be generated.',
|
||||||
'playground.uploadfile.sizeError':
|
'playground.uploadfile.sizeError':
|
||||||
'File size exceeds the limit. Maximum allowed: {size}.'
|
'File size exceeds the limit. Maximum allowed: {size}.',
|
||||||
|
'playground.uploadImage.url.invalid':
|
||||||
|
'Please enter a direct image URL (e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
'playground.uploadImage.url.holder': 'Enter an image URL',
|
||||||
|
'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'playground.rerank.query.validate': 'The query is required.'
|
// 1. 'playground.rerank.query.validate': 'The query is required.'
|
||||||
// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.',
|
// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.',
|
||||||
// 3. 'playground.uploadfile.sizeError': 'File size exceeds the limit. Maximum allowed: {size}.'
|
// 3. 'playground.uploadfile.sizeError': 'File size exceeds the limit. Maximum allowed: {size}.'
|
||||||
|
// 4. 'playground.uploadImage.url.invalid': 'Please enter a direct image URL(e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
// 5. 'playground.uploadImage.url.holder': 'Enter an image URL'
|
||||||
|
// 6. 'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ export default {
|
|||||||
'На Kubernetes кластере, который необходимо добавить, выполните следующую команду, чтобы присоединить его узлы к кластеру.',
|
'На Kubernetes кластере, который необходимо добавить, выполните следующую команду, чтобы присоединить его узлы к кластеру.',
|
||||||
'cluster.provider.comingsoon': 'Скоро будет',
|
'cluster.provider.comingsoon': 'Скоро будет',
|
||||||
'clusters.addworker.nvidiaNotes-01':
|
'clusters.addworker.nvidiaNotes-01':
|
||||||
'If multiple outbound IPs exist, specify the one you want the worker to use. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
'Если существует несколько исходящих IP-адресов, укажите тот, который должен использовать воркер. Пожалуйста, перепроверьте с помощью <span class="bold-text">hostname -I | xargs -n1</span>.',
|
||||||
'clusters.addworker.nvidiaNotes-02':
|
'clusters.addworker.nvidiaNotes-02':
|
||||||
'If a model directory already exists on the worker, you can specify the path to mount it.',
|
'Если директория с моделями уже существует на воркере, вы можете указать путь для её монтирования.',
|
||||||
'clusters.addworker.hygonNotes':
|
'clusters.addworker.hygonNotes':
|
||||||
'Если директория <span class="bold-text">/opt/hyhal</span> не существует, создайте символическую ссылку на путь установки Hygon: <span class="bold-text">/opt/hyhal</span>. Аналогично для директории <span class="bold-text">/opt/dtk</span>.',
|
'Если директория <span class="bold-text">/opt/hyhal</span> не существует, создайте символическую ссылку на путь установки Hygon: <span class="bold-text">/opt/hyhal</span>. Аналогично для директории <span class="bold-text">/opt/dtk</span>.',
|
||||||
'clusters.addworker.corexNotes':
|
'clusters.addworker.corexNotes':
|
||||||
@@ -60,49 +60,33 @@ export default {
|
|||||||
'clusters.addworker.cambriconNotes':
|
'clusters.addworker.cambriconNotes':
|
||||||
'Если директория <span class="bold-text">/usr/local/neuware</span> не существует, создайте символическую ссылку на путь установки Cambricon: <span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.',
|
'Если директория <span class="bold-text">/usr/local/neuware</span> не существует, создайте символическую ссылку на путь установки Cambricon: <span class="bold-text">ln -s /path/to/neuware /usr/local/neuware</span>.',
|
||||||
'clusters.addworker.hygonNotes-02':
|
'clusters.addworker.hygonNotes-02':
|
||||||
'If failed to detect devices, please try to remove <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
'Если не удается обнаружить устройства, попробуйте удалить <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
||||||
'clusters.addworker.selectCluster': 'Select Cluster',
|
'clusters.addworker.selectCluster': 'Выбрать кластер',
|
||||||
'clusters.addworker.selectCluster.tips':
|
'clusters.addworker.selectCluster.tips':
|
||||||
'For non-Docker clusters, please register clusters or manage worker pools from the Clusters page.',
|
'Для не-Docker кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.',
|
||||||
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
'clusters.addworker.selectGPU': 'Выбрать производителя GPU',
|
||||||
'clusters.addworker.checkEnv': 'Check Environment',
|
'clusters.addworker.checkEnv': 'Проверить окружение',
|
||||||
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
'clusters.addworker.specifyArgs': 'Указать аргументы',
|
||||||
'clusters.addworker.runCommand': 'Run Command',
|
'clusters.addworker.runCommand': 'Выполнить команду',
|
||||||
'clusters.addworker.specifyWorkerIP': 'Specify Worker IP',
|
'clusters.addworker.specifyWorkerIP': 'Указать IP воркера',
|
||||||
'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
|
'clusters.addworker.detectWorkerIP': 'Автоопределение IP воркера',
|
||||||
'clusters.addworker.enterWorkerIP': 'Enter worker IP',
|
'clusters.addworker.enterWorkerIP': 'Введите IP воркера',
|
||||||
'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
|
'clusters.addworker.enterWorkerIP.error': 'Пожалуйста, введите IP воркера.',
|
||||||
'clusters.addworker.extraVolume': 'Additional Volume Mount',
|
'clusters.addworker.extraVolume': 'Дополнительное монтирование тома',
|
||||||
'clusters.addworker.configSummary': 'Configuration Summary',
|
'clusters.addworker.configSummary': 'Сводка конфигурации',
|
||||||
'clusters.addworker.gpuVendor': 'GPU Vendor',
|
'clusters.addworker.gpuVendor': 'Производитель GPU',
|
||||||
'clusters.addworker.workerIP': 'Worker IP',
|
'clusters.addworker.workerIP': 'IP воркера',
|
||||||
'clusters.addworker.notSpecified': 'Not Specified',
|
'clusters.addworker.notSpecified': 'Не указано',
|
||||||
'clusters.addworker.autoDetect': 'Auto-detect',
|
'clusters.addworker.autoDetect': 'Автоопределение',
|
||||||
'clusters.addworker.extraVolume.holder':
|
'clusters.addworker.extraVolume.holder':
|
||||||
'e.g. /data/models (path must start with /)',
|
'напр. /data/models (путь должен начинаться с /)',
|
||||||
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device'
|
'clusters.addworker.vendorNotes.title': 'Примечания для устройств {vendor}',
|
||||||
|
'clusters.button.genToken':
|
||||||
|
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
|
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'clusters.addworker.hygonNotes-02': 'If failed to detect devices, please try to remove <span class="bold-text">--env ROCM_SMI_LIB_PATH=/opt/hyhal/lib</span>.',
|
// 1. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`,
|
||||||
// 2. 'clusters.addworker.selectCluster': 'Select Cluster',
|
// 2. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
// 3. 'clusters.addworker.selectCluster.tips': 'For non-Docker clusters, please register clusters or manage worker pools from the Clusters page.',
|
// ================================================================
|
||||||
// 4. 'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
|
||||||
// 5. 'clusters.addworker.checkEnv': 'Check Environment',
|
|
||||||
// 6. 'clusters.addworker.specifyArgs': 'Specify Arguments',
|
|
||||||
// 7. 'clusters.addworker.runCommand': 'Run Command',
|
|
||||||
// 8. 'clusters.addworker.specifyWorkerIP': 'Specify Worker IP',
|
|
||||||
// 9. 'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
|
|
||||||
// 10. 'clusters.addworker.enterWorkerIP': 'Enter worker IP',
|
|
||||||
// 11. 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
|
|
||||||
// 12. 'clusters.addworker.extraVolume': 'Additional Volume Mount',
|
|
||||||
// 13. 'clusters.addworker.configSummary': 'Configuration Summary',
|
|
||||||
// 14. 'clusters.addworker.gpuVendor': 'GPU Vendor',
|
|
||||||
// 15. 'clusters.addworker.workerIP': 'Worker IP',
|
|
||||||
// 16. 'clusters.addworker.notSpecified': 'Not Specified',
|
|
||||||
// 17. 'clusters.addworker.nvidiaNotes-01': 'If multiple outbound IPs exist, specify the one you want the worker to use. Please double-check with <span class="bold-text">hostname -I | xargs -n1</span>.',
|
|
||||||
// 18. 'clusters.addworker.nvidiaNotes-02': 'If a model directory already exists on the worker, you can specify the path to mount it.',
|
|
||||||
// 19. 'clusters.addworker.autoDetect': 'Auto-detect',
|
|
||||||
// 20. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)',
|
|
||||||
// 21. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device'
|
|
||||||
// ========== End of To-Do List ==========
|
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ export default {
|
|||||||
'dashboard.usage.datePicker.last7days': 'Последние 7 Days',
|
'dashboard.usage.datePicker.last7days': 'Последние 7 Days',
|
||||||
'dashboard.usage.datePicker.last30days': 'Последние 30 Days',
|
'dashboard.usage.datePicker.last30days': 'Последние 30 Days',
|
||||||
'dashboard.usage.datePicker.last60days': 'Последние 60 Days',
|
'dashboard.usage.datePicker.last60days': 'Последние 60 Days',
|
||||||
'dashboard.clusters': 'Clusters'
|
'dashboard.clusters': 'Кластеры'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'dashboard.clusters': 'Clusters',
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
+38
-36
@@ -2,7 +2,7 @@ export default {
|
|||||||
'models.button.deploy': 'Развернуть модель',
|
'models.button.deploy': 'Развернуть модель',
|
||||||
'models.title': 'Модели',
|
'models.title': 'Модели',
|
||||||
'models.title.edit': 'Редактировать модель',
|
'models.title.edit': 'Редактировать модель',
|
||||||
'models.table.models': 'модели',
|
'models.table.models': 'Модели',
|
||||||
'models.table.name': 'Название модели',
|
'models.table.name': 'Название модели',
|
||||||
'models.form.source': 'Источник',
|
'models.form.source': 'Источник',
|
||||||
'models.form.repoid': 'ID репозитория',
|
'models.form.repoid': 'ID репозитория',
|
||||||
@@ -13,8 +13,10 @@ export default {
|
|||||||
'models.form.env': 'Переменные окружения',
|
'models.form.env': 'Переменные окружения',
|
||||||
'models.form.configurations': 'Конфигурации',
|
'models.form.configurations': 'Конфигурации',
|
||||||
'models.form.s3address': 'S3-адрес',
|
'models.form.s3address': 'S3-адрес',
|
||||||
'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`, // Already translated
|
'models.form.partialoffload.tips':
|
||||||
'models.form.distribution.tips': `Позволяет переносить часть слоёв модели на один или несколько удалённых воркеров, когда ресурсов текущего воркера недостаточно.`,
|
'При включении CPU оффлоудинга GPUStack будет выделять оперативную память, если ресурсов GPU недостаточно. Вы должны правильно настроить бэкенд вывода для использования гибридного CPU+GPU или полного CPU вывода.',
|
||||||
|
'models.form.distribution.tips':
|
||||||
|
'Позволяет переносить часть слоёв модели на один или несколько удалённых воркеров, когда ресурсов текущего воркера недостаточно.',
|
||||||
'models.openinplayground': 'Открыть в Песочнице',
|
'models.openinplayground': 'Открыть в Песочнице',
|
||||||
'models.instances': 'инстансы',
|
'models.instances': 'инстансы',
|
||||||
'models.table.replicas.edit': 'Редактировать реплики',
|
'models.table.replicas.edit': 'Редактировать реплики',
|
||||||
@@ -69,13 +71,13 @@ export default {
|
|||||||
'models.form.ollamalink':
|
'models.form.ollamalink':
|
||||||
'Больше моделей в библиотеке <a href="https://www.ollama.com/library" target="_blank">Ollama</a>',
|
'Больше моделей в библиотеке <a href="https://www.ollama.com/library" target="_blank">Ollama</a>',
|
||||||
'models.form.backend_parameters.llamabox.placeholder':
|
'models.form.backend_parameters.llamabox.placeholder':
|
||||||
'например: --ctx-size=8192(параметр и значение разделены знаком =)',
|
'например: --ctx-size=8192(параметр и значение разделены знаком = или пробелом)',
|
||||||
'models.form.backend_parameters.vllm.placeholder':
|
'models.form.backend_parameters.vllm.placeholder':
|
||||||
'например: --max-model-len=8192(параметр и значение разделены знаком =)',
|
'например: --max-model-len=8192(параметр и значение разделены знаком = или пробелом)',
|
||||||
'models.form.backend_parameters.sglang.placeholder':
|
'models.form.backend_parameters.sglang.placeholder':
|
||||||
'например: --context-length=8192(параметр и значение разделены знаком =)',
|
'например: --context-length=8192(параметр и значение разделены знаком = или пробелом)',
|
||||||
'models.form.backend_parameters.vllm.tips':
|
'models.form.backend_parameters.vllm.tips':
|
||||||
'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
'Для получения подробной информации о параметрах {backend} см. <a href={link} target="_blank">здесь</a>.',
|
||||||
'models.logs.pagination.prev': 'Предыдущие {lines} строк',
|
'models.logs.pagination.prev': 'Предыдущие {lines} строк',
|
||||||
'models.logs.pagination.next': 'Следующие {lines} строк',
|
'models.logs.pagination.next': 'Следующие {lines} строк',
|
||||||
'models.logs.pagination.last': 'Последняя страница',
|
'models.logs.pagination.last': 'Последняя страница',
|
||||||
@@ -89,11 +91,11 @@ export default {
|
|||||||
'models.form.backend.llamabox':
|
'models.form.backend.llamabox':
|
||||||
'Для моделей формата GGUF. Поддержка Linux, macOS и Windows.',
|
'Для моделей формата GGUF. Поддержка Linux, macOS и Windows.',
|
||||||
'models.form.backend.vllm':
|
'models.form.backend.vllm':
|
||||||
'Built-in support for NVIDIA, AMD, Ascend, Hygon, Iluvatar, and MetaX devices.',
|
'Встроенная поддержка устройств NVIDIA, AMD, Ascend, Hygon, Iluvatar и MetaX.',
|
||||||
'models.form.backend.voxbox': 'Only supports NVIDIA GPUs and CPUs.',
|
'models.form.backend.voxbox': 'Поддерживает только GPU NVIDIA и CPU.',
|
||||||
'models.form.backend.mindie': 'Only supports Ascend NPUs.',
|
'models.form.backend.mindie': 'Поддерживает только Ascend NPU.',
|
||||||
'models.form.backend.sglang':
|
'models.form.backend.sglang':
|
||||||
'Built-in support for NVIDIA/AMD GPUs and Ascend NPUs.',
|
'Встроенная поддержка GPU NVIDIA/AMD и Ascend NPU.',
|
||||||
'models.form.search.gguftips':
|
'models.form.search.gguftips':
|
||||||
'Для воркеров на macOS/Windows отметьте GGUF (для аудиомоделей снимите).',
|
'Для воркеров на macOS/Windows отметьте GGUF (для аудиомоделей снимите).',
|
||||||
'models.form.button.addlabel': 'Добавить метку',
|
'models.form.button.addlabel': 'Добавить метку',
|
||||||
@@ -120,9 +122,9 @@ export default {
|
|||||||
'models.form.moreparameters': 'Описание параметров',
|
'models.form.moreparameters': 'Описание параметров',
|
||||||
'models.table.vram.allocated': 'Выделенная VRAM',
|
'models.table.vram.allocated': 'Выделенная VRAM',
|
||||||
'models.form.backend.warning':
|
'models.form.backend.warning':
|
||||||
'The selected backend does not support GGUF models. Please add a backend with GGUF support in the Inference Backend.',
|
'Выбранный бэкенд не поддерживает модели GGUF. Пожалуйста, добавьте бэкенд с поддержкой GGUF в разделе Бэкенды вывода.',
|
||||||
'models.form.backend.warning.gguf':
|
'models.form.backend.warning.gguf':
|
||||||
'Please ensure that the selected custom backend supports GGUF models.',
|
'Пожалуйста, убедитесь, что выбранный пользовательский бэкенд поддерживает модели GGUF.',
|
||||||
'models.form.ollama.warning':
|
'models.form.ollama.warning':
|
||||||
'Чтобы развернуть бэкенд для моделей Ollama с использованием llama-box , выполните следующие шаги.',
|
'Чтобы развернуть бэкенд для моделей Ollama с использованием llama-box , выполните следующие шаги.',
|
||||||
'models.form.backend.warning.llamabox':
|
'models.form.backend.warning.llamabox':
|
||||||
@@ -147,7 +149,8 @@ export default {
|
|||||||
'Изменения вступят в силу только после удаления и повторного создания инстанса.',
|
'Изменения вступят в силу только после удаления и повторного создания инстанса.',
|
||||||
'models.table.download.progress': 'Прогресс',
|
'models.table.download.progress': 'Прогресс',
|
||||||
'models.table.button.apiAccessInfo': 'Доступ к API',
|
'models.table.button.apiAccessInfo': 'Доступ к API',
|
||||||
'models.table.button.apiAccessInfo.tips': `Для интеграции этой модели со сторонними приложениями используйте следующие данные: URL доступа, имя модели и ключ API. Эти учетные данные необходимы для обеспечения правильного подключения и использования сервиса модели.`, // Translated
|
'models.table.button.apiAccessInfo.tips':
|
||||||
|
'Для интеграции этой модели со сторонними приложениями используйте следующие данные: URL доступа, имя модели и ключ API. Эти учетные данные необходимы для обеспечения правильного подключения и использования сервиса модели.',
|
||||||
'models.table.apiAccessInfo.endpoint': 'URL доступа',
|
'models.table.apiAccessInfo.endpoint': 'URL доступа',
|
||||||
'models.table.apiAccessInfo.modelName': 'Имя модели',
|
'models.table.apiAccessInfo.modelName': 'Имя модели',
|
||||||
'models.table.apiAccessInfo.apikey': 'Ключ API',
|
'models.table.apiAccessInfo.apikey': 'Ключ API',
|
||||||
@@ -165,7 +168,8 @@ export default {
|
|||||||
'<span class="bold-text">После обновления до версии (v0.7.0),</span> все ранее развёрнутые модели продолжат работать в обычном режиме.',
|
'<span class="bold-text">После обновления до версии (v0.7.0),</span> все ранее развёрнутые модели продолжат работать в обычном режиме.',
|
||||||
'models.ollama.deprecated.issue':
|
'models.ollama.deprecated.issue':
|
||||||
'См. связанную проблему: <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979 on GitHub</a>.',
|
'См. связанную проблему: <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">#1979 on GitHub</a>.',
|
||||||
'models.ollama.deprecated.notice': `Источник моделей Ollama объявлен устаревшим начиная с версии v0.6.1. Подробности см. в <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">соответствующем issue на GitHub</a>.`,
|
'models.ollama.deprecated.notice':
|
||||||
|
'Источник моделей Ollama объявлен устаревшим начиная с версии v0.6.1. Подробности см. в <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">соответствующем issue на GitHub</a>.',
|
||||||
'models.backend.mindie.310p':
|
'models.backend.mindie.310p':
|
||||||
'Ascend 310P поддерживает только FP16, поэтому необходимо установить --dtype=float16.',
|
'Ascend 310P поддерживает только FP16, поэтому необходимо установить --dtype=float16.',
|
||||||
'models.form.gpuCount': 'GPU на реплику',
|
'models.form.gpuCount': 'GPU на реплику',
|
||||||
@@ -181,9 +185,9 @@ export default {
|
|||||||
'models.table.accessScope.all': 'Все пользователи',
|
'models.table.accessScope.all': 'Все пользователи',
|
||||||
'models.table.userSelection': 'Выбор пользователей',
|
'models.table.userSelection': 'Выбор пользователей',
|
||||||
'models.button.accessSettings.tips':
|
'models.button.accessSettings.tips':
|
||||||
'Changes to access settings take effect after one minute.',
|
'Изменения в настройках доступа вступают в силу через одну минуту.',
|
||||||
'models.table.userSelection.tips':
|
'models.table.userSelection.tips':
|
||||||
'Admin users can access all models by default.',
|
'Администраторы по умолчанию имеют доступ ко всем моделям.',
|
||||||
'models.table.filterByName': 'Фильтр по имени пользователя',
|
'models.table.filterByName': 'Фильтр по имени пользователя',
|
||||||
'models.table.admin': 'Администратор',
|
'models.table.admin': 'Администратор',
|
||||||
'models.table.noselected': 'Пользователи не выбраны',
|
'models.table.noselected': 'Пользователи не выбраны',
|
||||||
@@ -197,7 +201,7 @@ export default {
|
|||||||
'models.form.maxCPUSize': 'Максимальный размер CPU кэша (ГиБ)',
|
'models.form.maxCPUSize': 'Максимальный размер CPU кэша (ГиБ)',
|
||||||
'models.form.remoteURL': 'URL удаленного хранилища',
|
'models.form.remoteURL': 'URL удаленного хранилища',
|
||||||
'models.form.remoteURL.tips':
|
'models.form.remoteURL.tips':
|
||||||
'Refer to the <a href="https://docs.lmcache.ai/api_reference/configurations.html" target="_blank">configuration documentation</a> for details.',
|
'Подробности см. в <a href="https://docs.lmcache.ai/api_reference/configurations.html" target="_blank">документации по конфигурации</a>.',
|
||||||
'models.form.runCommandPlaceholder':
|
'models.form.runCommandPlaceholder':
|
||||||
'напр., vllm serve Qwen/Qwen2.5-1.5B-Instruct',
|
'напр., vllm serve Qwen/Qwen2.5-1.5B-Instruct',
|
||||||
'models.accessSettings.public': 'Публичный',
|
'models.accessSettings.public': 'Публичный',
|
||||||
@@ -212,7 +216,7 @@ export default {
|
|||||||
'models.form.gpusAllocationType.auto': 'Авто',
|
'models.form.gpusAllocationType.auto': 'Авто',
|
||||||
'models.form.gpusAllocationType.custom': 'Вручную',
|
'models.form.gpusAllocationType.custom': 'Вручную',
|
||||||
'models.form.gpusAllocationType.auto.tips':
|
'models.form.gpusAllocationType.auto.tips':
|
||||||
'The system automatically calculates the GPU count per replica, using powers of two by default and capped by the selected GPUs.',
|
'Система автоматически вычисляет количество GPU на реплику, по умолчанию используя степени двойки и ограничиваясь выбранными GPU.',
|
||||||
'models.form.gpusAllocationType.custom.tips':
|
'models.form.gpusAllocationType.custom.tips':
|
||||||
'Вы можете указать точное количество GPU на реплику.',
|
'Вы можете указать точное количество GPU на реплику.',
|
||||||
'models.mymodels.status.inactive': 'Остановлен',
|
'models.mymodels.status.inactive': 'Остановлен',
|
||||||
@@ -252,31 +256,29 @@ export default {
|
|||||||
'models.form.backend.custom': 'Пользовательский',
|
'models.form.backend.custom': 'Пользовательский',
|
||||||
'models.form.rules.name':
|
'models.form.rules.name':
|
||||||
'До 63 символов; только буквы, цифры, точки (.), подчёркивания (_) и дефисы (-); должно начинаться и заканчиваться буквенно-цифровым символом.',
|
'До 63 символов; только буквы, цифры, точки (.), подчёркивания (_) и дефисы (-); должно начинаться и заканчиваться буквенно-цифровым символом.',
|
||||||
'models.catalog.button.explore': 'Explore More Models',
|
'models.catalog.button.explore': 'Исследовать больше моделей',
|
||||||
'models.catalog.precision': 'Точность',
|
'models.catalog.precision': 'Точность',
|
||||||
'models.form.gpuPerReplica.tips': 'Введите произвольное число',
|
'models.form.gpuPerReplica.tips': 'Введите произвольное число',
|
||||||
'models.form.generic_proxy': 'Включить универсальный прокси',
|
'models.form.generic_proxy': 'Включить универсальный прокси',
|
||||||
'models.form.generic_proxy.tips':
|
'models.form.generic_proxy.tips':
|
||||||
'After enabling the generic proxy, you can access URI paths that do not follow the OpenAI API standard.',
|
'После включения универсального прокси вы можете получать доступ к URI-путям, которые не следуют стандарту OpenAI API.',
|
||||||
'models.form.generic_proxy.button': 'Универсальный прокси',
|
'models.form.generic_proxy.button': 'Универсальный прокси',
|
||||||
'models.accessControlModal.includeusers': 'Включить пользователей',
|
'models.accessControlModal.includeusers': 'Включить пользователей',
|
||||||
'models.table.genericProxy':
|
'models.table.genericProxy':
|
||||||
'Use the following path prefix, and set the model name in either the <span class="bold-text">X-GPUStack-Model</span> request header or the model field in the request body. All requests under this path prefix will be forwarded to the inference backend.'
|
'Используйте следующий префикс пути и укажите имя модели либо в заголовке запроса <span class="bold-text">X-GPUStack-Model</span>, либо в поле model в теле запроса. Все запросы с этим префиксом пути будут перенаправлены в бэкенд вывода.',
|
||||||
|
'models.form.backendVersion.deprecated': 'Устаревший',
|
||||||
|
'models.accessSettings.public.desc':
|
||||||
|
'Accessible to anyone without authentication.',
|
||||||
|
'models.accessSettings.authed.tips':
|
||||||
|
'Accessible to all authenticated platform users.',
|
||||||
|
'models.accessSettings.allowedUsers.tips':
|
||||||
|
'Only designated users can access the model.',
|
||||||
|
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'models.catalog.button.explore': 'Explore More Models',
|
// 1. 'models.accessSettings.public.desc': 'Accessible to anyone without authentication.',
|
||||||
// 2. 'models.table.genericProxy': 'Use the following path prefix, and set the model name in either the <span class="bold-text">X-GPUStack-Model</span> request header or the model field in the request body. All requests under this path prefix will be forwarded to the inference backend.',
|
// 2. 'models.accessSettings.authed.tips': 'Accessible to all authenticated platform users.',
|
||||||
// 3. 'models.form.backend.vllm': 'Built-in support for NVIDIA, AMD, Ascend, Hygon, Iluvatar, and MetaX devices.',
|
// 3. 'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.',
|
||||||
// 4. 'models.form.backend.voxbox': 'Only supports NVIDIA GPUs and CPUs.',
|
// 4. 'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`
|
||||||
// 5. 'models.form.backend.mindie': 'Only supports Ascend NPUs.',
|
|
||||||
// 6. 'models.form.backend.sglang': 'Built-in support for NVIDIA/AMD GPUs and Ascend NPUs.',
|
|
||||||
// 7. 'models.form.gpusAllocationType.auto.tips': 'The system automatically calculates the GPU count per replica, using powers of two by default and capped by the selected GPUs.',
|
|
||||||
// 8. 'models.form.backend_parameters.vllm.tips': 'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
|
|
||||||
// 9. 'models.button.accessSettings.tips': 'Changes to access settings take effect after one minute.',
|
|
||||||
// 10. 'models.table.userSelection.tips': 'Admin users can access all models by default.',
|
|
||||||
// 11. 'models.form.generic_proxy.tips': 'After enabling the generic proxy, you can access URI paths that do not follow the OpenAI API standard.',
|
|
||||||
// 12. 'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
|
|
||||||
// 13. 'models.form.backend.warning': 'The selected backend does not support GGUF models. Please add a backend with GGUF support in the Inference Backend.',
|
|
||||||
// 14. 'models.form.backend.warning.gguf': 'Please ensure that the selected custom backend supports GGUF models.',
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
export default {
|
export default {
|
||||||
'noresult.button.add': 'Добавить сейчас',
|
'noresult.button.add': 'Добавить сейчас',
|
||||||
'noresult.mymodels.title': 'Нет доступных моделей',
|
'noresult.mymodels.title': 'Нет доступных моделей',
|
||||||
'noresult.mymodels.subTitle': 'Обратитесь к администратору для получения доступа.',
|
'noresult.mymodels.subTitle':
|
||||||
|
'Обратитесь к администратору для получения доступа.',
|
||||||
'noresult.mymodels.nofound': 'Подходящие модели не найдены',
|
'noresult.mymodels.nofound': 'Подходящие модели не найдены',
|
||||||
'noresult.deployments.title': 'Нет развернутых моделей',
|
'noresult.deployments.title': 'Нет развернутых моделей',
|
||||||
'noresult.deployments.subTitle': 'Вы еще не развернули ни одной модели. Нажмите кнопку ниже, чтобы начать.',
|
'noresult.deployments.subTitle':
|
||||||
|
'Вы еще не развернули ни одной модели. Нажмите кнопку ниже, чтобы начать.',
|
||||||
'noresult.gpus.title': 'GPU устройства не обнаружены',
|
'noresult.gpus.title': 'GPU устройства не обнаружены',
|
||||||
'noresult.gpus.subTitle': 'Проверьте, что статус Worker READY.',
|
'noresult.gpus.subTitle': 'Проверьте, что статус Worker READY.',
|
||||||
'noresult.gpus.nofound': 'Подходящие GPU устройства не найдены.',
|
'noresult.gpus.nofound': 'Подходящие GPU устройства не найдены.',
|
||||||
@@ -32,5 +34,18 @@ export default {
|
|||||||
'noresult.keys.nofound': 'Подходящие API-ключи не найдены.',
|
'noresult.keys.nofound': 'Подходящие API-ключи не найдены.',
|
||||||
'noresult.catalog.title': 'Нет моделей',
|
'noresult.catalog.title': 'Нет моделей',
|
||||||
'noresult.catalog.subTitle': 'Модели еще не настроены.',
|
'noresult.catalog.subTitle': 'Модели еще не настроены.',
|
||||||
'noresult.catalog.nofound': 'Подходящие модели не найдены.'
|
'noresult.catalog.nofound': 'Подходящие модели не найдены.',
|
||||||
|
'noresult.resources.cluster':
|
||||||
|
'No clusters available. Add a cluster to get started.',
|
||||||
|
'noresult.resources.worker':
|
||||||
|
'No workers available. Add a worker to get started.',
|
||||||
|
'noresult.resources.gotocluster': 'Create Your First Cluster',
|
||||||
|
'noresult.resources.gotoworker': 'Add Worker'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'noresult.resources.cluster': 'No clusters available. Add a cluster to get started.',
|
||||||
|
// 2. 'noresult.resources.worker': 'No workers available. Add a worker to get started.',
|
||||||
|
// 3. 'noresult.resources.gotocluster': 'Create Your First Cluster',
|
||||||
|
// 4. 'noresult.resources.gotoworker': 'Add Worker'
|
||||||
|
// ========================================================================
|
||||||
|
|||||||
@@ -150,10 +150,18 @@ export default {
|
|||||||
'playground.model.noavailable.tips2':
|
'playground.model.noavailable.tips2':
|
||||||
'Если нужная модель не отображается, убедитесь, что она запущена и ей присвоена правильная категория. Если категория указана неверно, её можно изменить вручную в настройках модели.',
|
'Если нужная модель не отображается, убедитесь, что она запущена и ей присвоена правильная категория. Если категория указана неверно, её можно изменить вручную в настройках модели.',
|
||||||
'playground.rerank.query.validate': 'Необходимо указать запрос.',
|
'playground.rerank.query.validate': 'Необходимо указать запрос.',
|
||||||
'playground.image.generate.error': 'Произошла ошибка. Не удалось сгенерировать изображение.',
|
'playground.image.generate.error':
|
||||||
'playground.uploadfile.sizeError': 'Размер файла превышает ограничение. Максимальный размер: {size}.'
|
'Произошла ошибка. Не удалось сгенерировать изображение.',
|
||||||
|
'playground.uploadfile.sizeError':
|
||||||
|
'Размер файла превышает ограничение. Максимальный размер: {size}.',
|
||||||
|
'playground.uploadImage.url.invalid':
|
||||||
|
'Please enter a direct image URL (e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
'playground.uploadImage.url.holder': 'Enter an image URL',
|
||||||
|
'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'playground.uploadImage.url.invalid': 'Please enter a direct image URL(e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
// 2. 'playground.uploadImage.url.holder': 'Enter an image URL',
|
||||||
|
// 3. 'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -78,5 +78,9 @@ export default {
|
|||||||
'clusters.addworker.autoDetect': '自动检测',
|
'clusters.addworker.autoDetect': '自动检测',
|
||||||
'clusters.addworker.extraVolume.holder':
|
'clusters.addworker.extraVolume.holder':
|
||||||
'例如:/data/models(路径需以 / 开头)',
|
'例如:/data/models(路径需以 / 开头)',
|
||||||
'clusters.addworker.vendorNotes.title': '{vendor}设备注意事项'
|
'clusters.addworker.vendorNotes.title': '{vendor}设备注意事项',
|
||||||
|
'clusters.button.genToken':
|
||||||
|
'需要创建令牌?点击<a href="{link}" target="_blank">这里</a>。',
|
||||||
|
'clusters.addworker.amdNotes-01':
|
||||||
|
'如果 <span class="bold-text">/opt/rocm</span> 目录不存在,请创建一个指向已安装 ROCm 路径的符号链接:<span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>。'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,11 +68,11 @@ export default {
|
|||||||
'models.form.ollamalink':
|
'models.form.ollamalink':
|
||||||
'在 <a href="https://www.ollama.com/library" target="_blank">Ollama Library</a> 中查找',
|
'在 <a href="https://www.ollama.com/library" target="_blank">Ollama Library</a> 中查找',
|
||||||
'models.form.backend_parameters.llamabox.placeholder':
|
'models.form.backend_parameters.llamabox.placeholder':
|
||||||
'例如,--ctx-size=8192(参数名和值用 = 号分隔)',
|
'例如,--ctx-size=8192(参数名和值用 = 号或空格分隔)',
|
||||||
'models.form.backend_parameters.vllm.placeholder':
|
'models.form.backend_parameters.vllm.placeholder':
|
||||||
'例如,--max-model-len=8192(参数名和值用 = 号分隔)',
|
'例如,--max-model-len=8192(参数名和值用 = 号或空格分隔)',
|
||||||
'models.form.backend_parameters.sglang.placeholder':
|
'models.form.backend_parameters.sglang.placeholder':
|
||||||
'例如,--context-length=8192(参数名和值用 = 号分隔)',
|
'例如,--context-length=8192(参数名和值用 = 号或空格分隔)',
|
||||||
'models.form.backend_parameters.vllm.tips':
|
'models.form.backend_parameters.vllm.tips':
|
||||||
'更多 {backend} 参数说明查看<a href={link} target="_blank">这里</a>。',
|
'更多 {backend} 参数说明查看<a href={link} target="_blank">这里</a>。',
|
||||||
'models.logs.pagination.prev': '上一 {lines} 行',
|
'models.logs.pagination.prev': '上一 {lines} 行',
|
||||||
@@ -247,5 +247,10 @@ export default {
|
|||||||
'models.form.generic_proxy.button': '通用代理',
|
'models.form.generic_proxy.button': '通用代理',
|
||||||
'models.accessControlModal.includeusers': '显示用户',
|
'models.accessControlModal.includeusers': '显示用户',
|
||||||
'models.table.genericProxy':
|
'models.table.genericProxy':
|
||||||
'使用以下路径前缀,并在请求头 <span class="bold-text">X-GPUStack-Model</span> 或请求体中的 model 字段设置模型名称后访问该模型。该路径的所有子路径请求会被转发到推理后端。'
|
'使用以下路径前缀,并在请求头 <span class="bold-text">X-GPUStack-Model</span> 或请求体中的 model 字段设置模型名称后访问该模型。该路径的所有子路径请求会被转发到推理后端。',
|
||||||
|
'models.form.backendVersion.deprecated': '已弃用',
|
||||||
|
'models.accessSettings.public.desc': '任何人无需认证即可访问。',
|
||||||
|
'models.accessSettings.authed.tips': '平台内所有已认证用户可访问。',
|
||||||
|
'models.accessSettings.allowedUsers.tips': '仅允许选定的特定用户访问。',
|
||||||
|
'models.form.backendVersions.tips': `如需使用更多版本,请前往{link}页面并编辑对应的后端以添加版本。`
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,5 +33,9 @@ export default {
|
|||||||
'noresult.keys.nofound': '未找到匹配的 API 密钥',
|
'noresult.keys.nofound': '未找到匹配的 API 密钥',
|
||||||
'noresult.catalog.title': '暂无模型',
|
'noresult.catalog.title': '暂无模型',
|
||||||
'noresult.catalog.subTitle': '尚未配置任何模型。',
|
'noresult.catalog.subTitle': '尚未配置任何模型。',
|
||||||
'noresult.catalog.nofound': '未找到匹配的模型'
|
'noresult.catalog.nofound': '未找到匹配的模型',
|
||||||
|
'noresult.resources.cluster': '暂无可用集群,请添加集群以开始使用。',
|
||||||
|
'noresult.resources.worker': '暂无可用节点,请添加节点以开始使用。',
|
||||||
|
'noresult.resources.gotocluster': '创建您的第一个集群',
|
||||||
|
'noresult.resources.gotoworker': '添加节点'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -148,5 +148,10 @@ export default {
|
|||||||
'若预期的模型未显示,请检查模型是否已正常运行并被正确分类。如分类不正确,请编辑模型并手动调整其类别。',
|
'若预期的模型未显示,请检查模型是否已正常运行并被正确分类。如分类不正确,请编辑模型并手动调整其类别。',
|
||||||
'playground.rerank.query.validate': '查询内容不能为空',
|
'playground.rerank.query.validate': '查询内容不能为空',
|
||||||
'playground.image.generate.error': '出了一点问题,图片未能生成。',
|
'playground.image.generate.error': '出了一点问题,图片未能生成。',
|
||||||
'playground.uploadfile.sizeError': '上传的文件大小超过限制,最大允许 {size}。'
|
'playground.uploadfile.sizeError':
|
||||||
|
'上传的文件大小超过限制,最大允许 {size}。',
|
||||||
|
'playground.uploadImage.url.invalid':
|
||||||
|
'请输入直接的图片链接(例如:https://…/image.png)。按 ESC 可取消。',
|
||||||
|
'playground.uploadImage.url.holder': '请输入图片链接',
|
||||||
|
'playground.uploadImage.url.button': '从链接添加图片'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DoubleRightOutlined } from '@ant-design/icons';
|
import { DoubleRightOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button } from 'antd';
|
import { Button, FloatButton } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -45,6 +45,7 @@ const InfiniteScroller: React.FC<
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<FloatButton.BackTop visibilityHeight={1000} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
interface ScrollerContextProps {
|
interface ScrollerContextProps {
|
||||||
total: number;
|
total: number; // total pages
|
||||||
current: number;
|
current: number;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
refresh: (nextPage: number) => void;
|
refresh: (nextPage: number) => void;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Empty, EmptyProps, Typography } from 'antd';
|
import { Button, Empty, EmptyProps, Typography } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -54,9 +54,19 @@ const NoResult: React.FC<
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
loadend?: boolean;
|
loadend?: boolean;
|
||||||
dataSource?: any[];
|
dataSource?: any[];
|
||||||
|
buttonText?: React.ReactNode;
|
||||||
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const { filters, noFoundText, loadend, loading, dataSource } = props;
|
const {
|
||||||
|
filters,
|
||||||
|
noFoundText,
|
||||||
|
loadend,
|
||||||
|
loading,
|
||||||
|
dataSource,
|
||||||
|
buttonText,
|
||||||
|
onClick
|
||||||
|
} = props;
|
||||||
|
|
||||||
const hasFilters = useMemo(() => {
|
const hasFilters = useMemo(() => {
|
||||||
const filterValues = _.omit(filters, ['page', 'perPage']);
|
const filterValues = _.omit(filters, ['page', 'perPage']);
|
||||||
@@ -70,6 +80,15 @@ const NoResult: React.FC<
|
|||||||
});
|
});
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
|
const renderChildren = () => {
|
||||||
|
if (!buttonText || !onClick) return null;
|
||||||
|
return (
|
||||||
|
<Button color="primary" variant="filled" onClick={onClick}>
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!loading && loadend && !dataSource?.length ? (
|
{!loading && loadend && !dataSource?.length ? (
|
||||||
@@ -96,7 +115,7 @@ const NoResult: React.FC<
|
|||||||
</Description>
|
</Description>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{!hasFilters && props.children}
|
{!hasFilters && renderChildren()}
|
||||||
</StyledEmpty>
|
</StyledEmpty>
|
||||||
) : (
|
) : (
|
||||||
<span></span>
|
<span></span>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { PageActionType } from '@/config/types';
|
|||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||||
import { Button, ConfigProvider, Table } from 'antd';
|
import { ConfigProvider, Table } from 'antd';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import NoResult from '../_components/no-result';
|
import NoResult from '../_components/no-result';
|
||||||
import PageBox from '../_components/page-box';
|
import PageBox from '../_components/page-box';
|
||||||
@@ -111,11 +111,9 @@ const APIKeys: React.FC = () => {
|
|||||||
})}
|
})}
|
||||||
title={intl.formatMessage({ id: 'noresult.keys.title' })}
|
title={intl.formatMessage({ id: 'noresult.keys.title' })}
|
||||||
subTitle={intl.formatMessage({ id: 'noresult.keys.subTitle' })}
|
subTitle={intl.formatMessage({ id: 'noresult.keys.subTitle' })}
|
||||||
>
|
onClick={handleAddKey}
|
||||||
<Button type="primary" onClick={handleAddKey}>
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
{intl.formatMessage({ id: 'noresult.button.add' })}
|
></NoResult>
|
||||||
</Button>
|
|
||||||
</NoResult>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import CardSkeleton from '@/components/templates/card-skelton';
|
|||||||
import breakpoints from '@/config/breakpoints';
|
import breakpoints from '@/config/breakpoints';
|
||||||
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
||||||
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
||||||
import { Col, FloatButton, Row, Spin } from 'antd';
|
import { Col, Row, Spin } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import ResizeObserver from 'rc-resize-observer';
|
import ResizeObserver from 'rc-resize-observer';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
@@ -139,7 +139,6 @@ const CardList: React.FC<BackendListProps> = (props) => {
|
|||||||
<ListSkeleton span={span} loading={loading} isFirst={isFirst} />
|
<ListSkeleton span={span} loading={loading} isFirst={isFirst} />
|
||||||
</InfiniteScroller>
|
</InfiniteScroller>
|
||||||
</ResizeObserver>
|
</ResizeObserver>
|
||||||
<FloatButton.BackTop visibilityHeight={1000} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import ScrollerModal from '@/components/scroller-modal';
|
import ScrollerModal from '@/components/scroller-modal';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { VersionListItem } from '../config/types';
|
import { VersionListItem } from '../config/types';
|
||||||
import VersionInfo from '../forms/version-info';
|
import VersionInfo from '../forms/version-info';
|
||||||
@@ -7,12 +9,14 @@ import VersionInfo from '../forms/version-info';
|
|||||||
interface VersionInfoModalProps {
|
interface VersionInfoModalProps {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
currentData?: any;
|
currentData?: any;
|
||||||
|
addVersion?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VersionInfoModal: React.FC<VersionInfoModalProps> = ({
|
const VersionInfoModal: React.FC<VersionInfoModalProps> = ({
|
||||||
open,
|
open,
|
||||||
currentData,
|
currentData,
|
||||||
|
addVersion,
|
||||||
onClose
|
onClose
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -55,7 +59,14 @@ const VersionInfoModal: React.FC<VersionInfoModalProps> = ({
|
|||||||
return (
|
return (
|
||||||
<ScrollerModal
|
<ScrollerModal
|
||||||
open={open}
|
open={open}
|
||||||
title={intl.formatMessage({ id: 'backend.versions' })}
|
title={
|
||||||
|
<div className="flex-center gap-16">
|
||||||
|
<span>{intl.formatMessage({ id: 'backend.versions' })}</span>
|
||||||
|
<Button onClick={addVersion} type="link" size="small">
|
||||||
|
<PlusOutlined /> {intl.formatMessage({ id: 'backend.addVersion' })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
width={600}
|
width={600}
|
||||||
centered
|
centered
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
@@ -63,22 +74,6 @@ const VersionInfoModal: React.FC<VersionInfoModalProps> = ({
|
|||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
onOk={onClose}
|
onOk={onClose}
|
||||||
onCancel={onClose}
|
onCancel={onClose}
|
||||||
styles={{
|
|
||||||
content: {
|
|
||||||
padding: '0 0 16px 0'
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
padding: 'var(--ant-modal-content-padding)',
|
|
||||||
paddingBottom: '0'
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
padding: '16px 24px 32px'
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
padding: '16px 24px',
|
|
||||||
margin: '0'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
footer={null}
|
footer={null}
|
||||||
>
|
>
|
||||||
<VersionInfo versionConfigs={versionConfigs} />
|
<VersionInfo versionConfigs={versionConfigs} />
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import SGLangLogo from '@/assets/logo/sglang.png';
|
|||||||
import vLLMLogo from '@/assets/logo/vllm.png';
|
import vLLMLogo from '@/assets/logo/vllm.png';
|
||||||
import VoxBoxLogo from '@/assets/logo/voxbox.png';
|
import VoxBoxLogo from '@/assets/logo/voxbox.png';
|
||||||
import icons from '@/components/icon-font/icons';
|
import icons from '@/components/icon-font/icons';
|
||||||
import { GPUSTACK_API_BASE_URL } from '@/config/settings';
|
|
||||||
import { backendOptionsMap } from '@/pages/llmodels/config/backend-parameters';
|
import { backendOptionsMap } from '@/pages/llmodels/config/backend-parameters';
|
||||||
import {
|
import {
|
||||||
GPUDriverMap,
|
GPUDriverMap,
|
||||||
@@ -153,37 +152,37 @@ export const frameworks = [
|
|||||||
label: 'CANN',
|
label: 'CANN',
|
||||||
value: GPUDriverMap.ASCEND,
|
value: GPUDriverMap.ASCEND,
|
||||||
tips: ManufacturerMap[GPUDriverMap.ASCEND],
|
tips: ManufacturerMap[GPUDriverMap.ASCEND],
|
||||||
locale: true
|
tipLocale: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'DTK',
|
label: 'DTK',
|
||||||
value: GPUDriverMap.HYGON,
|
value: GPUDriverMap.HYGON,
|
||||||
tips: ManufacturerMap[GPUDriverMap.HYGON],
|
tips: ManufacturerMap[GPUDriverMap.HYGON],
|
||||||
locale: true
|
tipLocale: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'MACA',
|
label: 'MACA',
|
||||||
value: GPUDriverMap.METAX,
|
value: GPUDriverMap.METAX,
|
||||||
tips: ManufacturerMap[GPUDriverMap.METAX],
|
tips: ManufacturerMap[GPUDriverMap.METAX],
|
||||||
locale: true
|
tipLocale: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'CoreX',
|
label: 'CoreX',
|
||||||
value: GPUDriverMap.ILUVATAR,
|
value: GPUDriverMap.ILUVATAR,
|
||||||
tips: ManufacturerMap[GPUDriverMap.ILUVATAR],
|
tips: ManufacturerMap[GPUDriverMap.ILUVATAR],
|
||||||
locale: true
|
tipLocale: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'MUSA',
|
label: 'MUSA',
|
||||||
value: GPUDriverMap.MOORE_THREADS,
|
value: GPUDriverMap.MOORE_THREADS,
|
||||||
tips: ManufacturerMap[GPUDriverMap.MOORE_THREADS],
|
tips: ManufacturerMap[GPUDriverMap.MOORE_THREADS],
|
||||||
locale: true
|
tipLocale: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Neuware',
|
label: 'Neuware',
|
||||||
value: GPUDriverMap.CAMBRICON,
|
value: GPUDriverMap.CAMBRICON,
|
||||||
tips: ManufacturerMap[GPUDriverMap.CAMBRICON],
|
tips: ManufacturerMap[GPUDriverMap.CAMBRICON],
|
||||||
locale: true
|
tipLocale: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'CPU',
|
label: 'CPU',
|
||||||
@@ -191,11 +190,23 @@ export const frameworks = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const yamlTemplate = `# backend configuration template
|
export const yamlTemplate = `# ----------------------------------------
|
||||||
|
# custom backend configuration template
|
||||||
|
# ----------------------------------------
|
||||||
|
# backend_name:
|
||||||
|
# - required
|
||||||
|
# - must be endwith '-custom'
|
||||||
|
# version_configs:
|
||||||
|
# - image_name: required
|
||||||
|
# - run_command: optional
|
||||||
|
# - custom_framework:
|
||||||
|
# - optional
|
||||||
|
# - choose from: ${Object.values(GPUDriverMap).join(', ')}, CPU
|
||||||
|
|
||||||
backend_name: vllm-custom
|
backend_name: vllm-custom
|
||||||
description: this is my custom vllm backend
|
description: this is my custom vllm backend
|
||||||
default_version: v0.11.0
|
default_version: v0.11.0
|
||||||
health_check_path: /${GPUSTACK_API_BASE_URL}/models
|
health_check_path: /v1/models
|
||||||
default_backend_param:
|
default_backend_param:
|
||||||
- --host
|
- --host
|
||||||
default_run_command: vllm serve {{model_path}} --port {{port}} --host {{worker_ip}} --served-model-name {{model_name}}
|
default_run_command: vllm serve {{model_path}} --port {{port}} --host {{worker_ip}} --served-model-name {{model_name}}
|
||||||
|
|||||||
@@ -66,8 +66,7 @@ const FilterBox = styled.div`
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
padding-bottom: 16px;
|
margin-block: 16px;
|
||||||
padding-top: 16px;
|
|
||||||
background-color: var(--ant-color-bg-container);
|
background-color: var(--ant-color-bg-container);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ const VersionsForm: React.FC<AddModalProps> = ({
|
|||||||
<span>
|
<span>
|
||||||
{option.label}
|
{option.label}
|
||||||
{data.tips ? (
|
{data.tips ? (
|
||||||
data.locale ? (
|
data.tipLocale ? (
|
||||||
<span className="text-tertiary m-l-4">{` [${intl.formatMessage({ id: data.tips })}]`}</span>
|
<span className="text-tertiary m-l-4">{` [${intl.formatMessage({ id: data.tips })}]`}</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-tertiary m-l-4">{` [${data.tips}]`}</span>
|
<span className="text-tertiary m-l-4">{` [${data.tips}]`}</span>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { PageActionType } from '@/config/types';
|
|||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||||
import { Button } from 'antd';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context';
|
import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context';
|
||||||
@@ -155,6 +154,18 @@ const BackendList = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddVersion = () => {
|
||||||
|
setOpenVersionInfoModal({
|
||||||
|
open: false,
|
||||||
|
currentData: undefined
|
||||||
|
});
|
||||||
|
setOpenModalStatus({
|
||||||
|
open: true,
|
||||||
|
action: 'edit',
|
||||||
|
currentData: openVersionInfoModal.currentData
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const loadMore = useMemoizedFn((nextPage: number) => {
|
const loadMore = useMemoizedFn((nextPage: number) => {
|
||||||
fetchData({
|
fetchData({
|
||||||
query: {
|
query: {
|
||||||
@@ -209,11 +220,9 @@ const BackendList = () => {
|
|||||||
})}
|
})}
|
||||||
title={intl.formatMessage({ id: 'noresult.backend.title' })}
|
title={intl.formatMessage({ id: 'noresult.backend.title' })}
|
||||||
subTitle={intl.formatMessage({ id: 'noresult.backend.subTitle' })}
|
subTitle={intl.formatMessage({ id: 'noresult.backend.subTitle' })}
|
||||||
>
|
onClick={handleAddBackend}
|
||||||
<Button type="primary" onClick={handleAddBackend}>
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
{intl.formatMessage({ id: 'noresult.button.add' })}
|
></NoResult>
|
||||||
</Button>
|
|
||||||
</NoResult>
|
|
||||||
</ScrollerContext.Provider>
|
</ScrollerContext.Provider>
|
||||||
<AddModal
|
<AddModal
|
||||||
action={openModalStatus.action}
|
action={openModalStatus.action}
|
||||||
@@ -229,6 +238,7 @@ const BackendList = () => {
|
|||||||
}
|
}
|
||||||
></AddModal>
|
></AddModal>
|
||||||
<VersionInfoModal
|
<VersionInfoModal
|
||||||
|
addVersion={handleAddVersion}
|
||||||
open={openVersionInfoModal.open}
|
open={openVersionInfoModal.open}
|
||||||
currentData={openVersionInfoModal.currentData as ListItem}
|
currentData={openVersionInfoModal.currentData as ListItem}
|
||||||
onClose={() =>
|
onClose={() =>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { clusterSessionAtom } from '@/atoms/clusters';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import PageBreadcrumb from '@/pages/_components/page-breadcrumb';
|
import PageBreadcrumb from '@/pages/_components/page-breadcrumb';
|
||||||
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -16,6 +18,7 @@ import FooterButtons from './components/footer-buttons';
|
|||||||
import ProviderCatalog from './components/provider-catalog';
|
import ProviderCatalog from './components/provider-catalog';
|
||||||
import { ProviderType, ProviderValueMap } from './config';
|
import { ProviderType, ProviderValueMap } from './config';
|
||||||
import providerList from './config/providers';
|
import providerList from './config/providers';
|
||||||
|
import { StepsContext } from './config/steps-context';
|
||||||
import { ClusterFormData } from './config/types';
|
import { ClusterFormData } from './config/types';
|
||||||
import { moduleMap, moduleRegistry } from './step-forms/module-registry';
|
import { moduleMap, moduleRegistry } from './step-forms/module-registry';
|
||||||
import useStepList from './step-forms/use-step-list';
|
import useStepList from './step-forms/use-step-list';
|
||||||
@@ -39,6 +42,7 @@ const ClusterCreate = () => {
|
|||||||
const action =
|
const action =
|
||||||
(searchParams.get('action') as PageActionType) || PageAction.CREATE;
|
(searchParams.get('action') as PageActionType) || PageAction.CREATE;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [clusterSession, setClusterSession] = useAtom(clusterSessionAtom);
|
||||||
const [credentialList, setCredentialList] = useState<
|
const [credentialList, setCredentialList] = useState<
|
||||||
Global.BaseOption<number, { provider: ProviderType }>[]
|
Global.BaseOption<number, { provider: ProviderType }>[]
|
||||||
>([]);
|
>([]);
|
||||||
@@ -86,6 +90,7 @@ const ClusterCreate = () => {
|
|||||||
}));
|
}));
|
||||||
}, [action, extraData.provider, stepList]);
|
}, [action, extraData.provider, stepList]);
|
||||||
|
|
||||||
|
// before moving to the next step, get all form values
|
||||||
const getFormFieldsValue = () => {
|
const getFormFieldsValue = () => {
|
||||||
setFormValues((prev) => {
|
setFormValues((prev) => {
|
||||||
const newFormValues = _.cloneDeep(prev);
|
const newFormValues = _.cloneDeep(prev);
|
||||||
@@ -203,7 +208,7 @@ const ClusterCreate = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this function is used to render the modules in the current step
|
* this function is used to render the modules in the current step, they are not forms
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const renderModules = () => {
|
const renderModules = () => {
|
||||||
@@ -253,6 +258,14 @@ const ClusterCreate = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
if (clusterSession?.firstAddCluster) {
|
||||||
|
setClusterSession({
|
||||||
|
firstAddCluster: false,
|
||||||
|
firstAddWorker: false
|
||||||
|
});
|
||||||
|
navigate(`/cluster-management/clusters/list`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -293,10 +306,16 @@ const ClusterCreate = () => {
|
|||||||
current={extraData.provider}
|
current={extraData.provider}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{renderModules()}
|
<StepsContext.Provider
|
||||||
<Container>
|
value={{
|
||||||
<Content>{renderForms()}</Content>
|
formValues: formValues
|
||||||
</Container>
|
}}
|
||||||
|
>
|
||||||
|
{renderModules()}
|
||||||
|
<Container>
|
||||||
|
<Content>{renderForms()}</Content>
|
||||||
|
</Container>
|
||||||
|
</StepsContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
</PageContainerInner>
|
</PageContainerInner>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { expandKeysAtom } from '@/atoms/clusters';
|
import { clusterSessionAtom, expandKeysAtom } from '@/atoms/clusters';
|
||||||
import DeleteModal from '@/components/delete-modal';
|
import DeleteModal from '@/components/delete-modal';
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import { FilterBar } from '@/components/page-tools';
|
import { FilterBar } from '@/components/page-tools';
|
||||||
@@ -11,7 +11,7 @@ import useTableFetch from '@/hooks/use-table-fetch';
|
|||||||
import useWatchList from '@/hooks/use-watch-list';
|
import useWatchList from '@/hooks/use-watch-list';
|
||||||
import { useIntl, useNavigate } from '@umijs/max';
|
import { useIntl, useNavigate } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Button, message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import NoResult from '../_components/no-result';
|
import NoResult from '../_components/no-result';
|
||||||
@@ -33,7 +33,11 @@ import {
|
|||||||
K8sStepsFromCluter
|
K8sStepsFromCluter
|
||||||
} from './components/add-worker/config';
|
} from './components/add-worker/config';
|
||||||
import PoolRows from './components/pool-rows';
|
import PoolRows from './components/pool-rows';
|
||||||
import { ProviderType, ProviderValueMap } from './config';
|
import {
|
||||||
|
ClusterStatusValueMap,
|
||||||
|
ProviderType,
|
||||||
|
ProviderValueMap
|
||||||
|
} from './config';
|
||||||
import {
|
import {
|
||||||
ClusterListItem,
|
ClusterListItem,
|
||||||
CredentialListItem,
|
CredentialListItem,
|
||||||
@@ -65,6 +69,7 @@ const Clusters: React.FC = () => {
|
|||||||
});
|
});
|
||||||
const { watchDataList: allWorkerPoolList } = useWatchList(WORKER_POOLS_API);
|
const { watchDataList: allWorkerPoolList } = useWatchList(WORKER_POOLS_API);
|
||||||
const [expandAtom] = useAtom(expandKeysAtom);
|
const [expandAtom] = useAtom(expandKeysAtom);
|
||||||
|
const [clusterSession, setClusterSession] = useAtom(clusterSessionAtom);
|
||||||
const { handleExpandChange, handleExpandAll, expandedRowKeys } =
|
const { handleExpandChange, handleExpandAll, expandedRowKeys } =
|
||||||
useExpandedRowKeys(expandAtom);
|
useExpandedRowKeys(expandAtom);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -116,7 +121,7 @@ const Clusters: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickDropdown = (item: any) => {
|
const handleClickDropdown = () => {
|
||||||
navigate(`/cluster-management/clusters/create?action=${PageAction.CREATE}`);
|
navigate(`/cluster-management/clusters/create?action=${PageAction.CREATE}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -241,6 +246,35 @@ const Clusters: React.FC = () => {
|
|||||||
fetchCredentialList();
|
fetchCredentialList();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
clusterSession?.firstAddWorker &&
|
||||||
|
dataSource.loadend &&
|
||||||
|
dataSource.dataList?.length > 0
|
||||||
|
) {
|
||||||
|
const targetCluster = dataSource.dataList.find(
|
||||||
|
(cluster) =>
|
||||||
|
cluster.state === ClusterStatusValueMap.Ready &&
|
||||||
|
!cluster.workers &&
|
||||||
|
!cluster.worker_pools?.length
|
||||||
|
);
|
||||||
|
|
||||||
|
if (targetCluster) {
|
||||||
|
const actionMap = {
|
||||||
|
[ProviderValueMap.Docker]: 'add_worker',
|
||||||
|
[ProviderValueMap.Kubernetes]: 'register_cluster',
|
||||||
|
[ProviderValueMap.DigitalOcean]: 'addPool'
|
||||||
|
};
|
||||||
|
handleSelect(
|
||||||
|
actionMap[targetCluster.provider as string],
|
||||||
|
targetCluster
|
||||||
|
);
|
||||||
|
// reset session
|
||||||
|
setClusterSession(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [clusterSession, dataSource.loadend, dataSource.dataList]);
|
||||||
|
|
||||||
const renderChildren = (
|
const renderChildren = (
|
||||||
list: any,
|
list: any,
|
||||||
options: { parent?: any; [key: string]: any }
|
options: { parent?: any; [key: string]: any }
|
||||||
@@ -305,11 +339,9 @@ const Clusters: React.FC = () => {
|
|||||||
subTitle={intl.formatMessage({
|
subTitle={intl.formatMessage({
|
||||||
id: 'noresult.cluster.subTitle'
|
id: 'noresult.cluster.subTitle'
|
||||||
})}
|
})}
|
||||||
>
|
onClick={handleClickDropdown}
|
||||||
<Button type="primary" onClick={handleClickDropdown}>
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
{intl.formatMessage({ id: 'noresult.button.add' })}
|
></NoResult>
|
||||||
</Button>
|
|
||||||
</NoResult>
|
|
||||||
}
|
}
|
||||||
pagination={{
|
pagination={{
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
|
|||||||
@@ -7,12 +7,24 @@ import useAppUtils from '@/hooks/use-app-utils';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { ProviderType, ProviderValueMap } from '../config';
|
import { ProviderType, ProviderValueMap } from '../config';
|
||||||
import {
|
import {
|
||||||
CredentialFormData as FormData,
|
CredentialFormData as FormData,
|
||||||
CredentialListItem as ListItem
|
CredentialListItem as ListItem
|
||||||
} from '../config/types';
|
} from '../config/types';
|
||||||
|
|
||||||
|
const ExtraContent = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 6px;
|
||||||
|
right: 32px;
|
||||||
|
z-index: 1;
|
||||||
|
background: var(--ant-color-bg-container);
|
||||||
|
padding-inline: 8px;
|
||||||
|
`;
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
title: string;
|
title: string;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
@@ -100,8 +112,24 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealInput.Password
|
<SealInput.Password
|
||||||
label={intl.formatMessage({ id: 'clusters.credential.token' })}
|
label={intl.formatMessage({
|
||||||
|
id: 'clusters.credential.token'
|
||||||
|
})}
|
||||||
required={action === PageAction.CREATE}
|
required={action === PageAction.CREATE}
|
||||||
|
description={
|
||||||
|
<span
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: 'clusters.button.genToken'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: 'https://cloud.digitalocean.com/account/api/tokens'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
}
|
||||||
></SealInput.Password>
|
></SealInput.Password>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const SelectVendor = () => {
|
|||||||
updateField('currentGPU', GPUDriverMap.NVIDIA);
|
updateField('currentGPU', GPUDriverMap.NVIDIA);
|
||||||
updateField('workerCommand', {
|
updateField('workerCommand', {
|
||||||
label: 'NVIDIA',
|
label: 'NVIDIA',
|
||||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#nvidia-cuda',
|
link: 'https://docs.gpustack.ai/latest/installation/nvidia/installation/#prerequisites',
|
||||||
notes: AddWorkerDockerNotes[GPUDriverMap.NVIDIA]
|
notes: AddWorkerDockerNotes[GPUDriverMap.NVIDIA]
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import useAppUtils from '@/hooks/use-app-utils';
|
|||||||
import { CardContainer } from '@/pages/llmodels/components/gpu-card';
|
import { CardContainer } from '@/pages/llmodels/components/gpu-card';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Button, Form } from 'antd';
|
import { Button, Form } from 'antd';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -24,6 +25,7 @@ import React, {
|
|||||||
forwardRef,
|
forwardRef,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -85,15 +87,6 @@ const NotFoundContent = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NotFoundImageContent = () => {
|
|
||||||
const intl = useIntl();
|
|
||||||
return (
|
|
||||||
<NoContent>
|
|
||||||
{intl.formatMessage({ id: 'clusters.create.noImages' })}
|
|
||||||
</NoContent>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const RenderLabel = (data: {
|
const RenderLabel = (data: {
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
vendor: string;
|
vendor: string;
|
||||||
@@ -191,37 +184,45 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentData) {
|
if (currentData) {
|
||||||
|
// when change the region, shoudle check the instance type and the os Image.
|
||||||
|
const selectOSImage = osImageList.find(
|
||||||
|
(item) => item.os_image === currentData.os_image
|
||||||
|
);
|
||||||
|
const selectInstanceType = instanceTypeList.find(
|
||||||
|
(item) => item.value === currentData.instance_type
|
||||||
|
);
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...currentData
|
...currentData,
|
||||||
|
instance_type: selectInstanceType?.value || '',
|
||||||
|
os_image: selectOSImage?.os_image || '',
|
||||||
|
image_name: selectOSImage?.value || ''
|
||||||
});
|
});
|
||||||
setInstanceSpec({
|
|
||||||
...currentData.instance_spec
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [currentData]);
|
|
||||||
|
|
||||||
const imageLabelRender = (data: {
|
setInstanceSpec(() => {
|
||||||
label: React.ReactNode;
|
return selectInstanceType ? currentData.instance_spec : {};
|
||||||
value: string | number;
|
});
|
||||||
}) => {
|
|
||||||
if (action === PageAction.EDIT) {
|
|
||||||
const vendor = _.split(currentData?.image_name || '', ' ')[0];
|
|
||||||
const iconType = _.get(vendorIconMap, vendor.toLowerCase());
|
|
||||||
return (
|
|
||||||
<div className="flex-center gap-8">
|
|
||||||
{iconType && <IconFont type={iconType}></IconFont>}
|
|
||||||
{currentData?.image_name || currentData?.os_image}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const selectImage = osImageList.find((item) => item.value === data.value);
|
}, [currentData, instanceTypeList, osImageList]);
|
||||||
if (selectImage) {
|
|
||||||
return (
|
const updateImageList = useMemoizedFn((instanceSpec: Record<string, any>) => {
|
||||||
<RenderLabel label={data.label} vendor={selectImage.vendor || ''} />
|
if (instanceSpec.count === 8) {
|
||||||
);
|
return osImageList.filter((item) => item.os_image === 'gpu-h100x8-base');
|
||||||
}
|
}
|
||||||
return data.value;
|
|
||||||
};
|
if (instanceSpec.count === 1 && instanceSpec.vendor === 'amd') {
|
||||||
|
return osImageList.filter((item) => item.os_image === 'gpu-amd-base');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceSpec.count === 1 && instanceSpec.vendor === 'nvidia') {
|
||||||
|
return osImageList.filter((item) => item.os_image === 'gpu-h100x1-base');
|
||||||
|
}
|
||||||
|
|
||||||
|
return osImageList;
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageList = useMemo(() => {
|
||||||
|
return updateImageList(instanceSpec);
|
||||||
|
}, [osImageList, instanceSpec, updateImageList]);
|
||||||
|
|
||||||
const instanceLabelRender = (data: {
|
const instanceLabelRender = (data: {
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
@@ -230,6 +231,10 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
const currentInstanceSpec =
|
const currentInstanceSpec =
|
||||||
instanceTypeList.find((item) => item.value === data.value) ||
|
instanceTypeList.find((item) => item.value === data.value) ||
|
||||||
instanceSpec;
|
instanceSpec;
|
||||||
|
|
||||||
|
if (!currentInstanceSpec || _.isEmpty(currentInstanceSpec)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<RenderLabel
|
<RenderLabel
|
||||||
label={data.label || currentInstanceSpec?.label || data.value}
|
label={data.label || currentInstanceSpec?.label || data.value}
|
||||||
@@ -238,27 +243,28 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOsImageChange = (value: string) => {
|
const handleOsImageChange = (value: string, option: any) => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
image_name:
|
os_image: option.os_image || value
|
||||||
osImageList.find((item) => item.value === value)?.label || value
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInstanceTypeChange = (value: string, option: any) => {
|
const handleInstanceTypeChange = (value: string, option: any) => {
|
||||||
setInstanceSpec({
|
const newInstanceSpec = {
|
||||||
...option.specInfo,
|
...option.specInfo,
|
||||||
label: option.label,
|
label: option.label,
|
||||||
vendor: option.vendor,
|
vendor: option.vendor,
|
||||||
description: option.description
|
description: option.description,
|
||||||
});
|
count: option.count
|
||||||
|
};
|
||||||
|
setInstanceSpec({ ...newInstanceSpec });
|
||||||
|
|
||||||
|
const newImageList = updateImageList({ ...newInstanceSpec });
|
||||||
|
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
instance_spec: {
|
os_image: newImageList[0]?.os_image,
|
||||||
...option.specInfo,
|
image_name: newImageList[0]?.value,
|
||||||
label: option.label,
|
instance_spec: { ...newInstanceSpec }
|
||||||
vendor: option.vendor,
|
|
||||||
description: option.description
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -402,7 +408,7 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="os_image"
|
name="image_name"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -420,9 +426,7 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
styles: { header: { marginBlock: 5 } }
|
styles: { header: { marginBlock: 5 } }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
labelRender={imageLabelRender}
|
options={imageList}
|
||||||
placeholder={currentData?.image_name}
|
|
||||||
options={osImageList}
|
|
||||||
disabled={action === PageAction.EDIT}
|
disabled={action === PageAction.EDIT}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: 'clusters.workerpool.osImage'
|
id: 'clusters.workerpool.osImage'
|
||||||
@@ -462,7 +466,7 @@ const PoolForm: React.FC<AddModalProps> = forwardRef((props, ref) => {
|
|||||||
></LabelSelector>
|
></LabelSelector>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<VolumesConfig disabled={action === PageAction.EDIT}></VolumesConfig>
|
<VolumesConfig disabled={action === PageAction.EDIT}></VolumesConfig>
|
||||||
<Form.Item<FormData> name="image_name" hidden>
|
<Form.Item<FormData> name="os_image" hidden>
|
||||||
<SealInput.Input></SealInput.Input>
|
<SealInput.Input></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<InstanceSpecData instanceSpec={instanceSpec} />
|
<InstanceSpecData instanceSpec={instanceSpec} />
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
|
|||||||
key: GPUDriverMap.NVIDIA,
|
key: GPUDriverMap.NVIDIA,
|
||||||
locale: false,
|
locale: false,
|
||||||
notes: AddWorkerDockerNotes[GPUDriverMap.NVIDIA],
|
notes: AddWorkerDockerNotes[GPUDriverMap.NVIDIA],
|
||||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#nvidia-cuda',
|
link: 'https://docs.gpustack.ai/latest/installation/nvidia/installation/#prerequisites',
|
||||||
icon: <IconFont type="icon-nvidia2" style={{ fontSize: 32 }} />
|
icon: <IconFont type="icon-nvidia2" style={{ fontSize: 32 }} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,7 +67,7 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
|
|||||||
key: GPUDriverMap.AMD,
|
key: GPUDriverMap.AMD,
|
||||||
locale: false,
|
locale: false,
|
||||||
notes: AddWorkerDockerNotes[GPUDriverMap.AMD],
|
notes: AddWorkerDockerNotes[GPUDriverMap.AMD],
|
||||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#amd-rocm',
|
link: 'https://docs.gpustack.ai/latest/installation/amd/installation/#prerequisites',
|
||||||
icon: (
|
icon: (
|
||||||
<IconFont
|
<IconFont
|
||||||
type="icon-amd"
|
type="icon-amd"
|
||||||
@@ -82,17 +82,17 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
|
|||||||
key: GPUDriverMap.ASCEND,
|
key: GPUDriverMap.ASCEND,
|
||||||
locale: false,
|
locale: false,
|
||||||
notes: AddWorkerDockerNotes[GPUDriverMap.ASCEND],
|
notes: AddWorkerDockerNotes[GPUDriverMap.ASCEND],
|
||||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#ascend-cann',
|
link: 'https://docs.gpustack.ai/latest/installation/ascend/installation/#prerequisites',
|
||||||
icon: <ProviderImage src={ascendLogo} showBg />
|
icon: <ProviderImage src={ascendLogo} showBg />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({ id: 'vendor.hygon' }),
|
label: intl.formatMessage({ id: 'vendor.hygon' }),
|
||||||
description: 'common.tag.experimental',
|
description: '',
|
||||||
value: GPUDriverMap.HYGON,
|
value: GPUDriverMap.HYGON,
|
||||||
key: GPUDriverMap.HYGON,
|
key: GPUDriverMap.HYGON,
|
||||||
locale: false,
|
locale: false,
|
||||||
notes: AddWorkerDockerNotes[GPUDriverMap.HYGON],
|
notes: AddWorkerDockerNotes[GPUDriverMap.HYGON],
|
||||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#hygon-dtk',
|
link: 'https://docs.gpustack.ai/latest/installation/hygon/installation/#prerequisites',
|
||||||
icon: <ProviderImage src={hyponPNG} />
|
icon: <ProviderImage src={hyponPNG} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -102,7 +102,7 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
|
|||||||
key: GPUDriverMap.MOORE_THREADS,
|
key: GPUDriverMap.MOORE_THREADS,
|
||||||
locale: false,
|
locale: false,
|
||||||
notes: AddWorkerDockerNotes[GPUDriverMap.MOORE_THREADS],
|
notes: AddWorkerDockerNotes[GPUDriverMap.MOORE_THREADS],
|
||||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#moore-threads-musa',
|
link: 'https://docs.gpustack.ai/latest/installation/mthreads/installation/#prerequisites',
|
||||||
icon: <ProviderImage src={moorePNG} />
|
icon: <ProviderImage src={moorePNG} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -112,7 +112,7 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
|
|||||||
key: GPUDriverMap.ILUVATAR,
|
key: GPUDriverMap.ILUVATAR,
|
||||||
locale: false,
|
locale: false,
|
||||||
notes: AddWorkerDockerNotes[GPUDriverMap.ILUVATAR],
|
notes: AddWorkerDockerNotes[GPUDriverMap.ILUVATAR],
|
||||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#iluvatar-corex',
|
link: 'https://docs.gpustack.ai/latest/installation/iluvatar/installation/#prerequisites',
|
||||||
icon: <ProviderImage src={iluvatarWEBP} showBg />
|
icon: <ProviderImage src={iluvatarWEBP} showBg />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -122,7 +122,7 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
|
|||||||
key: GPUDriverMap.CAMBRICON,
|
key: GPUDriverMap.CAMBRICON,
|
||||||
locale: false,
|
locale: false,
|
||||||
notes: AddWorkerDockerNotes[GPUDriverMap.CAMBRICON],
|
notes: AddWorkerDockerNotes[GPUDriverMap.CAMBRICON],
|
||||||
link: 'https://docs.gpustack.ai/latest/installation/installation-requirements/#cambricon-mlu',
|
link: 'https://docs.gpustack.ai/latest/installation/cambricon/installation/#prerequisites',
|
||||||
icon: <ProviderImage src={CambriconPNG} />
|
icon: <ProviderImage src={CambriconPNG} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -131,6 +131,7 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
|
|||||||
value: GPUDriverMap.METAX,
|
value: GPUDriverMap.METAX,
|
||||||
key: GPUDriverMap.METAX,
|
key: GPUDriverMap.METAX,
|
||||||
locale: false,
|
locale: false,
|
||||||
|
link: 'https://docs.gpustack.ai/latest/installation/metax/installation/?h=meta#prerequisites',
|
||||||
notes: AddWorkerDockerNotes[GPUDriverMap.METAX],
|
notes: AddWorkerDockerNotes[GPUDriverMap.METAX],
|
||||||
icon: <ProviderImage src={metaxLogo} showBg />
|
icon: <ProviderImage src={metaxLogo} showBg />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
export interface StepsContextProps {
|
||||||
|
formValues: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StepsContext = createContext<StepsContextProps>({
|
||||||
|
formValues: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useStepsContext = () => useContext(StepsContext);
|
||||||
@@ -7,7 +7,7 @@ import type { PageActionType } from '@/config/types';
|
|||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import { useIntl, useNavigate } from '@umijs/max';
|
import { useIntl, useNavigate } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Button, ConfigProvider, Table, message } from 'antd';
|
import { ConfigProvider, Table, message } from 'antd';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import NoResult from '../_components/no-result';
|
import NoResult from '../_components/no-result';
|
||||||
@@ -164,14 +164,9 @@ const Credentials: React.FC = () => {
|
|||||||
subTitle={intl.formatMessage({
|
subTitle={intl.formatMessage({
|
||||||
id: 'noresult.credentials.subTitle'
|
id: 'noresult.credentials.subTitle'
|
||||||
})}
|
})}
|
||||||
>
|
onClick={() => handleAddCredential(addActions[0])}
|
||||||
<Button
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
type="primary"
|
></NoResult>
|
||||||
onClick={() => handleAddCredential(addActions[0])}
|
|
||||||
>
|
|
||||||
{intl.formatMessage({ id: 'noresult.button.add' })}
|
|
||||||
</Button>
|
|
||||||
</NoResult>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
export interface StepsContextProps {
|
||||||
|
formValues: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StepsContext = createContext<StepsContextProps>({
|
||||||
|
formValues: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useStepsContext = () => useContext(StepsContext);
|
||||||
@@ -126,6 +126,7 @@ export const useProviderRegions = () => {
|
|||||||
const label = formatLabel(item);
|
const label = formatLabel(item);
|
||||||
const description = `${label} ${item.gpu_info?.count}X`;
|
const description = `${label} ${item.gpu_info?.count}X`;
|
||||||
return {
|
return {
|
||||||
|
count: item.gpu_info?.count,
|
||||||
label: `${description} - ${formatSpec(specInfo)}`,
|
label: `${description} - ${formatSpec(specInfo)}`,
|
||||||
value: item.slug,
|
value: item.slug,
|
||||||
description: description,
|
description: description,
|
||||||
@@ -150,7 +151,8 @@ export const useProviderRegions = () => {
|
|||||||
.map((item: any) => {
|
.map((item: any) => {
|
||||||
return {
|
return {
|
||||||
label: item.description,
|
label: item.description,
|
||||||
value: item.slug,
|
value: item.description,
|
||||||
|
os_image: item.slug,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
vendor: _.camelCase(item.distribution),
|
vendor: _.camelCase(item.distribution),
|
||||||
@@ -171,9 +173,12 @@ export const useProviderRegions = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateOSImages = (region: string, allImages?: any[]) => {
|
const updateOSImages = (region: string, allImages?: any[]) => {
|
||||||
const list = (allImages || allOSImageList).filter((item) =>
|
const list = (allImages || allOSImageList).filter(
|
||||||
item.regions.includes(region)
|
(item) =>
|
||||||
|
item.regions.includes(region) &&
|
||||||
|
['debian', 'ubuntu'].includes(item.vendor)
|
||||||
);
|
);
|
||||||
|
console.log('osimagelist========', list);
|
||||||
setOSImageList(list);
|
setOSImageList(list);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ const WorkerPoolsForm = forwardRef((props: WorkerPoolsFormProps, ref) => {
|
|||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
console.log('gatherFormValues========', resultList);
|
|
||||||
return resultList.filter((item) => item);
|
return resultList.filter((item) => item);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,7 +134,6 @@ const WorkerPoolsForm = forwardRef((props: WorkerPoolsFormProps, ref) => {
|
|||||||
const newWorkerPools = worker_pools.map(
|
const newWorkerPools = worker_pools.map(
|
||||||
(poolData: NodePoolFormData, index: number) => [index, poolData]
|
(poolData: NodePoolFormData, index: number) => [index, poolData]
|
||||||
);
|
);
|
||||||
console.log('newWorkerPools========', newWorkerPools);
|
|
||||||
setWorkerPoolList(new Map(newWorkerPools));
|
setWorkerPoolList(new Map(newWorkerPools));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,14 +141,12 @@ const WorkerPoolsForm = forwardRef((props: WorkerPoolsFormProps, ref) => {
|
|||||||
const values = Object.values(formRefs.current).map((form) =>
|
const values = Object.values(formRefs.current).map((form) =>
|
||||||
form?.getFieldsValue()
|
form?.getFieldsValue()
|
||||||
);
|
);
|
||||||
console.log('getFieldsValue========', values);
|
|
||||||
return {
|
return {
|
||||||
worker_pools: values
|
worker_pools: values
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnToggle = (open: boolean, key: number) => {
|
const handleOnToggle = (open: boolean, key: number) => {
|
||||||
console.log('Active keys changed:', key);
|
|
||||||
if (open) {
|
if (open) {
|
||||||
setActiveKey((prev) => new Set([key]));
|
setActiveKey((prev) => new Set([key]));
|
||||||
} else {
|
} else {
|
||||||
@@ -170,7 +166,6 @@ const WorkerPoolsForm = forwardRef((props: WorkerPoolsFormProps, ref) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentData) {
|
if (currentData) {
|
||||||
console.log('currentData===========1=', currentData);
|
|
||||||
setFieldsValue(currentData);
|
setFieldsValue(currentData);
|
||||||
}
|
}
|
||||||
}, [currentData]);
|
}, [currentData]);
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
|
|
||||||
:global(.ant-card-body) {
|
:global(.ant-card-body) {
|
||||||
height: 110px;
|
height: 96px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
border-radius: var(--ant-border-radius-lg);
|
border-radius: var(--ant-border-radius-lg);
|
||||||
border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--ant-color-border);
|
||||||
|
padding: 16px 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AlertBlockInfo from '@/components/alert-info/block';
|
import AlertBlockInfo from '@/components/alert-info/block';
|
||||||
|
import TooltipList from '@/components/tooltip-list';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import TransferInner from '@/pages/_components/transfer';
|
import TransferInner from '@/pages/_components/transfer';
|
||||||
@@ -29,7 +30,34 @@ import { AccessControlFormData, ListItem } from '../../config/types';
|
|||||||
|
|
||||||
type TransferKey = string | number | bigint;
|
type TransferKey = string | number | bigint;
|
||||||
|
|
||||||
|
const accessScopeTips = [
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
text: 'models.accessSettings.authed',
|
||||||
|
locale: true
|
||||||
|
},
|
||||||
|
tips: 'models.accessSettings.authed.tips'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
text: 'models.accessSettings.allowedUsers',
|
||||||
|
locale: true
|
||||||
|
},
|
||||||
|
tips: 'models.accessSettings.allowedUsers.tips'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
text: 'models.accessSettings.public',
|
||||||
|
locale: true
|
||||||
|
},
|
||||||
|
tips: 'models.accessSettings.public.desc'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const Label = styled.div`
|
const Label = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-block: 8px 12px;
|
margin-block: 8px 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -263,7 +291,13 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
access_policy: action === PageAction.CREATE ? 'authed' : undefined
|
access_policy: action === PageAction.CREATE ? 'authed' : undefined
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Label>{intl.formatMessage({ id: 'models.table.accessScope' })}</Label>
|
<Label>
|
||||||
|
{intl.formatMessage({ id: 'models.table.accessScope' })}
|
||||||
|
<Tooltip title={<TooltipList list={accessScopeTips}></TooltipList>}>
|
||||||
|
<QuestionCircleOutlined />
|
||||||
|
</Tooltip>
|
||||||
|
</Label>
|
||||||
|
|
||||||
<Form.Item<AccessControlFormData> name="access_policy" noStyle>
|
<Form.Item<AccessControlFormData> name="access_policy" noStyle>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
onChange={handleOnPolicyChange}
|
onChange={handleOnPolicyChange}
|
||||||
@@ -307,7 +341,7 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
|
|||||||
id: 'models.table.userSelection.tips'
|
id: 'models.table.userSelection.tips'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<QuestionCircleOutlined style={{ marginLeft: 4 }} />
|
<QuestionCircleOutlined />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
<Form.Item<AccessControlFormData> name="users">
|
<Form.Item<AccessControlFormData> name="users">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import breakpoints from '@/config/breakpoints';
|
|||||||
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
||||||
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Col, FloatButton, Row, Spin } from 'antd';
|
import { Col, Row, Spin } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import ResizeObserver from 'rc-resize-observer';
|
import ResizeObserver from 'rc-resize-observer';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
@@ -106,7 +106,6 @@ const CatalogList: React.FC<CatalogListProps> = (props) => {
|
|||||||
<ListSkeleton span={span} loading={loading} isFirst={isFirst} />
|
<ListSkeleton span={span} loading={loading} isFirst={isFirst} />
|
||||||
</InfiniteScroller>
|
</InfiniteScroller>
|
||||||
</ResizeObserver>
|
</ResizeObserver>
|
||||||
<FloatButton.BackTop visibilityHeight={1000} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -177,9 +177,9 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initClusterId = (): number => {
|
const initClusterId = (): number => {
|
||||||
const cluster_id = clusterList?.find(
|
const cluster_id =
|
||||||
(item) => item.state === ClusterStatusValueMap.Ready
|
clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready)
|
||||||
)?.value;
|
?.value || clusterList?.[0]?.value;
|
||||||
|
|
||||||
return cluster_id as number;
|
return cluster_id as number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
|
|
||||||
const { checkOnlyAscendNPU } = useCheckBackend();
|
const { checkOnlyAscendNPU } = useCheckBackend();
|
||||||
const {
|
const {
|
||||||
handleShowCompatibleAlert,
|
|
||||||
setWarningStatus,
|
setWarningStatus,
|
||||||
handleBackendChangeBefore,
|
handleBackendChangeBefore,
|
||||||
cancelEvaluate,
|
cancelEvaluate,
|
||||||
@@ -258,7 +257,10 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelInfo = onSelectModel(selectedModel, props.source);
|
const modelInfo = onSelectModel(selectedModel, {
|
||||||
|
source: props.source,
|
||||||
|
defaultBackend: form.current?.getFieldValue?.('backend')
|
||||||
|
});
|
||||||
|
|
||||||
form.current?.setFieldsValue?.({
|
form.current?.setFieldsValue?.({
|
||||||
..._.omit(modelInfo, ['name']),
|
..._.omit(modelInfo, ['name']),
|
||||||
@@ -323,13 +325,17 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
form.current?.resetFields(resetFields);
|
form.current?.resetFields(resetFields);
|
||||||
const modelInfo = onSelectModel(item, props.source);
|
const modelInfo = onSelectModel(item, {
|
||||||
|
source: props.source
|
||||||
|
});
|
||||||
form.current?.setFieldsValue?.({
|
form.current?.setFieldsValue?.({
|
||||||
...defaultFormValues,
|
...defaultFormValues,
|
||||||
...modelInfo,
|
...modelInfo,
|
||||||
categories: getCategory(item)
|
categories: getCategory(item)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('modelInfo:', modelInfo);
|
||||||
|
|
||||||
let warningStatus: MessageStatus = {
|
let warningStatus: MessageStatus = {
|
||||||
show: true,
|
show: true,
|
||||||
title: '',
|
title: '',
|
||||||
@@ -359,7 +365,9 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
requestModelId: updateRequestModelId()
|
requestModelId: updateRequestModelId()
|
||||||
});
|
});
|
||||||
handleCancelFiles();
|
handleCancelFiles();
|
||||||
const modelInfo = onSelectModel(item, props.source);
|
const modelInfo = onSelectModel(item, {
|
||||||
|
source: props.source
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
evaluateStateRef.current.state === EvaluateProccess.model &&
|
evaluateStateRef.current.state === EvaluateProccess.model &&
|
||||||
@@ -435,7 +443,7 @@ const AddModal: FC<AddModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
const cluster_id =
|
const cluster_id =
|
||||||
clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready)
|
clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready)
|
||||||
?.value || '';
|
?.value || clusterList?.[0]?.value;
|
||||||
|
|
||||||
return cluster_id;
|
return cluster_id;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ const draftModelDownloadList: ColumnProps[] = [
|
|||||||
title: 'models.form.draftModel',
|
title: 'models.form.draftModel',
|
||||||
locale: true,
|
locale: true,
|
||||||
key: 'draft_model',
|
key: 'draft_model',
|
||||||
|
style: {
|
||||||
|
wordBreak: 'break-word'
|
||||||
|
},
|
||||||
width: 280
|
width: 280
|
||||||
},
|
},
|
||||||
...statusColumn
|
...statusColumn
|
||||||
|
|||||||
@@ -580,7 +580,14 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<div className={SearchStyle['search-bar']}>{renderHFSearch()}</div>
|
<div className={SearchStyle['search-bar']}>{renderHFSearch()}</div>
|
||||||
<ColumnWrapper maxHeight={'calc(100vh - 210px)'}>
|
<ColumnWrapper
|
||||||
|
maxHeight={'calc(100vh - 210px)'}
|
||||||
|
styles={{
|
||||||
|
container: {
|
||||||
|
paddingTop: 0
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SearchResult
|
<SearchResult
|
||||||
loading={dataSource.loading}
|
loading={dataSource.loading}
|
||||||
resultList={dataSource.dataList}
|
resultList={dataSource.dataList}
|
||||||
|
|||||||
@@ -429,6 +429,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
data: row
|
data: row
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSelect = useMemoizedFn(async (val: any, row: ListItem) => {
|
const handleSelect = useMemoizedFn(async (val: any, row: ListItem) => {
|
||||||
try {
|
try {
|
||||||
if (val === 'edit') {
|
if (val === 'edit') {
|
||||||
@@ -716,15 +717,11 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
subTitle={intl.formatMessage({
|
subTitle={intl.formatMessage({
|
||||||
id: 'noresult.deployments.subTitle'
|
id: 'noresult.deployments.subTitle'
|
||||||
})}
|
})}
|
||||||
>
|
onClick={() => handleClickDropdown({ key: 'catalog' })}
|
||||||
<Button
|
buttonText={intl?.formatMessage?.({
|
||||||
type="primary"
|
id: 'models.table.button.deploy'
|
||||||
iconPosition="end"
|
})}
|
||||||
onClick={() => handleClickDropdown({ key: 'catalog' })}
|
></NoResult>
|
||||||
>
|
|
||||||
{intl?.formatMessage?.({ id: 'models.table.button.deploy' })}
|
|
||||||
</Button>
|
|
||||||
</NoResult>
|
|
||||||
}
|
}
|
||||||
pagination={{
|
pagination={{
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
|
|||||||
@@ -307,7 +307,12 @@ export interface BackendOption {
|
|||||||
default_backend_param: string[];
|
default_backend_param: string[];
|
||||||
default_version: string;
|
default_version: string;
|
||||||
isBuiltIn: boolean;
|
isBuiltIn: boolean;
|
||||||
versions: { label: string; value: string; title?: string }[];
|
versions: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
title?: string;
|
||||||
|
is_deprecated: boolean;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessControlFormData {
|
export interface AccessControlFormData {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const BackendParametersList: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Form.Item<FormData> name="backend_parameters">
|
<Form.Item<FormData> name="backend_parameters">
|
||||||
<ListInput
|
<ListInput
|
||||||
|
trim={false}
|
||||||
placeholder={
|
placeholder={
|
||||||
backendParamsHolderTips[backend]
|
backendParamsHolderTips[backend]
|
||||||
? intl.formatMessage({
|
? intl.formatMessage({
|
||||||
|
|||||||
@@ -1,19 +1,35 @@
|
|||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import TooltipList from '@/components/tooltip-list';
|
import TooltipList from '@/components/tooltip-list';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
import { useIntl } from '@umijs/max';
|
import { CaretDownOutlined, InfoCircleOutlined } from '@ant-design/icons';
|
||||||
import { Form } from 'antd';
|
import { useIntl, useNavigate } from '@umijs/max';
|
||||||
|
import { Form, Select } from 'antd';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { backendTipsList } from '../config';
|
import { backendTipsList } from '../config';
|
||||||
import { backendOptionsMap } from '../config/backend-parameters';
|
import { backendOptionsMap } from '../config/backend-parameters';
|
||||||
import { useFormContext } from '../config/form-context';
|
import { useFormContext } from '../config/form-context';
|
||||||
|
|
||||||
|
const CaretDownWrapper = styled.span`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
.anticon {
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const BackendFields: React.FC = () => {
|
const BackendFields: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const navigate = useNavigate();
|
||||||
const { getRuleMessage } = useAppUtils();
|
const { getRuleMessage } = useAppUtils();
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
const { onValuesChange, backendOptions, onBackendChange } = useFormContext();
|
const { onValuesChange, backendOptions, onBackendChange } = useFormContext();
|
||||||
const backend = Form.useWatch('backend', form);
|
const backend = Form.useWatch('backend', form);
|
||||||
|
const [showDeprecated, setShowDeprecated] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const handleBackendVersionOnChange = (value: any) => {
|
const handleBackendVersionOnChange = (value: any) => {
|
||||||
onValuesChange?.({}, form.getFieldsValue());
|
onValuesChange?.({}, form.getFieldsValue());
|
||||||
@@ -45,9 +61,17 @@ const BackendFields: React.FC = () => {
|
|||||||
return options;
|
return options;
|
||||||
}, [backendOptions, intl]);
|
}, [backendOptions, intl]);
|
||||||
|
|
||||||
const backendVersions = useMemo(() => {
|
const backendVersions = useMemo((): {
|
||||||
|
builtIn: any[];
|
||||||
|
custom: any[];
|
||||||
|
deprecated: any[];
|
||||||
|
} => {
|
||||||
if (!backend || backend === backendOptionsMap.custom) {
|
if (!backend || backend === backendOptionsMap.custom) {
|
||||||
return [];
|
return {
|
||||||
|
builtIn: [],
|
||||||
|
custom: [],
|
||||||
|
deprecated: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the backend item from backendOptions
|
// find the backend item from backendOptions
|
||||||
@@ -57,34 +81,33 @@ const BackendFields: React.FC = () => {
|
|||||||
|
|
||||||
// if it's a custom backend,
|
// if it's a custom backend,
|
||||||
if (backendItem && !backendItem.isBuiltIn) {
|
if (backendItem && !backendItem.isBuiltIn) {
|
||||||
return versions;
|
return {
|
||||||
|
builtIn: [],
|
||||||
|
custom: versions.filter((item) => !item.is_deprecated),
|
||||||
|
deprecated: versions.filter((item) => item.is_deprecated)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the value if endts with '-custom', if true, remove the suffix add to "Cutom" group, if not , add to "Built-in" group
|
// check the value if endts with '-custom', if true, remove the suffix add to "Cutom" group, if not , add to "Built-in" group
|
||||||
|
|
||||||
|
// ============ Built-in Versions ============
|
||||||
const builtInVersions = versions.filter(
|
const builtInVersions = versions.filter(
|
||||||
(item) => !item.value?.endsWith('-custom')
|
(item) => !item.value?.endsWith('-custom') && !item.is_deprecated
|
||||||
);
|
|
||||||
const customVersions = versions.filter((item) =>
|
|
||||||
item.value?.endsWith('-custom')
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = [];
|
// ============ Custom Versions ============
|
||||||
|
const customVersions = versions.filter(
|
||||||
|
(item) => item.value?.endsWith('-custom') && !item.is_deprecated
|
||||||
|
);
|
||||||
|
|
||||||
if (builtInVersions.length > 0) {
|
// ============ Deprecated Versions ============
|
||||||
options.push({
|
const deprecatedVersions = versions.filter((item) => item.is_deprecated);
|
||||||
label: intl.formatMessage({ id: 'backend.builtin' }),
|
|
||||||
options: builtInVersions
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customVersions.length > 0) {
|
return {
|
||||||
options.push({
|
builtIn: builtInVersions,
|
||||||
label: intl.formatMessage({ id: 'models.form.backend.custom' }),
|
custom: customVersions,
|
||||||
options: customVersions
|
deprecated: deprecatedVersions
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}, [backend, backendOptions, intl]);
|
}, [backend, backendOptions, intl]);
|
||||||
|
|
||||||
const optionRender = (option: any) => {
|
const optionRender = (option: any) => {
|
||||||
@@ -95,6 +118,50 @@ const BackendFields: React.FC = () => {
|
|||||||
return option.title;
|
return option.title;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderVersionOptions = (values: any[], label: string) => {
|
||||||
|
if (!values || values.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Select.OptGroup label={label}>
|
||||||
|
{values.map((item) => (
|
||||||
|
<Select.Option key={item.value} value={item.value} label={item.label}>
|
||||||
|
{item.label}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select.OptGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDeprecatedVersionOptions = (values: any[]) => {
|
||||||
|
if (!values || values.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Select.OptGroup
|
||||||
|
label={
|
||||||
|
<CaretDownWrapper onClick={() => setShowDeprecated(!showDeprecated)}>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'models.form.backendVersion.deprecated'
|
||||||
|
})}
|
||||||
|
<CaretDownOutlined rotate={showDeprecated ? 0 : -90} />
|
||||||
|
</CaretDownWrapper>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{showDeprecated &&
|
||||||
|
values.map((item) => (
|
||||||
|
<Select.Option
|
||||||
|
key={item.value}
|
||||||
|
value={item.value}
|
||||||
|
label={item.label}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select.OptGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -120,15 +187,45 @@ const BackendFields: React.FC = () => {
|
|||||||
<Form.Item name="backend_version">
|
<Form.Item name="backend_version">
|
||||||
<SealSelect
|
<SealSelect
|
||||||
allowClear
|
allowClear
|
||||||
options={backendVersions}
|
showSearch
|
||||||
optionRender={optionRender}
|
|
||||||
labelRender={labelRender}
|
labelRender={labelRender}
|
||||||
placeholder={intl.formatMessage({
|
placeholder={intl.formatMessage({
|
||||||
id: 'models.form.backendVersion.holder'
|
id: 'models.form.backendVersion.holder'
|
||||||
})}
|
})}
|
||||||
onChange={handleBackendVersionOnChange}
|
onChange={handleBackendVersionOnChange}
|
||||||
label={intl.formatMessage({ id: 'models.form.backendVersion' })}
|
label={intl.formatMessage({ id: 'models.form.backendVersion' })}
|
||||||
></SealSelect>
|
footer={
|
||||||
|
<dl className="flex" style={{ marginBottom: 0 }}>
|
||||||
|
<dt>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</dt>
|
||||||
|
<dd style={{ marginLeft: 8, marginBottom: 0 }}>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: 'models.form.backendVersions.tips'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: (
|
||||||
|
<a onClick={() => navigate('/resources/backends')}>
|
||||||
|
{intl.formatMessage({ id: 'backends.title' })}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{renderVersionOptions(
|
||||||
|
backendVersions.builtIn,
|
||||||
|
intl.formatMessage({ id: 'backend.builtin' })
|
||||||
|
)}
|
||||||
|
{renderVersionOptions(
|
||||||
|
backendVersions.custom,
|
||||||
|
intl.formatMessage({ id: 'models.form.backend.custom' })
|
||||||
|
)}
|
||||||
|
{renderDeprecatedVersionOptions(backendVersions.deprecated)}
|
||||||
|
</SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
|
|||||||
item.state === ClusterStatusValueMap.Ready
|
item.state === ClusterStatusValueMap.Ready
|
||||||
? item.label
|
? item.label
|
||||||
: `${item.label} [${ClusterStatusLabelMap[item.state as string]}]`,
|
: `${item.label} [${ClusterStatusLabelMap[item.state as string]}]`,
|
||||||
disabled: item.state !== ClusterStatusValueMap.Ready,
|
|
||||||
value: item.value
|
value: item.value
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const CustomBackend: React.FC = () => {
|
|||||||
<SealInput.Input
|
<SealInput.Input
|
||||||
required
|
required
|
||||||
allowClear
|
allowClear
|
||||||
|
onBlur={handleImageNameOnBlur}
|
||||||
label={intl.formatMessage({ id: 'backend.imageName' })}
|
label={intl.formatMessage({ id: 'backend.imageName' })}
|
||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -65,6 +66,7 @@ const CustomBackend: React.FC = () => {
|
|||||||
scaleSize={false}
|
scaleSize={false}
|
||||||
alwaysFocus={true}
|
alwaysFocus={true}
|
||||||
autoSize={{ minRows: 2, maxRows: 4 }}
|
autoSize={{ minRows: 2, maxRows: 4 }}
|
||||||
|
onBlur={handleRunCommandOnBlur}
|
||||||
label={intl.formatMessage({ id: 'backend.runCommand' })}
|
label={intl.formatMessage({ id: 'backend.runCommand' })}
|
||||||
description={intl.formatMessage({
|
description={intl.formatMessage({
|
||||||
id: 'backend.form.defaultExecuteCommand.tips'
|
id: 'backend.form.defaultExecuteCommand.tips'
|
||||||
|
|||||||
@@ -173,6 +173,16 @@ export const useCheckCompatibility = () => {
|
|||||||
|
|
||||||
const handleEvaluate = async (data: any) => {
|
const handleEvaluate = async (data: any) => {
|
||||||
try {
|
try {
|
||||||
|
// when no cluster selected, show warning and prompt user to add cluster first
|
||||||
|
if (!data.cluster_id) {
|
||||||
|
setWarningStatus({
|
||||||
|
show: true,
|
||||||
|
title: '',
|
||||||
|
type: 'warning',
|
||||||
|
message: intl.formatMessage({ id: 'noresult.resources.cluster' })
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
checkTokenRef.current?.cancel();
|
checkTokenRef.current?.cancel();
|
||||||
checkTokenRef.current = createAxiosToken();
|
checkTokenRef.current = createAxiosToken();
|
||||||
setWarningStatus({
|
setWarningStatus({
|
||||||
@@ -407,6 +417,7 @@ export const useCheckCompatibility = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when custom backend, and no run_command or image_name, skip evaluate
|
||||||
if (
|
if (
|
||||||
backendOptionsMap.custom === allValues.backend &&
|
backendOptionsMap.custom === allValues.backend &&
|
||||||
(!allValues.run_command || !allValues.image_name)
|
(!allValues.run_command || !allValues.image_name)
|
||||||
@@ -504,7 +515,11 @@ export const useSelectModel = (data: { gpuOptions: any[] }) => {
|
|||||||
// just for setting the model name or repo_id, and the backend, Since the model type is fixed.
|
// just for setting the model name or repo_id, and the backend, Since the model type is fixed.
|
||||||
const { gpuOptions } = data;
|
const { gpuOptions } = data;
|
||||||
|
|
||||||
const onSelectModel = (selectModel: any, source: string) => {
|
const onSelectModel = (
|
||||||
|
selectModel: any,
|
||||||
|
options: { source: string; defaultBackend?: string }
|
||||||
|
) => {
|
||||||
|
const { source, defaultBackend } = options;
|
||||||
let name = _.split(selectModel.name, '/').slice(-1)[0];
|
let name = _.split(selectModel.name, '/').slice(-1)[0];
|
||||||
const reg = /(-gguf)$/i;
|
const reg = /(-gguf)$/i;
|
||||||
name = _.toLower(name).replace(reg, '');
|
name = _.toLower(name).replace(reg, '');
|
||||||
@@ -512,7 +527,7 @@ export const useSelectModel = (data: { gpuOptions: any[] }) => {
|
|||||||
const modelTaskData = recognizeAudioModel(selectModel, source);
|
const modelTaskData = recognizeAudioModel(selectModel, source);
|
||||||
|
|
||||||
const backend = checkCurrentbackend({
|
const backend = checkCurrentbackend({
|
||||||
defaultBackend: backendOptionsMap.vllm,
|
defaultBackend: defaultBackend || backendOptionsMap.vllm,
|
||||||
isAudio: modelTaskData.type === modelTaskMap.audio,
|
isAudio: modelTaskData.type === modelTaskMap.audio,
|
||||||
isGGUF: selectModel.isGGUF,
|
isGGUF: selectModel.isGGUF,
|
||||||
gpuOptions: gpuOptions
|
gpuOptions: gpuOptions
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import AutoTooltip from '@/components/auto-tooltip';
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import DropdownButtons from '@/components/drop-down-buttons';
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
import { SealColumnProps } from '@/components/seal-table/types';
|
import { SealColumnProps } from '@/components/seal-table/types';
|
||||||
import { GPUSTACK_API_BASE_URL } from '@/config/settings';
|
import { OPENAI_COMPATIBLE } from '@/config/settings';
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
@@ -94,7 +94,7 @@ const useModelsColumns = ({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={intl.formatMessage(
|
title={intl.formatMessage(
|
||||||
{ id: 'models.form.replicas.tips' },
|
{ id: 'models.form.replicas.tips' },
|
||||||
{ api: `${window.location.origin}/${GPUSTACK_API_BASE_URL}` }
|
{ api: `${window.location.origin}/${OPENAI_COMPATIBLE}` }
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span style={{ fontWeight: 'var(--font-weight-medium)' }}>
|
<span style={{ fontWeight: 'var(--font-weight-medium)' }}>
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { clusterSessionAtom } from '@/atoms/clusters';
|
||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
|
import NoResult from '@/pages/_components/no-result';
|
||||||
|
import { useIntl, useNavigate } from '@umijs/max';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
const useNoResourceResult = (props: {
|
||||||
|
iconType: string;
|
||||||
|
loading?: boolean;
|
||||||
|
loadend?: boolean;
|
||||||
|
dataSource?: any[];
|
||||||
|
queryParams?: Record<string, any>;
|
||||||
|
title: React.ReactNode;
|
||||||
|
noClusters?: boolean;
|
||||||
|
noWorkers?: boolean;
|
||||||
|
subTitle?: React.ReactNode;
|
||||||
|
defaultContent?: {
|
||||||
|
subTitle: string;
|
||||||
|
noFoundText: string;
|
||||||
|
buttonText: string;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const {
|
||||||
|
noClusters,
|
||||||
|
noWorkers,
|
||||||
|
defaultContent,
|
||||||
|
loading,
|
||||||
|
loadend,
|
||||||
|
dataSource,
|
||||||
|
queryParams,
|
||||||
|
iconType,
|
||||||
|
title,
|
||||||
|
subTitle
|
||||||
|
} = props;
|
||||||
|
const [, setClusterSession] = useAtom(clusterSessionAtom);
|
||||||
|
|
||||||
|
const handleClick = useMemoizedFn(() => {
|
||||||
|
if (noClusters) {
|
||||||
|
setClusterSession({
|
||||||
|
firstAddWorker: false,
|
||||||
|
firstAddCluster: true
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate(
|
||||||
|
`/cluster-management/clusters/create?action=${PageAction.CREATE}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noWorkers) {
|
||||||
|
setClusterSession({
|
||||||
|
firstAddWorker: true,
|
||||||
|
firstAddCluster: false
|
||||||
|
});
|
||||||
|
navigate(`/cluster-management/clusters/list`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusContent = useMemo(() => {
|
||||||
|
if (noClusters) {
|
||||||
|
return {
|
||||||
|
subTitle:
|
||||||
|
subTitle || intl.formatMessage({ id: 'noresult.resources.cluster' }),
|
||||||
|
noFoundText: defaultContent?.noFoundText || '',
|
||||||
|
buttonText: intl.formatMessage({
|
||||||
|
id: 'noresult.resources.gotocluster'
|
||||||
|
}),
|
||||||
|
onClick: handleClick
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noWorkers) {
|
||||||
|
return {
|
||||||
|
subTitle:
|
||||||
|
subTitle || intl.formatMessage({ id: 'noresult.resources.worker' }),
|
||||||
|
noFoundText: defaultContent?.noFoundText || '',
|
||||||
|
buttonText: intl.formatMessage({ id: 'noresult.resources.gotoworker' }),
|
||||||
|
onClick: handleClick
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...defaultContent
|
||||||
|
};
|
||||||
|
}, [noClusters, noWorkers, intl]);
|
||||||
|
|
||||||
|
const noResourceResult = (
|
||||||
|
<NoResult
|
||||||
|
loading={loading}
|
||||||
|
loadend={loadend}
|
||||||
|
dataSource={dataSource}
|
||||||
|
image={<IconFont type={iconType} />}
|
||||||
|
filters={queryParams}
|
||||||
|
noFoundText={statusContent.noFoundText}
|
||||||
|
title={title}
|
||||||
|
subTitle={statusContent.subTitle}
|
||||||
|
onClick={statusContent.onClick}
|
||||||
|
buttonText={statusContent.buttonText}
|
||||||
|
></NoResult>
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
noResourceResult
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useNoResourceResult;
|
||||||
@@ -26,9 +26,10 @@ export default function useQueryBackends() {
|
|||||||
default_backend_param: item.default_backend_param || [],
|
default_backend_param: item.default_backend_param || [],
|
||||||
default_version: item.default_version,
|
default_version: item.default_version,
|
||||||
isBuiltIn: item.is_built_in,
|
isBuiltIn: item.is_built_in,
|
||||||
versions: (item.versions || []).map((vItem) => ({
|
versions: (item.versions || []).map((vItem, index) => ({
|
||||||
label: vItem.version,
|
label: vItem.version,
|
||||||
value: vItem.version,
|
value: vItem.version,
|
||||||
|
is_deprecated: vItem.is_deprecated,
|
||||||
title: vItem.version.replace(/-custom$/, '')
|
title: vItem.version.replace(/-custom$/, '')
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const useStyles = createStyles(({ token, css }) => ({
|
|||||||
const LoginForm = () => {
|
const LoginForm = () => {
|
||||||
const [messageApi, contextHolder] = message.useMessage();
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
const [userInfo, setUserInfo] = useAtom(userAtom);
|
const [, setUserInfo] = useAtom(userAtom);
|
||||||
const { initialState, setInitialState } = useModel('@@initialState') || {};
|
const { initialState, setInitialState } = useModel('@@initialState') || {};
|
||||||
const [authError, setAuthError] = useState<Error | null>(null);
|
const [authError, setAuthError] = useState<Error | null>(null);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { initialPasswordAtom, userAtom } from '@/atoms/user';
|
import { initialPasswordAtom, userAtom } from '@/atoms/user';
|
||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import { PasswordReg } from '@/config';
|
import { PasswordReg } from '@/config';
|
||||||
import { CRYPT_TEXT } from '@/utils/localstore/index';
|
import {
|
||||||
|
CRYPT_TEXT,
|
||||||
|
IS_FIRST_LOGIN,
|
||||||
|
writeState
|
||||||
|
} from '@/utils/localstore/index';
|
||||||
import { LockOutlined } from '@ant-design/icons';
|
import { LockOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Form, message } from 'antd';
|
import { Button, Form, message } from 'antd';
|
||||||
@@ -34,10 +38,12 @@ const PasswordForm: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await setUserInfo({
|
await setUserInfo({
|
||||||
...userInfo,
|
...(userInfo || {}),
|
||||||
require_password_change: false
|
require_password_change: false
|
||||||
});
|
});
|
||||||
setInitialPassword('');
|
setInitialPassword('');
|
||||||
|
// Reset first login flag
|
||||||
|
writeState(IS_FIRST_LOGIN, null);
|
||||||
gotoDefaultPage(userInfo);
|
gotoDefaultPage(userInfo);
|
||||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -201,9 +201,11 @@ const GroundSTT: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
const handleUploadChange = useCallback(
|
const handleUploadChange = useCallback(
|
||||||
async (data: { file: any; fileList: any }) => {
|
async (data: { file: any; fileList: any }) => {
|
||||||
const res = await readAudioFile(data.file);
|
try {
|
||||||
setAudioData(res);
|
const res = await readAudioFile(data.file);
|
||||||
setTokenResult(null);
|
setAudioData(res);
|
||||||
|
setTokenResult(null);
|
||||||
|
} catch (error) {}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Roles } from '../config';
|
import { Roles } from '../config';
|
||||||
import { AudioFormat, MessageItem } from '../config/types';
|
import { AudioFormat, MessageItem } from '../config/types';
|
||||||
|
import useAddImage from '../hooks/use-add-image';
|
||||||
import '../style/message-input.less';
|
import '../style/message-input.less';
|
||||||
import ThumbImg from './thumb-img';
|
import ThumbImg from './thumb-img';
|
||||||
import UploadImg from './upload-img';
|
|
||||||
|
|
||||||
const AudioWrapper = styled.div`
|
const AudioWrapper = styled.div`
|
||||||
padding-block: 10px;
|
padding-block: 10px;
|
||||||
@@ -163,8 +163,11 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
content: '',
|
content: '',
|
||||||
imgs: []
|
imgs: []
|
||||||
});
|
});
|
||||||
|
const [isFromUrl, setIsFromUrl] = useState(false);
|
||||||
|
const [openImgTips, setOpenImgTips] = useState(false);
|
||||||
const uidCountRef = useRef(0);
|
const uidCountRef = useRef(0);
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
|
const inputImgRef = useRef<any>(null);
|
||||||
|
|
||||||
const updateUidCount = () => {
|
const updateUidCount = () => {
|
||||||
uidCountRef.current += 1;
|
uidCountRef.current += 1;
|
||||||
@@ -358,6 +361,11 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { ImageURLInput, UploadImageButton } = useAddImage({
|
||||||
|
handleUpdateImgList: handleUpdateImgList,
|
||||||
|
updateUidCount: updateUidCount
|
||||||
|
});
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleInputChange: handleInputChange
|
handleInputChange: handleInputChange
|
||||||
}));
|
}));
|
||||||
@@ -411,12 +419,10 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
{checkLabel}
|
{checkLabel}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
)}
|
)}
|
||||||
{actions.includes('upload') && message.role === Roles.User && (
|
{actions.includes('upload') &&
|
||||||
<UploadImg
|
message.role === Roles.User &&
|
||||||
handleUpdateImgList={handleUpdateImgList}
|
UploadImageButton}
|
||||||
size="middle"
|
|
||||||
></UploadImg>
|
|
||||||
)}
|
|
||||||
{actions.includes('upload') && message.role === Roles.User && (
|
{actions.includes('upload') && message.role === Roles.User && (
|
||||||
<UploadAudio
|
<UploadAudio
|
||||||
maxFileSize={1024 * 1024}
|
maxFileSize={1024 * 1024}
|
||||||
@@ -462,6 +468,7 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{ImageURLInput}
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
{actions.includes('add') && (
|
{actions.includes('add') && (
|
||||||
@@ -533,6 +540,7 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
</AudioWrapper>
|
</AudioWrapper>
|
||||||
)}
|
)}
|
||||||
</ImgsWrapper>
|
</ImgsWrapper>
|
||||||
|
|
||||||
<div className="input-box">
|
<div className="input-box">
|
||||||
{actions.includes('paste') ? (
|
{actions.includes('paste') ? (
|
||||||
<TextArea
|
<TextArea
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Tooltip } from 'antd';
|
import { Button, Tooltip } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Roles } from '../../config';
|
import { Roles } from '../../config';
|
||||||
@@ -20,7 +21,7 @@ import {
|
|||||||
MessageItem,
|
MessageItem,
|
||||||
MessageItemAction
|
MessageItemAction
|
||||||
} from '../../config/types';
|
} from '../../config/types';
|
||||||
import UploadImg from '../upload-img';
|
import useAddImage from '../../hooks/use-add-image';
|
||||||
|
|
||||||
interface MessageActionsProps {
|
interface MessageActionsProps {
|
||||||
data: MessageItem;
|
data: MessageItem;
|
||||||
@@ -57,32 +58,47 @@ const MessageActions: React.FC<MessageActionsProps> = ({
|
|||||||
file: any;
|
file: any;
|
||||||
fileList: any[];
|
fileList: any[];
|
||||||
}) => {
|
}) => {
|
||||||
const base64Audio = await convertFileToBase64(audio.file);
|
try {
|
||||||
const audioData = await readAudioFile(audio.file);
|
const base64Audio = await convertFileToBase64(audio.file);
|
||||||
updateMessage?.({
|
const audioData = await readAudioFile(audio.file);
|
||||||
role: data.role,
|
updateMessage?.({
|
||||||
content: data.content,
|
role: data.role,
|
||||||
uid: data.uid,
|
content: data.content,
|
||||||
imgs: data.imgs || [],
|
uid: data.uid,
|
||||||
audio: [
|
imgs: data.imgs || [],
|
||||||
{
|
audio: [
|
||||||
uid: audio.file.uid || audio.fileList[0].uid,
|
{
|
||||||
format: audioTypeMap[audio.file.type] as AudioFormat,
|
uid: audio.file.uid || audio.fileList[0].uid,
|
||||||
base64: base64Audio.split(',')[1],
|
format: audioTypeMap[audio.file.type] as AudioFormat,
|
||||||
data: _.pick(audioData, ['url', 'name', 'duration'])
|
base64: base64Audio.split(',')[1],
|
||||||
}
|
data: _.pick(audioData, ['url', 'name', 'duration'])
|
||||||
]
|
}
|
||||||
});
|
]
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error uploading audio file', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { ImageURLInput, UploadImageButton, isFromUrl } = useAddImage({
|
||||||
|
size: 'small',
|
||||||
|
handleUpdateImgList: handleUpdateImgList,
|
||||||
|
updateUidCount: () => `img-${Date.now()}`
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{ImageURLInput}
|
||||||
{actions.length > 1 && !loading ? (
|
{actions.length > 1 && !loading ? (
|
||||||
<div className="actions">
|
<div
|
||||||
|
className={classNames('actions', {
|
||||||
|
'has-url-input': isFromUrl
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="actions-wrap gap-5">
|
<div className="actions-wrap gap-5">
|
||||||
{actions.includes('upload') && data.role === Roles.User && (
|
{actions.includes('upload') &&
|
||||||
<UploadImg handleUpdateImgList={handleUpdateImgList} />
|
data.role === Roles.User &&
|
||||||
)}
|
UploadImageButton}
|
||||||
{actions.includes('upload') && data.role === Roles.User && (
|
{actions.includes('upload') && data.role === Roles.User && (
|
||||||
<UploadAudio
|
<UploadAudio
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -16,10 +16,8 @@ import ThumbImg from '../thumb-img';
|
|||||||
import ThinkContent from './think-content';
|
import ThinkContent from './think-content';
|
||||||
|
|
||||||
const AudioWrapper = styled.div`
|
const AudioWrapper = styled.div`
|
||||||
padding-top: 10px;
|
|
||||||
height: max-content;
|
height: max-content;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
margin-inline: 10px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ThumbImgWrapper = styled.div.attrs({
|
const ThumbImgWrapper = styled.div.attrs({
|
||||||
@@ -29,6 +27,12 @@ const ThumbImgWrapper = styled.div.attrs({
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding: 0px;
|
||||||
|
gap: 8px;
|
||||||
|
&.has-content {
|
||||||
|
padding-inline: 8px;
|
||||||
|
padding-block: 8px 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface MessageBodyProps {
|
interface MessageBodyProps {
|
||||||
@@ -179,31 +183,6 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteLastImage = useCallback(() => {
|
|
||||||
if (data.imgs && data.imgs?.length > 0) {
|
|
||||||
const newImgList = [...(data.imgs || [])];
|
|
||||||
const lastImage = newImgList.pop();
|
|
||||||
if (lastImage) {
|
|
||||||
handleDeleteImg(lastImage.uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [data.imgs, handleDeleteImg]);
|
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
(event: any) => {
|
|
||||||
if (
|
|
||||||
event.key === 'Backspace' &&
|
|
||||||
data.content === '' &&
|
|
||||||
data.imgs &&
|
|
||||||
data.imgs?.length > 0
|
|
||||||
) {
|
|
||||||
// inputref blur
|
|
||||||
event.preventDefault();
|
|
||||||
handleDeleteLastImage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[data, handleDeleteLastImage]
|
|
||||||
);
|
|
||||||
const handleClickWrapper = (e: any) => {
|
const handleClickWrapper = (e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -218,7 +197,7 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
|
|||||||
data.imgs?.length || (data.audio && data.audio?.length > 0)
|
data.imgs?.length || (data.audio && data.audio?.length > 0)
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="justify-start ">
|
<div className="justify-start">
|
||||||
<ThumbImg
|
<ThumbImg
|
||||||
editable={editable}
|
editable={editable}
|
||||||
dataList={data.imgs || []}
|
dataList={data.imgs || []}
|
||||||
@@ -249,7 +228,12 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
|
|||||||
})}
|
})}
|
||||||
onClick={handleClickWrapper}
|
onClick={handleClickWrapper}
|
||||||
>
|
>
|
||||||
<ThumbImgWrapper>
|
<ThumbImgWrapper
|
||||||
|
className={classNames({
|
||||||
|
'has-content':
|
||||||
|
data.imgs?.length || (data.audio && data.audio?.length > 0)
|
||||||
|
})}
|
||||||
|
>
|
||||||
<ThumbImg
|
<ThumbImg
|
||||||
style={{ paddingBlockEnd: 0 }}
|
style={{ paddingBlockEnd: 0 }}
|
||||||
editable={editable}
|
editable={editable}
|
||||||
@@ -257,7 +241,7 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
|
|||||||
onDelete={handleDeleteImg}
|
onDelete={handleDeleteImg}
|
||||||
/>
|
/>
|
||||||
{data.audio && data.audio.length > 0 && (
|
{data.audio && data.audio.length > 0 && (
|
||||||
<AudioWrapper className={data.imgs?.length ? '' : 'm-l-10'}>
|
<AudioWrapper>
|
||||||
<SimpleAudio
|
<SimpleAudio
|
||||||
url={data.audio?.[0]?.data.url}
|
url={data.audio?.[0]?.data.url}
|
||||||
name={data.audio?.[0]?.data.name}
|
name={data.audio?.[0]?.data.name}
|
||||||
|
|||||||
@@ -184,4 +184,4 @@ const UploadImg: React.FC<UploadImgProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(UploadImg);
|
export default UploadImg;
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import DropDownActions from '@/components/drop-down-actions';
|
||||||
|
import {
|
||||||
|
DeleteOutlined,
|
||||||
|
LinkOutlined,
|
||||||
|
PictureOutlined,
|
||||||
|
UploadOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button, Input, Tooltip } from 'antd';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import UploadImg from '../components/upload-img';
|
||||||
|
|
||||||
|
const ImgInputWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
|
.del-btn {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: var(--ant-color-bg-container);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.del-btn {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const useAddImage = (options: {
|
||||||
|
size?: 'small' | 'middle' | 'large';
|
||||||
|
handleUpdateImgList: (
|
||||||
|
list: { uid: number | string; dataUrl: string }[]
|
||||||
|
) => void;
|
||||||
|
updateUidCount: () => number | string;
|
||||||
|
}) => {
|
||||||
|
const { handleUpdateImgList, updateUidCount, size = 'middle' } = options;
|
||||||
|
const intl = useIntl();
|
||||||
|
const [isFromUrl, setIsFromUrl] = useState(false);
|
||||||
|
const [openImgTips, setOpenImgTips] = useState(false);
|
||||||
|
const inputImgRef = useRef<any>(null);
|
||||||
|
|
||||||
|
const handleAddImgFromUrl = () => {
|
||||||
|
setIsFromUrl(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
inputImgRef.current?.focus?.();
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputImageUrl = async (e: any) => {
|
||||||
|
const url = e.target.value?.trim();
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
handleUpdateImgList([
|
||||||
|
{
|
||||||
|
uid: updateUidCount(),
|
||||||
|
dataUrl: url
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
setOpenImgTips(false);
|
||||||
|
setIsFromUrl(false);
|
||||||
|
} else {
|
||||||
|
setOpenImgTips(true);
|
||||||
|
}
|
||||||
|
// set openImgTips to false after next frame if is not valid
|
||||||
|
if (!url) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setOpenImgTips(false);
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsFromUrl(false);
|
||||||
|
setOpenImgTips(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnEscape = (e: any) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ImageURLInput = isFromUrl ? (
|
||||||
|
<Tooltip
|
||||||
|
open={openImgTips}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'playground.uploadImage.url.invalid'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ImgInputWrapper>
|
||||||
|
<Input
|
||||||
|
ref={inputImgRef}
|
||||||
|
status={openImgTips ? 'error' : ''}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'playground.uploadImage.url.holder'
|
||||||
|
})}
|
||||||
|
style={{ width: 360, height: 32 }}
|
||||||
|
onBlur={handleInputImageUrl}
|
||||||
|
onPressEnter={handleInputImageUrl}
|
||||||
|
onKeyDown={handleOnEscape}
|
||||||
|
></Input>
|
||||||
|
<div className="del-btn">
|
||||||
|
<Button
|
||||||
|
onClick={handleClose}
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
></Button>
|
||||||
|
</div>
|
||||||
|
</ImgInputWrapper>
|
||||||
|
</Tooltip>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const UploadImageButton = (
|
||||||
|
<DropDownActions
|
||||||
|
placement={'topLeft'}
|
||||||
|
menu={{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<UploadImg
|
||||||
|
handleUpdateImgList={handleUpdateImgList}
|
||||||
|
size="middle"
|
||||||
|
>
|
||||||
|
<UploadOutlined className="m-r-8" />
|
||||||
|
{intl.formatMessage({ id: 'playground.img.upload' })}
|
||||||
|
</UploadImg>
|
||||||
|
),
|
||||||
|
key: 'upload_image'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({
|
||||||
|
id: 'playground.uploadImage.url.button'
|
||||||
|
}),
|
||||||
|
key: 'add_image_url',
|
||||||
|
icon: <LinkOutlined />,
|
||||||
|
onClick: handleAddImgFromUrl
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="text" size={size} icon={<PictureOutlined />}></Button>
|
||||||
|
</DropDownActions>
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFromUrl,
|
||||||
|
ImageURLInput,
|
||||||
|
UploadImageButton
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAddImage;
|
||||||
@@ -156,11 +156,15 @@ const Playground: React.FC = () => {
|
|||||||
activeKey={activeKey}
|
activeKey={activeKey}
|
||||||
key="view-code-buttons"
|
key="view-code-buttons"
|
||||||
/>,
|
/>,
|
||||||
<Divider
|
<div key="divider-wrapper">
|
||||||
key="divider"
|
{activeKey === 'chat' && (
|
||||||
type="vertical"
|
<Divider
|
||||||
style={{ height: 24, marginInline: 16 }}
|
key="divider"
|
||||||
/>,
|
type="vertical"
|
||||||
|
style={{ height: 24, marginInline: 16 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>,
|
||||||
<ExtraContent key="extra-content" />
|
<ExtraContent key="extra-content" />
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -33,6 +33,11 @@
|
|||||||
border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--ant-color-border);
|
||||||
border-radius: var(--border-radius-base);
|
border-radius: var(--border-radius-base);
|
||||||
background-color: var(--color-white-1);
|
background-color: var(--color-white-1);
|
||||||
|
|
||||||
|
&.has-url-input {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-wrap {
|
.actions-wrap {
|
||||||
|
|||||||
@@ -58,5 +58,4 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 10px;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import DeleteModal from '@/components/delete-modal';
|
import DeleteModal from '@/components/delete-modal';
|
||||||
import IconFont from '@/components/icon-font';
|
|
||||||
import { FilterBar } from '@/components/page-tools';
|
import { FilterBar } from '@/components/page-tools';
|
||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import PageBox from '@/pages/_components/page-box';
|
import PageBox from '@/pages/_components/page-box';
|
||||||
@@ -11,11 +10,11 @@ import {
|
|||||||
} from '@/pages/cluster-management/config';
|
} from '@/pages/cluster-management/config';
|
||||||
import { ClusterListItem } from '@/pages/cluster-management/config/types';
|
import { ClusterListItem } from '@/pages/cluster-management/config/types';
|
||||||
import useAddWorker from '@/pages/cluster-management/hooks/use-add-worker';
|
import useAddWorker from '@/pages/cluster-management/hooks/use-add-worker';
|
||||||
|
import useNoResourceResult from '@/pages/llmodels/hooks/use-no-resource-result';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Button, ConfigProvider, Table, message } from 'antd';
|
import { ConfigProvider, Table, message } from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import NoResult from '../../_components/no-result';
|
|
||||||
import {
|
import {
|
||||||
WORKERS_API,
|
WORKERS_API,
|
||||||
deleteWorker,
|
deleteWorker,
|
||||||
@@ -188,27 +187,33 @@ const Workers: React.FC = () => {
|
|||||||
if (!currentData) {
|
if (!currentData) {
|
||||||
currentData = clusterData.list[0];
|
currentData = clusterData.list[0];
|
||||||
}
|
}
|
||||||
handleAddWorker(currentData as ClusterListItem);
|
if (currentData) {
|
||||||
|
handleAddWorker(currentData as ClusterListItem);
|
||||||
|
} else {
|
||||||
|
message.info(intl.formatMessage({ id: 'noresult.resources.cluster' }));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { noResourceResult } = useNoResourceResult({
|
||||||
|
loadend: dataSource.loadend,
|
||||||
|
loading: dataSource.loading,
|
||||||
|
dataSource: dataSource.dataList,
|
||||||
|
queryParams: queryParams,
|
||||||
|
iconType: 'icon-resources',
|
||||||
|
title: intl.formatMessage({ id: 'noresult.workers.title' }),
|
||||||
|
noClusters: !clusterData.list.length,
|
||||||
|
noWorkers: dataSource.dataList.length === 0 && clusterData.list.length > 0,
|
||||||
|
defaultContent: {
|
||||||
|
subTitle: intl.formatMessage({ id: 'noresult.workers.subTitle' }),
|
||||||
|
noFoundText: intl.formatMessage({ id: 'noresult.workers.nofound' }),
|
||||||
|
buttonText: intl.formatMessage({ id: 'noresult.workers.button.add' }),
|
||||||
|
onClick: handleOnAddWorker
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const renderEmpty = (type?: string) => {
|
const renderEmpty = (type?: string) => {
|
||||||
if (type !== 'Table') return;
|
if (type !== 'Table') return;
|
||||||
return (
|
return noResourceResult;
|
||||||
<NoResult
|
|
||||||
loading={dataSource.loading}
|
|
||||||
loadend={dataSource.loadend}
|
|
||||||
dataSource={dataSource.dataList}
|
|
||||||
image={<IconFont type="icon-resources" />}
|
|
||||||
filters={queryParams}
|
|
||||||
noFoundText={intl.formatMessage({ id: 'noresult.workers.nofound' })}
|
|
||||||
title={intl.formatMessage({ id: 'noresult.workers.title' })}
|
|
||||||
subTitle={intl.formatMessage({ id: 'noresult.workers.subTitle' })}
|
|
||||||
>
|
|
||||||
<Button type="primary" onClick={handleOnAddWorker}>
|
|
||||||
{intl.formatMessage({ id: 'noresult.workers.button.add' })}
|
|
||||||
</Button>
|
|
||||||
</NoResult>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClusterChange = (value: number) => {
|
const handleClusterChange = (value: number) => {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export const GPUsConfigs: Record<
|
|||||||
[GPUDriverMap.ILUVATAR]: {
|
[GPUDriverMap.ILUVATAR]: {
|
||||||
label: ManufacturerMap[GPUDriverMap.ILUVATAR],
|
label: ManufacturerMap[GPUDriverMap.ILUVATAR],
|
||||||
value: GPUDriverMap.ILUVATAR,
|
value: GPUDriverMap.ILUVATAR,
|
||||||
runtime: 'iluvatar', // TODO: confirm runtime name
|
runtime: 'iluvatar',
|
||||||
driver: 'ixsmi'
|
driver: 'ixsmi'
|
||||||
},
|
},
|
||||||
[GPUDriverMap.CAMBRICON]: {
|
[GPUDriverMap.CAMBRICON]: {
|
||||||
@@ -142,7 +142,7 @@ const setImageArgs = (params: any) => {
|
|||||||
--token ${params.token} \\`;
|
--token ${params.token} \\`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// avaliable for NVIDIA、AMD、MThreads
|
// avaliable for NVIDIA、MThreads
|
||||||
const registerWorker = (params: {
|
const registerWorker = (params: {
|
||||||
server: string;
|
server: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
@@ -162,6 +162,27 @@ const registerWorker = (params: {
|
|||||||
${params.workerIP ? `--advertise-address ${params.workerIP} \\` : ''}`;
|
${params.workerIP ? `--advertise-address ${params.workerIP} \\` : ''}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// avaliable for AMD
|
||||||
|
const registerAMDWorker = (params: {
|
||||||
|
server: string;
|
||||||
|
tag: string;
|
||||||
|
token: string;
|
||||||
|
image: string;
|
||||||
|
gpu: string;
|
||||||
|
workerIP?: string;
|
||||||
|
modelDir?: string;
|
||||||
|
}) => {
|
||||||
|
const config = GPUsConfigs[params.gpu];
|
||||||
|
const commonArgs = setNormalArgs(params);
|
||||||
|
const imageArgs = setImageArgs(params);
|
||||||
|
// remove empty enter lines and trailing backslash
|
||||||
|
return `${commonArgs}
|
||||||
|
--volume /opt/rocm:/opt/rocm:ro \\
|
||||||
|
--runtime ${config.runtime} \\
|
||||||
|
${imageArgs}
|
||||||
|
${params.workerIP ? `--advertise-address ${params.workerIP} \\` : ''}`;
|
||||||
|
};
|
||||||
|
|
||||||
// avaliable for Ascend
|
// avaliable for Ascend
|
||||||
const registerAscendWorker = (params: {
|
const registerAscendWorker = (params: {
|
||||||
server: string;
|
server: string;
|
||||||
@@ -264,7 +285,7 @@ const registerCambriconWorker = (params: {
|
|||||||
|
|
||||||
export const registerAddWokerCommandMap = {
|
export const registerAddWokerCommandMap = {
|
||||||
[GPUDriverMap.NVIDIA]: registerWorker,
|
[GPUDriverMap.NVIDIA]: registerWorker,
|
||||||
[GPUDriverMap.AMD]: registerWorker,
|
[GPUDriverMap.AMD]: registerAMDWorker,
|
||||||
[GPUDriverMap.ASCEND]: registerAscendWorker,
|
[GPUDriverMap.ASCEND]: registerAscendWorker,
|
||||||
[GPUDriverMap.HYGON]: registerHygonWorker,
|
[GPUDriverMap.HYGON]: registerHygonWorker,
|
||||||
[GPUDriverMap.ILUVATAR]: registerIluvatarWorker,
|
[GPUDriverMap.ILUVATAR]: registerIluvatarWorker,
|
||||||
@@ -275,7 +296,7 @@ export const registerAddWokerCommandMap = {
|
|||||||
|
|
||||||
export const AddWorkerDockerNotes: Record<string, string[]> = {
|
export const AddWorkerDockerNotes: Record<string, string[]> = {
|
||||||
[GPUDriverMap.NVIDIA]: [],
|
[GPUDriverMap.NVIDIA]: [],
|
||||||
[GPUDriverMap.AMD]: [],
|
[GPUDriverMap.AMD]: ['clusters.addworker.amdNotes-01'],
|
||||||
[GPUDriverMap.MOORE_THREADS]: [],
|
[GPUDriverMap.MOORE_THREADS]: [],
|
||||||
[GPUDriverMap.ASCEND]: [],
|
[GPUDriverMap.ASCEND]: [],
|
||||||
[GPUDriverMap.HYGON]: [
|
[GPUDriverMap.HYGON]: [
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { PageActionType } from '@/config/types';
|
|||||||
import useTableFetch from '@/hooks/use-table-fetch';
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
import { useIntl, useModel } from '@umijs/max';
|
import { useIntl, useModel } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Button, ConfigProvider, message, Table } from 'antd';
|
import { ConfigProvider, message, Table } from 'antd';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import NoResult from '../_components/no-result';
|
import NoResult from '../_components/no-result';
|
||||||
import PageBox from '../_components/page-box';
|
import PageBox from '../_components/page-box';
|
||||||
@@ -150,11 +150,9 @@ const Users: React.FC = () => {
|
|||||||
})}
|
})}
|
||||||
title={intl.formatMessage({ id: 'noresult.users.title' })}
|
title={intl.formatMessage({ id: 'noresult.users.title' })}
|
||||||
subTitle={intl.formatMessage({ id: 'noresult.users.subTitle' })}
|
subTitle={intl.formatMessage({ id: 'noresult.users.subTitle' })}
|
||||||
>
|
onClick={handleAddUser}
|
||||||
<Button type="primary" onClick={handleAddUser}>
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
{intl.formatMessage({ id: 'noresult.button.add' })}
|
></NoResult>
|
||||||
</Button>
|
|
||||||
</NoResult>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,50 +28,43 @@ export const loadAudioData = async (
|
|||||||
url: string;
|
url: string;
|
||||||
}> => {
|
}> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
const audioBlob = new Blob([data], { type: type });
|
||||||
const audioBlob = new Blob([data], { type: type });
|
const fileSize = convertFileSize(audioBlob.size);
|
||||||
const fileSize = convertFileSize(audioBlob.size);
|
|
||||||
|
|
||||||
const audio = document.createElement('audio');
|
const audio = document.createElement('audio');
|
||||||
const url = URL.createObjectURL(audioBlob);
|
const url = URL.createObjectURL(audioBlob);
|
||||||
audio.src = url;
|
audio.src = url;
|
||||||
|
|
||||||
audio.addEventListener('loadedmetadata', () => {
|
audio.addEventListener('loadedmetadata', () => {
|
||||||
const duration = audio.duration;
|
const duration = audio.duration;
|
||||||
resolve({
|
resolve({
|
||||||
data: audioBlob,
|
data: audioBlob,
|
||||||
size: fileSize,
|
size: fileSize,
|
||||||
type: type,
|
type: type,
|
||||||
duration: Math.ceil(duration),
|
duration: Math.ceil(duration),
|
||||||
url: url
|
url: url
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
audio.addEventListener('ended', () => {
|
audio.addEventListener('ended', () => {
|
||||||
URL.revokeObjectURL(audio.src);
|
URL.revokeObjectURL(audio.src);
|
||||||
});
|
});
|
||||||
|
|
||||||
audio.addEventListener('error', () => {
|
audio.addEventListener('error', () => {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
message.error('Failed to load audio metadata invalid file');
|
message.error('Failed to load audio metadata invalid file');
|
||||||
reject(new Error('Failed to load audio metadata invalid file'));
|
reject(new Error('Failed to load audio metadata invalid file'));
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
console.log('error====', error);
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readAudioFile = async (
|
export const readAudioFile = async (
|
||||||
file: File
|
file: File
|
||||||
): Promise<{ url: string; name: string; duration: number }> => {
|
): Promise<{ url: string; name: string; duration: number }> => {
|
||||||
console.log('file====', file);
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = async function (e: any) {
|
reader.onload = async function (e: any) {
|
||||||
try {
|
try {
|
||||||
console.log('file====', file);
|
|
||||||
const arrayBuffer = e.target.result;
|
const arrayBuffer = e.target.result;
|
||||||
const audioData = await loadAudioData(arrayBuffer, file.type);
|
const audioData = await loadAudioData(arrayBuffer, file.type);
|
||||||
resolve({
|
resolve({
|
||||||
@@ -79,7 +72,6 @@ export const readAudioFile = async (
|
|||||||
name: file.name
|
name: file.name
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error====', error);
|
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user