fix: access control, voices options

This commit is contained in:
jialin
2025-11-11 15:47:32 +08:00
parent ee5c30934b
commit 84187eedc4
19 changed files with 196 additions and 59 deletions
+2
View File
@@ -4,6 +4,8 @@ import { atom, getDefaultStore } from 'jotai';
// models expand keys: create, update , delete, // models expand keys: create, update , delete,
export const modelsExpandKeysAtom = atom<string[]>([]); export const modelsExpandKeysAtom = atom<string[]>([]);
export const modelsSessionAtom = atom<Record<string, any>>({});
export const requestIdAtom = atom<number>(0); export const requestIdAtom = atom<number>(0);
export const setRquestId = () => { export const setRquestId = () => {
@@ -1,4 +1,5 @@
import { Checkbox } from 'antd'; import { Checkbox } from 'antd';
import AutoComplete from '../auto-complete';
import SealInput from '../seal-input'; import SealInput from '../seal-input';
import SealSelect from '../seal-select'; import SealSelect from '../seal-select';
import Slider from '../seal-slider'; import Slider from '../seal-slider';
@@ -12,6 +13,7 @@ const components: {
Input: typeof SealInput.Input; Input: typeof SealInput.Input;
Checkbox: typeof Checkbox; Checkbox: typeof Checkbox;
Switch: typeof Switch; Switch: typeof Switch;
AutoComplete: typeof AutoComplete;
} = { } = {
InputNumber: SealInput.Number, InputNumber: SealInput.Number,
Select: SealSelect, Select: SealSelect,
@@ -19,7 +21,8 @@ const components: {
TextArea: SealInput.TextArea, TextArea: SealInput.TextArea,
Input: SealInput.Input, Input: SealInput.Input,
Checkbox: Checkbox, Checkbox: Checkbox,
Switch: Switch Switch: Switch,
AutoComplete: AutoComplete
}; };
export default components; export default components;
+2 -1
View File
@@ -247,5 +247,6 @@ export default {
'models.form.gpuPerReplica.tips': 'Enter a custom number', 'models.form.gpuPerReplica.tips': 'Enter a custom number',
'models.form.generic_proxy': 'Enable Generic Proxy', 'models.form.generic_proxy': 'Enable Generic Proxy',
'models.form.generic_proxy.tips': 'Enable Generic Proxy', 'models.form.generic_proxy.tips': 'Enable Generic Proxy',
'models.form.generic_proxy.button': 'Generic Proxy' 'models.form.generic_proxy.button': 'Generic Proxy',
'models.accessControlModal.includeusers': 'Include Users'
}; };
+4 -2
View File
@@ -247,7 +247,8 @@ export default {
'models.form.gpuPerReplica.tips': 'Enter a custom number', 'models.form.gpuPerReplica.tips': 'Enter a custom number',
'models.form.generic_proxy': 'Enable Generic Proxy', 'models.form.generic_proxy': 'Enable Generic Proxy',
'models.form.generic_proxy.tips': 'Enable Generic Proxy', 'models.form.generic_proxy.tips': 'Enable Generic Proxy',
'models.form.generic_proxy.button': 'Generic Proxy' 'models.form.generic_proxy.button': 'Generic Proxy',
'models.accessControlModal.includeusers': 'Include Users'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -322,6 +323,7 @@ export default {
// 52. 'models.form.gpuPerReplica.tips': 'Enter a custom number', // 52. 'models.form.gpuPerReplica.tips': 'Enter a custom number',
// 53. 'models.form.generic_proxy': 'Enable Generic Proxy', // 53. 'models.form.generic_proxy': 'Enable Generic Proxy',
// 54. 'models.form.generic_proxy.tips': 'Enable Generic Proxy', // 54. 'models.form.generic_proxy.tips': 'Enable Generic Proxy',
// 55. 'models.form.generic_proxy.button': 'Generic Proxy' // 55. 'models.form.generic_proxy.button': 'Generic Proxy',
// 56. 'models.accessControlModal.includeusers': 'Include Users'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+4 -2
View File
@@ -248,7 +248,8 @@ export default {
'models.form.gpuPerReplica.tips': 'Enter a custom number', 'models.form.gpuPerReplica.tips': 'Enter a custom number',
'models.form.generic_proxy': 'Enable Generic Proxy', 'models.form.generic_proxy': 'Enable Generic Proxy',
'models.form.generic_proxy.tips': 'Enable Generic Proxy', 'models.form.generic_proxy.tips': 'Enable Generic Proxy',
'models.form.generic_proxy.button': 'Generic Proxy' 'models.form.generic_proxy.button': 'Generic Proxy',
'models.accessControlModal.includeusers': 'Include Users'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -256,5 +257,6 @@ export default {
// 2. 'models.form.gpuPerReplica.tips': 'Enter a custom number', // 2. 'models.form.gpuPerReplica.tips': 'Enter a custom number',
// 3. 'models.form.generic_proxy': 'Enable Generic Proxy', // 3. 'models.form.generic_proxy': 'Enable Generic Proxy',
// 4. 'models.form.generic_proxy.tips': 'Enable Generic Proxy', // 4. 'models.form.generic_proxy.tips': 'Enable Generic Proxy',
// 5. 'models.form.generic_proxy.button': 'Generic Proxy' // 5. 'models.form.generic_proxy.button': 'Generic Proxy',
// 6. 'models.accessControlModal.includeusers': 'Include Users',
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+2 -1
View File
@@ -234,5 +234,6 @@ export default {
'models.form.gpuPerReplica.tips': '输入自定义数值', 'models.form.gpuPerReplica.tips': '输入自定义数值',
'models.form.generic_proxy': '启用通用代理', 'models.form.generic_proxy': '启用通用代理',
'models.form.generic_proxy.tips': '启用通用代理', 'models.form.generic_proxy.tips': '启用通用代理',
'models.form.generic_proxy.button': '通用代理' 'models.form.generic_proxy.button': '通用代理',
'models.accessControlModal.includeusers': '显示用户'
}; };
+14 -1
View File
@@ -86,7 +86,8 @@ export const gpuColorMap: Record<string, string> = {
dtk: 'magenta', dtk: 'magenta',
musa: 'cyan', musa: 'cyan',
corex: 'purple', corex: 'purple',
maca: 'geekblue' maca: 'geekblue',
neuware: 'gold'
}; };
export const getGpuColor = (gpuType: string) => { export const getGpuColor = (gpuType: string) => {
@@ -146,10 +147,22 @@ export const frameworks = [
label: 'DTK', label: 'DTK',
value: 'dtk' value: 'dtk'
}, },
{
label: 'MACA',
value: 'maca'
},
{ {
label: 'CoreX', label: 'CoreX',
value: 'corex' value: 'corex'
}, },
{
label: 'MUSA',
value: 'musa'
},
{
label: 'Neuware',
value: 'neuware'
},
{ {
label: 'CPU', label: 'CPU',
value: 'cpu' value: 'cpu'
+1 -1
View File
@@ -372,7 +372,7 @@ export async function queryCatalogItemSpec(
params: { id: number }, params: { id: number },
options?: any options?: any
) { ) {
return await request<Global.PageResponse<CatalogSpec>>( return request<Global.PageResponse<CatalogSpec>>(
`/model-sets/${params.id}/specs`, `/model-sets/${params.id}/specs`,
{ {
method: 'GET', method: 'GET',
+11 -3
View File
@@ -1,4 +1,4 @@
import { modelsExpandKeysAtom } from '@/atoms/models'; import { modelsExpandKeysAtom, modelsSessionAtom } from '@/atoms/models';
import IconFont from '@/components/icon-font'; import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools'; import { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
@@ -14,7 +14,7 @@ import _ from 'lodash';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import NoResult from '../_components/no-result'; import NoResult from '../_components/no-result';
import PageBox from '../_components/page-box'; import PageBox from '../_components/page-box';
import { createModel, queryCatalogList } from './apis'; import { createModel, queryCatalogItemSpec, queryCatalogList } from './apis';
import CatalogList from './components/catalog-list'; import CatalogList from './components/catalog-list';
import DelopyBuiltInModal from './components/deploy-builtin-modal'; import DelopyBuiltInModal from './components/deploy-builtin-modal';
import { modelCategories, modelSourceMap } from './config'; import { modelCategories, modelSourceMap } from './config';
@@ -50,6 +50,7 @@ const Catalog: React.FC = () => {
source: modelSourceMap.huggingface_value source: modelSourceMap.huggingface_value
}); });
const [modelsExpandKeys, setModelsExpandKeys] = useAtom(modelsExpandKeysAtom); const [modelsExpandKeys, setModelsExpandKeys] = useAtom(modelsExpandKeysAtom);
const [, setModelsSession] = useAtom(modelsSessionAtom);
const cacheData = React.useRef<CatalogItemType[]>([]); const cacheData = React.useRef<CatalogItemType[]>([]);
const categoryOptions = [ const categoryOptions = [
@@ -184,7 +185,14 @@ const Catalog: React.FC = () => {
}); });
}); });
const handleDeployFromOtherHubs = () => { const handleDeployFromOtherHubs = async () => {
try {
const id = dataSource.dataList?.[0]?.id;
const res: any = await queryCatalogItemSpec({ id });
setModelsSession({
source: res?.items?.[0]?.source
});
} catch (error) {}
navigate('/models/deployments'); navigate('/models/deployments');
}; };
@@ -1,9 +1,16 @@
import AlertBlockInfo from '@/components/alert-info/block'; import AlertBlockInfo from '@/components/alert-info/block';
import TransferInner from '@/pages/_components/transfer'; import TransferInner from '@/pages/_components/transfer';
import { queryUsersList } from '@/pages/users/apis'; import { queryUsersList } from '@/pages/users/apis';
import { DownOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Empty, Form, Radio } from 'antd'; import { Checkbox, Dropdown, DropdownProps, Empty, Form, Radio } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import {
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useState
} from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { queryModelAccessUserList } from '../../apis'; import { queryModelAccessUserList } from '../../apis';
import { AccessControlFormData, ListItem } from '../../config/types'; import { AccessControlFormData, ListItem } from '../../config/types';
@@ -29,20 +36,42 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
const accessPolicy = Form.useWatch('access_policy', form); const accessPolicy = Form.useWatch('access_policy', form);
const [targetKeys, setTargetKeys] = useState<TransferKey[]>([]); const [targetKeys, setTargetKeys] = useState<TransferKey[]>([]);
const [totalPages, setTotalPages] = useState(0); const [totalPages, setTotalPages] = useState(0);
const [userList, setUserList] = useState<{ title: string; key: number }[]>( const [userList, setUserList] = useState<
[] { title: string; key: number; is_admin: boolean; is_active: boolean }[]
); >([]);
const [open, setOpen] = useState(false);
const [filterInUsers, setFilterInUsers] = useState<Set<string>>(new Set());
const [queryParams, setQueryParams] = useState<Global.SearchParams>({ const [queryParams, setQueryParams] = useState<Global.SearchParams>({
page: -1 page: -1
}); });
const dataList = useMemo(() => {
if (filterInUsers.size === 0) {
return userList.filter((user) => {
return !user.is_admin && user.is_active;
});
}
return userList.filter((user) => {
const isAdmin = user.is_admin;
const isActive = user.is_active;
if (!filterInUsers.has('admin') && isAdmin) {
return false;
}
if (!filterInUsers.has('inactive') && !isActive) {
return false;
}
return true;
});
}, [userList, filterInUsers]);
const getUserList = async (query: Global.SearchParams) => { const getUserList = async (query: Global.SearchParams) => {
try { try {
const res = await queryUsersList(query); const res = await queryUsersList(query);
const options = res.items.map((item) => ({ const options = res.items.map((item) => ({
title: item.username, title: item.username,
key: item.id, key: item.id,
is_admin: item.is_admin is_admin: item.is_admin as boolean,
is_active: item.is_active as boolean
})); }));
setTotalPages(res.pagination.totalPage); setTotalPages(res.pagination.totalPage);
setUserList(options); setUserList(options);
@@ -65,6 +94,30 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
console.log('search:', dir, value); console.log('search:', dir, value);
}; };
const handleCheck = (e: any, type: string) => {
const newSet = new Set(filterInUsers);
if (e.target.checked) {
newSet.add(type);
} else {
newSet.delete(type);
}
setFilterInUsers(newSet);
};
const handleOpenChange: DropdownProps['onOpenChange'] = (nextOpen, info) => {
if (info.source === 'trigger' || nextOpen) {
setOpen(nextOpen);
}
};
const handleFilterChange = (e: any) => {
if (e.target.checked) {
setFilterInUsers(new Set(['admin', 'inactive']));
} else {
setFilterInUsers(new Set());
}
};
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
submit: () => { submit: () => {
form.submit(); form.submit();
@@ -97,6 +150,51 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
getUserList(queryParams); getUserList(queryParams);
}, [currentData?.id]); }, [currentData?.id]);
const renderFilterDropdown = () => {
return (
<Dropdown
open={open}
onOpenChange={handleOpenChange}
key="filter-dropdown"
menu={{
items: [
{
label: (
<Checkbox
checked={filterInUsers.has('admin')}
onChange={(e: any) => handleCheck(e, 'admin')}
>
{intl.formatMessage({ id: 'models.table.admin' })}
</Checkbox>
),
key: '0'
},
{
label: (
<Checkbox
checked={filterInUsers.has('inactive')}
onChange={(e: any) => handleCheck(e, 'inactive')}
>
{intl.formatMessage({ id: 'users.status.deactivate' })}
</Checkbox>
),
key: '1'
}
]
}}
>
<span>
<span className="m-r-8" style={{ cursor: 'default' }}>
{intl.formatMessage({
id: 'models.accessControlModal.includeusers'
})}
</span>
<DownOutlined />
</span>
</Dropdown>
);
};
return ( return (
<Form <Form
form={form} form={form}
@@ -149,11 +247,11 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
</Label> </Label>
<Form.Item<AccessControlFormData> name="users"> <Form.Item<AccessControlFormData> name="users">
<TransferInner <TransferInner
dataSource={userList} dataSource={dataList}
targetKeys={targetKeys} targetKeys={targetKeys}
pagination={false} pagination={false}
titles={[ titles={[
intl.formatMessage({ id: 'models.table.users.all' }), renderFilterDropdown(),
intl.formatMessage({ intl.formatMessage({
id: 'models.table.users.selected' id: 'models.table.users.selected'
}) })
@@ -188,9 +286,11 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
<span className="flex-center gap-4"> <span className="flex-center gap-4">
<span>{item.title}</span> <span>{item.title}</span>
<span className="text-tertiary"> <span className="text-tertiary">
{item.is_admin {!item.is_active
? `[${intl.formatMessage({ id: 'models.table.admin' })}]` ? `[${intl.formatMessage({ id: 'users.status.inactive' })}]`
: ''} : item.is_admin
? `[${intl.formatMessage({ id: 'models.table.admin' })}]`
: ''}
</span> </span>
</span> </span>
)} )}
@@ -9,7 +9,7 @@ import AccessControlForm from './form';
const AccessControlModal: React.FC< const AccessControlModal: React.FC<
Global.ScrollerModalProps<ListItem, AccessControlFormData> Global.ScrollerModalProps<ListItem, AccessControlFormData>
> = ({ open, title, currentData, action, onOk, onCancel }) => { > = ({ open, title, currentData, action, onCancel }) => {
const intl = useIntl(); const intl = useIntl();
const form = useRef<any>(null); const form = useRef<any>(null);
+12 -3
View File
@@ -1,4 +1,4 @@
import { modelsExpandKeysAtom } from '@/atoms/models'; import { modelsExpandKeysAtom, modelsSessionAtom } from '@/atoms/models';
import DeleteModal from '@/components/delete-modal'; import DeleteModal from '@/components/delete-modal';
import DropDownActions from '@/components/drop-down-actions'; import DropDownActions from '@/components/drop-down-actions';
import DropdownButtons from '@/components/drop-down-buttons'; import DropdownButtons from '@/components/drop-down-buttons';
@@ -137,6 +137,7 @@ const Models: React.FC<ModelsProps> = ({
isGGUF: false isGGUF: false
}); });
const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom); const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom);
const [modelsSession, setModelsSession] = useAtom(modelsSessionAtom);
const intl = useIntl(); const intl = useIntl();
const navigate = useNavigate(); const navigate = useNavigate();
const rowSelection = useTableRowSelection(); const rowSelection = useTableRowSelection();
@@ -591,7 +592,16 @@ const Models: React.FC<ModelsProps> = ({
}); });
}; };
const handleSubmitAccessControl = (data: any) => {}; useEffect(() => {
if (modelsSession.source && loadend) {
handleClickDropdown({
key: modelsSession.source
});
}
return () => {
setModelsSession({});
};
}, [loadend]);
return ( return (
<> <>
@@ -774,7 +784,6 @@ const Models: React.FC<ModelsProps> = ({
></APIAccessInfoModal> ></APIAccessInfoModal>
<AccessControlModal <AccessControlModal
onCancel={handleCancelAccessControl} onCancel={handleCancelAccessControl}
onOk={handleSubmitAccessControl}
title={openAccessControlModal.title} title={openAccessControlModal.title}
open={openAccessControlModal.open} open={openAccessControlModal.open}
currentData={openAccessControlModal.currentData} currentData={openAccessControlModal.currentData}
@@ -17,6 +17,8 @@
align-items: center; align-items: center;
width: 32px; width: 32px;
height: 32px; height: 32px;
border-radius: var(--ant-border-radius);
overflow: hidden;
img { img {
height: 32px; height: 32px;
@@ -5,7 +5,6 @@ import { Form } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { import React, {
forwardRef, forwardRef,
memo,
useCallback, useCallback,
useEffect, useEffect,
useId, useId,
@@ -194,4 +193,4 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = forwardRef(
} }
); );
export default memo(ParamsSettings); export default ParamsSettings;
+13 -9
View File
@@ -1,6 +1,7 @@
import { setRouteCache } from '@/atoms/route-cache'; import { setRouteCache } from '@/atoms/route-cache';
import AlertInfo from '@/components/alert-info'; import AlertInfo from '@/components/alert-info';
import IconFont from '@/components/icon-font'; import IconFont from '@/components/icon-font';
import AutoComplete from '@/components/seal-form/auto-complete';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import SpeechContent from '@/components/speech-content'; import SpeechContent from '@/components/speech-content';
import routeCachekey from '@/config/route-cachekey'; import routeCachekey from '@/config/route-cachekey';
@@ -274,17 +275,20 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
const renderExtra = useMemo(() => { const renderExtra = useMemo(() => {
return paramsConfig.map((item: ParamsSchema) => { return paramsConfig.map((item: ParamsSchema) => {
const comProps = {
...item.attrs,
options: item.name === 'voice' ? voiceList : item.options,
label: item.label.isLocalized
? intl.formatMessage({ id: item.label.text })
: item.label.text
};
return ( return (
<Form.Item name={item.name} rules={item.rules} key={item.name}> <Form.Item name={item.name} rules={item.rules} key={item.name}>
<SealSelect {item.type === 'AutoComplete' ? (
{...item.attrs} <AutoComplete {...comProps} />
options={item.name === 'voice' ? voiceList : item.options} ) : (
label={ <SealSelect {...comProps}></SealSelect>
item.label.isLocalized )}
? intl.formatMessage({ id: item.label.text })
: item.label.text
}
></SealSelect>
</Form.Item> </Form.Item>
); );
}); });
+2 -2
View File
@@ -32,7 +32,7 @@ export const imageSizeOptions: {
export const TTSParamsConfig: ParamsSchema[] = [ export const TTSParamsConfig: ParamsSchema[] = [
{ {
type: 'Select', type: 'AutoComplete',
name: 'voice', name: 'voice',
options: [], options: [],
label: { label: {
@@ -41,7 +41,7 @@ export const TTSParamsConfig: ParamsSchema[] = [
}, },
rules: [ rules: [
{ {
required: true, required: false,
message: 'Voice is required' message: 'Voice is required'
} }
] ]
+4 -4
View File
@@ -38,13 +38,13 @@ export interface MessageItem {
type SchemaType = type SchemaType =
| 'Input' | 'Input'
| 'InputNumber' | 'InputNumber'
| 'Textarea' | 'TextArea'
| 'Select' | 'Select'
| 'Slider' | 'Slider'
| 'TextArea'
| 'Checkbox'
| 'Textarea' | 'Textarea'
| 'Switch'; | 'Checkbox'
| 'Switch'
| 'AutoComplete';
export interface ParamsSchema { export interface ParamsSchema {
type: SchemaType; type: SchemaType;
+6 -6
View File
@@ -1,12 +1,12 @@
export const GPUDriverMap = { export const GPUDriverMap = {
NVIDIA: 'cuda', NVIDIA: 'cuda',
AMD: 'rocm', AMD: 'rocm',
ASCEND: 'npu', ASCEND: 'cann',
HYGON: 'dcu', HYGON: 'dtk',
MOORE_THREADS: 'musa', MOORE_THREADS: 'musa',
ILUVATAR: 'corex', ILUVATAR: 'corex',
CAMBRICON: 'cambricon', CAMBRICON: 'neuware',
METAX: 'metax' METAX: 'maca'
}; };
const ManufacturerMap = { const ManufacturerMap = {
@@ -45,7 +45,7 @@ export const GPUsConfigs: Record<
[GPUDriverMap.HYGON]: { [GPUDriverMap.HYGON]: {
label: ManufacturerMap[GPUDriverMap.HYGON], label: ManufacturerMap[GPUDriverMap.HYGON],
value: GPUDriverMap.HYGON, value: GPUDriverMap.HYGON,
runtime: 'dcu', // TODO: confirm runtime name runtime: '', // TODO: confirm runtime name
driver: 'hy-smi' driver: 'hy-smi'
}, },
[GPUDriverMap.MOORE_THREADS]: { [GPUDriverMap.MOORE_THREADS]: {
@@ -57,7 +57,7 @@ export const GPUsConfigs: Record<
[GPUDriverMap.ILUVATAR]: { [GPUDriverMap.ILUVATAR]: {
label: ManufacturerMap[GPUDriverMap.ILUVATAR], label: ManufacturerMap[GPUDriverMap.ILUVATAR],
value: GPUDriverMap.ILUVATAR, value: GPUDriverMap.ILUVATAR,
runtime: 'corex', // TODO: confirm runtime name runtime: '', // TODO: confirm runtime name
driver: 'ixsmi' driver: 'ixsmi'
}, },
[GPUDriverMap.CAMBRICON]: { [GPUDriverMap.CAMBRICON]: {
-9
View File
@@ -68,15 +68,6 @@ export const addWorkerGuide: Record<string, any> = {
} }
}; };
export const containerInstallOptions = [
{ label: 'NVIDIA CUDA', value: 'cuda' },
{ label: 'AMD ROCm', value: 'rocm' },
{ label: 'Ascend CANN', value: 'npu' },
{ label: 'Hygon DTK', value: 'dcu' },
{ label: 'Moore Threads MUSA', value: 'musa' },
{ label: 'Iluvatar Corex', value: 'corex' }
];
export const ModelfileStateMap = { export const ModelfileStateMap = {
Error: 'error', Error: 'error',
Downloading: 'downloading', Downloading: 'downloading',