fix: my models status
This commit is contained in:
+14
-4
@@ -13,6 +13,7 @@ import {
|
|||||||
writeState
|
writeState
|
||||||
} from '@/utils/localstore/index';
|
} from '@/utils/localstore/index';
|
||||||
import { RequestConfig, history } from '@umijs/max';
|
import { RequestConfig, history } from '@umijs/max';
|
||||||
|
import { message } from 'antd';
|
||||||
|
|
||||||
const loginPath = '/login';
|
const loginPath = '/login';
|
||||||
|
|
||||||
@@ -53,13 +54,24 @@ export async function getInitialState(): Promise<{
|
|||||||
}): Promise<Global.UserInfo> => {
|
}): Promise<Global.UserInfo> => {
|
||||||
try {
|
try {
|
||||||
const data = await queryCurrentUserState({
|
const data = await queryCurrentUserState({
|
||||||
skipErrorHandler: config?.skipErrorHandler ?? false
|
skipErrorHandler: true
|
||||||
});
|
});
|
||||||
if (data.is_admin) {
|
if (data.is_admin) {
|
||||||
getUpdateCheck();
|
getUpdateCheck();
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
const data = error?.response?.data;
|
||||||
|
if (data?.code === 401 && data?.message.includes('deactivate')) {
|
||||||
|
message.error({
|
||||||
|
content: (
|
||||||
|
<div>
|
||||||
|
<span>{data?.message}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
duration: 5
|
||||||
|
});
|
||||||
|
}
|
||||||
history.push(loginPath);
|
history.push(loginPath);
|
||||||
}
|
}
|
||||||
return {} as Global.UserInfo;
|
return {} as Global.UserInfo;
|
||||||
@@ -81,9 +93,7 @@ export async function getInitialState(): Promise<{
|
|||||||
getAppVersionInfo();
|
getAppVersionInfo();
|
||||||
|
|
||||||
if (![loginPath].includes(location.pathname)) {
|
if (![loginPath].includes(location.pathname)) {
|
||||||
const userInfo = await fetchUserInfo({
|
const userInfo = await fetchUserInfo();
|
||||||
skipErrorHandler: location.pathname === '/'
|
|
||||||
});
|
|
||||||
checkDefaultPage(userInfo);
|
checkDefaultPage(userInfo);
|
||||||
return {
|
return {
|
||||||
fetchUserInfo,
|
fetchUserInfo,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import './index.less';
|
|||||||
interface TagsWrapperProps {
|
interface TagsWrapperProps {
|
||||||
gap?: number;
|
gap?: number;
|
||||||
dataList: any[];
|
dataList: any[];
|
||||||
renderTag: (item: any) => React.ReactNode;
|
renderTag: (item: any, index?: number) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagsWrapper: React.FC<TagsWrapperProps> = (props) => {
|
const TagsWrapper: React.FC<TagsWrapperProps> = (props) => {
|
||||||
@@ -94,7 +94,7 @@ const TagsWrapper: React.FC<TagsWrapperProps> = (props) => {
|
|||||||
{_.map(
|
{_.map(
|
||||||
_.slice(dataList, hiddenIndices.start, hiddenIndices.end),
|
_.slice(dataList, hiddenIndices.start, hiddenIndices.end),
|
||||||
(item: any, index: number) => {
|
(item: any, index: number) => {
|
||||||
return <span key={index}>{renderTag?.(item)}</span>;
|
return <span key={index}>{renderTag?.(item, index)}</span>;
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -108,7 +108,7 @@ const TagsWrapper: React.FC<TagsWrapperProps> = (props) => {
|
|||||||
_.slice(dataList, hiddenIndices.end),
|
_.slice(dataList, hiddenIndices.end),
|
||||||
(item: any, index: number) => {
|
(item: any, index: number) => {
|
||||||
return {
|
return {
|
||||||
label: renderTag?.(item),
|
label: renderTag?.(item, index),
|
||||||
key: index,
|
key: index,
|
||||||
onClick: handleClick
|
onClick: handleClick
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -261,5 +261,6 @@ export default {
|
|||||||
'common.button.edit.item': 'Edit {name}',
|
'common.button.edit.item': 'Edit {name}',
|
||||||
'common.button.terminal': 'Terminal',
|
'common.button.terminal': 'Terminal',
|
||||||
'common.button.addItem': 'Add Item',
|
'common.button.addItem': 'Add Item',
|
||||||
'common.help.default': 'Default: {content}'
|
'common.help.default': 'Default: {content}',
|
||||||
|
'common.filter.status': 'Filter by status'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -203,5 +203,8 @@ export default {
|
|||||||
'models.form.gpusAllocationType.auto.tips':
|
'models.form.gpusAllocationType.auto.tips':
|
||||||
'System calculates GPUs per replica automatically.',
|
'System calculates GPUs per replica automatically.',
|
||||||
'models.form.gpusAllocationType.custom.tips':
|
'models.form.gpusAllocationType.custom.tips':
|
||||||
'You can specify the exact number of GPUs per replica.'
|
'You can specify the exact number of GPUs per replica.',
|
||||||
|
'models.mymodels.status.inactive': 'Stopped',
|
||||||
|
'models.mymodels.status.degrade': 'Abnormal',
|
||||||
|
'models.mymodels.status.active': 'Active'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -261,7 +261,8 @@ export default {
|
|||||||
'common.button.edit.item': 'Edit {name}',
|
'common.button.edit.item': 'Edit {name}',
|
||||||
'common.button.terminal': 'Terminal',
|
'common.button.terminal': 'Terminal',
|
||||||
'common.button.addItem': 'Add Item',
|
'common.button.addItem': 'Add Item',
|
||||||
'common.help.default': 'Default: {content}'
|
'common.help.default': 'Default: {content}',
|
||||||
|
'common.filter.status': 'Filter by status'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -288,5 +289,6 @@ export default {
|
|||||||
// 21. 'common.button.edit.item': 'Edit {name}',
|
// 21. 'common.button.edit.item': 'Edit {name}',
|
||||||
// 22. 'common.button.terminal': 'Terminal',
|
// 22. 'common.button.terminal': 'Terminal',
|
||||||
// 23. 'common.button.addItem': 'Add Item',
|
// 23. 'common.button.addItem': 'Add Item',
|
||||||
// 24. 'common.help.default': 'Default: {content}'
|
// 24. 'common.help.default': 'Default: {content}',
|
||||||
|
// 25. 'common.filter.status': 'Filter by status'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -200,7 +200,10 @@ export default {
|
|||||||
'models.form.gpusAllocationType.auto.tips':
|
'models.form.gpusAllocationType.auto.tips':
|
||||||
'System calculates GPUs per replica automatically.',
|
'System calculates GPUs per replica automatically.',
|
||||||
'models.form.gpusAllocationType.custom.tips':
|
'models.form.gpusAllocationType.custom.tips':
|
||||||
'You can specify the exact number of GPUs per replica.'
|
'You can specify the exact number of GPUs per replica.',
|
||||||
|
'models.mymodels.status.inactive': 'Stopped',
|
||||||
|
'models.mymodels.status.degrade': 'Abnormal',
|
||||||
|
'models.mymodels.status.active': 'Active'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -240,6 +243,9 @@ export default {
|
|||||||
// 35. 'models.form.gpusAllocationType.auto': 'Auto',
|
// 35. 'models.form.gpusAllocationType.auto': 'Auto',
|
||||||
// 36. 'models.form.gpusAllocationType.custom': 'Custom',
|
// 36. 'models.form.gpusAllocationType.custom': 'Custom',
|
||||||
// 37. 'models.form.gpusAllocationType.auto.tips': 'System calculates GPUs per replica automatically.',
|
// 37. 'models.form.gpusAllocationType.auto.tips': 'System calculates GPUs per replica automatically.',
|
||||||
// 38. 'models.form.gpusAllocationType.custom.tips': 'You can specify the exact number of GPUs per replica.'
|
// 38. 'models.form.gpusAllocationType.custom.tips': 'You can specify the exact number of GPUs per replica.',
|
||||||
|
// 39. 'models.mymodels.status.inactive': 'Stopped',
|
||||||
|
// 41. 'models.mymodels.status.degrade': 'Abnormal',
|
||||||
|
// 42. 'models.mymodels.status.active': 'Active'
|
||||||
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -260,9 +260,10 @@ export default {
|
|||||||
'common.button.edit.item': 'Редактировать {name}',
|
'common.button.edit.item': 'Редактировать {name}',
|
||||||
'common.button.terminal': 'Терминал',
|
'common.button.terminal': 'Терминал',
|
||||||
'common.button.addItem': 'Добавить элемент',
|
'common.button.addItem': 'Добавить элемент',
|
||||||
'common.help.default': 'По умолчанию: {content}'
|
'common.help.default': 'По умолчанию: {content}',
|
||||||
|
'common.filter.status': 'Filter by status'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'common.filter.status': 'Filter by status'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -187,20 +187,30 @@ export default {
|
|||||||
'models.form.chunkSize': 'Размер чанков кэша',
|
'models.form.chunkSize': 'Размер чанков кэша',
|
||||||
'models.form.maxCPUSize': 'Максимальный размер CPU кэша (ГиБ)',
|
'models.form.maxCPUSize': 'Максимальный размер CPU кэша (ГиБ)',
|
||||||
'models.form.remoteURL': 'URL удаленного хранилища',
|
'models.form.remoteURL': 'URL удаленного хранилища',
|
||||||
'models.form.runCommandPlaceholder': 'напр., vllm serve Qwen/Qwen2.5-1.5B-Instruct',
|
'models.form.runCommandPlaceholder':
|
||||||
|
'напр., vllm serve Qwen/Qwen2.5-1.5B-Instruct',
|
||||||
'models.accessSettings.public': 'Публичный',
|
'models.accessSettings.public': 'Публичный',
|
||||||
'models.accessSettings.authed': 'Аутентифицированный',
|
'models.accessSettings.authed': 'Аутентифицированный',
|
||||||
'models.accessSettings.allowedUsers': 'Разрешенные пользователи',
|
'models.accessSettings.allowedUsers': 'Разрешенные пользователи',
|
||||||
'models.accessSettings.public.tips': 'При публичном доступе любой пользователь может получить доступ к модели без аутентификации, что может привести к риску утечки данных.',
|
'models.accessSettings.public.tips':
|
||||||
|
'При публичном доступе любой пользователь может получить доступ к модели без аутентификации, что может привести к риску утечки данных.',
|
||||||
'models.table.button.deploy': 'Развернуть сейчас',
|
'models.table.button.deploy': 'Развернуть сейчас',
|
||||||
'models.form.backendVersion.holder': 'Введите или выберите версию',
|
'models.form.backendVersion.holder': 'Введите или выберите версию',
|
||||||
'models.form.gpusperreplica': 'GPU на реплику',
|
'models.form.gpusperreplica': 'GPU на реплику',
|
||||||
'models.form.gpusAllocationType': 'Тип распределения GPU',
|
'models.form.gpusAllocationType': 'Тип распределения GPU',
|
||||||
'models.form.gpusAllocationType.auto': 'Авто',
|
'models.form.gpusAllocationType.auto': 'Авто',
|
||||||
'models.form.gpusAllocationType.custom': 'Вручную',
|
'models.form.gpusAllocationType.custom': 'Вручную',
|
||||||
'models.form.gpusAllocationType.auto.tips': 'Система автоматически рассчитывает количество GPU на реплику.',
|
'models.form.gpusAllocationType.auto.tips':
|
||||||
'models.form.gpusAllocationType.custom.tips': 'Вы можете указать точное количество GPU на реплику.'
|
'Система автоматически рассчитывает количество GPU на реплику.',
|
||||||
|
'models.form.gpusAllocationType.custom.tips':
|
||||||
|
'Вы можете указать точное количество GPU на реплику.',
|
||||||
|
'models.mymodels.status.inactive': 'Stopped',
|
||||||
|
'models.mymodels.status.degrade': 'Abnormal',
|
||||||
|
'models.mymodels.status.active': 'Active'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'models.mymodels.status.inactive': 'Stopped',
|
||||||
|
// 3. 'models.mymodels.status.degrade': 'Abnormal',
|
||||||
|
// 4. 'models.mymodels.status.active': 'Active'
|
||||||
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -253,5 +253,6 @@ export default {
|
|||||||
'common.button.edit.item': '编辑 {name}',
|
'common.button.edit.item': '编辑 {name}',
|
||||||
'common.button.terminal': '终端',
|
'common.button.terminal': '终端',
|
||||||
'common.button.addItem': '添加项',
|
'common.button.addItem': '添加项',
|
||||||
'common.help.default': '默认: {content}'
|
'common.help.default': '默认: {content}',
|
||||||
|
'common.filter.status': '按状态筛选'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -191,5 +191,8 @@ export default {
|
|||||||
'models.form.gpusAllocationType.custom': '自定义',
|
'models.form.gpusAllocationType.custom': '自定义',
|
||||||
'models.form.gpusAllocationType.auto.tips':
|
'models.form.gpusAllocationType.auto.tips':
|
||||||
'系统自动计算每个副本的 GPU 数量。',
|
'系统自动计算每个副本的 GPU 数量。',
|
||||||
'models.form.gpusAllocationType.custom.tips': '您可以指定每个副本的 GPU 数量'
|
'models.form.gpusAllocationType.custom.tips': '您可以指定每个副本的 GPU 数量',
|
||||||
|
'models.mymodels.status.inactive': '已停止',
|
||||||
|
'models.mymodels.status.degrade': '异常',
|
||||||
|
'models.mymodels.status.active': '可用'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,31 +1,24 @@
|
|||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import StatusTag from '@/components/status-tag';
|
import StatusTag from '@/components/status-tag';
|
||||||
|
import TagWrapper from '@/components/tags-wrapper';
|
||||||
import ThemeTag from '@/components/tags-wrapper/theme-tag';
|
import ThemeTag from '@/components/tags-wrapper/theme-tag';
|
||||||
import Card from '@/components/templates/card';
|
import Card from '@/components/templates/card';
|
||||||
import { StatusMaps } from '@/config';
|
|
||||||
import { useIntl, useNavigate } from '@umijs/max';
|
import { useIntl, useNavigate } from '@umijs/max';
|
||||||
import { Button } from 'antd';
|
import { Button } 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';
|
||||||
import { modelCategories, modelCategoriesMap, modelSourceMap } from '../config';
|
import {
|
||||||
|
modelCategories,
|
||||||
|
modelCategoriesMap,
|
||||||
|
modelSourceMap,
|
||||||
|
MyModelsStatusLabelMap,
|
||||||
|
MyModelsStatusMap,
|
||||||
|
MyModelsStatusValueMap
|
||||||
|
} from '../config';
|
||||||
import { categoryToPathMap } from '../config/button-actions';
|
import { categoryToPathMap } from '../config/button-actions';
|
||||||
import { categoryConfig } from './model-tag';
|
import { categoryConfig } from './model-tag';
|
||||||
|
|
||||||
const MyModelsStatusValueMap = {
|
|
||||||
Inactive: 'Inactive',
|
|
||||||
Starting: 'Starting',
|
|
||||||
Degrade: 'Degrade',
|
|
||||||
Active: 'Active'
|
|
||||||
};
|
|
||||||
|
|
||||||
const MyModelsStatusMap = {
|
|
||||||
[MyModelsStatusValueMap.Inactive]: StatusMaps.inactive,
|
|
||||||
[MyModelsStatusValueMap.Starting]: StatusMaps.transitioning,
|
|
||||||
[MyModelsStatusValueMap.Degrade]: StatusMaps.warning,
|
|
||||||
[MyModelsStatusValueMap.Active]: StatusMaps.success
|
|
||||||
};
|
|
||||||
|
|
||||||
const CardWrapper = styled.div`
|
const CardWrapper = styled.div`
|
||||||
&:hover {
|
&:hover {
|
||||||
.content {
|
.content {
|
||||||
@@ -36,6 +29,14 @@ const CardWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const Dot = styled.span`
|
||||||
|
background-color: var(--ant-color-text-quaternary);
|
||||||
|
border-radius: 50%;
|
||||||
|
flex: none;
|
||||||
|
height: 3px;
|
||||||
|
width: 3px;
|
||||||
|
`;
|
||||||
|
|
||||||
const ModelItemContent = styled.div`
|
const ModelItemContent = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -63,6 +64,7 @@ const ModelItemContent = styled.div`
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
.extra-info {
|
.extra-info {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -105,6 +107,14 @@ const sourceIconMap = {
|
|||||||
[modelSourceMap.modelscope_value]: 'icon-tu2'
|
[modelSourceMap.modelscope_value]: 'icon-tu2'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderTag = (item: any, index = 0) => {
|
||||||
|
return (
|
||||||
|
<ThemeTag key={item} className="tag-item" color="purple">
|
||||||
|
{item}
|
||||||
|
</ThemeTag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ModelItem: React.FC<{
|
const ModelItem: React.FC<{
|
||||||
model: Record<string, any>;
|
model: Record<string, any>;
|
||||||
onClick: (model: any) => void;
|
onClick: (model: any) => void;
|
||||||
@@ -155,16 +165,15 @@ const ModelItem: React.FC<{
|
|||||||
if (!model.replicas && !model.ready_replicas) {
|
if (!model.replicas && !model.ready_replicas) {
|
||||||
return MyModelsStatusValueMap.Inactive;
|
return MyModelsStatusValueMap.Inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.replicas > 0 && !model.ready_replicas) {
|
if (model.replicas > 0 && !model.ready_replicas) {
|
||||||
return MyModelsStatusValueMap.Starting;
|
|
||||||
}
|
|
||||||
if (model.replicas > 1 && model.replicas !== model.ready_replicas) {
|
|
||||||
return MyModelsStatusValueMap.Degrade;
|
return MyModelsStatusValueMap.Degrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.replicas === model.ready_replicas && model.replicas > 0) {
|
if (model.replicas === model.ready_replicas && model.replicas > 0) {
|
||||||
return MyModelsStatusValueMap.Active;
|
return MyModelsStatusValueMap.Active;
|
||||||
}
|
}
|
||||||
return MyModelsStatusValueMap.Inactive;
|
return MyModelsStatusValueMap.Degrade;
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -177,18 +186,20 @@ const ModelItem: React.FC<{
|
|||||||
ghost
|
ghost
|
||||||
header={
|
header={
|
||||||
<Header>
|
<Header>
|
||||||
<span className="text">
|
<span className="text gap-8">
|
||||||
<IconFont
|
<IconFont
|
||||||
type={sourceIconMap[model.source]}
|
type={sourceIconMap[model.source]}
|
||||||
style={{ marginRight: 8, fontSize: 24 }}
|
style={{ fontSize: 24 }}
|
||||||
/>
|
/>
|
||||||
<span>{model.name}</span>
|
<span>{model.name}</span>
|
||||||
</span>
|
</span>
|
||||||
<StatusTag
|
<StatusTag
|
||||||
maxTooltipWidth={400}
|
maxTooltipWidth={400}
|
||||||
statusValue={{
|
statusValue={{
|
||||||
status: MyModelsStatusMap[status],
|
status: MyModelsStatusMap[model.status],
|
||||||
text: status,
|
text: intl.formatMessage({
|
||||||
|
id: MyModelsStatusLabelMap[model.status]
|
||||||
|
}),
|
||||||
message: model.state_message
|
message: model.state_message
|
||||||
}}
|
}}
|
||||||
></StatusTag>
|
></StatusTag>
|
||||||
@@ -214,16 +225,27 @@ const ModelItem: React.FC<{
|
|||||||
</ThemeTag>
|
</ThemeTag>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{maxToken > 0 && (
|
{maxToken > 0 && (
|
||||||
<ThemeTag className="tag-item" color="purple">
|
<>
|
||||||
{maxToken}K context
|
<Dot></Dot>
|
||||||
</ThemeTag>
|
<ThemeTag className="tag-item" color="purple">
|
||||||
|
{maxToken}K context
|
||||||
|
</ThemeTag>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{model.meta?.voices?.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Dot></Dot>
|
||||||
|
<TagWrapper
|
||||||
|
gap={8}
|
||||||
|
dataList={model.meta?.voices}
|
||||||
|
renderTag={renderTag}
|
||||||
|
></TagWrapper>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{[
|
{[MyModelsStatusValueMap.Active].includes(model.status) && (
|
||||||
MyModelsStatusValueMap.Active,
|
|
||||||
MyModelsStatusValueMap.Degrade
|
|
||||||
].includes(status) && (
|
|
||||||
<Button
|
<Button
|
||||||
size="middle"
|
size="middle"
|
||||||
className="btn"
|
className="btn"
|
||||||
|
|||||||
@@ -188,6 +188,24 @@ export const status: any = {
|
|||||||
[InstanceStatusMap.Unreachable]: StatusMaps.error
|
[InstanceStatusMap.Unreachable]: StatusMaps.error
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MyModelsStatusValueMap = {
|
||||||
|
Inactive: 'Inactive',
|
||||||
|
Degrade: 'Degrade',
|
||||||
|
Active: 'Active'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MyModelsStatusMap = {
|
||||||
|
[MyModelsStatusValueMap.Inactive]: StatusMaps.inactive,
|
||||||
|
[MyModelsStatusValueMap.Degrade]: StatusMaps.error,
|
||||||
|
[MyModelsStatusValueMap.Active]: StatusMaps.success
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MyModelsStatusLabelMap = {
|
||||||
|
[MyModelsStatusValueMap.Inactive]: 'models.mymodels.status.inactive',
|
||||||
|
[MyModelsStatusValueMap.Degrade]: 'models.mymodels.status.degrade',
|
||||||
|
[MyModelsStatusValueMap.Active]: 'models.mymodels.status.active'
|
||||||
|
};
|
||||||
|
|
||||||
export const ScheduleValueMap = {
|
export const ScheduleValueMap = {
|
||||||
Auto: 'auto',
|
Auto: 'auto',
|
||||||
Manual: 'manual',
|
Manual: 'manual',
|
||||||
@@ -334,7 +352,8 @@ export const excludeFields = [
|
|||||||
'backend',
|
'backend',
|
||||||
'gpu_selector',
|
'gpu_selector',
|
||||||
'run_command',
|
'run_command',
|
||||||
'image_name'
|
'image_name',
|
||||||
|
'extended_kv_cache.enabled'
|
||||||
];
|
];
|
||||||
|
|
||||||
// ingore fields when compare old and new data
|
// ingore fields when compare old and new data
|
||||||
@@ -357,7 +376,8 @@ export const updateExcludeFields = [
|
|||||||
'gpu_selector',
|
'gpu_selector',
|
||||||
'categories',
|
'categories',
|
||||||
'env',
|
'env',
|
||||||
'replicas'
|
'replicas',
|
||||||
|
'extended_kv_cache.enabled'
|
||||||
];
|
];
|
||||||
|
|
||||||
export const formFields = [
|
export const formFields = [
|
||||||
|
|||||||
@@ -214,8 +214,19 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFieldPaths = (obj: Record<string, any>, prefix = ''): string => {
|
||||||
|
const result = Object.entries(obj).flatMap(([key, value]) => {
|
||||||
|
const path = prefix ? `${prefix}.${key}` : key;
|
||||||
|
return typeof value === 'object' && value !== null
|
||||||
|
? getFieldPaths(value, path)
|
||||||
|
: [path];
|
||||||
|
});
|
||||||
|
|
||||||
|
return result[0] || '';
|
||||||
|
};
|
||||||
|
|
||||||
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
|
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
|
||||||
const fieldName = Object.keys(changedValues)[0];
|
const fieldName = getFieldPaths(changedValues);
|
||||||
|
|
||||||
if (excludeFields.includes(fieldName)) {
|
if (excludeFields.includes(fieldName)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,13 +3,32 @@ import SealInputNumber from '@/components/seal-form/input-number';
|
|||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
|
import { useFormContext } from '../config/form-context';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
const KVCacheForm = () => {
|
const KVCacheForm = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const form = Form.useFormInstance();
|
const form = Form.useFormInstance();
|
||||||
|
const { onValuesChange } = useFormContext();
|
||||||
const kvCacheEnabled = Form.useWatch(['extended_kv_cache', 'enabled'], form);
|
const kvCacheEnabled = Form.useWatch(['extended_kv_cache', 'enabled'], form);
|
||||||
|
|
||||||
|
const handleOnChange = async (e: any) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
extended_kv_cache: {
|
||||||
|
enabled: true,
|
||||||
|
chunk_size: 256,
|
||||||
|
max_local_cpu_size: 10,
|
||||||
|
remote_url: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 200);
|
||||||
|
});
|
||||||
|
onValuesChange?.({}, form.getFieldsValue());
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ paddingBottom: 22 }}>
|
<div style={{ paddingBottom: 22 }}>
|
||||||
@@ -20,6 +39,7 @@ const KVCacheForm = () => {
|
|||||||
style={{ padding: '0 10px', marginBottom: 0 }}
|
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||||
>
|
>
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
|
onChange={handleOnChange}
|
||||||
label={intl.formatMessage({ id: 'models.form.extendedkvcache' })}
|
label={intl.formatMessage({ id: 'models.form.extendedkvcache' })}
|
||||||
></CheckboxField>
|
></CheckboxField>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -9,14 +9,41 @@ import { PageContainer } from '@ant-design/pro-components';
|
|||||||
import { useIntl, useNavigate } from '@umijs/max';
|
import { useIntl, useNavigate } from '@umijs/max';
|
||||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||||
import { Button, Input, Space } from 'antd';
|
import { Button, Input, Space } from 'antd';
|
||||||
import React from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context';
|
import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context';
|
||||||
import NoResult from '../_components/no-result';
|
import NoResult from '../_components/no-result';
|
||||||
import { MY_MODELS_API, queryMyModels } from './apis';
|
import { MY_MODELS_API, queryMyModels } from './apis';
|
||||||
import ModelItem from './components/model-item';
|
import ModelItem from './components/model-item';
|
||||||
import { categoryOptions, modelCategoriesMap } from './config';
|
import {
|
||||||
|
categoryOptions,
|
||||||
|
modelCategoriesMap,
|
||||||
|
MyModelsStatusValueMap
|
||||||
|
} from './config';
|
||||||
import { categoryToPathMap } from './config/button-actions';
|
import { categoryToPathMap } from './config/button-actions';
|
||||||
|
|
||||||
|
const Dot = ({ color }: { color: string }) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
backgroundColor: color,
|
||||||
|
borderRadius: '50%',
|
||||||
|
height: 8,
|
||||||
|
width: 8,
|
||||||
|
display: 'flex'
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const optionRender = (item: any) => {
|
||||||
|
return (
|
||||||
|
<span className="flex-center gap-8">
|
||||||
|
<Dot color={item.data?.color}></Dot>
|
||||||
|
{item.label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const UserModels: React.FC = () => {
|
const UserModels: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
@@ -36,6 +63,33 @@ const UserModels: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [status, setStatus] = React.useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
const statusOptions = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: MyModelsStatusValueMap.Active,
|
||||||
|
color: 'var(--ant-color-success)',
|
||||||
|
label: intl.formatMessage({
|
||||||
|
id: 'models.mymodels.status.active'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MyModelsStatusValueMap.Inactive,
|
||||||
|
color: 'var(--ant-color-fill)',
|
||||||
|
label: intl.formatMessage({
|
||||||
|
id: 'models.mymodels.status.inactive'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MyModelsStatusValueMap.Degrade,
|
||||||
|
color: 'var(--ant-color-error)',
|
||||||
|
label: intl.formatMessage({
|
||||||
|
id: 'models.mymodels.status.degrade'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, [intl]);
|
||||||
|
|
||||||
const handleCategoryChange = (value: string) => {
|
const handleCategoryChange = (value: string) => {
|
||||||
handleQueryChange({
|
handleQueryChange({
|
||||||
@@ -77,6 +131,46 @@ const UserModels: React.FC = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleStatusChange = (value: string) => {
|
||||||
|
setStatus(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const labelRender = (item: any) => {
|
||||||
|
const current = statusOptions.find((option) => option.value === item.value);
|
||||||
|
return (
|
||||||
|
<span className="flex-center gap-8">
|
||||||
|
<Dot color={current!.color}></Dot>
|
||||||
|
{item.label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatus = useCallback((model: any) => {
|
||||||
|
if (!model.replicas && !model.ready_replicas) {
|
||||||
|
return MyModelsStatusValueMap.Inactive;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.replicas > 0 && !model.ready_replicas) {
|
||||||
|
return MyModelsStatusValueMap.Degrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.replicas === model.ready_replicas && model.replicas > 0) {
|
||||||
|
return MyModelsStatusValueMap.Active;
|
||||||
|
}
|
||||||
|
return MyModelsStatusValueMap.Degrade;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const dataList = useMemo(() => {
|
||||||
|
const result = dataSource.dataList.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
status: getStatus(item)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (!status) return result;
|
||||||
|
return result.filter((item) => item.status === status);
|
||||||
|
}, [dataSource.dataList, status]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
ghost
|
ghost
|
||||||
@@ -117,6 +211,18 @@ const UserModels: React.FC = () => {
|
|||||||
options={categoryOptions}
|
options={categoryOptions}
|
||||||
onChange={handleCategoryChange}
|
onChange={handleCategoryChange}
|
||||||
></BaseSelect>
|
></BaseSelect>
|
||||||
|
<BaseSelect
|
||||||
|
allowClear
|
||||||
|
showSearch={false}
|
||||||
|
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
|
||||||
|
style={{ width: 180 }}
|
||||||
|
size="large"
|
||||||
|
maxTagCount={1}
|
||||||
|
optionRender={optionRender}
|
||||||
|
labelRender={labelRender}
|
||||||
|
options={statusOptions}
|
||||||
|
onChange={handleStatusChange}
|
||||||
|
></BaseSelect>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
@@ -135,7 +241,7 @@ const UserModels: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardList
|
<CardList
|
||||||
dataList={dataSource.dataList}
|
dataList={dataList}
|
||||||
loading={dataSource.loading}
|
loading={dataSource.loading}
|
||||||
activeId={false}
|
activeId={false}
|
||||||
isFirst={!dataSource.loadend}
|
isFirst={!dataSource.loadend}
|
||||||
@@ -145,9 +251,9 @@ const UserModels: React.FC = () => {
|
|||||||
<NoResult
|
<NoResult
|
||||||
loading={dataSource.loading}
|
loading={dataSource.loading}
|
||||||
loadend={dataSource.loadend}
|
loadend={dataSource.loadend}
|
||||||
dataSource={dataSource.dataList}
|
dataSource={dataList}
|
||||||
image={<IconFont type="icon-models" />}
|
image={<IconFont type="icon-models" />}
|
||||||
filters={queryParams}
|
filters={{ ...queryParams, status: status }}
|
||||||
noFoundText={intl.formatMessage({
|
noFoundText={intl.formatMessage({
|
||||||
id: 'noresult.mymodels.nofound'
|
id: 'noresult.mymodels.nofound'
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user