chore: access form test
This commit is contained in:
@@ -51,6 +51,13 @@ export default function useWatchList<T = Record<string, any>>(API: string) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteItemFromCache = (id: number) => {
|
||||||
|
cacheWatchDataListRef.current = cacheWatchDataListRef.current.filter(
|
||||||
|
(item) => item.id !== id
|
||||||
|
);
|
||||||
|
setWatchDataList(cacheWatchDataListRef.current);
|
||||||
|
};
|
||||||
|
|
||||||
const getAllDataList = useMemoizedFn(async () => {
|
const getAllDataList = useMemoizedFn(async () => {
|
||||||
try {
|
try {
|
||||||
listRequestTokenRef.current?.cancel?.();
|
listRequestTokenRef.current?.cancel?.();
|
||||||
@@ -78,6 +85,7 @@ export default function useWatchList<T = Record<string, any>>(API: string) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
watchDataList
|
watchDataList,
|
||||||
|
deleteItemFromCache: handleDeleteItemFromCache
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ export default {
|
|||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
||||||
'common.button.edit.item': 'Edit {name}',
|
'common.button.edit.item': 'Edit {name}',
|
||||||
|
'common.button.copy.item': 'Duplicate {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}',
|
||||||
@@ -273,5 +274,6 @@ export default {
|
|||||||
'common.button.enable': 'Enable',
|
'common.button.enable': 'Enable',
|
||||||
'common.button.disable': 'Disable',
|
'common.button.disable': 'Disable',
|
||||||
'common.status.enabled': 'Enabled',
|
'common.status.enabled': 'Enabled',
|
||||||
'common.status.disabled': 'Disabled'
|
'common.status.disabled': 'Disabled',
|
||||||
|
'common.button.duplicate': 'Duplicate'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ export default {
|
|||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
||||||
'common.button.edit.item': 'Edit {name}',
|
'common.button.edit.item': 'Edit {name}',
|
||||||
|
'common.button.copy.item': 'Duplicate {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}',
|
||||||
@@ -273,7 +274,8 @@ export default {
|
|||||||
'common.button.enable': 'Enable',
|
'common.button.enable': 'Enable',
|
||||||
'common.button.disable': 'Disable',
|
'common.button.disable': 'Disable',
|
||||||
'common.status.enabled': 'Enabled',
|
'common.status.enabled': 'Enabled',
|
||||||
'common.status.disabled': 'Disabled'
|
'common.status.disabled': 'Disabled',
|
||||||
|
'common.button.duplicate': 'Duplicate'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -313,4 +315,5 @@ export default {
|
|||||||
// 34. 'common.button.disable': 'Disable',
|
// 34. 'common.button.disable': 'Disable',
|
||||||
// 35. 'common.status.enabled': 'Enabled',
|
// 35. 'common.status.enabled': 'Enabled',
|
||||||
// 36. 'common.status.disabled': 'Disabled'
|
// 36. 'common.status.disabled': 'Disabled'
|
||||||
|
// 33. 'common.button.duplicate': 'Duplicate'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ export default {
|
|||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Единый вход не настроен в этой системе. Пожалуйста, обратитесь к администратору.',
|
'Единый вход не настроен в этой системе. Пожалуйста, обратитесь к администратору.',
|
||||||
'common.button.edit.item': 'Редактировать {name}',
|
'common.button.edit.item': 'Редактировать {name}',
|
||||||
|
'common.button.copy.item': 'Duplicate {name}',
|
||||||
'common.button.terminal': 'Терминал',
|
'common.button.terminal': 'Терминал',
|
||||||
'common.button.addItem': 'Добавить элемент',
|
'common.button.addItem': 'Добавить элемент',
|
||||||
'common.help.default': 'По умолчанию: {content}',
|
'common.help.default': 'По умолчанию: {content}',
|
||||||
@@ -272,12 +273,11 @@ export default {
|
|||||||
'common.button.enable': 'Enable',
|
'common.button.enable': 'Enable',
|
||||||
'common.button.disable': 'Disable',
|
'common.button.disable': 'Disable',
|
||||||
'common.status.enabled': 'Enabled',
|
'common.status.enabled': 'Enabled',
|
||||||
'common.status.disabled': 'Disabled'
|
'common.status.disabled': 'Disabled',
|
||||||
|
'common.button.duplicate': 'Duplicate'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'common.button.enable': 'Enable',
|
// 1. 'common.button.copy.item': 'Duplicate {name}',
|
||||||
// 2. 'common.button.disable': 'Disable',
|
// 2. 'common.button.duplicate': 'Duplicate'
|
||||||
// 3. 'common.status.enabled': 'Enabled',
|
|
||||||
// 4. 'common.status.disabled': 'Disabled'
|
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ export default {
|
|||||||
'common.external.login': '使用 {type} 登录',
|
'common.external.login': '使用 {type} 登录',
|
||||||
'common.sso.noConfig': '该系统未启用单点登录,请联系管理员。',
|
'common.sso.noConfig': '该系统未启用单点登录,请联系管理员。',
|
||||||
'common.button.edit.item': '编辑 {name}',
|
'common.button.edit.item': '编辑 {name}',
|
||||||
|
'common.button.copy.item': '复制 {name}',
|
||||||
'common.button.terminal': '终端',
|
'common.button.terminal': '终端',
|
||||||
'common.button.addItem': '添加项',
|
'common.button.addItem': '添加项',
|
||||||
'common.help.default': '默认: {content}',
|
'common.help.default': '默认: {content}',
|
||||||
@@ -265,5 +266,6 @@ export default {
|
|||||||
'common.button.enable': '启用',
|
'common.button.enable': '启用',
|
||||||
'common.button.disable': '禁用',
|
'common.button.disable': '禁用',
|
||||||
'common.status.enabled': '已启用',
|
'common.status.enabled': '已启用',
|
||||||
'common.status.disabled': '未启用'
|
'common.status.disabled': '未启用',
|
||||||
|
'common.button.duplicate': '复制'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
|
import IconFont from '@/components/icon-font';
|
||||||
import { StatusMaps } from '@/config';
|
import { StatusMaps } from '@/config';
|
||||||
import { EditOutlined } from '@ant-design/icons';
|
import {
|
||||||
|
AudioOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
PictureOutlined,
|
||||||
|
WechatWorkOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import React from 'react';
|
||||||
import { backendOptionsMap } from './backend-parameters';
|
import { backendOptionsMap } from './backend-parameters';
|
||||||
|
|
||||||
export const backendTipsList = [
|
export const backendTipsList = [
|
||||||
@@ -290,12 +297,45 @@ export const modelCategoriesMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const categoryOptions = [
|
export const categoryOptions = [
|
||||||
{ label: 'LLM', value: modelCategoriesMap.llm },
|
{
|
||||||
{ label: 'Embedding', value: modelCategoriesMap.embedding },
|
label: 'LLM',
|
||||||
{ label: 'Reranker', value: modelCategoriesMap.reranker },
|
value: modelCategoriesMap.llm,
|
||||||
{ label: 'Image', value: modelCategoriesMap.image },
|
icon: React.createElement(WechatWorkOutlined, { style: { color: 'green' } })
|
||||||
{ label: 'Text-to-Speech', value: modelCategoriesMap.text_to_speech },
|
},
|
||||||
{ label: 'Speech-to-Text', value: modelCategoriesMap.speech_to_text }
|
{
|
||||||
|
label: 'Embedding',
|
||||||
|
value: modelCategoriesMap.embedding,
|
||||||
|
icon: React.createElement(IconFont, {
|
||||||
|
type: 'icon-cube',
|
||||||
|
style: { color: 'magenta' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Reranker',
|
||||||
|
value: modelCategoriesMap.reranker,
|
||||||
|
icon: React.createElement(IconFont, {
|
||||||
|
type: 'icon-rank1',
|
||||||
|
style: { color: 'cyan' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Image',
|
||||||
|
value: modelCategoriesMap.image,
|
||||||
|
icon: React.createElement(PictureOutlined, { style: { color: 'orange' } })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Text-to-Speech',
|
||||||
|
value: modelCategoriesMap.text_to_speech,
|
||||||
|
icon: React.createElement(IconFont, {
|
||||||
|
type: 'icon-sound-wave',
|
||||||
|
style: { color: 'geekblue' }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Speech-to-Text',
|
||||||
|
value: modelCategoriesMap.speech_to_text,
|
||||||
|
icon: React.createElement(AudioOutlined, { style: { color: 'processing' } })
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const modelCategories = [
|
export const modelCategories = [
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const rowActionList = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
label: 'common.button.copy',
|
label: 'common.button.duplicate',
|
||||||
icon: icons.CopyOutlined
|
icon: icons.CopyOutlined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import { AccessItem, FormData } from '../config/types';
|
import { AccessItem, AccessPointItem, FormData } from '../config/types';
|
||||||
|
|
||||||
export const ACCESS_API = '/model-accesses';
|
export const ACCESS_API = '/model-accesses';
|
||||||
|
|
||||||
@@ -36,12 +36,41 @@ export async function deleteAccess(id: number) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryAccessPoints(params: { id: string }, options?: any) {
|
export async function queryAccessPoints(params: { id: number }, options?: any) {
|
||||||
return request<Global.BaseOption<string>[]>(
|
return request<Global.PageResponse<AccessPointItem>>(
|
||||||
`${ACCESS_API}/${params.id}/access`,
|
`${ACCESS_POINTS_API}?access_id=${params.id}`,
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
page: -1
|
||||||
|
},
|
||||||
cancelToken: options?.token
|
cancelToken: options?.token
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteAccessPoint(id: number) {
|
||||||
|
return request(`${ACCESS_POINTS_API}/${id}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateAccessPoint(params: {
|
||||||
|
id: number;
|
||||||
|
data: Partial<AccessPointItem>;
|
||||||
|
}) {
|
||||||
|
return request(`${ACCESS_POINTS_API}/${params.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setAccessPointAsFallback(params: {
|
||||||
|
id: number;
|
||||||
|
data: Partial<AccessPointItem>;
|
||||||
|
}) {
|
||||||
|
return request(`${ACCESS_POINTS_API}/${params.id}/set-fallback`, {
|
||||||
|
method: 'POST',
|
||||||
|
data: params.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import { Col, Row } from 'antd';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { mockAccessPointList } from '../config/mock';
|
import { EndpointStatus, EndpointStatusLabelMap } from '../config';
|
||||||
|
import { AccessPointItem } from '../config/types';
|
||||||
const CellContent = styled.div`
|
const CellContent = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -17,9 +18,7 @@ const CellContent = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
interface ProviderModelProps {
|
interface ProviderModelProps {
|
||||||
dataList: any[];
|
dataList: AccessPointItem[];
|
||||||
provider: string;
|
|
||||||
providerId: number;
|
|
||||||
onSelect: (val: any, record: any) => void;
|
onSelect: (val: any, record: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,27 +60,32 @@ const AccessItem: React.FC<AccessItemProps> = ({ onSelect, data }) => {
|
|||||||
paddingInline: 'var(--ant-table-cell-padding-inline)'
|
paddingInline: 'var(--ant-table-cell-padding-inline)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AutoTooltip ghost>qwen3-0.6b-zduxy</AutoTooltip>
|
<AutoTooltip ghost>{data.provider_model_name}</AutoTooltip>
|
||||||
</CellContent>
|
</CellContent>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={3}>
|
<Col span={3}>
|
||||||
<CellContent>OpenAI</CellContent>
|
<CellContent>-</CellContent>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4}>
|
<Col span={4}>
|
||||||
<CellContent>
|
<CellContent>
|
||||||
{data.weight && (
|
{data.weight && (
|
||||||
<AutoTooltip ghost>
|
<AutoTooltip ghost>
|
||||||
{intl.formatMessage({ id: 'accesses.form.endpoint.weight' })}:
|
{intl.formatMessage({ id: 'accesses.form.endpoint.weight' })}:{' '}
|
||||||
20 /
|
{data.weight}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
)}
|
)}
|
||||||
{data.is_fallback && (
|
|
||||||
<span>
|
{data.fallback_status_codes &&
|
||||||
{intl.formatMessage({
|
data.fallback_status_codes?.length > 0 && (
|
||||||
id: 'accesses.table.label.fallback'
|
<>
|
||||||
})}
|
<span style={{ marginInline: 8 }}>/</span>
|
||||||
</span>
|
<span>
|
||||||
)}
|
{intl.formatMessage({
|
||||||
|
id: 'accesses.table.label.fallback'
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</CellContent>
|
</CellContent>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={3}>
|
<Col span={3}>
|
||||||
@@ -89,8 +93,8 @@ const AccessItem: React.FC<AccessItemProps> = ({ onSelect, data }) => {
|
|||||||
<AutoTooltip ghost>
|
<AutoTooltip ghost>
|
||||||
<StatusTag
|
<StatusTag
|
||||||
statusValue={{
|
statusValue={{
|
||||||
status: 'success',
|
status: EndpointStatus[data.state],
|
||||||
text: 'Ready',
|
text: EndpointStatusLabelMap[data.state],
|
||||||
message: ''
|
message: ''
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -100,7 +104,7 @@ const AccessItem: React.FC<AccessItemProps> = ({ onSelect, data }) => {
|
|||||||
<Col span={5}>
|
<Col span={5}>
|
||||||
<CellContent style={{ marginLeft: -6 }}>
|
<CellContent style={{ marginLeft: -6 }}>
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
{dayjs().format('YYYY-MM-DD HH:mm:ss')}
|
{dayjs(data.created_at).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
</CellContent>
|
</CellContent>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -112,7 +116,7 @@ const AccessItem: React.FC<AccessItemProps> = ({ onSelect, data }) => {
|
|||||||
>
|
>
|
||||||
<DropdownButtons
|
<DropdownButtons
|
||||||
items={childActionList}
|
items={childActionList}
|
||||||
onSelect={onSelect}
|
onSelect={(val) => onSelect(val, data)}
|
||||||
></DropdownButtons>
|
></DropdownButtons>
|
||||||
</CellContent>
|
</CellContent>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -126,7 +130,7 @@ const AccessPoints: React.FC<ProviderModelProps> = ({ dataList, onSelect }) => {
|
|||||||
console.log('AccessPoints dataList:', dataList);
|
console.log('AccessPoints dataList:', dataList);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{mockAccessPointList.map((item, index) => (
|
{dataList.map((item, index) => (
|
||||||
<AccessItem data={item} key={index} onSelect={onSelect}></AccessItem>
|
<AccessItem data={item} key={index} onSelect={onSelect}></AccessItem>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ import icons from '@/components/icon-font/icons';
|
|||||||
import { StatusMaps } from '@/config';
|
import { StatusMaps } from '@/config';
|
||||||
import { StatusType } from '@/config/types';
|
import { StatusType } from '@/config/types';
|
||||||
|
|
||||||
export const ProviderStatusValueMap: Record<string, string> = {
|
export const EndpointsStatusValueMap: Record<string, string> = {
|
||||||
Ready: 'Ready',
|
Active: 'active',
|
||||||
InActive: 'Inactive'
|
Inactive: 'inactive'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProviderStatusLabelMap = {
|
export const EndpointStatusLabelMap = {
|
||||||
[ProviderStatusValueMap.Ready]: 'Ready',
|
[EndpointsStatusValueMap.Active]: 'Active',
|
||||||
[ProviderStatusValueMap.InActive]: 'Inactive'
|
[EndpointsStatusValueMap.Inactive]: 'Inactive'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProviderStatus: Record<string, StatusType> = {
|
export const EndpointStatus: Record<string, StatusType> = {
|
||||||
[ProviderStatusValueMap.Ready]: StatusMaps.success,
|
[EndpointsStatusValueMap.Active]: StatusMaps.success,
|
||||||
[ProviderStatusValueMap.InActive]: StatusMaps.error
|
[EndpointsStatusValueMap.Inactive]: StatusMaps.error
|
||||||
};
|
};
|
||||||
|
|
||||||
// actions for each row
|
// actions for each row
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import { AccessItem, AccessPointItem } from './types';
|
|
||||||
|
|
||||||
// mock data for AccessItem 5 items
|
|
||||||
export const mockDataList: AccessItem[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'model-A',
|
|
||||||
source: 'Deployments',
|
|
||||||
accessPoints: 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'model-B',
|
|
||||||
source: 'Qwen',
|
|
||||||
accessPoints: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: 'model-C',
|
|
||||||
source: 'OpenAI',
|
|
||||||
accessPoints: 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: 'model-D',
|
|
||||||
source: 'Deepseek',
|
|
||||||
accessPoints: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
name: 'model-E',
|
|
||||||
source: 'Anthropic',
|
|
||||||
accessPoints: 4
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// mock data for AccessPointItem 2 items
|
|
||||||
export const mockAccessPointList: AccessPointItem[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'Access Point 1',
|
|
||||||
type: 'API',
|
|
||||||
is_fallback: true,
|
|
||||||
endpoint: 'https://api.example.com/endpoint1',
|
|
||||||
created_at: '2024-01-01T12:00:00Z'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'Access Point 2',
|
|
||||||
type: 'SDK',
|
|
||||||
is_fallback: false,
|
|
||||||
created_at: '2024-02-01T12:00:00Z'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,3 +1,23 @@
|
|||||||
|
export interface FormData {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
categories: any[];
|
||||||
|
meta: Record<string, any>;
|
||||||
|
fallback_endpoint: {
|
||||||
|
provider_model_name?: string;
|
||||||
|
model_id?: number;
|
||||||
|
provider_id?: number;
|
||||||
|
fallback_status_codes?: string[];
|
||||||
|
};
|
||||||
|
endpoints: {
|
||||||
|
provider_model_name?: string;
|
||||||
|
weight?: number | null;
|
||||||
|
model_id?: number;
|
||||||
|
provider_id?: number;
|
||||||
|
fallback_status_codes?: string[];
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface AccessItem {
|
export interface AccessItem {
|
||||||
id: number;
|
id: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@@ -8,20 +28,9 @@ export interface AccessItem {
|
|||||||
categories: string[];
|
categories: string[];
|
||||||
meta: Record<string, any>;
|
meta: Record<string, any>;
|
||||||
created_by_model: boolean;
|
created_by_model: boolean;
|
||||||
endpoint_count: number;
|
endpoints: number;
|
||||||
}
|
ready_endpoints: number;
|
||||||
|
access_policy: string;
|
||||||
export interface FormData {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
categories: any[];
|
|
||||||
meta: Record<string, any>;
|
|
||||||
endpoints: {
|
|
||||||
provider_model_name: string;
|
|
||||||
weight: number;
|
|
||||||
model_id: number;
|
|
||||||
provider_id: number;
|
|
||||||
}[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessPointItem {
|
export interface AccessPointItem {
|
||||||
@@ -30,7 +39,7 @@ export interface AccessPointItem {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
deleted_at: string;
|
deleted_at: string;
|
||||||
provider_model_name: string;
|
provider_model_name: string;
|
||||||
weight: number;
|
weight: number | null;
|
||||||
model_id: number;
|
model_id: number;
|
||||||
provider_id: number;
|
provider_id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import SealSelect from '@/components/seal-form/seal-select';
|
import SealSelect from '@/components/seal-form/seal-select';
|
||||||
import { modelCategories } from '@/pages/llmodels/config';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
|
import { categoryOptions } from '@/pages/llmodels/config';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
@@ -8,6 +9,7 @@ import { FormData } from '../config/types';
|
|||||||
const Basic = () => {
|
const Basic = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const form = Form.useFormInstance<FormData>();
|
const form = Form.useFormInstance<FormData>();
|
||||||
|
const { getRuleMessage } = useAppUtils();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item name="name" data-field="name">
|
<Form.Item name="name" data-field="name">
|
||||||
@@ -22,11 +24,17 @@ const Basic = () => {
|
|||||||
getValueProps={(value) => ({
|
getValueProps={(value) => ({
|
||||||
value: Array.isArray(value) ? value[0] || null : value
|
value: Array.isArray(value) ? value[0] || null : value
|
||||||
})}
|
})}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: getRuleMessage('select', 'models.form.categories')
|
||||||
|
}
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
options={modelCategories}
|
required
|
||||||
|
options={categoryOptions}
|
||||||
label={intl.formatMessage({ id: 'models.form.categories' })}
|
label={intl.formatMessage({ id: 'models.form.categories' })}
|
||||||
allowNull
|
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="description">
|
<Form.Item name="description">
|
||||||
|
|||||||
@@ -1,81 +1,72 @@
|
|||||||
import { LabelSelectorContext } from '@/components/label-selector/context';
|
|
||||||
import MetadataList from '@/components/metadata-list';
|
import MetadataList from '@/components/metadata-list';
|
||||||
import SealCascader from '@/components/seal-form/seal-cascader';
|
import SealCascader from '@/components/seal-form/seal-cascader';
|
||||||
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 _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useState } from 'react';
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
|
import useEndpointSourceModels from '../hooks/use-endpoint-source-models';
|
||||||
|
|
||||||
const providerModelList = [
|
const EndpointsForm = forwardRef((props, ref) => {
|
||||||
{
|
|
||||||
label: 'Deployments',
|
|
||||||
value: 'deployments',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: 'qwen3-0.6b',
|
|
||||||
value: 'qwen3-0.6b',
|
|
||||||
key: '1',
|
|
||||||
provider_id: 1,
|
|
||||||
model_id: 101
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'deepseek',
|
|
||||||
value: 'deepseek',
|
|
||||||
key: '2',
|
|
||||||
provider_id: 1,
|
|
||||||
model_id: 102
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Doubao',
|
|
||||||
value: 'doubao',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: 'qwen3-0.6b',
|
|
||||||
value: 'qwen3-0.6b',
|
|
||||||
key: '3',
|
|
||||||
provider_id: 2,
|
|
||||||
model_id: 201
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'deepseek',
|
|
||||||
value: 'deepseek',
|
|
||||||
key: '4',
|
|
||||||
provider_id: 2,
|
|
||||||
model_id: 202
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const Endpoints = () => {
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { sourceModels, loading, fetchSourceModels } =
|
||||||
|
useEndpointSourceModels();
|
||||||
const form = Form.useFormInstance<FormData>();
|
const form = Form.useFormInstance<FormData>();
|
||||||
const endpoints = Form.useWatch('endpoints', form);
|
const endpoints = Form.useWatch('endpoints', form) || [];
|
||||||
|
const [fallbackValues, setFallbackValues] = useState<{ value: any[] }>({
|
||||||
|
value: []
|
||||||
|
});
|
||||||
const [dataList, setDataList] = useState<
|
const [dataList, setDataList] = useState<
|
||||||
{
|
{
|
||||||
provider_model_name: string;
|
weight: number | null;
|
||||||
weight: number;
|
value: any[];
|
||||||
model_id: number;
|
|
||||||
provider_id: number;
|
|
||||||
}[]
|
}[]
|
||||||
>(endpoints || []);
|
>([]);
|
||||||
|
|
||||||
const handleEndpointsChange = (labels: Record<string, any>) => {
|
useImperativeHandle(ref, () => ({
|
||||||
form.setFieldValue('endpoints', labels);
|
initFallbackValues: (values: { value: any[] }) => {
|
||||||
|
setFallbackValues(values);
|
||||||
|
},
|
||||||
|
initDataList: (
|
||||||
|
list: {
|
||||||
|
weight: number | null;
|
||||||
|
value: any[];
|
||||||
|
}[]
|
||||||
|
) => {
|
||||||
|
setDataList(list);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handleEndpointsChange = (
|
||||||
|
value: any[],
|
||||||
|
index: number,
|
||||||
|
options: any[]
|
||||||
|
) => {
|
||||||
|
const selectedOption =
|
||||||
|
options?.find?.((opt) => opt.value === value[1]) || {};
|
||||||
|
const endpointList = [...endpoints];
|
||||||
|
endpointList[index] = {
|
||||||
|
weight: endpointList[index]?.weight || null,
|
||||||
|
...selectedOption?.data
|
||||||
|
};
|
||||||
|
|
||||||
|
form.setFieldValue('endpoints', [...endpointList]);
|
||||||
|
|
||||||
|
const newDataList = [...dataList];
|
||||||
|
newDataList[index] = {
|
||||||
|
weight: newDataList[index]?.weight || null,
|
||||||
|
value: value
|
||||||
|
};
|
||||||
|
setDataList(newDataList);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnAdd = () => {
|
const handleOnAdd = () => {
|
||||||
const newDataList = [
|
const newDataList = [
|
||||||
...dataList,
|
...dataList,
|
||||||
{
|
{
|
||||||
provider_model_name: '',
|
weight: null,
|
||||||
weight: 1,
|
value: []
|
||||||
model_id: 0,
|
|
||||||
provider_id: 0
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
setDataList(newDataList);
|
setDataList(newDataList);
|
||||||
@@ -84,115 +75,128 @@ const Endpoints = () => {
|
|||||||
const handleOnDelete = (index: number, item: any) => {
|
const handleOnDelete = (index: number, item: any) => {
|
||||||
const newDataList = dataList.filter((_, i) => i !== index);
|
const newDataList = dataList.filter((_, i) => i !== index);
|
||||||
setDataList(newDataList);
|
setDataList(newDataList);
|
||||||
|
|
||||||
|
const endpointList = [...endpoints];
|
||||||
|
endpointList.splice(index, 1);
|
||||||
|
form.setFieldValue('endpoints', [...endpointList]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFallbackChange = (e: any, index: number) => {
|
const handleFallbackChange = (value: any[], options?: any[]) => {
|
||||||
const checked = e.target.checked;
|
const selectedOption =
|
||||||
const newDataList = dataList.map((item, i) => {
|
options?.find?.((opt) => opt.value === value[1]) || {};
|
||||||
if (i === index) {
|
|
||||||
return {
|
console.log('fallback selected option data:', value);
|
||||||
...item,
|
form.setFieldValue('fallback_endpoint', {
|
||||||
is_fallback: checked
|
...selectedOption?.data
|
||||||
};
|
|
||||||
} else if (checked) {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
is_fallback: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
});
|
||||||
|
setFallbackValues({
|
||||||
|
value: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnWeightChange = (value: any, index: number) => {
|
||||||
|
const endpointList = [...endpoints];
|
||||||
|
if (endpointList[index]) {
|
||||||
|
endpointList[index] = {
|
||||||
|
...endpointList[index],
|
||||||
|
weight: value
|
||||||
|
};
|
||||||
|
form.setFieldValue('endpoints', [...endpointList]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDataList = [...dataList];
|
||||||
|
newDataList[index] = {
|
||||||
|
...newDataList[index],
|
||||||
|
weight: value
|
||||||
|
};
|
||||||
setDataList(newDataList);
|
setDataList(newDataList);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSourceModels();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LabelSelectorContext.Provider
|
<Form.Item
|
||||||
value={{
|
name="endpoints"
|
||||||
placeholder: ['Model', 'Weight'],
|
data-field="endpoints"
|
||||||
options: [
|
rules={[
|
||||||
{
|
({ getFieldValue }) => ({
|
||||||
label: 'max_token_len',
|
validator(rule, value) {
|
||||||
value: 'max_token_len'
|
if (_.keys(value).length > 0) {
|
||||||
},
|
if (_.some(_.keys(value), (k: string) => !value[k])) {
|
||||||
{
|
return Promise.reject(
|
||||||
label: 'max_context_len',
|
intl.formatMessage(
|
||||||
value: 'max_context_len'
|
{
|
||||||
}
|
id: 'common.validate.value'
|
||||||
]
|
},
|
||||||
}}
|
{
|
||||||
>
|
name: intl.formatMessage({
|
||||||
<Form.Item
|
id: 'models.form.selector'
|
||||||
name="endpoints"
|
})
|
||||||
data-field="endpoints"
|
}
|
||||||
rules={[
|
)
|
||||||
({ getFieldValue }) => ({
|
);
|
||||||
validator(rule, value) {
|
|
||||||
if (_.keys(value).length > 0) {
|
|
||||||
if (_.some(_.keys(value), (k: string) => !value[k])) {
|
|
||||||
return Promise.reject(
|
|
||||||
intl.formatMessage(
|
|
||||||
{
|
|
||||||
id: 'common.validate.value'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: intl.formatMessage({
|
|
||||||
id: 'models.form.selector'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
})
|
return Promise.resolve();
|
||||||
]}
|
}
|
||||||
|
})
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<MetadataList
|
||||||
|
label={''}
|
||||||
|
styles={{
|
||||||
|
wrapper: {
|
||||||
|
paddingTop: 14
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
dataList={dataList}
|
||||||
|
btnText={intl.formatMessage({ id: 'accesses.form.endpoint.add' })}
|
||||||
|
onAdd={handleOnAdd}
|
||||||
|
onDelete={handleOnDelete}
|
||||||
>
|
>
|
||||||
<MetadataList
|
{(item, index) => (
|
||||||
label={''}
|
<>
|
||||||
styles={{
|
<SealCascader
|
||||||
wrapper: {
|
required
|
||||||
paddingTop: 14
|
showSearch
|
||||||
}
|
expandTrigger="hover"
|
||||||
}}
|
multiple={false}
|
||||||
dataList={dataList}
|
onChange={(value, options) =>
|
||||||
btnText={intl.formatMessage({ id: 'accesses.form.endpoint.add' })}
|
handleEndpointsChange(value, index, options)
|
||||||
onAdd={handleOnAdd}
|
}
|
||||||
onDelete={handleOnDelete}
|
classNames={{
|
||||||
>
|
popup: {
|
||||||
{(item, index) => (
|
root: 'cascader-popup-wrapper gpu-selector'
|
||||||
<>
|
}
|
||||||
<SealCascader
|
}}
|
||||||
required
|
maxTagCount={1}
|
||||||
showSearch
|
placeholder={intl.formatMessage({
|
||||||
expandTrigger="hover"
|
id: 'accesses.form.endpoint.model'
|
||||||
multiple={false}
|
})}
|
||||||
classNames={{
|
value={item.value}
|
||||||
popup: {
|
options={sourceModels}
|
||||||
root: 'cascader-popup-wrapper gpu-selector'
|
showCheckedStrategy="SHOW_CHILD"
|
||||||
}
|
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||||
}}
|
></SealCascader>
|
||||||
maxTagCount={1}
|
<span className="seprator">:</span>
|
||||||
placeholder={intl.formatMessage({
|
<SealInput.Number
|
||||||
id: 'accesses.form.endpoint.model'
|
style={{ flex: 100 }}
|
||||||
})}
|
min={0}
|
||||||
options={providerModelList}
|
value={item.weight}
|
||||||
showCheckedStrategy="SHOW_CHILD"
|
onChange={(value) => handleOnWeightChange(value, index)}
|
||||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
placeholder={intl.formatMessage({
|
||||||
></SealCascader>
|
id: 'accesses.form.endpoint.weight'
|
||||||
<span className="seprator">:</span>
|
})}
|
||||||
<SealInput.Number
|
></SealInput.Number>
|
||||||
style={{ flex: 100 }}
|
</>
|
||||||
placeholder={intl.formatMessage({
|
)}
|
||||||
id: 'accesses.form.endpoint.weight'
|
</MetadataList>
|
||||||
})}
|
</Form.Item>
|
||||||
></SealInput.Number>
|
<Form.Item name="fallback_endpoint">
|
||||||
</>
|
<div>
|
||||||
)}
|
|
||||||
</MetadataList>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="fallback_endpoint">
|
|
||||||
<SealCascader
|
<SealCascader
|
||||||
showSearch
|
showSearch
|
||||||
expandTrigger="hover"
|
expandTrigger="hover"
|
||||||
@@ -206,14 +210,16 @@ const Endpoints = () => {
|
|||||||
id: 'accesses.form.endpoint.fallback'
|
id: 'accesses.form.endpoint.fallback'
|
||||||
})}
|
})}
|
||||||
maxTagCount={1}
|
maxTagCount={1}
|
||||||
options={providerModelList}
|
value={fallbackValues.value}
|
||||||
|
options={sourceModels}
|
||||||
|
onChange={(value, options) => handleFallbackChange(value, options)}
|
||||||
showCheckedStrategy="SHOW_CHILD"
|
showCheckedStrategy="SHOW_CHILD"
|
||||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||||
></SealCascader>
|
></SealCascader>
|
||||||
</Form.Item>
|
</div>
|
||||||
</LabelSelectorContext.Provider>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default Endpoints;
|
export default EndpointsForm;
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import CollapsePanel from '@/pages/_components/collapse-panel';
|
import CollapsePanel from '@/pages/_components/collapse-panel';
|
||||||
import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context';
|
import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context';
|
||||||
import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs';
|
import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
import _ from 'lodash';
|
||||||
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
import { FormData, AccessItem as ListItem } from '../config/types';
|
import { FormData, AccessItem as ListItem } from '../config/types';
|
||||||
|
import useEditEndpoints from '../hooks/use-edit-endpoints';
|
||||||
import Basic from './basic';
|
import Basic from './basic';
|
||||||
import Endpoints from './endpoints';
|
import Endpoints from './endpoints';
|
||||||
import MetaData from './meta-data';
|
import MetaData from './meta-data';
|
||||||
@@ -32,7 +41,8 @@ const AccessForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
|||||||
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.BASIC]);
|
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.BASIC]);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const scrollTabsRef = useRef<any>(null);
|
const scrollTabsRef = useRef<any>(null);
|
||||||
|
const endpointsRef = useRef<any>(null);
|
||||||
|
const { generateEndpointData, fetchEndpoints } = useEditEndpoints();
|
||||||
const segmentOptions = [
|
const segmentOptions = [
|
||||||
{
|
{
|
||||||
value: TABKeysMap.BASIC,
|
value: TABKeysMap.BASIC,
|
||||||
@@ -58,10 +68,104 @@ const AccessForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
|||||||
setActiveKey(key);
|
setActiveKey(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatEndpoints = (values: FormData) => {
|
||||||
|
console.log('formatEndpoints values:', values);
|
||||||
|
let endPoints = [...values.endpoints];
|
||||||
|
let fallbackEndpoint = values.fallback_endpoint;
|
||||||
|
|
||||||
|
if (fallbackEndpoint && endPoints.length > 0) {
|
||||||
|
endPoints = endPoints?.map((ep) => {
|
||||||
|
if (ep.model_id === fallbackEndpoint.model_id && ep.model_id) {
|
||||||
|
return {
|
||||||
|
...ep,
|
||||||
|
fallback_status_codes: ['4xx', '5xx']
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
ep.provider_id === fallbackEndpoint.provider_id &&
|
||||||
|
ep.provider_model_name === fallbackEndpoint.provider_model_name &&
|
||||||
|
!fallbackEndpoint.model_id
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...ep,
|
||||||
|
fallback_status_codes: ['4xx', '5xx']
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return ep;
|
||||||
|
});
|
||||||
|
} else if (fallbackEndpoint) {
|
||||||
|
endPoints.push({
|
||||||
|
...fallbackEndpoint,
|
||||||
|
weight: null,
|
||||||
|
fallback_status_codes: ['4xx', '5xx']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return endPoints;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnFinish = (values: FormData) => {
|
||||||
|
const endpoints = formatEndpoints(values);
|
||||||
|
const data = {
|
||||||
|
..._.omit(values, ['endpoints', 'fallback_endpoint']),
|
||||||
|
endpoints: endpoints
|
||||||
|
};
|
||||||
|
console.log('data=========', data);
|
||||||
|
onFinish(data);
|
||||||
|
};
|
||||||
|
|
||||||
const handleOnCollapseChange = (keys: string | string[]) => {
|
const handleOnCollapseChange = (keys: string | string[]) => {
|
||||||
setActiveKey(Array.isArray(keys) ? keys : [keys]);
|
setActiveKey(Array.isArray(keys) ? keys : [keys]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initEditionForm = async () => {
|
||||||
|
const endpointList = await fetchEndpoints(currentData!.id);
|
||||||
|
const { endpoints, fallbackEndpoint } =
|
||||||
|
generateEndpointData(endpointList);
|
||||||
|
console.log(
|
||||||
|
'endpoints:',
|
||||||
|
endpoints,
|
||||||
|
'fallbackEndpoint:',
|
||||||
|
fallbackEndpoint
|
||||||
|
);
|
||||||
|
|
||||||
|
// init form values
|
||||||
|
form.setFieldsValue({
|
||||||
|
...currentData,
|
||||||
|
endpoints: endpoints,
|
||||||
|
fallback_endpoint: fallbackEndpoint
|
||||||
|
});
|
||||||
|
|
||||||
|
// init endpoints form list
|
||||||
|
endpointsRef.current?.initDataList(
|
||||||
|
endpoints?.map((ep) => ({
|
||||||
|
weight: ep.weight,
|
||||||
|
value: ep.model_id
|
||||||
|
? ['deployments', ep.model_id]
|
||||||
|
: [ep.provider_id, ep.provider_model_name]
|
||||||
|
})) || []
|
||||||
|
);
|
||||||
|
|
||||||
|
// init fallback value
|
||||||
|
if (fallbackEndpoint) {
|
||||||
|
endpointsRef.current?.initFallbackValues({
|
||||||
|
value: fallbackEndpoint.model_id
|
||||||
|
? ['deployments', fallbackEndpoint.model_id]
|
||||||
|
: [
|
||||||
|
fallbackEndpoint.provider_id,
|
||||||
|
fallbackEndpoint.provider_model_name
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (action === PageAction.EDIT && currentData) {
|
||||||
|
initEditionForm();
|
||||||
|
} else {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}, [action, currentData, form]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
submit: () => {
|
submit: () => {
|
||||||
form.submit();
|
form.submit();
|
||||||
@@ -86,14 +190,10 @@ const AccessForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
|||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={onFinish}
|
onFinish={handleOnFinish}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
categories: [],
|
categories: [],
|
||||||
meta: {
|
meta: {}
|
||||||
size: null,
|
|
||||||
activated_size: null,
|
|
||||||
dimensions: null
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Basic />
|
<Basic />
|
||||||
@@ -112,7 +212,7 @@ const AccessForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
|||||||
key: TABKeysMap.ENDPOINTS,
|
key: TABKeysMap.ENDPOINTS,
|
||||||
label: intl.formatMessage({ id: 'accesses.form.endpoint.title' }),
|
label: intl.formatMessage({ id: 'accesses.form.endpoint.title' }),
|
||||||
forceRender: true,
|
forceRender: true,
|
||||||
children: <Endpoints></Endpoints>
|
children: <Endpoints ref={endpointsRef}></Endpoints>
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
></CollapsePanel>
|
></CollapsePanel>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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 { tableSorter } from '@/config/settings';
|
import { tableSorter } from '@/config/settings';
|
||||||
|
import ModelTag from '@/pages/_components/model-tag';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -23,18 +24,23 @@ const useAccessColumns = (
|
|||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
span: 5,
|
span: 5,
|
||||||
render: (text: string, record: AccessItem) => (
|
render: (text: string, record: AccessItem) => (
|
||||||
<>
|
<span className="flex-center" style={{ maxWidth: '100%' }}>
|
||||||
<AutoTooltip ghost title={text}>
|
<AutoTooltip ghost title={text}>
|
||||||
{text}
|
<span className="m-r-5">{text}</span>
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
</>
|
<ModelTag categoryKey={record.categories?.[0]}></ModelTag>
|
||||||
|
</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'accesses.table.accessPoints' }),
|
title: intl.formatMessage({ id: 'accesses.table.accessPoints' }),
|
||||||
dataIndex: 'accessPoints',
|
dataIndex: 'endpoints',
|
||||||
span: 10,
|
span: 10,
|
||||||
render: (value: number) => <span>{value}</span>
|
render: (value: number, record: AccessItem) => (
|
||||||
|
<span>
|
||||||
|
{record.ready_endpoints} / {value}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { queryAccessPoints } from '../apis';
|
||||||
|
import { AccessPointItem } from '../config/types';
|
||||||
|
|
||||||
|
const useEditEndpoints = () => {
|
||||||
|
const generateEndpointData = (endpoints: AccessPointItem[]) => {
|
||||||
|
const fallbackEndpoint =
|
||||||
|
endpoints?.filter(
|
||||||
|
(ep) => ep.fallback_status_codes && ep.fallback_status_codes?.length > 0
|
||||||
|
)?.[0] || null;
|
||||||
|
|
||||||
|
const endPoints = endpoints?.filter(
|
||||||
|
(ep) =>
|
||||||
|
!ep.fallback_status_codes || ep.fallback_status_codes?.length === 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
endpoints: endPoints,
|
||||||
|
fallbackEndpoint: fallbackEndpoint
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchEndpoints = async (accessId: number) => {
|
||||||
|
try {
|
||||||
|
const res = await queryAccessPoints({ id: accessId });
|
||||||
|
return res.items || [];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
generateEndpointData,
|
||||||
|
fetchEndpoints
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useEditEndpoints;
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { queryModelsList } from '@/pages/llmodels/apis';
|
||||||
|
import { ListItem as ModelListItem } from '@/pages/llmodels/config/types';
|
||||||
|
import { queryMaasProviders } from '@/pages/maas-provider/apis';
|
||||||
|
import { MaasProviderItem } from '@/pages/maas-provider/config/types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
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>;
|
||||||
|
|
||||||
|
const useEndpointSourceModels = () => {
|
||||||
|
const [sourceModels, setSourceModels] = useState<CascaderOption[]>([]);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const fetchSourceModels = async (params?: any) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const [models, providers] = await Promise.all([
|
||||||
|
queryModelsList({ page: -1, ...params }),
|
||||||
|
queryMaasProviders({ page: -1, ...params })
|
||||||
|
]);
|
||||||
|
|
||||||
|
const modelsList = [
|
||||||
|
{
|
||||||
|
label: 'Deployments',
|
||||||
|
value: 'deployments',
|
||||||
|
parent: true,
|
||||||
|
children: models.items?.map?.((model: ModelListItem) => ({
|
||||||
|
label: model.name,
|
||||||
|
value: model.id,
|
||||||
|
data: {
|
||||||
|
model_id: model.id
|
||||||
|
},
|
||||||
|
source: 'deployment'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
].filter((group) => group.children && group.children.length > 0);
|
||||||
|
|
||||||
|
const providerOptions: CascaderOption[] = providers.items
|
||||||
|
?.map?.((provider: MaasProviderItem) => ({
|
||||||
|
label: provider.name,
|
||||||
|
value: provider.id,
|
||||||
|
parent: true,
|
||||||
|
children: provider.models?.map?.((model) => ({
|
||||||
|
label: model.name,
|
||||||
|
value: model.name,
|
||||||
|
data: {
|
||||||
|
provider_model_name: model.name,
|
||||||
|
provider_id: provider.id
|
||||||
|
},
|
||||||
|
source: 'providerModel'
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
.filter((group) => group.children && group.children.length > 0);
|
||||||
|
|
||||||
|
setSourceModels([...modelsList, ...providerOptions]);
|
||||||
|
} catch (error) {
|
||||||
|
setSourceModels([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceModels,
|
||||||
|
loading,
|
||||||
|
fetchSourceModels
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useEndpointSourceModels;
|
||||||
@@ -21,13 +21,16 @@ import AccessControlModal from '../llmodels/components/access-control-modal';
|
|||||||
import {
|
import {
|
||||||
ACCESS_API,
|
ACCESS_API,
|
||||||
ACCESS_POINTS_API,
|
ACCESS_POINTS_API,
|
||||||
|
createAccess,
|
||||||
deleteAccess,
|
deleteAccess,
|
||||||
|
deleteAccessPoint,
|
||||||
|
queryAccessPoints,
|
||||||
queryModelAccesses,
|
queryModelAccesses,
|
||||||
|
setAccessPointAsFallback,
|
||||||
updateAccess
|
updateAccess
|
||||||
} from './apis';
|
} from './apis';
|
||||||
import AccessPoints from './components/access-points';
|
import AccessPoints from './components/access-points';
|
||||||
import AddAccessModal from './components/add-access-modal';
|
import AddAccessModal from './components/add-access-modal';
|
||||||
import { mockDataList } from './config/mock';
|
|
||||||
import { FormData, AccessItem as ListItem } from './config/types';
|
import { FormData, AccessItem as ListItem } from './config/types';
|
||||||
import useAccessColumns from './hooks/use-access-columns';
|
import useAccessColumns from './hooks/use-access-columns';
|
||||||
import useAccessControl from './hooks/use-access-control';
|
import useAccessControl from './hooks/use-access-control';
|
||||||
@@ -49,11 +52,12 @@ const Accesses: React.FC = () => {
|
|||||||
} = useTableFetch<ListItem>({
|
} = useTableFetch<ListItem>({
|
||||||
fetchAPI: queryModelAccesses,
|
fetchAPI: queryModelAccesses,
|
||||||
deleteAPI: deleteAccess,
|
deleteAPI: deleteAccess,
|
||||||
watch: false,
|
watch: true,
|
||||||
API: ACCESS_API,
|
API: ACCESS_API,
|
||||||
contentForDelete: 'menu.models.access'
|
contentForDelete: 'menu.models.access'
|
||||||
});
|
});
|
||||||
const { watchDataList: allAccessPoints } = useWatchList(ACCESS_POINTS_API);
|
const { watchDataList: allAccessPoints, deleteItemFromCache } =
|
||||||
|
useWatchList(ACCESS_POINTS_API);
|
||||||
const [expandAtom] = useAtom(expandKeysAtom);
|
const [expandAtom] = useAtom(expandKeysAtom);
|
||||||
const { handleExpandChange, handleExpandAll, expandedRowKeys } =
|
const { handleExpandChange, handleExpandAll, expandedRowKeys } =
|
||||||
useExpandedRowKeys(expandAtom);
|
useExpandedRowKeys(expandAtom);
|
||||||
@@ -84,6 +88,11 @@ const Accesses: React.FC = () => {
|
|||||||
id: openAccessModalStatus.currentData!.id
|
id: openAccessModalStatus.currentData!.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (openAccessModalStatus.action === PageAction.CREATE) {
|
||||||
|
await createAccess({
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
closeAccessModal();
|
closeAccessModal();
|
||||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
@@ -98,10 +107,7 @@ const Accesses: React.FC = () => {
|
|||||||
const handleEditProvider = (row: ListItem) => {
|
const handleEditProvider = (row: ListItem) => {
|
||||||
openAccessModal(
|
openAccessModal(
|
||||||
PageAction.EDIT,
|
PageAction.EDIT,
|
||||||
intl.formatMessage(
|
intl.formatMessage({ id: 'common.button.edit.item' }, { name: row.name }),
|
||||||
{ id: 'clusters.edit.cluster' },
|
|
||||||
{ cluster: row.name }
|
|
||||||
),
|
|
||||||
row
|
row
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -135,13 +141,13 @@ const Accesses: React.FC = () => {
|
|||||||
const loadChildrenData = useMemoizedFn(
|
const loadChildrenData = useMemoizedFn(
|
||||||
async (row: ListItem, options?: any) => {
|
async (row: ListItem, options?: any) => {
|
||||||
const params = {
|
const params = {
|
||||||
cluster_id: row.id,
|
id: row.id
|
||||||
page: -1
|
|
||||||
};
|
};
|
||||||
// const data = await queryAccessPoints(params, {
|
const res = await queryAccessPoints(params, {
|
||||||
// token: options?.token
|
token: options?.token
|
||||||
// });
|
});
|
||||||
return [1];
|
|
||||||
|
return res.items || [];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -149,24 +155,40 @@ const Accesses: React.FC = () => {
|
|||||||
handleTableChange({}, {}, order, { action: 'sort' });
|
handleTableChange({}, {}, order, { action: 'sort' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChildSelect = useMemoizedFn((val: any, record: any) => {
|
const handleDeleteEndpoint = (row: any) => {
|
||||||
if (val === 'fallback') {
|
modalRef.current?.show({
|
||||||
console.log('open fallback settings modal', record);
|
content: 'accesses.table.accessPoints',
|
||||||
}
|
okText: 'common.button.delete',
|
||||||
|
operation: 'common.delete.single.confirm',
|
||||||
|
name: row.name,
|
||||||
|
async onOk() {
|
||||||
|
await deleteAccessPoint(row.id);
|
||||||
|
deleteItemFromCache?.(row.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChildSelect = useMemoizedFn(async (val: any, record: any) => {
|
||||||
|
try {
|
||||||
|
if (val === 'fallback') {
|
||||||
|
await setAccessPointAsFallback({
|
||||||
|
id: record.id,
|
||||||
|
data: {
|
||||||
|
fallback_status_codes: ['4xx', '5xx']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
|
} else if (val === 'delete') {
|
||||||
|
handleDeleteEndpoint(record);
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderChildren = (
|
const renderChildren = (
|
||||||
list: any,
|
list: any,
|
||||||
options: { parent?: any; [key: string]: any }
|
options: { parent?: any; [key: string]: any }
|
||||||
) => {
|
) => {
|
||||||
return (
|
return <AccessPoints dataList={list} onSelect={onChildSelect} />;
|
||||||
<AccessPoints
|
|
||||||
dataList={list}
|
|
||||||
provider={options.parent?.provider}
|
|
||||||
providerId={options.parent?.id}
|
|
||||||
onSelect={onChildSelect}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = useAccessColumns(handleSelect);
|
const columns = useAccessColumns(handleSelect);
|
||||||
@@ -201,7 +223,7 @@ const Accesses: React.FC = () => {
|
|||||||
renderChildren={renderChildren}
|
renderChildren={renderChildren}
|
||||||
onTableSort={handleOnSortChange}
|
onTableSort={handleOnSortChange}
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
dataSource={mockDataList}
|
dataSource={dataSource.dataList}
|
||||||
loading={dataSource.loading}
|
loading={dataSource.loading}
|
||||||
loadend={dataSource.loadend}
|
loadend={dataSource.loadend}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
|
|||||||
Reference in New Issue
Block a user