style: model list buttons
This commit is contained in:
@@ -10,6 +10,11 @@ interface DropdownButtonsProps {
|
||||
items: MenuProps['items'];
|
||||
size?: 'small' | 'middle' | 'large';
|
||||
trigger?: Trigger[];
|
||||
showText?: boolean;
|
||||
disabled?: boolean;
|
||||
variant?: 'filled' | 'outlined';
|
||||
color?: string;
|
||||
extra?: React.ReactNode;
|
||||
onSelect: (val: any, item?: any) => void;
|
||||
}
|
||||
|
||||
@@ -17,9 +22,16 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
||||
items,
|
||||
size = 'middle',
|
||||
trigger = ['hover'],
|
||||
showText,
|
||||
disabled,
|
||||
variant,
|
||||
color,
|
||||
extra,
|
||||
onSelect
|
||||
}) => {
|
||||
const headItem = _.head(items);
|
||||
const intl = useIntl();
|
||||
|
||||
const handleMenuClick = (item: any) => {
|
||||
const selectItem = _.find(items, { key: item.key });
|
||||
onSelect(item.key, selectItem);
|
||||
@@ -37,19 +49,20 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
||||
return (
|
||||
<>
|
||||
{items?.length === 1 ? (
|
||||
<Tooltip title={intl.formatMessage({ id: _.head(items)?.label })}>
|
||||
<Tooltip title={intl.formatMessage({ id: headItem?.label })}>
|
||||
<Button
|
||||
className={classNames('dropdown-button', size)}
|
||||
{..._.head(items)}
|
||||
icon={_.get(items, '0.icon')}
|
||||
icon={headItem?.icon}
|
||||
size={size}
|
||||
{..._.get(items, '0.props')}
|
||||
{...headItem?.props}
|
||||
onClick={handleButtonClick}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Dropdown.Button
|
||||
disabled={disabled}
|
||||
trigger={trigger}
|
||||
type="primary"
|
||||
dropdownRender={(menus: any) => {
|
||||
return (
|
||||
<div
|
||||
@@ -67,9 +80,10 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
||||
<Button
|
||||
{...item.props}
|
||||
type="text"
|
||||
size="middle"
|
||||
size={size}
|
||||
icon={item.icon}
|
||||
key={item.key}
|
||||
disabled={item.disabled}
|
||||
onClick={() => handleMenuClick(item)}
|
||||
style={{ width: '100%', justifyContent: 'flex-start' }}
|
||||
>
|
||||
@@ -81,21 +95,45 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
||||
);
|
||||
}}
|
||||
buttonsRender={([leftButton, rightButton]) => [
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: _.head(items)?.label })}
|
||||
key="leftButton"
|
||||
>
|
||||
<Button
|
||||
className={classNames('dropdown-button', size)}
|
||||
onClick={handleButtonClick}
|
||||
size={size}
|
||||
icon={_.head(items)?.icon}
|
||||
></Button>
|
||||
</Tooltip>,
|
||||
<>
|
||||
{showText ? (
|
||||
<Button
|
||||
{...headItem?.props}
|
||||
disabled={headItem?.disabled || disabled}
|
||||
className={classNames('dropdown-button', size)}
|
||||
onClick={handleButtonClick}
|
||||
size={size}
|
||||
icon={headItem?.icon}
|
||||
variant={variant}
|
||||
color={color}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: headItem?.label
|
||||
})}
|
||||
{extra}
|
||||
</Button>
|
||||
) : (
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: headItem?.label })}
|
||||
key="leftButton"
|
||||
>
|
||||
<Button
|
||||
{...headItem?.props}
|
||||
className={classNames('dropdown-button', size)}
|
||||
onClick={handleButtonClick}
|
||||
size={size}
|
||||
icon={headItem?.icon}
|
||||
disabled={headItem?.disabled}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>,
|
||||
<Button
|
||||
icon={<MoreOutlined />}
|
||||
size={size}
|
||||
key="menu"
|
||||
variant={variant}
|
||||
color="default"
|
||||
className={classNames('dropdown-button', size)}
|
||||
></Button>
|
||||
]}
|
||||
|
||||
@@ -3,15 +3,20 @@ import React from 'react';
|
||||
import styles from './styles/wrapper.less';
|
||||
|
||||
const Wrapper: React.FC<{
|
||||
label?: string;
|
||||
label?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
labelExtra?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}> = ({ children, label, description, ...rest }) => {
|
||||
}> = ({ children, label, description, labelExtra, ...rest }) => {
|
||||
return (
|
||||
<div className={styles['wrapper']}>
|
||||
{label && (
|
||||
<span className="label">
|
||||
<LabelInfo label={label} description={description}></LabelInfo>
|
||||
<LabelInfo
|
||||
label={label}
|
||||
description={description}
|
||||
labelExtra={labelExtra}
|
||||
></LabelInfo>
|
||||
</span>
|
||||
)}
|
||||
{React.isValidElement(children)
|
||||
|
||||
@@ -8,17 +8,26 @@ import ListItem from './list-item';
|
||||
|
||||
interface ListInputProps {
|
||||
dataList: string[];
|
||||
label: string;
|
||||
label: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
btnText?: string;
|
||||
options?: Global.HintOptions[];
|
||||
placeholder?: string;
|
||||
labelExtra?: React.ReactNode;
|
||||
onChange: (data: string[]) => void;
|
||||
}
|
||||
|
||||
const ListInput: React.FC<ListInputProps> = (props) => {
|
||||
const intl = useIntl();
|
||||
const { dataList, label, description, onChange, btnText, options } = props;
|
||||
const {
|
||||
dataList,
|
||||
label,
|
||||
description,
|
||||
onChange,
|
||||
btnText,
|
||||
options,
|
||||
labelExtra
|
||||
} = props;
|
||||
const [list, setList] = React.useState<{ value: string; uid: number }[]>([]);
|
||||
const countRef = React.useRef(0);
|
||||
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
||||
@@ -71,7 +80,7 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
||||
}, [dataList]);
|
||||
|
||||
return (
|
||||
<Wrapper label={label} description={description}>
|
||||
<Wrapper label={label} description={description} labelExtra={labelExtra}>
|
||||
<>
|
||||
{_.map(list, (item: any, index: number) => {
|
||||
return (
|
||||
|
||||
@@ -7,9 +7,10 @@ interface NoteInfoProps {
|
||||
required?: boolean;
|
||||
label: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
labelExtra?: React.ReactNode;
|
||||
}
|
||||
const NoteInfo: React.FC<NoteInfoProps> = (props) => {
|
||||
const { required, description, label } = props || {};
|
||||
const { required, description, label, labelExtra } = props || {};
|
||||
if (!label) return null;
|
||||
|
||||
return (
|
||||
@@ -43,6 +44,7 @@ const NoteInfo: React.FC<NoteInfoProps> = (props) => {
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{labelExtra}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ interface WrapperProps {
|
||||
variant?: string;
|
||||
hasPrefix?: boolean;
|
||||
classList?: string;
|
||||
labelExtra?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ const Wrapper: React.FC<WrapperProps> = ({
|
||||
hasPrefix,
|
||||
noWrapperStyle,
|
||||
classList,
|
||||
labelExtra,
|
||||
onClick
|
||||
}) => {
|
||||
return (
|
||||
@@ -74,6 +76,7 @@ const Wrapper: React.FC<WrapperProps> = ({
|
||||
label={label}
|
||||
required={required}
|
||||
description={description}
|
||||
labelExtra={labelExtra}
|
||||
></LabelInfo>
|
||||
</label>
|
||||
{extra && <div className={wrapperStyle.extra}>{extra}</div>}
|
||||
|
||||
@@ -20,6 +20,7 @@ const SealInput: React.FC<InputProps & SealFormItemProps> = (props) => {
|
||||
checkStatus,
|
||||
trim = true,
|
||||
loading,
|
||||
labelExtra,
|
||||
...rest
|
||||
} = props;
|
||||
const [isFocus, setIsFocus] = useState(false);
|
||||
@@ -74,6 +75,7 @@ const SealInput: React.FC<InputProps & SealFormItemProps> = (props) => {
|
||||
<Wrapper
|
||||
status={checkStatus || status}
|
||||
label={label}
|
||||
labelExtra={labelExtra}
|
||||
isFocus={isFocus}
|
||||
required={required}
|
||||
description={description}
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface SealFormItemProps {
|
||||
addAfter?: React.ReactNode;
|
||||
allowNull?: boolean;
|
||||
loading?: React.ReactNode;
|
||||
labelExtra?: React.ReactNode;
|
||||
trim?: boolean;
|
||||
checkStatus?: 'success' | 'error' | 'warning' | '';
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ const TableRow: React.FC<
|
||||
childrenDataRef.current = childrenData;
|
||||
const axiosToken = useRef<any>(null);
|
||||
const [updateChild, setUpdateChild] = useState(true);
|
||||
const [currentExpand, setCurrentExpand] = useState(false);
|
||||
|
||||
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
|
||||
dataList: childrenData,
|
||||
limit: 100,
|
||||
setDataList: setChildrenData
|
||||
// callback: (list) => renderChildren?.(list)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -92,7 +92,10 @@ const TableRow: React.FC<
|
||||
></Empty>
|
||||
);
|
||||
}
|
||||
return renderChildren?.(childrenData, record);
|
||||
return renderChildren?.(childrenData, {
|
||||
parent: record,
|
||||
currentExpanded: currentExpand
|
||||
});
|
||||
};
|
||||
|
||||
const handlePolling = async () => {
|
||||
@@ -163,6 +166,7 @@ const TableRow: React.FC<
|
||||
|
||||
const handleRowExpand = async () => {
|
||||
onExpand?.(!expanded, record, record[rowKey]);
|
||||
setCurrentExpand(!expanded);
|
||||
|
||||
if (pollTimer.current) {
|
||||
clearInterval(pollTimer.current);
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 54px;
|
||||
padding-block: 5px;
|
||||
height: 54px;
|
||||
border-radius: var(--border-radius-mdium);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
|
||||
@@ -59,7 +59,10 @@ export interface SealTableProps {
|
||||
onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void;
|
||||
onExpand?: (expanded: boolean, record: any, rowKey: any) => void;
|
||||
onExpandAll?: (expanded: boolean) => void;
|
||||
renderChildren?: (data: any, parent?: any) => React.ReactNode;
|
||||
renderChildren?: (
|
||||
data: any,
|
||||
options: { parent?: any; [key: string]: any }
|
||||
) => React.ReactNode;
|
||||
loadChildren?: (record: any, options?: any) => Promise<any[]>;
|
||||
loadChildrenAPI?: (record: any) => string;
|
||||
contentRendered?: () => void;
|
||||
|
||||
@@ -14,7 +14,7 @@ const TableHeader = ({ columns }: TableHeaderProps) => {
|
||||
{columns.map((column: any, index: number) => {
|
||||
return (
|
||||
<th key={index}>
|
||||
<span className="cell-span">
|
||||
<span className="cell-span cell-header">
|
||||
{column.locale
|
||||
? intl.formatMessage({ id: column.title })
|
||||
: column.title}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
// border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,17 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
td {
|
||||
color: var(--color-white-quaternary);
|
||||
}
|
||||
|
||||
.cell-span {
|
||||
display: flex;
|
||||
padding: 4px 6px;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.cell-header {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,31 @@ import TableHeader from './header';
|
||||
import './index.less';
|
||||
import TableRow from './row';
|
||||
|
||||
interface ColumnProps {
|
||||
export interface ColumnProps {
|
||||
title: string;
|
||||
key: string;
|
||||
render?: (data: { dataIndex: string; row: any }) => any;
|
||||
render?: (data: {
|
||||
dataIndex: string;
|
||||
dataList?: any[];
|
||||
row: any;
|
||||
rowIndex?: number;
|
||||
colIndex?: number;
|
||||
}) => any;
|
||||
locale?: boolean;
|
||||
colSpan?: (params: {
|
||||
row: any;
|
||||
rowIndex: number;
|
||||
colIndex: number;
|
||||
dataIndex: string;
|
||||
dataList: any[];
|
||||
}) => number;
|
||||
rowSpan?: (params: {
|
||||
row: any;
|
||||
rowIndex: number;
|
||||
colIndex: number;
|
||||
dataIndex: string;
|
||||
dataList: any[];
|
||||
}) => number;
|
||||
}
|
||||
|
||||
interface SimpleTableProps {
|
||||
@@ -42,7 +62,9 @@ const SimpleTabel: React.FC<SimpleTableProps> = (props) => {
|
||||
return (
|
||||
<TableRow
|
||||
row={item}
|
||||
rowIndex={index}
|
||||
columns={columns}
|
||||
dataList={dataSource}
|
||||
key={rowKey ? item[rowKey] : index}
|
||||
></TableRow>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,88 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
interface TableRowProps {
|
||||
row: any;
|
||||
columns: any;
|
||||
rowIndex: number;
|
||||
dataList: any[];
|
||||
}
|
||||
const TableRow = ({ row, columns }: TableRowProps) => {
|
||||
|
||||
interface TableCellProps {
|
||||
row: any;
|
||||
column: any;
|
||||
rowIndex: number;
|
||||
colIndex: number;
|
||||
dataList: any[];
|
||||
}
|
||||
const TableCell: React.FC<TableCellProps> = (props: TableCellProps) => {
|
||||
const { row, column, rowIndex, colIndex, dataList } = props;
|
||||
|
||||
const renderContent = useMemo(() => {
|
||||
return column.render
|
||||
? column.render({
|
||||
dataIndex: column.key,
|
||||
dataList: dataList,
|
||||
row: row,
|
||||
rowIndex,
|
||||
colIndex: colIndex
|
||||
})
|
||||
: row[column.key];
|
||||
}, [column, row, rowIndex, colIndex, dataList]);
|
||||
|
||||
if (renderContent === null && (column.colSpan || column.rowSpan)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<td
|
||||
key={colIndex}
|
||||
rowSpan={column.rowSpan?.({
|
||||
row,
|
||||
rowIndex,
|
||||
colIndex: colIndex,
|
||||
dataIndex: column.key,
|
||||
dataList: dataList
|
||||
})}
|
||||
colSpan={column.colSpan?.({
|
||||
row,
|
||||
rowIndex,
|
||||
colIndex: colIndex,
|
||||
dataIndex: column.key,
|
||||
dataList: dataList
|
||||
})}
|
||||
>
|
||||
<span className="cell-span">
|
||||
{column.render
|
||||
? column.render({
|
||||
dataIndex: column.key,
|
||||
dataList: dataList,
|
||||
row: row,
|
||||
rowIndex,
|
||||
colIndex: colIndex
|
||||
})
|
||||
: row[column.key]}
|
||||
</span>
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
const TableRow = ({ row, columns, rowIndex, dataList }: TableRowProps) => {
|
||||
return (
|
||||
<tr>
|
||||
{columns.map((column: any, index: number) => {
|
||||
return (
|
||||
<td key={index}>
|
||||
<span className="cell-span">
|
||||
{column.render
|
||||
? column.render({ dataIndex: column.key, row: row })
|
||||
: row[column.key]}
|
||||
</span>
|
||||
</td>
|
||||
<TableCell
|
||||
key={index}
|
||||
rowIndex={rowIndex}
|
||||
colIndex={index}
|
||||
dataList={dataList}
|
||||
row={row}
|
||||
column={column}
|
||||
></TableCell>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TableRow);
|
||||
export default TableRow;
|
||||
|
||||
@@ -3,13 +3,16 @@ import { useCallback, useState } from 'react';
|
||||
export default function useExpandedRowKeys(defaultKeys: React.Key[] = []) {
|
||||
const [expandedRowKeys, setExpandedRowKeys] =
|
||||
useState<React.Key[]>(defaultKeys);
|
||||
const [currentExpand, setCurrentExpand] = useState<React.Key | null>(null);
|
||||
|
||||
const handleExpandChange = useCallback(
|
||||
(expanded: boolean, record: any, rowKey: any) => {
|
||||
if (expanded) {
|
||||
setExpandedRowKeys((keys) => [...keys, rowKey]);
|
||||
setCurrentExpand(rowKey);
|
||||
} else {
|
||||
setExpandedRowKeys((keys) => keys.filter((key) => key !== rowKey));
|
||||
setCurrentExpand(null);
|
||||
}
|
||||
},
|
||||
[]
|
||||
@@ -19,6 +22,7 @@ export default function useExpandedRowKeys(defaultKeys: React.Key[] = []) {
|
||||
(expanded: boolean, keys: React.Key[] = []) => {
|
||||
if (!expanded) {
|
||||
setExpandedRowKeys([]);
|
||||
setCurrentExpand(null);
|
||||
}
|
||||
if (expanded) {
|
||||
setExpandedRowKeys((prevKeys) => [...new Set([...prevKeys, ...keys])]);
|
||||
@@ -41,10 +45,13 @@ export default function useExpandedRowKeys(defaultKeys: React.Key[] = []) {
|
||||
|
||||
const clearExpandedRowKeys = () => {
|
||||
setExpandedRowKeys([]);
|
||||
setCurrentExpand(null);
|
||||
};
|
||||
|
||||
return {
|
||||
expandedRowKeys,
|
||||
currentExpand,
|
||||
setCurrentExpand,
|
||||
clearExpandedRowKeys,
|
||||
updateExpandedRowKeys,
|
||||
removeExpandedRowKey,
|
||||
|
||||
@@ -108,5 +108,7 @@ export default {
|
||||
'models.table.list.getStart':
|
||||
'<span style="margin-right: 5px;font-size: 13px;">Get started with</span> <span style="font-size: 14px;font-weight: 700">DeepSeek-R1-Distill-Qwen-1.5B</span>',
|
||||
'models.table.llamaAcrossworker': 'Llama-box Across Workers',
|
||||
'models.table.vllmAcrossworker': 'vLLM Across Workers'
|
||||
'models.table.vllmAcrossworker': 'vLLM Across Workers',
|
||||
'models.form.releases': 'Releases',
|
||||
'models.form.moreparameters': 'More Parameters'
|
||||
};
|
||||
|
||||
@@ -109,5 +109,8 @@ export default {
|
||||
'models.table.llamaAcrossworker':
|
||||
'TODO: Translate key "models.table.llamaAcrossworker"',
|
||||
'models.table.vllmAcrossworker':
|
||||
'TODO: Translate key "models.table.vllmAcrossworker"'
|
||||
'TODO: Translate key "models.table.vllmAcrossworker"',
|
||||
'models.form.releases': 'TODO: Translate key "models.form.releases"',
|
||||
'models.form.moreparameters':
|
||||
'TODO: Translate key "models.form.moreparameters"'
|
||||
};
|
||||
|
||||
@@ -104,5 +104,7 @@ export default {
|
||||
'models.table.list.getStart':
|
||||
'<span style="margin-right: 5px;font-size: 13px;">一键部署</span><span style="font-size: 14px;font-weight: 700">DeepSeek-R1-Distill-Qwen-1.5B</span><span style="margin-left: 5px;font-size: 13px;">立即使用!</span>',
|
||||
'models.table.llamaAcrossworker': 'Llama-box 跨节点',
|
||||
'models.table.vllmAcrossworker': 'vLLM 跨节点'
|
||||
'models.table.vllmAcrossworker': 'vLLM 跨节点',
|
||||
'models.form.releases': '版本',
|
||||
'models.form.moreparameters': '更多参数'
|
||||
};
|
||||
|
||||
@@ -101,12 +101,14 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
if (backend === backendOptionsMap.llamaBox) {
|
||||
return {
|
||||
backend: 'llama-box',
|
||||
releases: 'https://github.com/gpustack/llama-box/releases',
|
||||
link: 'https://github.com/gpustack/llama-box?tab=readme-ov-file#usage'
|
||||
};
|
||||
}
|
||||
if (backend === backendOptionsMap.vllm) {
|
||||
return {
|
||||
backend: 'vLLM',
|
||||
releases: 'https://github.com/vllm-project/vllm/releases',
|
||||
link: 'https://docs.vllm.ai/en/stable/serving/openai_compatible_server.html#command-line-arguments-for-the-server'
|
||||
};
|
||||
}
|
||||
@@ -327,6 +329,25 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
description={intl.formatMessage({
|
||||
id: 'models.form.backendVersion.tips'
|
||||
})}
|
||||
labelExtra={
|
||||
backendParamsTips?.releases && (
|
||||
<span
|
||||
style={{
|
||||
marginLeft: 5
|
||||
}}
|
||||
>
|
||||
(
|
||||
<Typography.Link
|
||||
style={{ lineHeight: 1 }}
|
||||
href={backendParamsTips?.releases}
|
||||
target="_blank"
|
||||
>
|
||||
{intl.formatMessage({ id: 'models.form.releases' })}
|
||||
</Typography.Link>
|
||||
)
|
||||
</span>
|
||||
)
|
||||
}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
|
||||
@@ -346,19 +367,20 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
||||
dataList={form.getFieldValue('backend_parameters') || []}
|
||||
onChange={handleBackendParametersChange}
|
||||
options={paramsConfig}
|
||||
description={
|
||||
backendParamsTips && (
|
||||
<span>
|
||||
{intl.formatMessage(
|
||||
{ id: 'models.form.backend_parameters.vllm.tips' },
|
||||
{ backend: backendParamsTips.backend || '' }
|
||||
)}{' '}
|
||||
labelExtra={
|
||||
backendParamsTips?.link && (
|
||||
<span style={{ marginLeft: 2 }}>
|
||||
(
|
||||
<Typography.Link
|
||||
href={backendParamsTips.link}
|
||||
style={{ lineHeight: 1 }}
|
||||
href={backendParamsTips?.link}
|
||||
target="_blank"
|
||||
>
|
||||
{intl.formatMessage({ id: 'common.text.here' })}
|
||||
{intl.formatMessage({
|
||||
id: 'models.form.moreparameters'
|
||||
})}
|
||||
</Typography.Link>
|
||||
)
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import AutoTooltip from '@/components/auto-tooltip';
|
||||
import DropdownButtons from '@/components/drop-down-buttons';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import RowChildren from '@/components/seal-table/components/row-children';
|
||||
import SimpleTabel from '@/components/simple-table';
|
||||
import SimpleTabel, { ColumnProps } from '@/components/simple-table';
|
||||
import StatusTag from '@/components/status-tag';
|
||||
import { HandlerOptions } from '@/hooks/use-chunk-fetch';
|
||||
import useDownloadStream from '@/hooks/use-download-stream';
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
DeleteOutlined,
|
||||
DownloadOutlined,
|
||||
HddFilled,
|
||||
InfoCircleOutlined
|
||||
InfoCircleOutlined,
|
||||
ThunderboltFilled
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import {
|
||||
@@ -27,17 +28,48 @@ import {
|
||||
} from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { MODEL_INSTANCE_API } from '../apis';
|
||||
import { InstanceStatusMap, InstanceStatusMapValue, status } from '../config';
|
||||
import { ModelInstanceListItem } from '../config/types';
|
||||
import '../style/instance-item.less';
|
||||
|
||||
const WorkerInfo = (props: {
|
||||
title: React.ReactNode;
|
||||
defaultOpen: boolean;
|
||||
}) => {
|
||||
const [open, setOpen] = React.useState(props.defaultOpen);
|
||||
useEffect(() => {
|
||||
if (props.defaultOpen) {
|
||||
setTimeout(() => {
|
||||
setOpen(false);
|
||||
}, 1000);
|
||||
}
|
||||
}, [props.defaultOpen]);
|
||||
return (
|
||||
<span className="server-info-wrapper">
|
||||
<Tooltip
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
title={props.title}
|
||||
overlayInnerStyle={{
|
||||
width: 'max-content',
|
||||
maxWidth: '400px'
|
||||
}}
|
||||
>
|
||||
<span className="server-info">
|
||||
<InfoCircleOutlined />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
interface InstanceItemProps {
|
||||
instanceData: ModelInstanceListItem;
|
||||
workerList: WorkerListItem[];
|
||||
modelData?: any;
|
||||
defaultOpenId: string;
|
||||
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
|
||||
}
|
||||
|
||||
@@ -76,7 +108,7 @@ const childActionList = [
|
||||
}
|
||||
];
|
||||
|
||||
const distributeCols = [
|
||||
const distributeCols: ColumnProps[] = [
|
||||
{
|
||||
title: 'Worker',
|
||||
key: 'worker_name'
|
||||
@@ -84,7 +116,7 @@ const distributeCols = [
|
||||
{
|
||||
title: 'IP',
|
||||
key: 'worker_ip',
|
||||
render: ({ row }: { row: ModelInstanceListItem }) => {
|
||||
render: ({ row }) => {
|
||||
return row.port ? `${row.worker_ip}:${row.port}` : row.worker_ip;
|
||||
}
|
||||
},
|
||||
@@ -94,9 +126,21 @@ const distributeCols = [
|
||||
key: 'gpu_index'
|
||||
},
|
||||
{
|
||||
title: 'resources.table.memory',
|
||||
title: 'resources.table.vram',
|
||||
locale: true,
|
||||
key: 'ram'
|
||||
key: 'vram',
|
||||
rowSpan: ({ row, rowIndex, colIndex, dataIndex, dataList }) => {
|
||||
return rowIndex === 0 ? dataList.length : 0;
|
||||
},
|
||||
render: ({ rowIndex, dataList }) => {
|
||||
if (rowIndex === 0) {
|
||||
return convertFileSize(
|
||||
_.sumBy(dataList, (item: any) => item.vram),
|
||||
2
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -153,6 +197,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
||||
instanceData,
|
||||
workerList,
|
||||
modelData,
|
||||
defaultOpenId,
|
||||
handleChildSelect
|
||||
}) => {
|
||||
const [api, contextHolder] = notification.useNotification({
|
||||
@@ -224,55 +269,32 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
||||
? `${instanceData.worker_ip}:${instanceData.port}`
|
||||
: instanceData.worker_ip;
|
||||
}
|
||||
let backend = modelData?.backend || '';
|
||||
if (modelData.backend_version) {
|
||||
backend += ` (${modelData.backend_version})`;
|
||||
}
|
||||
const vrams = instanceData.computed_resource_claim?.vram || {};
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<div>{instanceData.worker_name}</div>
|
||||
<div className="flex-center">
|
||||
<HddFilled className="m-r-5" />
|
||||
{instanceData.worker_name}
|
||||
{workerIp}
|
||||
</div>
|
||||
<div className="flex m-b-6 gap-6">
|
||||
<InfoItem label="IP" value={workerIp} width={180}></InfoItem>
|
||||
<InfoItem
|
||||
width={90}
|
||||
label={intl.formatMessage({ id: 'models.form.backend' })}
|
||||
value={backend}
|
||||
></InfoItem>
|
||||
<div className="flex-center">
|
||||
<IconFont type="icon-filled-gpu" className="m-r-5" />
|
||||
{intl.formatMessage({ id: 'models.table.gpuindex' })}: [
|
||||
{_.join(instanceData.gpu_indexes?.sort?.(), ',')}]
|
||||
</div>
|
||||
<div className="flex gap-6">
|
||||
<InfoItem
|
||||
width={180}
|
||||
label={intl.formatMessage({ id: 'models.table.gpuindex' })}
|
||||
value={Object.keys(vrams)
|
||||
?.sort?.()
|
||||
.map((index) => {
|
||||
return (
|
||||
<span className="flex-1" key={index}>
|
||||
<span className="index">
|
||||
[{index}] {''}
|
||||
</span>
|
||||
{convertFileSize(vrams?.[index], 0)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
></InfoItem>
|
||||
<InfoItem
|
||||
width={90}
|
||||
label={intl.formatMessage({ id: 'resources.table.memory' })}
|
||||
value={convertFileSize(
|
||||
instanceData.computed_resource_claim?.ram,
|
||||
0
|
||||
)}
|
||||
></InfoItem>
|
||||
<div className="flex-center">
|
||||
<ThunderboltFilled className="m-r-5" />
|
||||
{intl.formatMessage({ id: 'models.form.backend' })}:{' '}
|
||||
{modelData?.backend || ''}
|
||||
{modelData.backend_version ? `(${modelData.backend_version})` : ''}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [modelData, instanceData, intl]);
|
||||
|
||||
const calcTotalVram = (vram: Record<string, number>) => {
|
||||
return _.sum(_.values(vram));
|
||||
};
|
||||
|
||||
const renderDistributedServer = useCallback(
|
||||
(severList: any[]) => {
|
||||
const list = _.map(severList, (item: any) => {
|
||||
@@ -281,20 +303,35 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
||||
worker_name: data?.name,
|
||||
worker_ip: data?.ip,
|
||||
port: '',
|
||||
ram: convertFileSize(item.computed_resource_claim?.ram, 0),
|
||||
gpu_index: displayGPUs(item.computed_resource_claim?.vram || {})
|
||||
vram: calcTotalVram(item.computed_resource_claim?.vram || {}),
|
||||
gpu_index: _.keys(item.computed_resource_claim?.vram).join(',')
|
||||
};
|
||||
});
|
||||
|
||||
// const list = [
|
||||
// {
|
||||
// worker_name: 'worker1',
|
||||
// worker_ip: '192.168.50.23',
|
||||
// port: '',
|
||||
// vram: 21555525632,
|
||||
// gpu_index: '0,1'
|
||||
// },
|
||||
// {
|
||||
// worker_name: 'worker2',
|
||||
// worker_ip: '192.168.50.25',
|
||||
// port: '',
|
||||
// vram: 21555525632,
|
||||
// gpu_index: '2,3'
|
||||
// }
|
||||
// ];
|
||||
|
||||
const mainWorker = [
|
||||
{
|
||||
worker_name: `${instanceData.worker_name} (main)`,
|
||||
worker_name: `${instanceData.worker_name}`,
|
||||
worker_ip: `${instanceData.worker_ip}`,
|
||||
port: '',
|
||||
ram: convertFileSize(instanceData.computed_resource_claim?.ram, 0),
|
||||
gpu_index: displayGPUs(
|
||||
instanceData.computed_resource_claim?.vram || {}
|
||||
)
|
||||
vram: calcTotalVram(instanceData.computed_resource_claim?.vram || {}),
|
||||
gpu_index: `${instanceData.gpu_indexes?.join?.(',')}(main)`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -443,18 +480,12 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
||||
<AutoTooltip title={instanceData.name} ghost>
|
||||
<span className="m-r-5">{instanceData.name}</span>
|
||||
</AutoTooltip>
|
||||
<Tooltip
|
||||
title={renderWorkerInfo}
|
||||
overlayInnerStyle={{
|
||||
width: 'max-content',
|
||||
maxWidth: '400px'
|
||||
}}
|
||||
>
|
||||
<span className="server-info">
|
||||
<InfoCircleOutlined className="m-r-2" />{' '}
|
||||
{intl.formatMessage({ id: 'common.button.moreInfo' })}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{!!instanceData.worker_id && (
|
||||
<WorkerInfo
|
||||
title={renderWorkerInfo}
|
||||
defaultOpen={defaultOpenId === instanceData.name}
|
||||
></WorkerInfo>
|
||||
)}
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
@@ -468,10 +499,9 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
||||
>
|
||||
{renderOffloadInfo}
|
||||
{renderDistributionInfo(
|
||||
instanceData.distributed_servers?.rpc_servers || []
|
||||
)}
|
||||
{renderDistributionInfo(
|
||||
instanceData.distributed_servers?.ray_actors || []
|
||||
instanceData.distributed_servers?.rpc_servers ||
|
||||
instanceData.distributed_servers?.ray_actors ||
|
||||
[]
|
||||
)}
|
||||
</span>
|
||||
</Col>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
||||
import { Space } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { ModelInstanceListItem } from '../config/types';
|
||||
import '../style/instance-item.less';
|
||||
import InstanceItem from './instance-item';
|
||||
@@ -10,6 +10,7 @@ interface InstanceItemProps {
|
||||
list: ModelInstanceListItem[];
|
||||
workerList: WorkerListItem[];
|
||||
modelData?: any;
|
||||
currentExpanded?: string;
|
||||
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
|
||||
}
|
||||
|
||||
@@ -17,8 +18,26 @@ const Instances: React.FC<InstanceItemProps> = ({
|
||||
list,
|
||||
workerList,
|
||||
modelData,
|
||||
currentExpanded,
|
||||
handleChildSelect
|
||||
}) => {
|
||||
const [firstLoad, setFirstLoad] = React.useState(true);
|
||||
|
||||
const defaultOpenId = useMemo(() => {
|
||||
if (!currentExpanded) {
|
||||
return '';
|
||||
}
|
||||
const current = _.find(
|
||||
list,
|
||||
(item: ModelInstanceListItem) => item.worker_id
|
||||
);
|
||||
return current ? current.name : '';
|
||||
}, [currentExpanded, list]);
|
||||
|
||||
useEffect(() => {
|
||||
setFirstLoad(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
{_.map(list, (item: ModelInstanceListItem, index: number) => {
|
||||
@@ -28,6 +47,7 @@ const Instances: React.FC<InstanceItemProps> = ({
|
||||
modelData={modelData}
|
||||
workerList={workerList}
|
||||
instanceData={item}
|
||||
defaultOpenId={firstLoad ? defaultOpenId : ''}
|
||||
handleChildSelect={handleChildSelect}
|
||||
></InstanceItem>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
SyncOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
|
||||
import { useAccess, useIntl, useNavigate } from '@umijs/max';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
@@ -68,7 +68,6 @@ import {
|
||||
} from '../config';
|
||||
import { FormData, ListItem, ModelInstanceListItem } from '../config/types';
|
||||
import { useGenerateFormEditInitialValues } from '../hooks';
|
||||
import DeployDropdown from './deploy-dropdown';
|
||||
import DeployModal from './deploy-modal';
|
||||
import Instances from './instances';
|
||||
import ModelTag from './model-tag';
|
||||
@@ -120,7 +119,7 @@ const ActionList = [
|
||||
{
|
||||
label: 'common.button.start',
|
||||
key: 'start',
|
||||
icon: <IconFont type="icon-playcircle"></IconFont>
|
||||
icon: <IconFont type="icon-outline-play"></IconFont>
|
||||
},
|
||||
{
|
||||
label: 'common.button.delete',
|
||||
@@ -132,6 +131,27 @@ const ActionList = [
|
||||
}
|
||||
];
|
||||
|
||||
const ButtonList = [
|
||||
{
|
||||
label: 'common.button.start',
|
||||
key: 'start',
|
||||
icon: <IconFont type="icon-outline-play"></IconFont>
|
||||
},
|
||||
{
|
||||
label: 'common.button.stop',
|
||||
key: 'stop',
|
||||
icon: <IconFont type="icon-stop1"></IconFont>
|
||||
},
|
||||
{
|
||||
label: 'common.button.delete',
|
||||
key: 'delete',
|
||||
icon: <DeleteOutlined />,
|
||||
props: {
|
||||
danger: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const generateSource = (record: ListItem) => {
|
||||
if (record.source === modelSourceMap.modelscope_value) {
|
||||
return `${modelSourceMap.modelScope}/${record.model_scope_model_id}`;
|
||||
@@ -327,6 +347,12 @@ const Models: React.FC<ModelsProps> = ({
|
||||
}, []);
|
||||
|
||||
const sourceOptions = [
|
||||
{
|
||||
label: intl.formatMessage({ id: 'menu.models.modelCatalog' }),
|
||||
value: 'catalog',
|
||||
key: 'catalog',
|
||||
icon: <IconFont type="icon-catalog"></IconFont>
|
||||
},
|
||||
{
|
||||
label: 'Hugging Face',
|
||||
value: modelSourceMap.huggingface_value,
|
||||
@@ -345,12 +371,6 @@ const Models: React.FC<ModelsProps> = ({
|
||||
key: 'modelscope',
|
||||
icon: <IconFont type="icon-tu2"></IconFont>
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'menu.models.modelCatalog' }),
|
||||
value: 'catalog',
|
||||
key: 'catalog',
|
||||
icon: <IconFont type="icon-catalog"></IconFont>
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage({ id: 'models.form.localPath' }),
|
||||
value: modelSourceMap.local_path_value,
|
||||
@@ -659,11 +679,12 @@ const Models: React.FC<ModelsProps> = ({
|
||||
);
|
||||
|
||||
const renderChildren = useCallback(
|
||||
(list: any, parent?: any) => {
|
||||
(list: any, options: { parent?: any; [key: string]: any }) => {
|
||||
return (
|
||||
<Instances
|
||||
list={list}
|
||||
modelData={parent}
|
||||
currentExpanded={options.currentExpanded}
|
||||
modelData={options.parent}
|
||||
workerList={workerList}
|
||||
handleChildSelect={handleChildSelect}
|
||||
></Instances>
|
||||
@@ -739,6 +760,18 @@ const Models: React.FC<ModelsProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const handleActionSelect = (val: any) => {
|
||||
if (val === 'delete') {
|
||||
handleDeleteBatch();
|
||||
}
|
||||
if (val === 'start') {
|
||||
handleStartBatch();
|
||||
}
|
||||
if (val === 'stop') {
|
||||
handleStopBatch();
|
||||
}
|
||||
};
|
||||
|
||||
const columns: SealColumnProps[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -939,12 +972,6 @@ const Models: React.FC<ModelsProps> = ({
|
||||
onClick: handleClickDropdown
|
||||
}}
|
||||
trigger={['hover']}
|
||||
dropdownRender={() => (
|
||||
<DeployDropdown
|
||||
items={sourceOptions}
|
||||
onSelect={handleClickDropdown}
|
||||
></DeployDropdown>
|
||||
)}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Button
|
||||
@@ -955,7 +982,19 @@ const Models: React.FC<ModelsProps> = ({
|
||||
{intl?.formatMessage?.({ id: 'models.button.deploy' })}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Button
|
||||
<DropdownButtons
|
||||
items={ButtonList}
|
||||
extra={
|
||||
rowSelection.selectedRowKeys.length > 0 && (
|
||||
<span>({rowSelection.selectedRowKeys.length})</span>
|
||||
)
|
||||
}
|
||||
size="large"
|
||||
showText={true}
|
||||
disabled={!rowSelection.selectedRowKeys.length}
|
||||
onSelect={handleActionSelect}
|
||||
/>
|
||||
{/* <Button
|
||||
icon={<IconFont type="icon-outline-play"></IconFont>}
|
||||
onClick={handleStartBatch}
|
||||
disabled={!rowSelection.selectedRows.length}
|
||||
@@ -993,7 +1032,7 @@ const Models: React.FC<ModelsProps> = ({
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
</Access>
|
||||
</Access> */}
|
||||
</Space>
|
||||
}
|
||||
></PageTools>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import PageTools from '@/components/page-tools';
|
||||
import ProgressBar from '@/components/progress-bar';
|
||||
import InfoColumn from '@/components/simple-table/info-column';
|
||||
import useTableSort from '@/hooks/use-table-sort';
|
||||
import { convertFileSize } from '@/utils';
|
||||
import { SyncOutlined } from '@ant-design/icons';
|
||||
@@ -12,6 +13,33 @@ import { queryGpuDevicesList } from '../apis';
|
||||
import { GPUDeviceItem } from '../config/types';
|
||||
const { Column } = Table;
|
||||
|
||||
const fieldList = [
|
||||
{
|
||||
label: 'resources.table.total',
|
||||
key: 'total',
|
||||
locale: true,
|
||||
render: (val: any) => {
|
||||
return convertFileSize(val, 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'resources.table.used',
|
||||
key: 'used',
|
||||
locale: true,
|
||||
render: (val: any) => {
|
||||
return convertFileSize(val, 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'resources.table.allocated',
|
||||
key: 'allocated',
|
||||
locale: true,
|
||||
render: (val: any) => {
|
||||
return convertFileSize(val, 0);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const GPUList: React.FC = () => {
|
||||
console.log('GPUList======');
|
||||
const intl = useIntl();
|
||||
@@ -228,19 +256,10 @@ const GPUList: React.FC = () => {
|
||||
) * 100
|
||||
}
|
||||
label={
|
||||
<span className="flex-column">
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'resources.table.total' })}:{' '}
|
||||
{convertFileSize(record.memory?.total, 0)}
|
||||
</span>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'resources.table.used' })}:{' '}
|
||||
{convertFileSize(
|
||||
record.memory?.used || record.memory?.allocated,
|
||||
0
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
<InfoColumn
|
||||
fieldList={fieldList}
|
||||
data={record.memory}
|
||||
></InfoColumn>
|
||||
}
|
||||
></ProgressBar>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user