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,
export const modelsExpandKeysAtom = atom<string[]>([]);
export const modelsSessionAtom = atom<Record<string, any>>({});
export const requestIdAtom = atom<number>(0);
export const setRquestId = () => {
@@ -1,4 +1,5 @@
import { Checkbox } from 'antd';
import AutoComplete from '../auto-complete';
import SealInput from '../seal-input';
import SealSelect from '../seal-select';
import Slider from '../seal-slider';
@@ -12,6 +13,7 @@ const components: {
Input: typeof SealInput.Input;
Checkbox: typeof Checkbox;
Switch: typeof Switch;
AutoComplete: typeof AutoComplete;
} = {
InputNumber: SealInput.Number,
Select: SealSelect,
@@ -19,7 +21,8 @@ const components: {
TextArea: SealInput.TextArea,
Input: SealInput.Input,
Checkbox: Checkbox,
Switch: Switch
Switch: Switch,
AutoComplete: AutoComplete
};
export default components;
+2 -1
View File
@@ -247,5 +247,6 @@ export default {
'models.form.gpuPerReplica.tips': 'Enter a custom number',
'models.form.generic_proxy': '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.generic_proxy': '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) ==========
@@ -322,6 +323,7 @@ export default {
// 52. 'models.form.gpuPerReplica.tips': 'Enter a custom number',
// 53. 'models.form.generic_proxy': '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 ==========
+4 -2
View File
@@ -248,7 +248,8 @@ export default {
'models.form.gpuPerReplica.tips': 'Enter a custom number',
'models.form.generic_proxy': '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) ==========
@@ -256,5 +257,6 @@ export default {
// 2. 'models.form.gpuPerReplica.tips': 'Enter a custom number',
// 3. 'models.form.generic_proxy': '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 ==========
+2 -1
View File
@@ -234,5 +234,6 @@ export default {
'models.form.gpuPerReplica.tips': '输入自定义数值',
'models.form.generic_proxy': '启用通用代理',
'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',
musa: 'cyan',
corex: 'purple',
maca: 'geekblue'
maca: 'geekblue',
neuware: 'gold'
};
export const getGpuColor = (gpuType: string) => {
@@ -146,10 +147,22 @@ export const frameworks = [
label: 'DTK',
value: 'dtk'
},
{
label: 'MACA',
value: 'maca'
},
{
label: 'CoreX',
value: 'corex'
},
{
label: 'MUSA',
value: 'musa'
},
{
label: 'Neuware',
value: 'neuware'
},
{
label: 'CPU',
value: 'cpu'
+1 -1
View File
@@ -372,7 +372,7 @@ export async function queryCatalogItemSpec(
params: { id: number },
options?: any
) {
return await request<Global.PageResponse<CatalogSpec>>(
return request<Global.PageResponse<CatalogSpec>>(
`/model-sets/${params.id}/specs`,
{
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 { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config';
@@ -14,7 +14,7 @@ import _ from 'lodash';
import React, { useCallback, useEffect, useState } from 'react';
import NoResult from '../_components/no-result';
import PageBox from '../_components/page-box';
import { createModel, queryCatalogList } from './apis';
import { createModel, queryCatalogItemSpec, queryCatalogList } from './apis';
import CatalogList from './components/catalog-list';
import DelopyBuiltInModal from './components/deploy-builtin-modal';
import { modelCategories, modelSourceMap } from './config';
@@ -50,6 +50,7 @@ const Catalog: React.FC = () => {
source: modelSourceMap.huggingface_value
});
const [modelsExpandKeys, setModelsExpandKeys] = useAtom(modelsExpandKeysAtom);
const [, setModelsSession] = useAtom(modelsSessionAtom);
const cacheData = React.useRef<CatalogItemType[]>([]);
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');
};
@@ -1,9 +1,16 @@
import AlertBlockInfo from '@/components/alert-info/block';
import TransferInner from '@/pages/_components/transfer';
import { queryUsersList } from '@/pages/users/apis';
import { DownOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Empty, Form, Radio } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { Checkbox, Dropdown, DropdownProps, Empty, Form, Radio } from 'antd';
import {
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useState
} from 'react';
import styled from 'styled-components';
import { queryModelAccessUserList } from '../../apis';
import { AccessControlFormData, ListItem } from '../../config/types';
@@ -29,20 +36,42 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
const accessPolicy = Form.useWatch('access_policy', form);
const [targetKeys, setTargetKeys] = useState<TransferKey[]>([]);
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>({
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) => {
try {
const res = await queryUsersList(query);
const options = res.items.map((item) => ({
title: item.username,
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);
setUserList(options);
@@ -65,6 +94,30 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
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, () => ({
submit: () => {
form.submit();
@@ -97,6 +150,51 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
getUserList(queryParams);
}, [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 (
<Form
form={form}
@@ -149,11 +247,11 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
</Label>
<Form.Item<AccessControlFormData> name="users">
<TransferInner
dataSource={userList}
dataSource={dataList}
targetKeys={targetKeys}
pagination={false}
titles={[
intl.formatMessage({ id: 'models.table.users.all' }),
renderFilterDropdown(),
intl.formatMessage({
id: 'models.table.users.selected'
})
@@ -188,9 +286,11 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
<span className="flex-center gap-4">
<span>{item.title}</span>
<span className="text-tertiary">
{item.is_admin
? `[${intl.formatMessage({ id: 'models.table.admin' })}]`
: ''}
{!item.is_active
? `[${intl.formatMessage({ id: 'users.status.inactive' })}]`
: item.is_admin
? `[${intl.formatMessage({ id: 'models.table.admin' })}]`
: ''}
</span>
</span>
)}
@@ -9,7 +9,7 @@ import AccessControlForm from './form';
const AccessControlModal: React.FC<
Global.ScrollerModalProps<ListItem, AccessControlFormData>
> = ({ open, title, currentData, action, onOk, onCancel }) => {
> = ({ open, title, currentData, action, onCancel }) => {
const intl = useIntl();
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 DropDownActions from '@/components/drop-down-actions';
import DropdownButtons from '@/components/drop-down-buttons';
@@ -137,6 +137,7 @@ const Models: React.FC<ModelsProps> = ({
isGGUF: false
});
const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom);
const [modelsSession, setModelsSession] = useAtom(modelsSessionAtom);
const intl = useIntl();
const navigate = useNavigate();
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 (
<>
@@ -774,7 +784,6 @@ const Models: React.FC<ModelsProps> = ({
></APIAccessInfoModal>
<AccessControlModal
onCancel={handleCancelAccessControl}
onOk={handleSubmitAccessControl}
title={openAccessControlModal.title}
open={openAccessControlModal.open}
currentData={openAccessControlModal.currentData}
@@ -17,6 +17,8 @@
align-items: center;
width: 32px;
height: 32px;
border-radius: var(--ant-border-radius);
overflow: hidden;
img {
height: 32px;
@@ -5,7 +5,6 @@ import { Form } from 'antd';
import _ from 'lodash';
import React, {
forwardRef,
memo,
useCallback,
useEffect,
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 AlertInfo from '@/components/alert-info';
import IconFont from '@/components/icon-font';
import AutoComplete from '@/components/seal-form/auto-complete';
import SealSelect from '@/components/seal-form/seal-select';
import SpeechContent from '@/components/speech-content';
import routeCachekey from '@/config/route-cachekey';
@@ -274,17 +275,20 @@ const GroundTTS: React.FC<MessageProps> = forwardRef((props, ref) => {
const renderExtra = useMemo(() => {
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 (
<Form.Item name={item.name} rules={item.rules} key={item.name}>
<SealSelect
{...item.attrs}
options={item.name === 'voice' ? voiceList : item.options}
label={
item.label.isLocalized
? intl.formatMessage({ id: item.label.text })
: item.label.text
}
></SealSelect>
{item.type === 'AutoComplete' ? (
<AutoComplete {...comProps} />
) : (
<SealSelect {...comProps}></SealSelect>
)}
</Form.Item>
);
});
+2 -2
View File
@@ -32,7 +32,7 @@ export const imageSizeOptions: {
export const TTSParamsConfig: ParamsSchema[] = [
{
type: 'Select',
type: 'AutoComplete',
name: 'voice',
options: [],
label: {
@@ -41,7 +41,7 @@ export const TTSParamsConfig: ParamsSchema[] = [
},
rules: [
{
required: true,
required: false,
message: 'Voice is required'
}
]
+4 -4
View File
@@ -38,13 +38,13 @@ export interface MessageItem {
type SchemaType =
| 'Input'
| 'InputNumber'
| 'Textarea'
| 'TextArea'
| 'Select'
| 'Slider'
| 'TextArea'
| 'Checkbox'
| 'Textarea'
| 'Switch';
| 'Checkbox'
| 'Switch'
| 'AutoComplete';
export interface ParamsSchema {
type: SchemaType;
+6 -6
View File
@@ -1,12 +1,12 @@
export const GPUDriverMap = {
NVIDIA: 'cuda',
AMD: 'rocm',
ASCEND: 'npu',
HYGON: 'dcu',
ASCEND: 'cann',
HYGON: 'dtk',
MOORE_THREADS: 'musa',
ILUVATAR: 'corex',
CAMBRICON: 'cambricon',
METAX: 'metax'
CAMBRICON: 'neuware',
METAX: 'maca'
};
const ManufacturerMap = {
@@ -45,7 +45,7 @@ export const GPUsConfigs: Record<
[GPUDriverMap.HYGON]: {
label: ManufacturerMap[GPUDriverMap.HYGON],
value: GPUDriverMap.HYGON,
runtime: 'dcu', // TODO: confirm runtime name
runtime: '', // TODO: confirm runtime name
driver: 'hy-smi'
},
[GPUDriverMap.MOORE_THREADS]: {
@@ -57,7 +57,7 @@ export const GPUsConfigs: Record<
[GPUDriverMap.ILUVATAR]: {
label: ManufacturerMap[GPUDriverMap.ILUVATAR],
value: GPUDriverMap.ILUVATAR,
runtime: 'corex', // TODO: confirm runtime name
runtime: '', // TODO: confirm runtime name
driver: 'ixsmi'
},
[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 = {
Error: 'error',
Downloading: 'downloading',