chore: languages settings

This commit is contained in:
jialin
2024-06-24 14:00:06 +08:00
parent de96c3d81d
commit 64683fdeb6
32 changed files with 417 additions and 156 deletions
+8 -8
View File
@@ -6,7 +6,7 @@ export default [
// redirect: '' // redirect: ''
// }, // },
{ {
name: 'Dashboard', name: 'dashboard',
path: '/dashboard', path: '/dashboard',
key: 'dashboard', key: 'dashboard',
icon: 'home', icon: 'home',
@@ -14,7 +14,7 @@ export default [
component: './dashboard' component: './dashboard'
}, },
{ {
name: 'Playground', name: 'playground',
title: 'Playground', title: 'Playground',
path: '/playground', path: '/playground',
key: 'playground', key: 'playground',
@@ -22,28 +22,28 @@ export default [
component: './playground' component: './playground'
}, },
{ {
name: 'Models', name: 'models',
path: '/models', path: '/models',
key: 'models', key: 'models',
icon: 'Block', icon: 'Block',
component: './llmodels' component: './llmodels'
}, },
{ {
name: 'Resources', name: 'resources',
path: '/resources', path: '/resources',
key: 'resources', key: 'resources',
icon: 'CloudServer', icon: 'CloudServer',
component: './resources' component: './resources'
}, },
{ {
name: 'API Keys', name: 'apikeys',
path: '/api-keys', path: '/api-keys',
key: 'apikeys', key: 'apikeys',
icon: 'LockOutlined', icon: 'LockOutlined',
component: './api-keys' component: './api-keys'
}, },
{ {
name: 'Users', name: 'users',
path: '/users', path: '/users',
key: 'users', key: 'users',
icon: 'Team', icon: 'Team',
@@ -51,7 +51,7 @@ export default [
component: './users' component: './users'
}, },
{ {
name: 'Profile', name: 'profile',
path: '/profile', path: '/profile',
key: 'profile', key: 'profile',
hideInMenu: true, hideInMenu: true,
@@ -59,7 +59,7 @@ export default [
icon: 'User' icon: 'User'
}, },
{ {
name: 'Login', name: 'login',
path: '/login', path: '/login',
key: 'login', key: 'login',
layout: false, layout: false,
+8 -3
View File
@@ -10,6 +10,7 @@ import {
history, history,
matchRoutes, matchRoutes,
useAppData, useAppData,
useIntl,
useLocation, useLocation,
useNavigate, useNavigate,
type IRoute type IRoute
@@ -78,6 +79,7 @@ const mapRoutes = (routes: IRoute[], role: string) => {
export default (props: any) => { export default (props: any) => {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const intl = useIntl();
const { clientRoutes, pluginManager } = useAppData(); const { clientRoutes, pluginManager } = useAppData();
const initialInfo = (useModel && useModel('@@initialState')) || { const initialInfo = (useModel && useModel('@@initialState')) || {
@@ -94,7 +96,10 @@ export default (props: any) => {
locale: true locale: true
}; };
const formatMessage = undefined; const formatMessage = (args) => {
console.log('formatMessage', args);
return intl.formatMessage({ id: args.id });
};
const runtimeConfig = { const runtimeConfig = {
...initialInfo, ...initialInfo,
@@ -158,8 +163,8 @@ export default (props: any) => {
history.push(pathname); history.push(pathname);
} }
}} }}
formatMessage={userConfig.formatMessage || formatMessage} formatMessage={formatMessage}
menu={{ locale: userConfig.locale }} menu={{ locale: true }}
logo={Logo} logo={Logo}
menuItemRender={(menuItemProps, defaultDom) => { menuItemRender={(menuItemProps, defaultDom) => {
console.log('meurender=========', { defaultDom }); console.log('meurender=========', { defaultDom });
+9 -3
View File
@@ -16,6 +16,7 @@ export function getRightRenderContent(opts: {
setInitialState: any; setInitialState: any;
}) { }) {
console.log('runtimeConfig==', opts.runtimeConfig, opts); console.log('runtimeConfig==', opts.runtimeConfig, opts);
// const intl = useIntl();
if (opts.runtimeConfig.rightRender) { if (opts.runtimeConfig.rightRender) {
return opts.runtimeConfig.rightRender( return opts.runtimeConfig.rightRender(
opts.initialState, opts.initialState,
@@ -64,7 +65,10 @@ export function getRightRenderContent(opts: {
icon={ icon={
<span style={{ fontSize: '12px' }}> <span style={{ fontSize: '12px' }}>
<GlobalOutlined /> <GlobalOutlined />
<span className="m-l-8"></span> <span className="m-l-8">
{/* {FormattedMessage({ id: 'common.settings.language' })} */}
Language
</span>
</span> </span>
} }
></SelectLang> ></SelectLang>
@@ -83,7 +87,8 @@ export function getRightRenderContent(opts: {
label: ( label: (
<> <>
<SettingOutlined /> <SettingOutlined />
{/* {intl.formatMessage({ id: 'common.button.settings' })} */}
Settings
</> </>
), ),
onClick: () => { onClick: () => {
@@ -107,7 +112,8 @@ export function getRightRenderContent(opts: {
label: ( label: (
<> <>
<LogoutOutlined /> <LogoutOutlined />
退 {/* {intl.formatMessage({ id: 'common.button.logout' })} */}
Logout
</> </>
), ),
onClick: () => { onClick: () => {
+5 -1
View File
@@ -1,9 +1,13 @@
import common from './en-US/common'; import common from './en-US/common';
import menu from './en-US/menu'; import menu from './en-US/menu';
import models from './en-US/models'; import models from './en-US/models';
import playground from './en-US/playground';
import resources from './en-US/resources';
export default { export default {
...common, ...common,
...menu, ...menu,
...models ...models,
...playground,
...resources
}; };
+5
View File
@@ -0,0 +1,5 @@
export default {
'apikeys.title': 'API Keys',
'apikeys.button.create': 'New API Key',
'apikeys.form.expiretime': 'Expiration'
};
+7 -2
View File
@@ -41,6 +41,7 @@ export default {
'common.button.edit': 'Edit', 'common.button.edit': 'Edit',
'common.button.authorize': 'Role Authorization', 'common.button.authorize': 'Role Authorization',
'common.button.confirm': 'Confirm', 'common.button.confirm': 'Confirm',
'common.button.viewlog': 'View Logs',
'common.table.operation': 'Operation', 'common.table.operation': 'Operation',
'common.table.createTime': 'Created', 'common.table.createTime': 'Created',
'common.table.updateTime': 'Updated', 'common.table.updateTime': 'Updated',
@@ -173,6 +174,10 @@ export default {
'common.nodata.created': 'No {type} has been created yet', 'common.nodata.created': 'No {type} has been created yet',
'common.nodata.added': 'No {type} has been added yet', 'common.nodata.added': 'No {type} has been added yet',
'common.status.edited': 'Edited', 'common.status.edited': 'Edited',
'common.table.user': '用户', 'common.table.user': 'User',
'common.settings.instructions': 'Instructions' 'common.settings.instructions': 'Instructions',
'common.settings.language': 'Language',
'common.delete.confirm':
'Are you sure you want to delete the selected {type}?',
'common.filter.name': 'Filter by name'
}; };
+10 -1
View File
@@ -1 +1,10 @@
export default {}; export default {
'menu.dashboard': 'Dashboard',
'menu.playground': 'Playground',
'menu.models': 'Models',
'menu.resources': 'Resources',
'menu.apikeys': 'API Keys',
'menu.users': 'Users',
'menu.profile': 'Profile',
'menu.login': 'Login'
};
+12 -1
View File
@@ -1,3 +1,14 @@
export default { export default {
'models.button.deploy': 'Deploy Model' 'models.button.deploy': 'Deploy Model',
'models.title': 'Models',
'models.table.models': 'models',
'models.table.name': 'Model Name',
'models.form.source': 'Source',
'models.form.repoid': 'Repo ID',
'models.form.repoid.desc': 'Only .gguf format is supported',
'models.form.filename': 'File Name',
'models.form.replicas': 'Replicas',
'models.form.s3address': 'S3 Address',
'models.openinplayground': 'Open in Playground',
'models.instances': 'instances'
}; };
+17
View File
@@ -0,0 +1,17 @@
export default {
'playground.system.tips': 'Enter system message here...',
'playground.title': 'Playground',
'playground.system': 'System',
'playground.user': 'User',
'playground.assistant': 'Assistant',
'playground.newMessage': 'New Message',
'playground.viewcode': 'View Code',
'playground.model': 'Model',
'playground.parameters': 'Parameters',
'playground.viewcode.info':
'You can use the following code to start integrating your current prompt and settings into your application.',
'playground.completion': 'Completion',
'playground.prompt': 'Prompt',
'playground.tokenusage': 'Token Usage',
'models.openinplayground': 'Open in Playground'
};
+11
View File
@@ -0,0 +1,11 @@
export default {
'resources.title': 'Resources',
'resources.nodes': 'Nodes',
'resources.table.hostname': 'Hostname',
'resources.table.ip': 'IP',
'resources.table.cpu': 'CPU',
'resources.table.memory': 'Memory',
'resources.table.gpu': 'GPU',
'resources.table.disk': 'Storage',
'resources.table.vram': 'VRAM'
};
+5 -1
View File
@@ -1,9 +1,13 @@
import menu from './en-US/menu'; import menu from './en-US/menu';
import common from './zh-CN/common'; import common from './zh-CN/common';
import models from './zh-CN/models'; import models from './zh-CN/models';
import playground from './zh-CN/playground';
import resources from './zh-CN/resources';
export default { export default {
...common, ...common,
...menu, ...menu,
...models ...models,
...playground,
...resources
}; };
+5
View File
@@ -0,0 +1,5 @@
export default {
'apikeys.title': 'API 密钥',
'apikeys.button.create': '新建 API 密钥',
'apikeys.form.expiretime': '过期时间'
};
+5 -1
View File
@@ -40,6 +40,7 @@ export default {
'common.button.edit': '编辑', 'common.button.edit': '编辑',
'common.button.authorize': '角色授权', 'common.button.authorize': '角色授权',
'common.button.confirm': '确定', 'common.button.confirm': '确定',
'common.button.viewlog': '查看日志',
'common.table.operation': '操作', 'common.table.operation': '操作',
'common.table.createTime': '创建时间', 'common.table.createTime': '创建时间',
'common.table.updateTime': '更新时间', 'common.table.updateTime': '更新时间',
@@ -170,5 +171,8 @@ export default {
'common.nodata.added': '尚未添加{type}', 'common.nodata.added': '尚未添加{type}',
'common.status.edited': '已编辑', 'common.status.edited': '已编辑',
'common.table.user': '用户', 'common.table.user': '用户',
'common.settings.instructions': '操作指引' 'common.settings.instructions': '操作指引',
'common.settings.language': '语言',
'common.delete.confirm': '确定删除选中的{type}吗?',
'common.filter.name': '名称查询'
}; };
+10 -1
View File
@@ -1 +1,10 @@
export default {}; export default {
'menu.dashboard': '概览',
'menu.playground': '对话',
'menu.models': '模型',
'menu.resources': '资源',
'menu.apikeys': 'API 密钥',
'menu.users': '用户',
'menu.profile': '个人信息',
'menu.login': '登录'
};
+12 -1
View File
@@ -1,3 +1,14 @@
export default { export default {
'models.button.deploy': 'Deploy Model' 'models.button.deploy': '部署模型',
'models.title': '模型',
'models.table.models': '模型',
'models.table.name': '模型名称',
'models.form.source': '来源',
'models.form.repoid': '仓库 ID',
'models.form.repoid.desc': '只支持 .gguf 格式',
'models.form.filename': '文件名',
'models.form.replicas': '副本数',
'models.form.s3address': 'S3 地址',
'models.openinplayground': '在 Playground 中打开',
'models.instances': '实例'
}; };
+16
View File
@@ -0,0 +1,16 @@
export default {
'playground.system.tips': '在这里输入系统消息...',
'playground.title': '对话',
'playground.system': '系统',
'playground.user': '用户',
'playground.assistant': '助手',
'playground.newMessage': '新消息',
'playground.viewcode': '查看代码',
'playground.model': '模型',
'playground.parameters': '参数',
'playground.viewcode.info':
'你可以使用以下代码将当前的提示和设置集成到你的应用程序中。',
'playground.completion': '补全',
'playground.prompt': '提示',
'playground.tokenusage': 'Token 使用量'
};
+11
View File
@@ -0,0 +1,11 @@
export default {
'resources.title': '资源',
'resources.nodes': '节点',
'resources.table.hostname': '主机名',
'resources.table.ip': 'IP',
'resources.table.cpu': 'CPU',
'resources.table.memory': '内存',
'resources.table.gpu': 'GPU',
'resources.table.disk': '磁盘',
'resources.table.vram': '显存'
};
+1 -1
View File
@@ -1,7 +1,7 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { FormData, ListItem } from '../config/types'; import { FormData, ListItem } from '../config/types';
export const APIS_KEYS_API = '/api_keys'; export const APIS_KEYS_API = '/api-keys';
export async function queryApisKeysList( export async function queryApisKeysList(
params: Global.Pagination & { query?: string } params: Global.Pagination & { query?: string }
-31
View File
@@ -27,37 +27,6 @@ import { FormData, ListItem } from './config/types';
const { Column } = Table; const { Column } = Table;
const list = [
{
key: '1',
name: 'local',
secretKey: `auk_uzem...owsa`,
lastusedTime: '2024-05-22 12:20:10',
createTime: '2024-05-20 12:13:25'
},
{
key: '2',
name: 'dev',
secretKey: `auk_uzem...okwa`,
lastusedTime: '2024-05-19 13:30:22',
createTime: '2024-05-18 10:28:32'
},
{
key: '3',
name: 'prod',
secretKey: `auk_uzem...uuds`,
lastusedTime: '2024-05-18 10:28:32',
createTime: '2024-05-17 08:21:09'
},
{
key: '4',
name: 'test',
secretKey: `auk_uzem...uksa`,
lastusedTime: '2024-05-18 10:28:32',
createTime: '2024-05-16 13:33:23'
}
];
const Models: React.FC = () => { const Models: React.FC = () => {
const rowSelection = useTableRowSelection(); const rowSelection = useTableRowSelection();
const { sortOrder, setSortOrder } = useTableSort({ const { sortOrder, setSortOrder } = useTableSort({
+1 -1
View File
@@ -9,7 +9,7 @@ import {
export const MODELS_API = '/models'; export const MODELS_API = '/models';
export const MODEL_INSTANCE_API = '/model_instances'; export const MODEL_INSTANCE_API = '/model-instances';
// ===================== Models ===================== // ===================== Models =====================
export async function queryModelsList( export async function queryModelsList(
+124 -21
View File
@@ -5,6 +5,7 @@ import SealSelect from '@/components/seal-form/seal-select';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import { convertFileSize } from '@/utils'; import { convertFileSize } from '@/utils';
import { useIntl } from '@umijs/max';
import { Form, Input, Modal } from 'antd'; import { Form, Input, Modal } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -31,6 +32,7 @@ const sourceOptions = [
const AddModal: React.FC<AddModalProps> = (props) => { const AddModal: React.FC<AddModalProps> = (props) => {
const { title, action, open, onOk, onCancel } = props || {}; const { title, action, open, onOk, onCancel } = props || {};
const [form] = Form.useForm(); const [form] = Form.useForm();
const intl = useIntl();
const modelSource = Form.useWatch('source', form); const modelSource = Form.useWatch('source', form);
const [repoOptions, setRepoOptions] = useState< const [repoOptions, setRepoOptions] = useState<
{ label: string; value: string }[] { label: string; value: string }[]
@@ -42,7 +44,8 @@ const AddModal: React.FC<AddModalProps> = (props) => {
const initFormValue = () => { const initFormValue = () => {
if (action === PageAction.CREATE && open) { if (action === PageAction.CREATE && open) {
form.setFieldsValue({ form.setFieldsValue({
source: 'huggingface' source: 'huggingface',
replicas: 1
}); });
} }
}; };
@@ -51,9 +54,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
initFormValue(); initFormValue();
}, [open]); }, [open]);
const handleInputRepoChange = (value: string) => { const handleInputRepoChange = (value: string) => {};
console.log('repo change', value);
};
const fileNamLabel = (item: any) => { const fileNamLabel = (item: any) => {
return ( return (
@@ -87,7 +88,6 @@ const AddModal: React.FC<AddModalProps> = (props) => {
const handleRepoOnBlur = (e: any) => { const handleRepoOnBlur = (e: any) => {
const repo = form.getFieldValue('huggingface_repo_id'); const repo = form.getFieldValue('huggingface_repo_id');
console.log('repo blur', repo);
handleRepoSelect(repo); handleRepoSelect(repo);
}; };
@@ -119,28 +119,48 @@ const AddModal: React.FC<AddModalProps> = (props) => {
<> <>
<Form.Item<FormData> <Form.Item<FormData>
name="huggingface_repo_id" name="huggingface_repo_id"
rules={[{ required: true }]} rules={[
{
required: true,
message: intl.formatMessage(
{
id: 'common.form.rule.input'
},
{ name: intl.formatMessage({ id: 'models.form.repoid' }) }
)
}
]}
> >
<SealAutoComplete <SealAutoComplete
label="Repo ID" label={intl.formatMessage({ id: 'models.form.repoid' })}
required required
showSearch showSearch
onBlur={handleRepoOnBlur} onBlur={handleRepoOnBlur}
onChange={handleInputRepoChange} onChange={handleInputRepoChange}
onSearch={debounceSearch} onSearch={debounceSearch}
options={repoOptions} options={repoOptions}
description="Only .gguf format is supported" description={intl.formatMessage({ id: 'models.form.repoid.desc' })}
> >
<Input.Search style={{ width: '520px' }}></Input.Search> <Input.Search style={{ width: '520px' }}></Input.Search>
</SealAutoComplete> </SealAutoComplete>
</Form.Item> </Form.Item>
<Form.Item<FormData> <Form.Item<FormData>
name="huggingface_filename" name="huggingface_filename"
rules={[{ required: true }]} rules={[
{
required: true,
message: intl.formatMessage(
{
id: 'common.form.rule.input'
},
{ name: intl.formatMessage({ id: 'models.form.filename' }) }
)
}
]}
> >
<SealAutoComplete <SealAutoComplete
showSearch showSearch
label="File Name" label={intl.formatMessage({ id: 'models.form.filename' })}
required required
options={fileOptions} options={fileOptions}
></SealAutoComplete> ></SealAutoComplete>
@@ -152,8 +172,26 @@ const AddModal: React.FC<AddModalProps> = (props) => {
const renderS3Fields = () => { const renderS3Fields = () => {
return ( return (
<> <>
<Form.Item<FormData> name="s3_address" rules={[{ required: true }]}> <Form.Item<FormData>
<SealInput.Input label="S3 Address" required></SealInput.Input> name="s3_address"
rules={[
{
required: true,
message: intl.formatMessage(
{
id: 'common.form.rule.input'
},
{ name: intl.formatMessage({ id: 'models.form.s3address' }) }
)
}
]}
>
<SealInput.Input
label={intl.formatMessage({
id: 'models.form.s3address'
})}
required
></SealInput.Input>
</Form.Item> </Form.Item>
</> </>
); );
@@ -164,9 +202,22 @@ const AddModal: React.FC<AddModalProps> = (props) => {
<> <>
<Form.Item<FormData> <Form.Item<FormData>
name="ollama_library_model_name" name="ollama_library_model_name"
rules={[{ required: true }]} rules={[
{
required: true,
message: intl.formatMessage(
{
id: 'common.form.rule.input'
},
{ name: intl.formatMessage({ id: 'models.table.name' }) }
)
}
]}
> >
<SealInput.Input label="Model Name" required></SealInput.Input> <SealInput.Input
label={intl.formatMessage({ id: 'models.table.name' })}
required
></SealInput.Input>
</Form.Item> </Form.Item>
</> </>
); );
@@ -212,28 +263,80 @@ const AddModal: React.FC<AddModalProps> = (props) => {
} }
> >
<Form name="addModalForm" form={form} onFinish={onOk} preserve={false}> <Form name="addModalForm" form={form} onFinish={onOk} preserve={false}>
<Form.Item<FormData> name="name" rules={[{ required: true }]}> <Form.Item<FormData>
<SealInput.Input label="Name" required></SealInput.Input> name="name"
rules={[
{
required: true,
message: intl.formatMessage(
{
id: 'common.form.rule.input'
},
{ name: intl.formatMessage({ id: 'common.table.name' }) }
)
}
]}
>
<SealInput.Input
label={intl.formatMessage({
id: 'common.table.name'
})}
required
></SealInput.Input>
</Form.Item> </Form.Item>
<Form.Item<FormData> name="source" rules={[{ required: true }]}> <Form.Item<FormData>
name="source"
rules={[
{
required: true,
message: intl.formatMessage(
{
id: 'common.form.rule.select'
},
{ name: intl.formatMessage({ id: 'models.form.source' }) }
)
}
]}
>
<SealSelect <SealSelect
label="Source" label={intl.formatMessage({
id: 'models.form.source'
})}
options={sourceOptions} options={sourceOptions}
required required
onChange={handleSourceChange} onChange={handleSourceChange}
></SealSelect> ></SealSelect>
</Form.Item> </Form.Item>
{renderFieldsBySource()} {renderFieldsBySource()}
<Form.Item<FormData> name="replicas" rules={[{ required: true }]}> <Form.Item<FormData>
name="replicas"
rules={[
{
required: true,
message: intl.formatMessage(
{
id: 'common.form.rule.input'
},
{ name: intl.formatMessage({ id: 'models.form.replicas' }) }
)
}
]}
>
<SealInput.Number <SealInput.Number
style={{ width: '100%' }} style={{ width: '100%' }}
label="Replicas" label={intl.formatMessage({
id: 'models.form.replicas'
})}
required required
min={1} min={1}
></SealInput.Number> ></SealInput.Number>
</Form.Item> </Form.Item>
<Form.Item<FormData> name="description"> <Form.Item<FormData> name="description">
<SealInput.TextArea label="Description"></SealInput.TextArea> <SealInput.TextArea
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>
+43 -23
View File
@@ -178,7 +178,6 @@ const Models: React.FC = () => {
const handleAddModal = () => { const handleAddModal = () => {
setOpenAddModal(true); setOpenAddModal(true);
setAction(PageAction.CREATE); setAction(PageAction.CREATE);
setTitle('Deploy Model');
}; };
const handleClickMenu = (e: any) => { const handleClickMenu = (e: any) => {
@@ -186,10 +185,9 @@ const Models: React.FC = () => {
}; };
const handleModalOk = async (data: FormData) => { const handleModalOk = async (data: FormData) => {
console.log('handleModalOk', data);
await createModel({ data }); await createModel({ data });
setOpenAddModal(false); setOpenAddModal(false);
message.success('successfully!'); message.success(intl.formatMessage({ id: 'common.message.success' }));
}; };
const handleModalCancel = () => { const handleModalCancel = () => {
@@ -203,10 +201,13 @@ const Models: React.FC = () => {
const handleDelete = async (row: any) => { const handleDelete = async (row: any) => {
Modal.confirm({ Modal.confirm({
title: '', title: '',
content: 'Are you sure you want to delete the selected models?', content: intl.formatMessage(
{ id: 'common.delete.confirm' },
{ type: intl.formatMessage({ id: 'models.table.models' }) }
),
async onOk() { async onOk() {
await deleteModel(row.id); await deleteModel(row.id);
message.success('successfully!'); message.success(intl.formatMessage({ id: 'common.message.success' }));
fetchData(); fetchData();
}, },
onCancel() { onCancel() {
@@ -217,10 +218,13 @@ const Models: React.FC = () => {
const handleDeleteBatch = () => { const handleDeleteBatch = () => {
Modal.confirm({ Modal.confirm({
title: '', title: '',
content: 'Are you sure you want to delete the selected models?', content: intl.formatMessage(
{ id: 'common.delete.confirm' },
{ type: intl.formatMessage({ id: 'models.table.models' }) }
),
async onOk() { async onOk() {
await handleBatchRequest(rowSelection.selectedRowKeys, deleteModel); await handleBatchRequest(rowSelection.selectedRowKeys, deleteModel);
message.success('successfully!'); message.success(intl.formatMessage({ id: 'common.message.success' }));
fetchData(); fetchData();
}, },
onCancel() { onCancel() {
@@ -244,7 +248,7 @@ const Models: React.FC = () => {
source: row.source source: row.source
}; };
await createModelInstance({ data }); await createModelInstance({ data });
message.success('successfully!'); message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {} } catch (error) {}
}; };
@@ -258,11 +262,13 @@ const Models: React.FC = () => {
const handleDeleteInstace = (row: any) => { const handleDeleteInstace = (row: any) => {
Modal.confirm({ Modal.confirm({
title: '', title: '',
content: 'Are you sure you want to delete the instance?', content: intl.formatMessage(
{ id: 'common.delete.confirm' },
{ type: intl.formatMessage({ id: 'models.instances' }) }
),
async onOk() { async onOk() {
console.log('OK');
await deleteModelInstance(row.id); await deleteModelInstance(row.id);
message.success('successfully!'); message.success(intl.formatMessage({ id: 'common.message.success' }));
fetchData(); fetchData();
}, },
onCancel() { onCancel() {
@@ -343,7 +349,11 @@ const Models: React.FC = () => {
<Col span={7}> <Col span={7}>
{hoverChildIndex === `${item.id}-${index}` && ( {hoverChildIndex === `${item.id}-${index}` && (
<Space size={20}> <Space size={20}>
<Tooltip title="Delete"> <Tooltip
title={intl.formatMessage({
id: 'common.button.delete'
})}
>
<Button <Button
size="small" size="small"
danger danger
@@ -351,7 +361,11 @@ const Models: React.FC = () => {
icon={<DeleteOutlined></DeleteOutlined>} icon={<DeleteOutlined></DeleteOutlined>}
></Button> ></Button>
</Tooltip> </Tooltip>
<Tooltip title="View Logs"> <Tooltip
title={intl.formatMessage({
id: 'common.button.viewlog'
})}
>
<Button <Button
size="small" size="small"
onClick={() => handleViewLogs(item)} onClick={() => handleViewLogs(item)}
@@ -375,7 +389,7 @@ const Models: React.FC = () => {
<PageContainer <PageContainer
ghost ghost
header={{ header={{
title: 'Models' title: intl.formatMessage({ id: 'models.title' })
}} }}
extra={[]} extra={[]}
> >
@@ -384,7 +398,7 @@ const Models: React.FC = () => {
left={ left={
<Space> <Space>
<Input <Input
placeholder="名称查询" placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 300 }} style={{ width: 300 }}
allowClear allowClear
onChange={handleNameChange} onChange={handleNameChange}
@@ -413,7 +427,7 @@ const Models: React.FC = () => {
onClick={handleDeleteBatch} onClick={handleDeleteBatch}
disabled={!rowSelection.selectedRowKeys.length} disabled={!rowSelection.selectedRowKeys.length}
> >
Delete {intl?.formatMessage?.({ id: 'common.button.delete' })}
</Button> </Button>
</Access> </Access>
</Space> </Space>
@@ -440,7 +454,7 @@ const Models: React.FC = () => {
}} }}
> >
<SealColumn <SealColumn
title="Model Name" title={intl.formatMessage({ id: 'models.table.name' })}
dataIndex="name" dataIndex="name"
key="name" key="name"
width={400} width={400}
@@ -461,7 +475,7 @@ const Models: React.FC = () => {
/> />
<SealColumn <SealColumn
span={8} span={8}
title="Create Time" title={intl.formatMessage({ id: 'common.table.createTime' })}
dataIndex="created_at" dataIndex="created_at"
key="createTime" key="createTime"
defaultSortOrder="descend" defaultSortOrder="descend"
@@ -474,12 +488,16 @@ const Models: React.FC = () => {
/> />
<SealColumn <SealColumn
span={8} span={8}
title="Operation" title={intl.formatMessage({ id: 'common.table.operation' })}
key="operation" key="operation"
render={(text, record) => { render={(text, record) => {
return !record.transition ? ( return !record.transition ? (
<Space size={20}> <Space size={20}>
<Tooltip title="Open in PlayGround"> <Tooltip
title={intl.formatMessage({
id: 'models.openinplayground'
})}
>
<Button <Button
size="small" size="small"
type="primary" type="primary"
@@ -487,7 +505,9 @@ const Models: React.FC = () => {
icon={<WechatWorkOutlined />} icon={<WechatWorkOutlined />}
></Button> ></Button>
</Tooltip> </Tooltip>
<Tooltip title="Delete"> <Tooltip
title={intl.formatMessage({ id: 'common.button.delete' })}
>
<Button <Button
size="small" size="small"
type="primary" type="primary"
@@ -505,12 +525,12 @@ const Models: React.FC = () => {
<AddModal <AddModal
open={openAddModal} open={openAddModal}
action={action} action={action}
title={title} title={intl.formatMessage({ id: 'models.button.deploy' })}
onCancel={handleModalCancel} onCancel={handleModalCancel}
onOk={handleModalOk} onOk={handleModalOk}
></AddModal> ></AddModal>
<ViewLogsModal <ViewLogsModal
title="View Logs" title={intl.formatMessage({ id: 'common.button.viewlog' })}
open={openLogModal} open={openLogModal}
onCancel={handleLogModalCancel} onCancel={handleLogModalCancel}
></ViewLogsModal> ></ViewLogsModal>
+1 -1
View File
@@ -63,7 +63,7 @@ const Login = () => {
return ( return (
<Form <Form
form={form} form={form}
style={{ width: '400px', margin: '5% auto 0' }} style={{ width: '400px', margin: '0 auto', paddingTop: '5%' }}
onFinish={handleLogin} onFinish={handleLogin}
> >
<div>{renderLogo()}</div> <div>{renderLogo()}</div>
@@ -4,6 +4,7 @@ import {
PlusOutlined, PlusOutlined,
SaveOutlined SaveOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Col, Row, Space } from 'antd'; import { Button, Col, Row, Space } from 'antd';
import '../style/chat-footer.less'; import '../style/chat-footer.less';
@@ -17,6 +18,7 @@ interface ChatFooterProps {
} }
const ChatFooter: React.FC<ChatFooterProps> = (props) => { const ChatFooter: React.FC<ChatFooterProps> = (props) => {
const intl = useIntl();
const { onSubmit, onClear, onNewMessage, onView, feedback, disabled } = props; const { onSubmit, onClear, onNewMessage, onView, feedback, disabled } = props;
return ( return (
<div className="chat-footer"> <div className="chat-footer">
@@ -29,14 +31,14 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={onNewMessage} onClick={onNewMessage}
> >
New Message {intl.formatMessage({ id: 'playground.newMessage' })}
</Button> </Button>
<Button <Button
icon={<DeleteOutlined></DeleteOutlined>} icon={<DeleteOutlined></DeleteOutlined>}
onClick={onClear} onClick={onClear}
disabled={disabled} disabled={disabled}
> >
Clear {intl.formatMessage({ id: 'common.button.clear' })}
</Button> </Button>
</Space> </Space>
</Col> </Col>
@@ -48,7 +50,7 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
onClick={onView} onClick={onView}
disabled={disabled} disabled={disabled}
> >
View Code {intl.formatMessage({ id: 'playground.viewcode' })}
</Button> </Button>
<Button <Button
disabled={disabled} disabled={disabled}
@@ -56,7 +58,7 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
icon={<SaveOutlined></SaveOutlined>} icon={<SaveOutlined></SaveOutlined>}
onClick={onSubmit} onClick={onSubmit}
> >
Submit {intl.formatMessage({ id: 'common.button.submit' })}
</Button> </Button>
</Space> </Space>
</Col> </Col>
@@ -1,6 +1,7 @@
import TransitionWrapper from '@/components/transition'; import TransitionWrapper from '@/components/transition';
import { EyeInvisibleOutlined } from '@ant-design/icons'; import { EyeInvisibleOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { Button, Input, Spin } from 'antd'; import { Button, Input, Spin } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
@@ -34,6 +35,7 @@ const MessageList: React.FC<MessageProps> = (props) => {
} }
]); ]);
const intl = useIntl();
const [systemMessage, setSystemMessage] = useState(''); const [systemMessage, setSystemMessage] = useState('');
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -73,7 +75,8 @@ const MessageList: React.FC<MessageProps> = (props) => {
...messageList ...messageList
] ]
: [...messageList], : [...messageList],
...parameters ...parameters,
stream: true
}; };
const data = await execChatCompletions(chatParams); const data = await execChatCompletions(chatParams);
const assistant = _.get(data, ['choices', '0', 'message']); const assistant = _.get(data, ['choices', '0', 'message']);
@@ -118,14 +121,15 @@ const MessageList: React.FC<MessageProps> = (props) => {
const handleUpdateMessage = (index: number, message: MessageItemProps) => { const handleUpdateMessage = (index: number, message: MessageItemProps) => {
messageList[index] = message; messageList[index] = message;
console.log('updatemessage========', index, message);
setMessageList([...messageList]); setMessageList([...messageList]);
}; };
const renderLabel = () => { const renderLabel = () => {
return ( return (
<div className="system-message-wrap "> <div className="system-message-wrap ">
<span className="title">System</span> <span className="title">
{intl.formatMessage({ id: 'playground.system' })}
</span>
<Button type="primary" size="small"> <Button type="primary" size="small">
<EyeInvisibleOutlined /> <EyeInvisibleOutlined />
</Button> </Button>
@@ -145,7 +149,7 @@ const MessageList: React.FC<MessageProps> = (props) => {
value={systemMessage} value={systemMessage}
variant="filled" variant="filled"
autoSize={true} autoSize={true}
placeholder="Enter system message here..." placeholder={intl.formatMessage({ id: 'playground.system.tips' })}
onChange={handleSystemMessageChange} onChange={handleSystemMessageChange}
></Input.TextArea> ></Input.TextArea>
</TransitionWrapper> </TransitionWrapper>
@@ -193,9 +197,9 @@ const MessageList: React.FC<MessageProps> = (props) => {
open={show} open={show}
systemMessage={systemMessage} systemMessage={systemMessage}
messageList={messageList} messageList={messageList}
parameters={parameters} parameters={{ ...parameters, stream: true }}
onCancel={handleCloseViewCode} onCancel={handleCloseViewCode}
title="View code" title={intl.formatMessage({ id: 'playground.viewcode' })}
></ViewCodeModal> ></ViewCodeModal>
</div> </div>
); );
@@ -1,6 +1,6 @@
import { MinusCircleOutlined } from '@ant-design/icons'; import { MinusCircleOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Input } from 'antd'; import { Button, Input } from 'antd';
import _ from 'lodash';
import { memo, useEffect, useRef, useState } from 'react'; import { memo, useEffect, useRef, useState } from 'react';
import { Roles } from '../config'; import { Roles } from '../config';
import '../style/message-item.less'; import '../style/message-item.less';
@@ -18,6 +18,7 @@ const MessageItem: React.FC<{
isFocus: boolean; isFocus: boolean;
onDelete: () => void; onDelete: () => void;
}> = ({ message, isFocus, onDelete, updateMessage }) => { }> = ({ message, isFocus, onDelete, updateMessage }) => {
const intl = useIntl();
const [roleType, setRoleType] = useState(message.role); const [roleType, setRoleType] = useState(message.role);
const [isTyping, setIsTyping] = useState(false); const [isTyping, setIsTyping] = useState(false);
const [messageContent, setMessageContent] = useState(message.content); const [messageContent, setMessageContent] = useState(message.content);
@@ -87,7 +88,7 @@ const MessageItem: React.FC<{
<div className="message-item"> <div className="message-item">
<div className="role-type"> <div className="role-type">
<Button onClick={handleRoleChange} type="text"> <Button onClick={handleRoleChange} type="text">
{_.upperFirst(roleType)} {intl.formatMessage({ id: `playground.${roleType}` })}
</Button> </Button>
</div> </div>
<div className="message-content-input"> <div className="message-content-input">
@@ -3,6 +3,7 @@ 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 { INPUT_WIDTH } from '@/constants'; import { INPUT_WIDTH } from '@/constants';
import { queryModelsList } from '@/pages/llmodels/apis'; import { queryModelsList } from '@/pages/llmodels/apis';
import { useIntl } from '@umijs/max';
import { Button, Form, InputNumber, Slider } from 'antd'; import { Button, Form, InputNumber, Slider } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -29,6 +30,7 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
selectedModel, selectedModel,
setParams setParams
}) => { }) => {
const intl = useIntl();
const [ModelList, setModelList] = useState([]); const [ModelList, setModelList] = useState([]);
const initialValues = { const initialValues = {
seed: null, seed: null,
@@ -137,17 +139,22 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
onFinishFailed={handleOnFinishFailed} onFinishFailed={handleOnFinishFailed}
> >
<div> <div>
<h3 className="m-b-20 m-l-10">Model</h3> <h3 className="m-b-20 m-l-10">
{intl.formatMessage({ id: 'playground.model' })}
</h3>
<Form.Item<ParamsSettingsFormProps> <Form.Item<ParamsSettingsFormProps>
name="model" name="model"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<SealSelect options={ModelList} label="Model"></SealSelect> <SealSelect
options={ModelList}
label={intl.formatMessage({ id: 'playground.model' })}
></SealSelect>
</Form.Item> </Form.Item>
<h3 className="m-b-20 m-l-10 flex-between flex-center"> <h3 className="m-b-20 m-l-10 flex-between flex-center">
<span>Parameters</span> <span>{intl.formatMessage({ id: 'playground.parameters' })}</span>
<Button size="small" onClick={handleResetParams}> <Button size="small" onClick={handleResetParams}>
Reset {intl.formatMessage({ id: 'common.button.reset' })}
</Button> </Button>
</h3> </h3>
<Form.Item<ParamsSettingsFormProps> <Form.Item<ParamsSettingsFormProps>
@@ -195,10 +202,6 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
onChange={(val) => handleFieldValueChange(val, 'max_tokens')} onChange={(val) => handleFieldValueChange(val, 'max_tokens')}
></Slider> ></Slider>
</FieldWrapper> </FieldWrapper>
{/* <SealInput.Number
label="Max Tokens"
style={{ width: INPUT_WIDTH.mini }}
></SealInput.Number> */}
</Form.Item> </Form.Item>
<Form.Item<ParamsSettingsFormProps> <Form.Item<ParamsSettingsFormProps>
name="top_p" name="top_p"
@@ -1,3 +1,4 @@
import { useIntl } from '@umijs/max';
import { Space, Tooltip } from 'antd'; import { Space, Tooltip } from 'antd';
import '../style/reference-params.less'; import '../style/reference-params.less';
@@ -10,6 +11,7 @@ interface ReferenceParamsProps {
} }
const ReferenceParams = (props: ReferenceParamsProps) => { const ReferenceParams = (props: ReferenceParamsProps) => {
const intl = useIntl();
const { usage } = props; const { usage } = props;
if (!usage) { if (!usage) {
return null; return null;
@@ -19,12 +21,21 @@ const ReferenceParams = (props: ReferenceParamsProps) => {
<Tooltip <Tooltip
title={ title={
<Space> <Space>
<span>Completion: {usage.completion_tokens}</span> <span>
<span>Prompt: {usage.prompt_tokens}</span> {intl.formatMessage({ id: 'playground.completion' })}:{' '}
{usage.completion_tokens}
</span>
<span>
{intl.formatMessage({ id: 'playground.prompt' })}:{' '}
{usage.prompt_tokens}
</span>
</Space> </Space>
} }
> >
<span>Token Usage: {usage.total_tokens}</span> <span>
{intl.formatMessage({ id: 'playground.tokenusage' })}:{' '}
{usage.total_tokens}
</span>
</Tooltip> </Tooltip>
</div> </div>
); );
@@ -1,5 +1,6 @@
import EditorWrap from '@/components/editor-wrap'; import EditorWrap from '@/components/editor-wrap';
import Editor from '@monaco-editor/react'; import Editor from '@monaco-editor/react';
import { useIntl } from '@umijs/max';
import { Modal, Spin } from 'antd'; import { Modal, Spin } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
@@ -23,6 +24,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
parameters = {} parameters = {}
} = props || {}; } = props || {};
const intl = useIntl();
const editorRef = useRef(null); const editorRef = useRef(null);
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
const [codeValue, setCodeValue] = useState(''); const [codeValue, setCodeValue] = useState('');
@@ -34,16 +36,24 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
{ label: 'Nodejs', value: 'javascript' } { label: 'Nodejs', value: 'javascript' }
]; ];
useEffect(() => { const formatCode = () => {
generateCode(); if (editorRef.current) {
}, [lang, systemMessage, messageList, parameters]); setTimeout(() => {
editorRef.current
?.getAction?.('editor.action.formatDocument')
?.run()
.then(() => {
console.log('format success');
});
}, 100);
}
};
const generateCode = () => { const generateCode = () => {
if (lang === 'shell') { if (lang === 'shell') {
const systemList = systemMessage const systemList = systemMessage
? [{ role: 'system', content: systemMessage }] ? [{ role: 'system', content: systemMessage }]
: []; : [];
const code = `curl ${window.location.origin}/v1/chat/completions \n-H "Content-Type: application/json" \n-H "Authorization: Bearer $\{GPUSTACK_API_KEY}\" \n-d '${JSON.stringify( const code = `curl ${window.location.origin}/v1/chat/completions \n-H "Content-Type: application/json" \n-H "Authorization: Bearer $\{GPUSTACK_API_KEY}" \n-d '${JSON.stringify(
{ {
...parameters, ...parameters,
messages: [...systemList, ...messageList] messages: [...systemList, ...messageList]
@@ -92,19 +102,6 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
setLoaded(true); setLoaded(true);
}; };
function formatCode() {
if (editorRef.current) {
setTimeout(() => {
editorRef.current
?.getAction?.('editor.action.formatDocument')
?.run()
.then(() => {
console.log('format success');
});
}, 100);
}
}
const handleOnChangeLang = (value: string) => { const handleOnChangeLang = (value: string) => {
setLang(value); setLang(value);
}; };
@@ -124,6 +121,10 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
} }
}; };
useEffect(() => {
generateCode();
}, [lang, systemMessage, messageList, parameters]);
return ( return (
<> <>
<Modal <Modal
@@ -139,8 +140,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
footer={null} footer={null}
> >
<div style={{ marginBottom: '10px' }}> <div style={{ marginBottom: '10px' }}>
You can use the following code to start integrating your current {intl.formatMessage({ id: 'playground.viewcode.info' })}
prompt and settings into your application.
</div> </div>
<Spin spinning={!loaded}> <Spin spinning={!loaded}>
<EditorWrap <EditorWrap
@@ -162,10 +162,6 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
/> />
</EditorWrap> </EditorWrap>
</Spin> </Spin>
<div style={{ marginTop: '10px' }}>
our API Key can be foundhere You should use environment variables or a
secret management tool to expose your key to your applications.
</div>
</Modal> </Modal>
</> </>
); );
+2 -2
View File
@@ -5,10 +5,10 @@ export const Roles = {
export const playGroundRoles = [ export const playGroundRoles = [
{ {
key: 'user', key: 'user',
label: 'User' label: 'playground.user'
}, },
{ {
key: 'assistant', key: 'assistant',
label: 'Assistant' label: 'playground.assitant'
} }
]; ];
+21 -5
View File
@@ -4,6 +4,7 @@ import StatusTag from '@/components/status-tag';
import useTableRowSelection from '@/hooks/use-table-row-selection'; import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort'; import useTableSort from '@/hooks/use-table-sort';
import { SyncOutlined } from '@ant-design/icons'; import { SyncOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Input, Space, Table } from 'antd'; import { Button, Input, Space, Table } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -16,6 +17,7 @@ const Models: React.FC = () => {
const { sortOrder, setSortOrder } = useTableSort({ const { sortOrder, setSortOrder } = useTableSort({
defaultSortOrder: 'descend' defaultSortOrder: 'descend'
}); });
const intl = useIntl();
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [dataSource, setDataSource] = useState<ListItem[]>([]); const [dataSource, setDataSource] = useState<ListItem[]>([]);
@@ -86,7 +88,9 @@ const Models: React.FC = () => {
left={ left={
<Space> <Space>
<Input <Input
placeholder="名称查询" placeholder={intl.formatMessage({
id: 'common.filter.name'
})}
style={{ width: 300 }} style={{ width: 300 }}
onChange={handleNameChange} onChange={handleNameChange}
></Input> ></Input>
@@ -114,9 +118,13 @@ const Models: React.FC = () => {
onChange: handlePageChange onChange: handlePageChange
}} }}
> >
<Column title="Host Name" dataIndex="hostname" key="hostname" />
<Column <Column
title="State" title={intl.formatMessage({ id: 'resources.table.hostname' })}
dataIndex="hostname"
key="hostname"
/>
<Column
title={intl.formatMessage({ id: 'common.table.status' })}
dataIndex="state" dataIndex="state"
key="state" key="state"
render={(text, record: ListItem) => { render={(text, record: ListItem) => {
@@ -145,7 +153,7 @@ const Models: React.FC = () => {
}} }}
/> />
<Column <Column
title="Memory" title={intl.formatMessage({ id: 'resources.table.memory' })}
dataIndex="memory" dataIndex="memory"
key="Memory" key="Memory"
render={(text, record: ListItem) => { render={(text, record: ListItem) => {
@@ -177,7 +185,15 @@ const Models: React.FC = () => {
}} }}
/> />
<Column <Column
title="VRAM" title={intl.formatMessage({ id: 'resources.table.disk' })}
dataIndex="storage"
key="storage"
render={(text, record: ListItem) => {
return <ProgressBar percent={0}></ProgressBar>;
}}
/>
<Column
title={intl.formatMessage({ id: 'resources.table.vram' })}
dataIndex="VRAM" dataIndex="VRAM"
key="VRAM" key="VRAM"
render={(text, record: ListItem) => { render={(text, record: ListItem) => {
+4 -1
View File
@@ -1,4 +1,5 @@
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import type { TabsProps } from 'antd'; import type { TabsProps } from 'antd';
import { Tabs } from 'antd'; import { Tabs } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
@@ -20,6 +21,8 @@ const items: TabsProps['items'] = [
const Resources = () => { const Resources = () => {
const [activeKey, setActiveKey] = useState('test'); const [activeKey, setActiveKey] = useState('test');
const intl = useIntl();
const handleChangeTab = (key: string) => { const handleChangeTab = (key: string) => {
setActiveKey(key); setActiveKey(key);
}; };
@@ -28,7 +31,7 @@ const Resources = () => {
<PageContainer <PageContainer
ghost ghost
header={{ header={{
title: 'Resources' title: intl.formatMessage({ id: 'resources.title' })
}} }}
extra={[]} extra={[]}
> >