chore: show existing when add file
This commit is contained in:
@@ -547,6 +547,11 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
>
|
||||
<FormWrapper>
|
||||
<ColumnWrapper
|
||||
styles={{
|
||||
container: {
|
||||
paddingTop: 0
|
||||
}
|
||||
}}
|
||||
paddingBottom={
|
||||
warningStatus.show
|
||||
? Array.isArray(warningStatus.message)
|
||||
|
||||
@@ -8,13 +8,13 @@ import '../style/gpu-card.less';
|
||||
|
||||
const CardWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--ant-color-split);
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 5px;
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
@@ -25,7 +25,7 @@ const Description = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
`;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { modelCategories, modelCategoriesMap, modelSourceMap } from '../config';
|
||||
import { categoryToPathMap } from '../config/button-actions';
|
||||
import { categoryConfig } from './model-tag';
|
||||
|
||||
const MyModelsStatusValueMap = {
|
||||
Inactive: 'Inactive',
|
||||
@@ -198,10 +199,21 @@ const ModelItem: React.FC<{
|
||||
<div className="content">
|
||||
<div className="footer">
|
||||
<div className="extra-info">
|
||||
<ThemeTag className="tag-item" color="blue">
|
||||
{_.find(modelCategories, { value: model.categories?.[0] })
|
||||
?.label || model.categories?.[0]}
|
||||
</ThemeTag>
|
||||
{model.categories?.length > 0 &&
|
||||
model.categories.map((sItem: string) => {
|
||||
return (
|
||||
<ThemeTag
|
||||
icon={categoryConfig[sItem]?.icon}
|
||||
key={sItem}
|
||||
className="tag-item"
|
||||
color={categoryConfig[sItem]?.color || 'blue'}
|
||||
opacity={0.7}
|
||||
>
|
||||
{_.find(modelCategories, { value: sItem })?.label ||
|
||||
sItem}
|
||||
</ThemeTag>
|
||||
);
|
||||
})}
|
||||
{maxToken > 0 && (
|
||||
<ThemeTag className="tag-item" color="purple">
|
||||
{maxToken}K context
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { Tag } from 'antd';
|
||||
import { modelCategoriesMap } from '../config';
|
||||
|
||||
const categoryConfig = {
|
||||
export const categoryConfig = {
|
||||
[modelCategoriesMap.reranker]: {
|
||||
icon: <IconFont type="icon-rank1" />,
|
||||
color: 'cyan',
|
||||
|
||||
@@ -22,6 +22,7 @@ type AddModalProps = {
|
||||
width?: string | number;
|
||||
hasLinuxWorker?: boolean;
|
||||
workerOptions: any[];
|
||||
workersList?: any[];
|
||||
onOk: (values: FormData) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
@@ -47,7 +48,8 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
|
||||
hasLinuxWorker,
|
||||
source,
|
||||
width = 600,
|
||||
workerOptions
|
||||
workerOptions,
|
||||
workersList
|
||||
} = props || {};
|
||||
const SEARCH_SOURCE = [
|
||||
modelSourceMap.huggingface_value,
|
||||
@@ -66,7 +68,7 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
|
||||
if (source === modelSourceMap.huggingface_value) {
|
||||
const huggingFaceModel = {
|
||||
huggingface_repo_id: selectedModel.name,
|
||||
huggingface_filename: null
|
||||
huggingface_filename: fileName || null
|
||||
};
|
||||
return huggingFaceModel;
|
||||
}
|
||||
@@ -74,7 +76,7 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
|
||||
if (source === modelSourceMap.modelscope_value) {
|
||||
const modelScopeModel = {
|
||||
model_scope_model_id: selectedModel.name,
|
||||
model_scope_file_path: null
|
||||
model_scope_file_path: fileName || null
|
||||
};
|
||||
return modelScopeModel;
|
||||
}
|
||||
@@ -238,6 +240,9 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
|
||||
ref={form}
|
||||
onOk={handleOk}
|
||||
source={source}
|
||||
selectedModel={selectedModel}
|
||||
fileName={fileName}
|
||||
workersList={workersList}
|
||||
workerOptions={workerOptions}
|
||||
></TargetForm>
|
||||
</>
|
||||
|
||||
@@ -7,22 +7,64 @@ import { ModelFileFormData as FormData } from '@/pages/resources/config/types';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { forwardRef, useImperativeHandle, useMemo } from 'react';
|
||||
import { minimatch } from 'minimatch';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo
|
||||
} from 'react';
|
||||
import { localPathTipsList, modelSourceMap, sourceOptions } from '../config';
|
||||
import { useGenerateWorkersModelFileOptions } from '../hooks';
|
||||
|
||||
type EmptyObject = Record<never, never>;
|
||||
|
||||
type CascaderOption<T extends object = EmptyObject> = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
parent?: boolean;
|
||||
disabled?: boolean;
|
||||
index?: number;
|
||||
children?: CascaderOption<T>[];
|
||||
} & Partial<T>;
|
||||
|
||||
interface TargetFormProps {
|
||||
ref?: any;
|
||||
source: string;
|
||||
workerOptions: any[];
|
||||
workerOptions: CascaderOption<{ state: string }>[];
|
||||
workersList?: Global.BaseOption<
|
||||
number,
|
||||
{ state: string; labels: Record<string, string>; cluster_id: number }
|
||||
>[];
|
||||
selectedModel?: Record<string, any>;
|
||||
fileName?: string;
|
||||
onOk: (values: any) => void;
|
||||
}
|
||||
|
||||
const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
|
||||
const { onOk, source, workerOptions } = props;
|
||||
const {
|
||||
modelFileOptions,
|
||||
getModelFileList,
|
||||
generateWorkersModelFileOptions
|
||||
} = useGenerateWorkersModelFileOptions();
|
||||
const { onOk, source, workerOptions, workersList, selectedModel, fileName } =
|
||||
props;
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
const intl = useIntl();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
try {
|
||||
if (workersList && workersList?.length > 0) {
|
||||
const modelFiles = await getModelFileList();
|
||||
generateWorkersModelFileOptions(modelFiles, workersList || []);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
init();
|
||||
}, [workersList]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
form
|
||||
}));
|
||||
@@ -33,10 +75,6 @@ const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
|
||||
...data,
|
||||
worker_id: data.worker_id?.[1]
|
||||
});
|
||||
console.log('Form Data: ', {
|
||||
...data,
|
||||
worker_id: data.worker_id?.[1]
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnLocalPathBlur = (e: any) => {
|
||||
@@ -49,6 +87,32 @@ const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
|
||||
});
|
||||
};
|
||||
|
||||
const renderOptionNode = (props: { data: any }) => {
|
||||
const { data } = props;
|
||||
const currentWorker = modelFileOptions.find(
|
||||
(item) => item.value === data.value
|
||||
);
|
||||
|
||||
const isExisting = currentWorker?.children?.some((child) => {
|
||||
const isSameFile =
|
||||
child.fileName === (fileName || '') ||
|
||||
minimatch(child.fileName || '', fileName || '');
|
||||
|
||||
return child.repoId === selectedModel?.name && isSameFile;
|
||||
});
|
||||
|
||||
return (
|
||||
<span>
|
||||
{data.label}
|
||||
{isExisting && (
|
||||
<span className="text-tertiary m-l-4">
|
||||
({intl.formatMessage({ id: 'resources.modelfiles.form.exsting' })})
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const renderLocalPathFields = () => {
|
||||
return (
|
||||
<>
|
||||
@@ -136,6 +200,7 @@ const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
|
||||
label={intl.formatMessage({ id: 'resources.worker' })}
|
||||
options={workerOptions}
|
||||
showCheckedStrategy="SHOW_CHILD"
|
||||
optionNode={renderOptionNode}
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||
></SealCascader>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import CollapsePanel from '@/pages/_components/collapse-panel';
|
||||
import { useIntl } from '@umijs/max';
|
||||
@@ -122,22 +123,21 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
|
||||
const SegmentedTop = useMemo(() => {
|
||||
if (
|
||||
[
|
||||
modelSourceMap.huggingface_value,
|
||||
modelSourceMap.modelscope_value
|
||||
].includes(source) &&
|
||||
formKey === deployFormKeyMap.deployment
|
||||
modelSourceMap.local_path_value === source ||
|
||||
action === PageAction.EDIT ||
|
||||
formKey === deployFormKeyMap.catalog
|
||||
) {
|
||||
return {
|
||||
top: 50,
|
||||
offsetTop: 146
|
||||
top: 0,
|
||||
offsetTop: 96
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
top: 0,
|
||||
offsetTop: 96
|
||||
top: 50,
|
||||
offsetTop: 146
|
||||
};
|
||||
}, [source, formKey]);
|
||||
}, [source, formKey, action]);
|
||||
|
||||
const handleSumit = () => {
|
||||
form.submit();
|
||||
|
||||
@@ -98,7 +98,6 @@ const LocalPathForm: React.FC = () => {
|
||||
<SealInput.Input
|
||||
allowClear
|
||||
required
|
||||
onBlur={handleOnBlur}
|
||||
onFocus={handleOnFocus}
|
||||
label={intl.formatMessage({ id: 'models.form.filePath' })}
|
||||
description={<TooltipList list={localPathTipsList}></TooltipList>}
|
||||
|
||||
@@ -94,6 +94,14 @@ const ScheduleTypeForm: React.FC = () => {
|
||||
const handleOnStepReplica = (value: number | null) => {
|
||||
if (value === null) {
|
||||
form.setFieldValue(['gpu_selector', 'gpus_per_replica'], 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const isPowerOfTwo = (n: number) => (n & (n - 1)) === 0 && n !== 0; // check power of two
|
||||
if (!isPowerOfTwo(value)) {
|
||||
const newValue = Math.pow(2, Math.round(Math.log2(value)));
|
||||
form.setFieldValue(['gpu_selector', 'gpus_per_replica'], newValue);
|
||||
onValuesChange?.({}, form.getFieldsValue());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,12 +25,27 @@ export type MessageStatus = {
|
||||
evaluateResult?: EvaluateResult;
|
||||
};
|
||||
|
||||
export interface ModelFileOption {
|
||||
label: string;
|
||||
value: number;
|
||||
labels?: Record<string, string>;
|
||||
parent: boolean;
|
||||
repoId?: string;
|
||||
fileName?: string;
|
||||
[key: string]: any;
|
||||
children?: ModelFileOption[];
|
||||
}
|
||||
|
||||
export type WarningStausOptions = {
|
||||
lockAfterUpdate?: boolean;
|
||||
override?: boolean;
|
||||
};
|
||||
|
||||
export const useGenerateModelFileOptions = () => {
|
||||
export const useGenerateWorkersModelFileOptions = () => {
|
||||
const [modelFileOptions, setModelFileOptions] = useState<ModelFileOption[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const getModelFileList = async () => {
|
||||
try {
|
||||
const res = await queryModelFilesList({ page: 1, perPage: 100 });
|
||||
@@ -42,7 +57,7 @@ export const useGenerateModelFileOptions = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const generateModelFileOptions = (list: any[], workerList: any[]) => {
|
||||
const generateWorkersModelFileOptions = (list: any[], workerList: any[]) => {
|
||||
const workerFields = new Set(['name', 'id', 'ip', 'status']);
|
||||
const workersMap = new Map<number, WorkerListItem>();
|
||||
|
||||
@@ -54,7 +69,7 @@ export const useGenerateModelFileOptions = () => {
|
||||
|
||||
const result = Array.from(workersMap.values()).map((worker) => ({
|
||||
label: worker.name,
|
||||
value: worker.name,
|
||||
value: worker.id,
|
||||
labels: worker.labels,
|
||||
parent: true,
|
||||
children: list
|
||||
@@ -63,18 +78,16 @@ export const useGenerateModelFileOptions = () => {
|
||||
item.worker_id === worker.id && !!item.resolved_paths?.length
|
||||
)
|
||||
.map((item) => {
|
||||
const resolved_paths =
|
||||
Array.isArray(item.resolved_paths) && item.resolved_paths.length
|
||||
? item.resolved_paths[0].split('/')
|
||||
: [];
|
||||
const label =
|
||||
resolved_paths.length > 0 ? resolved_paths.pop() : 'Unknown File';
|
||||
return {
|
||||
label: item.resolved_paths[0] || '',
|
||||
value: item.resolved_paths[0] || '',
|
||||
worker_labels: worker.labels,
|
||||
worker_name: worker.name,
|
||||
parent: false,
|
||||
repoId: item.huggingface_repo_id || item.model_scope_model_id || '',
|
||||
fileName:
|
||||
item.huggingface_filename ||
|
||||
item.model_scope_file_path ||
|
||||
item.local_path ||
|
||||
'',
|
||||
...item
|
||||
};
|
||||
}),
|
||||
@@ -95,12 +108,15 @@ export const useGenerateModelFileOptions = () => {
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
setModelFileOptions(result);
|
||||
|
||||
return childrenList;
|
||||
};
|
||||
|
||||
return {
|
||||
getModelFileList,
|
||||
generateModelFileOptions
|
||||
modelFileOptions,
|
||||
generateWorkersModelFileOptions
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { GPUListItem, ListItem } from '../config/types';
|
||||
|
||||
type EmptyObject = Record<never, never>;
|
||||
|
||||
type CascaderOption<T extends object = EmptyObject> = {
|
||||
export type CascaderOption<T extends object = EmptyObject> = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
parent?: boolean;
|
||||
@@ -210,7 +210,10 @@ export const useGenerateWorkerOptions = () => {
|
||||
cluster_id: item.cluster_id,
|
||||
state: item.state,
|
||||
label: item.name,
|
||||
value: item.id
|
||||
value: item.id,
|
||||
id: item.id,
|
||||
labels: item.labels || {},
|
||||
name: item.name
|
||||
}))
|
||||
);
|
||||
setClusterList(
|
||||
|
||||
Reference in New Issue
Block a user