chore: version info modal

main
jialin 2 years ago
parent c5327d3e56
commit 320db56fbe

@ -1,9 +1,13 @@
import { defineConfig } from '@umijs/max';
import proxy from './proxy';
import routes from './routes';
import theme from './theme';
import { getBranchInfo } from './utils';
const CompressionWebpackPlugin = require('compression-webpack-plugin');
import proxy from './proxy';
import routes from './routes';
const versionInfo = getBranchInfo();
process.env.VERSION = JSON.stringify(versionInfo);
const env = process.env.NODE_ENV;
const isProduction = env === 'production';
@ -15,6 +19,7 @@ export default defineConfig({
history: {
type: 'hash'
},
base: process.env.npm_config_base || '/',
...(isProduction
? {

@ -1,16 +1,16 @@
const proxyTableList = ['cli', 'v1', 'auth', 'v1-openai'];
const proxyTableList = ['cli', 'v1', 'auth', 'v1-openai', 'version'];
// @ts-ingore
export default function createProxyTable(target?: string) {
const proxyTable = proxyTableList.reduce(
(obj: Record<string, object>, api) => {
const newTarget = target || 'http://localhost';
obj[`/${api}/`] = {
obj[`/${api}`] = {
target: newTarget,
changeOrigin: true,
secure: false,
ws: true,
pathRewrite: (pth: string) => pth.replace(`/^/${api}/`, `/${api}`),
pathRewrite: (pth: string) => pth.replace(`/^/${api}`, `/${api}`),
// onProxyRes: (proxyRes: any, req: any, res: any) => {
// if (req.headers.accept === 'text/event-stream') {
// res.writeHead(res.statusCode, {

@ -9,5 +9,5 @@ export const getBranchInfo = () => {
.execSync(`git tag --contains ${latestCommit}`)
.toString()
.trim();
return { version: versionTag, commitId: latestCommit };
return { version: versionTag || 'dev', commitId: latestCommit.slice(0, 7) };
};

@ -2,24 +2,15 @@ import { IApi } from '@umijs/max';
export default (api: IApi) => {
api.modifyHTML(($) => {
console.log('pllugins=========modifyHTML', $);
const info = JSON.parse(process.env.VERSION || '{}');
const env = process.env.NODE_ENV;
$('html').attr(
'data-version',
env === 'production' ? info.version : `${info.version}-${info.commitId}`
);
return $;
});
api.onStart(() => {
console.log('pllugins=========start');
console.log('start');
});
// api.modifyConfig((memo: any) => {
// // some beautiful code
// console.log('pllugins=========memo', memo);
// return memo;
// });
// api.addLayouts(() => {
// return [
// {
// id: 'layout',
// file: require.resolve('./src/global-layouts/index.tsx')
// }
// ];
// });
};

@ -1,6 +1,11 @@
import { GPUStackVersionAtom } from '@/atoms/user';
import { setAtomStorage } from '@/atoms/utils';
import { RequestConfig, history } from '@umijs/max';
import { requestConfig } from './request-config';
import { queryCurrentUserState } from './services/profile/apis';
import {
queryCurrentUserState,
queryVersionInfo
} from './services/profile/apis';
const loginPath = '/login';
let currentUserInfo: any = {};
@ -28,6 +33,18 @@ export async function getInitialState(): Promise<{
return {} as Global.UserInfo;
};
const getAppVersionInfo = async () => {
try {
const data = await queryVersionInfo();
console.log('versioninfo=========', data);
setAtomStorage(GPUStackVersionAtom, data);
} catch (error) {
console.error('queryVersionInfo error', error);
}
};
getAppVersionInfo();
if (![loginPath].includes(location.pathname)) {
const userInfo = await fetchUserInfo();
currentUserInfo = {
@ -43,6 +60,8 @@ export async function getInitialState(): Promise<{
};
}
console.log('app.tsx');
export const request: RequestConfig = {
baseURL: ' /v1',
...requestConfig

@ -1,7 +1,16 @@
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
export const userAtom = atomWithStorage<any>('userInfo', null);
export const GPUStackVersionAtom = atom<{
version: string;
git_commit: string;
}>({
version: '',
git_commit: ''
});
export const initialPasswordAtom = atomWithStorage<string>(
'initialPassword',
''

@ -8,7 +8,14 @@ export const clearAtomStorage = (atom: any) => {
store.set(atom, null);
};
export const getAtomStorage = (atom: any) => {
export const setAtomStorage = (atom: any, value: any) => {
if (!atom) {
return;
}
const store = getDefaultStore();
store.set(atom, value);
};
export const getAtomStorage = (atom: any): any => {
if (!atom) {
return null;
}

@ -5,14 +5,20 @@ import { Button } from 'antd';
type CopyButtonProps = {
text: string;
disabled?: boolean;
fontSize?: string;
type?: 'text' | 'primary' | 'dashed' | 'link' | 'default';
size?: 'small' | 'middle' | 'large';
shape?: 'circle' | 'round' | 'default';
style?: React.CSSProperties;
};
const CopyButton: React.FC<CopyButtonProps> = ({
text,
disabled,
type = 'text',
shape = 'circle',
fontSize = '14px',
style,
size = 'middle'
}) => {
const { copied, copyToClipboard } = useCopyToClipboard();
@ -24,21 +30,20 @@ const CopyButton: React.FC<CopyButtonProps> = ({
return (
<Button
type={type}
shape="circle"
shape={shape}
size={size}
onClick={handleCopy}
disabled={!!disabled}
>
{copied ? (
<CheckCircleFilled
style={{ color: 'var(--ant-color-success)', fontSize: '14px' }}
/>
) : (
<CopyOutlined
style={{ color: 'var(--ant-color-primary)', fontSize: '14px' }}
/>
)}
</Button>
icon={
copied ? (
<CheckCircleFilled
style={{ color: 'var(--ant-color-success)', fontSize: fontSize }}
/>
) : (
<CopyOutlined style={{ fontSize: fontSize, ...style }} />
)
}
></Button>
);
};

@ -22,7 +22,7 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
showHeader = true
}) => {
const handleChangeLang = (value: string) => {
onChangeLang && onChangeLang(value);
onChangeLang?.(value);
};
const renderHeader = () => {
if (header) {
@ -39,7 +39,13 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
options={langOptions}
onChange={handleChangeLang}
></Select>
<CopyButton text={copyText} />
<CopyButton
text={copyText}
size="small"
style={{
color: 'rgba(255,255,255,.7)'
}}
/>
</div>
);
}

@ -7,4 +7,10 @@
text-align: center;
font-size: var(--font-size-middle);
color: var(--color-text-2);
.footer-content-left-text {
display: flex;
justify-content: center;
align-items: center;
}
}

@ -1,9 +1,23 @@
import { GPUStackVersionAtom } from '@/atoms/user';
import { getAtomStorage } from '@/atoms/utils';
import VersionInfo from '@/components/version-info';
import externalLinks from '@/config/external-links';
import { useIntl } from '@umijs/max';
import { Space } from 'antd';
import { Button, Modal, Space } from 'antd';
import './index.less';
const Footer: React.FC = () => {
const intl = useIntl();
const showVersion = () => {
Modal.info({
icon: null,
centered: false,
width: 500,
content: <VersionInfo intl={intl} />
});
};
return (
<div className="footer">
<div className="footer-content">
@ -14,6 +28,19 @@ const Footer: React.FC = () => {
<span> {new Date().getFullYear()}</span>
<span> {intl.formatMessage({ id: 'settings.company' })}</span>
</Space>
<Space size={8} style={{ marginLeft: 18 }}>
<Button
type="link"
size="small"
href={externalLinks.documentation}
target="_blank"
>
{intl.formatMessage({ id: 'common.button.help' })}
</Button>
<Button type="link" size="small" onClick={showVersion}>
{getAtomStorage(GPUStackVersionAtom)?.version}
</Button>
</Space>
</div>
</div>
</div>

@ -20,12 +20,17 @@ type StatusTagProps = {
text: string;
message?: string;
};
type?: 'tag' | 'circle';
download?: {
percent: number;
};
};
const StatusTag: React.FC<StatusTagProps> = ({ statusValue, download }) => {
const StatusTag: React.FC<StatusTagProps> = ({
statusValue,
download,
type = 'tag'
}) => {
const { text, status } = statusValue;
const [statusColor, setStatusColor] = useState<{
text: string;

@ -0,0 +1,41 @@
.version-box {
display: flex;
margin-bottom: 40px;
flex-direction: column;
align-items: center;
.img {
margin-top: 16px;
text-align: center;
height: 30px;
img {
height: 100%;
}
}
.title {
font-weight: var(--font-weight-medium);
text-align: center;
font-size: var(--font-size-middle);
margin-block: 30px 10px;
}
.ver {
line-height: 32px;
display: flex;
font-size: var(--font-size-middle);
.label {
display: flex;
justify-content: flex-start;
width: 60px;
font-size: var(--font-size-middle);
font-weight: var(--font-weight-medium);
}
.val {
color: var(--ant-color-text-secondary);
}
}
}

@ -0,0 +1,39 @@
import Logo from '@/assets/images/gpustack-logo.png';
import { GPUStackVersionAtom } from '@/atoms/user';
import { getAtomStorage } from '@/atoms/utils';
import React from 'react';
import './index.less';
const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
// get the data attr from html
const version = document.documentElement.getAttribute('data-version');
return (
<div className="version-box">
<div className="img">
<img src={Logo} alt="logo" />
</div>
<div className="title">
{intl.formatMessage({ id: 'common.footer.version.title' })}
</div>
<div>
<div className="ver">
<span className="label">
{' '}
{intl.formatMessage({ id: 'common.footer.version.server' })}
</span>
<span className="val">
{getAtomStorage(GPUStackVersionAtom)?.version ||
getAtomStorage(GPUStackVersionAtom)?.git_commit}
</span>
</div>
<div className="ver">
<span className="label">UI </span>
<span className="val"> {version}</span>
</div>
</div>
</div>
);
};
export default VersionInfo;

@ -0,0 +1,6 @@
export default {
documentation: 'https://docs.gpustack.ai/',
github: 'https://github.com/gpustack/gpustack',
discord: 'https://discord.gg/2ZvXuaYq',
site: 'https://seal.io/'
};

@ -21,4 +21,6 @@ declare namespace Global {
require_password_change: boolean;
id: number;
}
type SearchParams = Pagination & { search?: string };
}

@ -1 +1 @@
// 应用前置、全局运行的逻辑时 会在这里执行
// 应用前置、全局运行的逻辑时 会在这里执行

@ -1,10 +1,10 @@
// @ts-nocheck
import { userAtom } from '@/atoms/user';
import VersionInfo from '@/components/version-info';
import { logout } from '@/pages/login/apis';
import { useAccessMarkedRoutes } from '@@/plugin-access';
import { useModel } from '@@/plugin-model';
import { ProLayout } from '@ant-design/pro-components';
import {
Link,
@ -17,6 +17,7 @@ import {
useNavigate,
type IRoute
} from '@umijs/max';
import { Modal } from 'antd';
import { useAtom } from 'jotai';
import { useMemo, useState } from 'react';
import Exception from './Exception';
@ -102,6 +103,15 @@ export default (props: any) => {
return intl.formatMessage({ id: args.id });
};
const showVersion = () => {
Modal.info({
icon: null,
centered: false,
width: 500,
content: <VersionInfo intl={intl} />
});
};
const runtimeConfig = {
...initialInfo,
logout: async (userInfo) => {
@ -109,6 +119,9 @@ export default (props: any) => {
await logout();
navigate(loginPath);
},
showVersion: () => {
return showVersion();
},
notFound: <span>404 not found</span>
};

@ -1,6 +1,7 @@
// @ts-nocheck
import avatarImg from '@/assets/images/avatar.png';
import externalLinks from '@/config/external-links';
import langConfigMap from '@/locales/lang-config-map';
import {
DiscordOutlined,
@ -27,6 +28,7 @@ export function getRightRenderContent(opts: {
intl: any;
}) {
const { intl, collapsed, siderWidth } = opts;
const allLocals = getAllLocales();
if (opts.runtimeConfig.rightRender) {
return opts.runtimeConfig.rightRender(
@ -73,22 +75,25 @@ export function getRightRenderContent(opts: {
key: 'site',
icon: <HomeOutlined />,
label: 'GPUStack',
url: 'https://gpustack.ai/'
url: externalLinks.site
},
{
key: 'github',
icon: <GithubOutlined />,
label: 'GitHub'
label: 'GitHub',
url: externalLinks.github
},
{
key: 'Discord',
icon: <DiscordOutlined />,
label: 'Discord'
label: 'Discord',
url: externalLinks.discord
},
{
key: 'docs',
icon: <ReadOutlined />,
label: intl.formatMessage({ id: 'common.button.docs' })
label: intl.formatMessage({ id: 'common.button.docs' }),
url: externalLinks.documentation
},
{
key: 'version',
@ -118,11 +123,26 @@ export function getRightRenderContent(opts: {
label: (
<span className="flex flex-center">
{item.icon}
<a className="m-l-8" href="#" target="_blank">
{item.label}
</a>
{item.key === 'version' ? (
<a className="m-l-8">{item.label}</a>
) : (
<a
className="m-l-8"
href={item.url}
target="_blank"
rel="noreferrer"
>
{item.label}
</a>
)}
</span>
)
),
onClick() {
if (item.key === 'version') {
// opts.runtimeConfig.showVersion();
opts.runtimeConfig.showVersion();
}
}
}))
}
]

@ -22,7 +22,7 @@ export default {
'playground.params.topp.tips':
'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered.',
'playground.params.seed.tips':
'Specify a random seed to ensure deterministic sampling. Using the same seed and parameters will produce the same results for repeated requests.',
'If specified, our system will make a best effort to sample deterministically, such that repeated requests with the same seed and parameters should return the same result.',
'playground.params.stop.tips':
'A stop sequence is a predefined or user-specified text string that signals the AI to stop generating further tokens when these sequences appear.'
};

@ -20,9 +20,9 @@ export default {
'playground.params.maxtokens.tips':
'生成的最大 token 数。输入标记和生成的标记的总长度受模型上下文长度的限制。',
'playground.params.topp.tips':
'通过核采样控制多样性0.5 表示考虑所有基于概率权重选项的一半。',
'通过核采样控制多样性0.5 表示考虑所有基于概率权重选项的一半。',
'playground.params.seed.tips':
'指定随机种子以确保确定性采样。使用相同的种子和参数对于重复请求将产生相同的结果。',
'如果指定,我们的系统将尽最大努力进行确定性采样,以便使用相同种子和参数的重复请求应返回相同的结果。',
'playground.params.stop.tips':
'停止序列是一个预定义或用户指定的文本字符串,当这些序列出现时,它会提示 AI 停止生成后续的标记。'
};

@ -3,9 +3,7 @@ import { FormData, ListItem } from '../config/types';
export const APIS_KEYS_API = '/api-keys';
export async function queryApisKeysList(
params: Global.Pagination & { query?: string }
) {
export async function queryApisKeysList(params: Global.SearchParams) {
return request<Global.PageResponse<ListItem>>(`${APIS_KEYS_API}`, {
method: 'GET',
params

@ -42,7 +42,7 @@ const Models: React.FC = () => {
const [queryParams, setQueryParams] = useState({
page: 1,
perPage: 10,
query: ''
search: ''
});
const handleShowSizeChange = (page: number, size: number) => {
@ -90,7 +90,7 @@ const Models: React.FC = () => {
const handleNameChange = (e: any) => {
setQueryParams({
...queryParams,
query: e.target.value
search: e.target.value
});
};

@ -13,7 +13,7 @@ export const MODEL_INSTANCE_API = '/model-instances';
// ===================== Models =====================
export async function queryModelsList(
params: Global.Pagination & { query?: string },
params: Global.SearchParams,
options?: any
) {
return request<Global.PageResponse<ListItem>>(`${MODELS_API}`, {

@ -1,9 +1,13 @@
import DropdownButtons from '@/components/drop-down-buttons';
import RowChildren from '@/components/seal-table/components/row-children';
import StatusTag from '@/components/status-tag';
import { DeleteOutlined, FieldTimeOutlined } from '@ant-design/icons';
import {
DeleteOutlined,
FieldTimeOutlined,
InfoCircleOutlined
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Col, Row, Space } from 'antd';
import { Col, Row, Space, Tooltip } from 'antd';
import dayjs from 'dayjs';
import _ from 'lodash';
import React from 'react';
@ -59,35 +63,44 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
});
};
const getWorkerIp = (item: ModelInstanceListItem) => {
const renderWorkerInfo = (item: ModelInstanceListItem) => {
let workerIp = '-';
if (item.worker_ip) {
return item.port ? `${item.worker_ip}:${item.port}` : item.worker_ip;
workerIp = item.port ? `${item.worker_ip}:${item.port}` : item.worker_ip;
}
return '-';
return (
<div>
<div>{item.worker_name}</div>
<div>{workerIp}</div>
</div>
);
};
return (
<Space size={16} direction="vertical" style={{ width: '100%' }}>
{_.map(list, (item: ModelInstanceListItem, index: number) => {
return (
<div
className="_2Q2Yw"
key={`${item.id}`}
style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}
>
<RowChildren key={`${item.id}_row`}>
<Row style={{ width: '100%' }} align="middle">
<Col span={4}>{item.name}</Col>
<Col span={3}>{item.worker_name || '-'}</Col>
<Col span={4}>
<span>
{item.source === 'huggingface'
? item.huggingface_filename
: item.ollama_library_model_name}
</span>
<Col
span={5}
style={{
paddingInline: 'var(--ant-table-cell-padding-inline)'
}}
>
<Tooltip title={renderWorkerInfo(item)}>
<span className="m-r-5">{item.name}</span>
<InfoCircleOutlined />
</Tooltip>
</Col>
<Col span={3}>
<Col span={6}></Col>
<Col span={4}>
<span
style={{ paddingLeft: '0px' }}
style={{ paddingLeft: '56px' }}
className="flex justify-center"
>
{item.state && (
@ -107,11 +120,11 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
</span>
</Col>
<Col span={5}>
<span style={{ paddingLeft: 36 }}>
<span style={{ paddingLeft: 38 }}>
{dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')}
</span>
</Col>
<Col span={5}>
<Col span={4}>
<div style={{ paddingLeft: 39 }}>
<DropdownButtons
items={setChildActionList(item)}

@ -337,15 +337,21 @@ const Models: React.FC<ModelsProps> = ({
dataIndex="name"
key="name"
width={400}
span={6}
span={5}
/>
<SealColumn
title={intl.formatMessage({ id: 'models.form.source' })}
dataIndex="source"
key="source"
span={4}
render={(text) => {
return modelSourceMap[text] || '-';
span={6}
render={(text, record: ListItem) => {
return (
<span>
{record.source === modelSourceMap.huggingface_value
? `${modelSourceMap.huggingface} / ${record.huggingface_filename}`
: `${modelSourceMap.ollama_library} / ${record.ollama_library_model_name}`}
</span>
);
}}
/>
<SealColumn
@ -369,7 +375,7 @@ const Models: React.FC<ModelsProps> = ({
}}
/>
<SealColumn
span={5}
span={4}
title={intl.formatMessage({ id: 'common.table.operation' })}
key="operation"
render={(text, record) => {

@ -15,7 +15,10 @@ export const ollamaModelOptions = [
export const modelSourceMap: Record<string, string> = {
huggingface: 'Hugging Face',
ollama_library: 'Ollama Library',
s3: 'S3'
s3: 'S3',
huggingface_value: 'huggingface',
ollama_library_value: 'ollama_library',
s3_value: 's3'
};
export const InstanceStatusMap = {

@ -2,6 +2,8 @@ export interface ListItem {
source: string;
huggingface_repo_id: string;
huggingface_file_name: string;
huggingface_filename: string;
ollama_library_model_name: string;
s3Address: string;
name: string;
description: string;

@ -24,7 +24,7 @@ const Models: React.FC = () => {
const [queryParams, setQueryParams] = useState({
page: 1,
perPage: 10,
query: ''
search: ''
});
// request data
@ -110,7 +110,7 @@ const Models: React.FC = () => {
(e: any) => {
setQueryParams({
...queryParams,
query: e.target.value
search: e.target.value
});
},
[queryParams]

@ -122,13 +122,16 @@ const MessageItem: React.FC<{
<div className="delete-btn">
<Space size={5}>
{message.content && (
<CopyButton text={message.content} size="small"></CopyButton>
<CopyButton
text={message.content}
size="small"
shape="default"
type="default"
fontSize="12px"
></CopyButton>
)}
<Button
type="text"
shape="circle"
size="small"
style={{ color: 'var(--ant-color-primary)' }}
onClick={handleDelete}
icon={<MinusCircleOutlined />}
></Button>

@ -66,7 +66,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const systemList = systemMessage
? [{ role: 'system', content: systemMessage }]
: [];
const code = `import OpenAI from "openai";\nconst openai = new OpenAI({\n"base_url": "/v1-openai", \n "gpustack_api_key": "$\{GPUSTACK_API_KEY}"\n });\n\nasync function main(){\nconst params = ${JSON.stringify(
const code = `import OpenAI from "openai";\nconst openai = new OpenAI();\n\nasync function main(){\nconst params = ${JSON.stringify(
{
...parameters,
messages: [...systemList, ...messageList]
@ -92,7 +92,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const systemList = systemMessage
? [{ role: 'system', content: systemMessage }]
: [];
const code = `from openai import OpenAI\nclient = OpenAI({\n "base_url": "/v1-openai", \n "gpustack_api_key": "$\{GPUSTACK_API_KEY}"\n })\n\ncompletion = client.chat.completions.create(\n${formattedParams} messages=${JSON.stringify([...systemList, ...messageList], null, 2)})\nprint(completion.choices[0].message)`;
const code = `from openai import OpenAI\nclient = OpenAI()\n\ncompletion = client.chat.completions.create(\n${formattedParams} messages=${JSON.stringify([...systemList, ...messageList], null, 2)})\nprint(completion.choices[0].message)`;
setCodeValue(code);
}
formatCode();

@ -4,18 +4,14 @@ import { GPUDeviceItem, ListItem } from '../config/types';
export const WORKERS_API = '/workers';
export const GPU_DEVICES_API = '/gpu-devices';
export async function queryWorkersList(
params: Global.Pagination & { query?: string }
) {
export async function queryWorkersList(params: Global.SearchParams) {
return request<Global.PageResponse<ListItem>>(`${WORKERS_API}`, {
methos: 'GET',
params
});
}
export async function queryGpuDevicesList(
params: Global.Pagination & { query?: string }
) {
export async function queryGpuDevicesList(params: Global.SearchParams) {
return request<Global.PageResponse<GPUDeviceItem>>(`${GPU_DEVICES_API}`, {
methos: 'GET',
params

@ -22,7 +22,7 @@ const GPUList: React.FC = () => {
const [queryParams, setQueryParams] = useState({
page: 1,
perPage: 10,
query: ''
search: ''
});
const handleShowSizeChange = (current: number, size: number) => {
setQueryParams({
@ -67,7 +67,7 @@ const GPUList: React.FC = () => {
const handleNameChange = (e: any) => {
setQueryParams({
...queryParams,
query: e.target.value
search: e.target.value
});
};

@ -27,7 +27,7 @@ const Resources: React.FC = () => {
const [queryParams, setQueryParams] = useState({
page: 1,
perPage: 10,
query: ''
search: ''
});
const fetchData = async () => {
@ -73,7 +73,7 @@ const Resources: React.FC = () => {
const handleNameChange = (e: any) => {
setQueryParams({
...queryParams,
query: e.target.value
search: e.target.value
});
};

@ -3,9 +3,7 @@ import { FormData, ListItem } from '../config/types';
export const USERS_API = '/users';
export async function queryUsersList(
params: Global.Pagination & { query?: string }
) {
export async function queryUsersList(params: Global.SearchParams) {
return request<Global.PageResponse<ListItem>>(`${USERS_API}`, {
methos: 'GET',
params

@ -43,7 +43,7 @@ const Users: React.FC = () => {
const [queryParams, setQueryParams] = useState({
page: 1,
perPage: 10,
query: ''
search: ''
});
const ActionList = [
@ -104,7 +104,7 @@ const Users: React.FC = () => {
const handleNameChange = (e: any) => {
setQueryParams({
...queryParams,
query: e.target.value
search: e.target.value
});
};

@ -3,7 +3,7 @@ import { clearAtomStorage } from '@/atoms/utils';
import { RequestConfig, history } from '@umijs/max';
import { message } from 'antd';
const NoBaseURLAPIs = ['/auth', '/v1-openai'];
const NoBaseURLAPIs = ['/auth', '/v1-openai', '/version'];
export const requestConfig: RequestConfig = {
errorConfig: {

@ -6,3 +6,9 @@ export async function queryCurrentUserState(opts?: Record<string, any>) {
...opts
});
}
export async function queryVersionInfo() {
return request(`/version`, {
method: 'GET'
});
}

Loading…
Cancel
Save