chore: version info modal

This commit is contained in:
jialin
2024-07-12 12:06:17 +08:00
parent c5327d3e56
commit 320db56fbe
38 changed files with 334 additions and 108 deletions
+8 -3
View File
@@ -1,9 +1,13 @@
import { defineConfig } from '@umijs/max'; import { defineConfig } from '@umijs/max';
import theme from './theme';
const CompressionWebpackPlugin = require('compression-webpack-plugin');
import proxy from './proxy'; import proxy from './proxy';
import routes from './routes'; import routes from './routes';
import theme from './theme';
import { getBranchInfo } from './utils';
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const versionInfo = getBranchInfo();
process.env.VERSION = JSON.stringify(versionInfo);
const env = process.env.NODE_ENV; const env = process.env.NODE_ENV;
const isProduction = env === 'production'; const isProduction = env === 'production';
@@ -15,6 +19,7 @@ export default defineConfig({
history: { history: {
type: 'hash' type: 'hash'
}, },
base: process.env.npm_config_base || '/', base: process.env.npm_config_base || '/',
...(isProduction ...(isProduction
? { ? {
+3 -3
View File
@@ -1,16 +1,16 @@
const proxyTableList = ['cli', 'v1', 'auth', 'v1-openai']; const proxyTableList = ['cli', 'v1', 'auth', 'v1-openai', 'version'];
// @ts-ingore // @ts-ingore
export default function createProxyTable(target?: string) { export default function createProxyTable(target?: string) {
const proxyTable = proxyTableList.reduce( const proxyTable = proxyTableList.reduce(
(obj: Record<string, object>, api) => { (obj: Record<string, object>, api) => {
const newTarget = target || 'http://localhost'; const newTarget = target || 'http://localhost';
obj[`/${api}/`] = { obj[`/${api}`] = {
target: newTarget, target: newTarget,
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
ws: true, ws: true,
pathRewrite: (pth: string) => pth.replace(`/^/${api}/`, `/${api}`), pathRewrite: (pth: string) => pth.replace(`/^/${api}`, `/${api}`),
// onProxyRes: (proxyRes: any, req: any, res: any) => { // onProxyRes: (proxyRes: any, req: any, res: any) => {
// if (req.headers.accept === 'text/event-stream') { // if (req.headers.accept === 'text/event-stream') {
// res.writeHead(res.statusCode, { // res.writeHead(res.statusCode, {
+1 -1
View File
@@ -9,5 +9,5 @@ export const getBranchInfo = () => {
.execSync(`git tag --contains ${latestCommit}`) .execSync(`git tag --contains ${latestCommit}`)
.toString() .toString()
.trim(); .trim();
return { version: versionTag, commitId: latestCommit }; return { version: versionTag || 'dev', commitId: latestCommit.slice(0, 7) };
}; };
+7 -16
View File
@@ -2,24 +2,15 @@ import { IApi } from '@umijs/max';
export default (api: IApi) => { export default (api: IApi) => {
api.modifyHTML(($) => { 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 $; return $;
}); });
api.onStart(() => { 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')
// }
// ];
// });
}; };
+20 -1
View File
@@ -1,6 +1,11 @@
import { GPUStackVersionAtom } from '@/atoms/user';
import { setAtomStorage } from '@/atoms/utils';
import { RequestConfig, history } from '@umijs/max'; import { RequestConfig, history } from '@umijs/max';
import { requestConfig } from './request-config'; import { requestConfig } from './request-config';
import { queryCurrentUserState } from './services/profile/apis'; import {
queryCurrentUserState,
queryVersionInfo
} from './services/profile/apis';
const loginPath = '/login'; const loginPath = '/login';
let currentUserInfo: any = {}; let currentUserInfo: any = {};
@@ -28,6 +33,18 @@ export async function getInitialState(): Promise<{
return {} as Global.UserInfo; 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)) { if (![loginPath].includes(location.pathname)) {
const userInfo = await fetchUserInfo(); const userInfo = await fetchUserInfo();
currentUserInfo = { currentUserInfo = {
@@ -43,6 +60,8 @@ export async function getInitialState(): Promise<{
}; };
} }
console.log('app.tsx');
export const request: RequestConfig = { export const request: RequestConfig = {
baseURL: ' /v1', baseURL: ' /v1',
...requestConfig ...requestConfig
+9
View File
@@ -1,7 +1,16 @@
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils';
export const userAtom = atomWithStorage<any>('userInfo', null); export const userAtom = atomWithStorage<any>('userInfo', null);
export const GPUStackVersionAtom = atom<{
version: string;
git_commit: string;
}>({
version: '',
git_commit: ''
});
export const initialPasswordAtom = atomWithStorage<string>( export const initialPasswordAtom = atomWithStorage<string>(
'initialPassword', 'initialPassword',
'' ''
+8 -1
View File
@@ -8,7 +8,14 @@ export const clearAtomStorage = (atom: any) => {
store.set(atom, null); 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) { if (!atom) {
return null; return null;
} }
+17 -12
View File
@@ -5,14 +5,20 @@ import { Button } from 'antd';
type CopyButtonProps = { type CopyButtonProps = {
text: string; text: string;
disabled?: boolean; disabled?: boolean;
fontSize?: string;
type?: 'text' | 'primary' | 'dashed' | 'link' | 'default'; type?: 'text' | 'primary' | 'dashed' | 'link' | 'default';
size?: 'small' | 'middle' | 'large'; size?: 'small' | 'middle' | 'large';
shape?: 'circle' | 'round' | 'default';
style?: React.CSSProperties;
}; };
const CopyButton: React.FC<CopyButtonProps> = ({ const CopyButton: React.FC<CopyButtonProps> = ({
text, text,
disabled, disabled,
type = 'text', type = 'text',
shape = 'circle',
fontSize = '14px',
style,
size = 'middle' size = 'middle'
}) => { }) => {
const { copied, copyToClipboard } = useCopyToClipboard(); const { copied, copyToClipboard } = useCopyToClipboard();
@@ -24,21 +30,20 @@ const CopyButton: React.FC<CopyButtonProps> = ({
return ( return (
<Button <Button
type={type} type={type}
shape="circle" shape={shape}
size={size} size={size}
onClick={handleCopy} onClick={handleCopy}
disabled={!!disabled} disabled={!!disabled}
> icon={
{copied ? ( copied ? (
<CheckCircleFilled <CheckCircleFilled
style={{ color: 'var(--ant-color-success)', fontSize: '14px' }} style={{ color: 'var(--ant-color-success)', fontSize: fontSize }}
/> />
) : ( ) : (
<CopyOutlined <CopyOutlined style={{ fontSize: fontSize, ...style }} />
style={{ color: 'var(--ant-color-primary)', fontSize: '14px' }} )
/> }
)} ></Button>
</Button>
); );
}; };
+8 -2
View File
@@ -22,7 +22,7 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
showHeader = true showHeader = true
}) => { }) => {
const handleChangeLang = (value: string) => { const handleChangeLang = (value: string) => {
onChangeLang && onChangeLang(value); onChangeLang?.(value);
}; };
const renderHeader = () => { const renderHeader = () => {
if (header) { if (header) {
@@ -39,7 +39,13 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
options={langOptions} options={langOptions}
onChange={handleChangeLang} onChange={handleChangeLang}
></Select> ></Select>
<CopyButton text={copyText} /> <CopyButton
text={copyText}
size="small"
style={{
color: 'rgba(255,255,255,.7)'
}}
/>
</div> </div>
); );
} }
+6
View File
@@ -7,4 +7,10 @@
text-align: center; text-align: center;
font-size: var(--font-size-middle); font-size: var(--font-size-middle);
color: var(--color-text-2); color: var(--color-text-2);
.footer-content-left-text {
display: flex;
justify-content: center;
align-items: center;
}
} }
+28 -1
View File
@@ -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 { useIntl } from '@umijs/max';
import { Space } from 'antd'; import { Button, Modal, Space } from 'antd';
import './index.less'; import './index.less';
const Footer: React.FC = () => { const Footer: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const showVersion = () => {
Modal.info({
icon: null,
centered: false,
width: 500,
content: <VersionInfo intl={intl} />
});
};
return ( return (
<div className="footer"> <div className="footer">
<div className="footer-content"> <div className="footer-content">
@@ -14,6 +28,19 @@ const Footer: React.FC = () => {
<span> {new Date().getFullYear()}</span> <span> {new Date().getFullYear()}</span>
<span> {intl.formatMessage({ id: 'settings.company' })}</span> <span> {intl.formatMessage({ id: 'settings.company' })}</span>
</Space> </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> </div>
</div> </div>
+6 -1
View File
@@ -20,12 +20,17 @@ type StatusTagProps = {
text: string; text: string;
message?: string; message?: string;
}; };
type?: 'tag' | 'circle';
download?: { download?: {
percent: number; percent: number;
}; };
}; };
const StatusTag: React.FC<StatusTagProps> = ({ statusValue, download }) => { const StatusTag: React.FC<StatusTagProps> = ({
statusValue,
download,
type = 'tag'
}) => {
const { text, status } = statusValue; const { text, status } = statusValue;
const [statusColor, setStatusColor] = useState<{ const [statusColor, setStatusColor] = useState<{
text: string; text: string;
+41
View File
@@ -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);
}
}
}
+39
View File
@@ -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;
+6
View File
@@ -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/'
};
+2
View File
@@ -21,4 +21,6 @@ declare namespace Global {
require_password_change: boolean; require_password_change: boolean;
id: number; id: number;
} }
type SearchParams = Pagination & { search?: string };
} }
+1 -1
View File
@@ -1 +1 @@
// 应用前置、全局运行的逻辑时 会在这里执行 // 应用前置、全局运行的逻辑时 会在这里执行
+14 -1
View File
@@ -1,10 +1,10 @@
// @ts-nocheck // @ts-nocheck
import { userAtom } from '@/atoms/user'; import { userAtom } from '@/atoms/user';
import VersionInfo from '@/components/version-info';
import { logout } from '@/pages/login/apis'; import { logout } from '@/pages/login/apis';
import { useAccessMarkedRoutes } from '@@/plugin-access'; import { useAccessMarkedRoutes } from '@@/plugin-access';
import { useModel } from '@@/plugin-model'; import { useModel } from '@@/plugin-model';
import { ProLayout } from '@ant-design/pro-components'; import { ProLayout } from '@ant-design/pro-components';
import { import {
Link, Link,
@@ -17,6 +17,7 @@ import {
useNavigate, useNavigate,
type IRoute type IRoute
} from '@umijs/max'; } from '@umijs/max';
import { Modal } from 'antd';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import Exception from './Exception'; import Exception from './Exception';
@@ -102,6 +103,15 @@ export default (props: any) => {
return intl.formatMessage({ id: args.id }); return intl.formatMessage({ id: args.id });
}; };
const showVersion = () => {
Modal.info({
icon: null,
centered: false,
width: 500,
content: <VersionInfo intl={intl} />
});
};
const runtimeConfig = { const runtimeConfig = {
...initialInfo, ...initialInfo,
logout: async (userInfo) => { logout: async (userInfo) => {
@@ -109,6 +119,9 @@ export default (props: any) => {
await logout(); await logout();
navigate(loginPath); navigate(loginPath);
}, },
showVersion: () => {
return showVersion();
},
notFound: <span>404 not found</span> notFound: <span>404 not found</span>
}; };
+28 -8
View File
@@ -1,6 +1,7 @@
// @ts-nocheck // @ts-nocheck
import avatarImg from '@/assets/images/avatar.png'; import avatarImg from '@/assets/images/avatar.png';
import externalLinks from '@/config/external-links';
import langConfigMap from '@/locales/lang-config-map'; import langConfigMap from '@/locales/lang-config-map';
import { import {
DiscordOutlined, DiscordOutlined,
@@ -27,6 +28,7 @@ export function getRightRenderContent(opts: {
intl: any; intl: any;
}) { }) {
const { intl, collapsed, siderWidth } = opts; const { intl, collapsed, siderWidth } = opts;
const allLocals = getAllLocales(); const allLocals = getAllLocales();
if (opts.runtimeConfig.rightRender) { if (opts.runtimeConfig.rightRender) {
return opts.runtimeConfig.rightRender( return opts.runtimeConfig.rightRender(
@@ -73,22 +75,25 @@ export function getRightRenderContent(opts: {
key: 'site', key: 'site',
icon: <HomeOutlined />, icon: <HomeOutlined />,
label: 'GPUStack', label: 'GPUStack',
url: 'https://gpustack.ai/' url: externalLinks.site
}, },
{ {
key: 'github', key: 'github',
icon: <GithubOutlined />, icon: <GithubOutlined />,
label: 'GitHub' label: 'GitHub',
url: externalLinks.github
}, },
{ {
key: 'Discord', key: 'Discord',
icon: <DiscordOutlined />, icon: <DiscordOutlined />,
label: 'Discord' label: 'Discord',
url: externalLinks.discord
}, },
{ {
key: 'docs', key: 'docs',
icon: <ReadOutlined />, icon: <ReadOutlined />,
label: intl.formatMessage({ id: 'common.button.docs' }) label: intl.formatMessage({ id: 'common.button.docs' }),
url: externalLinks.documentation
}, },
{ {
key: 'version', key: 'version',
@@ -118,11 +123,26 @@ export function getRightRenderContent(opts: {
label: ( label: (
<span className="flex flex-center"> <span className="flex flex-center">
{item.icon} {item.icon}
<a className="m-l-8" href="#" target="_blank"> {item.key === 'version' ? (
{item.label} <a className="m-l-8">{item.label}</a>
</a> ) : (
<a
className="m-l-8"
href={item.url}
target="_blank"
rel="noreferrer"
>
{item.label}
</a>
)}
</span> </span>
) ),
onClick() {
if (item.key === 'version') {
// opts.runtimeConfig.showVersion();
opts.runtimeConfig.showVersion();
}
}
})) }))
} }
] ]
+1 -1
View File
@@ -22,7 +22,7 @@ export default {
'playground.params.topp.tips': 'playground.params.topp.tips':
'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered.', 'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered.',
'playground.params.seed.tips': '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': '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.' 'A stop sequence is a predefined or user-specified text string that signals the AI to stop generating further tokens when these sequences appear.'
}; };
+2 -2
View File
@@ -20,9 +20,9 @@ export default {
'playground.params.maxtokens.tips': 'playground.params.maxtokens.tips':
'生成的最大 token 数。输入标记和生成的标记的总长度受模型上下文长度的限制。', '生成的最大 token 数。输入标记和生成的标记的总长度受模型上下文长度的限制。',
'playground.params.topp.tips': 'playground.params.topp.tips':
'通过核采样控制多样性:0.5 表示考虑所有基于概率权重选项的一半。', '通过核采样控制多样性:0.5 表示考虑所有基于概率权重选项的一半。',
'playground.params.seed.tips': 'playground.params.seed.tips':
'指定随机种子以确保确定性采样使用相同种子和参数对于重复请求将产生相同的结果。', '如果指定,我们的系统将尽最大努力进行确定性采样,以便使用相同种子和参数重复请求应返回相同的结果。',
'playground.params.stop.tips': 'playground.params.stop.tips':
'停止序列是一个预定义或用户指定的文本字符串,当这些序列出现时,它会提示 AI 停止生成后续的标记。' '停止序列是一个预定义或用户指定的文本字符串,当这些序列出现时,它会提示 AI 停止生成后续的标记。'
}; };
+1 -3
View File
@@ -3,9 +3,7 @@ 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.SearchParams) {
params: Global.Pagination & { query?: string }
) {
return request<Global.PageResponse<ListItem>>(`${APIS_KEYS_API}`, { return request<Global.PageResponse<ListItem>>(`${APIS_KEYS_API}`, {
method: 'GET', method: 'GET',
params params
+2 -2
View File
@@ -42,7 +42,7 @@ const Models: React.FC = () => {
const [queryParams, setQueryParams] = useState({ const [queryParams, setQueryParams] = useState({
page: 1, page: 1,
perPage: 10, perPage: 10,
query: '' search: ''
}); });
const handleShowSizeChange = (page: number, size: number) => { const handleShowSizeChange = (page: number, size: number) => {
@@ -90,7 +90,7 @@ const Models: React.FC = () => {
const handleNameChange = (e: any) => { const handleNameChange = (e: any) => {
setQueryParams({ setQueryParams({
...queryParams, ...queryParams,
query: e.target.value search: e.target.value
}); });
}; };
+1 -1
View File
@@ -13,7 +13,7 @@ export const MODEL_INSTANCE_API = '/model-instances';
// ===================== Models ===================== // ===================== Models =====================
export async function queryModelsList( export async function queryModelsList(
params: Global.Pagination & { query?: string }, params: Global.SearchParams,
options?: any options?: any
) { ) {
return request<Global.PageResponse<ListItem>>(`${MODELS_API}`, { return request<Global.PageResponse<ListItem>>(`${MODELS_API}`, {
+31 -18
View File
@@ -1,9 +1,13 @@
import DropdownButtons from '@/components/drop-down-buttons'; import DropdownButtons from '@/components/drop-down-buttons';
import RowChildren from '@/components/seal-table/components/row-children'; import RowChildren from '@/components/seal-table/components/row-children';
import StatusTag from '@/components/status-tag'; 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 { useIntl } from '@umijs/max';
import { Col, Row, Space } from 'antd'; import { Col, Row, Space, Tooltip } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; 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) { 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 ( return (
<Space size={16} direction="vertical" style={{ width: '100%' }}> <Space size={16} direction="vertical" style={{ width: '100%' }}>
{_.map(list, (item: ModelInstanceListItem, index: number) => { {_.map(list, (item: ModelInstanceListItem, index: number) => {
return ( return (
<div <div
className="_2Q2Yw"
key={`${item.id}`} key={`${item.id}`}
style={{ borderRadius: 'var(--ant-table-header-border-radius)' }} style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}
> >
<RowChildren key={`${item.id}_row`}> <RowChildren key={`${item.id}_row`}>
<Row style={{ width: '100%' }} align="middle"> <Row style={{ width: '100%' }} align="middle">
<Col span={4}>{item.name}</Col> <Col
<Col span={3}>{item.worker_name || '-'}</Col> span={5}
<Col span={4}> style={{
<span> paddingInline: 'var(--ant-table-cell-padding-inline)'
{item.source === 'huggingface' }}
? item.huggingface_filename >
: item.ollama_library_model_name} <Tooltip title={renderWorkerInfo(item)}>
</span> <span className="m-r-5">{item.name}</span>
<InfoCircleOutlined />
</Tooltip>
</Col> </Col>
<Col span={6}></Col>
<Col span={3}> <Col span={4}>
<span <span
style={{ paddingLeft: '0px' }} style={{ paddingLeft: '56px' }}
className="flex justify-center" className="flex justify-center"
> >
{item.state && ( {item.state && (
@@ -107,11 +120,11 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
</span> </span>
</Col> </Col>
<Col span={5}> <Col span={5}>
<span style={{ paddingLeft: 36 }}> <span style={{ paddingLeft: 38 }}>
{dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')} {dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')}
</span> </span>
</Col> </Col>
<Col span={5}> <Col span={4}>
<div style={{ paddingLeft: 39 }}> <div style={{ paddingLeft: 39 }}>
<DropdownButtons <DropdownButtons
items={setChildActionList(item)} items={setChildActionList(item)}
+11 -5
View File
@@ -337,15 +337,21 @@ const Models: React.FC<ModelsProps> = ({
dataIndex="name" dataIndex="name"
key="name" key="name"
width={400} width={400}
span={6} span={5}
/> />
<SealColumn <SealColumn
title={intl.formatMessage({ id: 'models.form.source' })} title={intl.formatMessage({ id: 'models.form.source' })}
dataIndex="source" dataIndex="source"
key="source" key="source"
span={4} span={6}
render={(text) => { render={(text, record: ListItem) => {
return modelSourceMap[text] || '-'; return (
<span>
{record.source === modelSourceMap.huggingface_value
? `${modelSourceMap.huggingface} / ${record.huggingface_filename}`
: `${modelSourceMap.ollama_library} / ${record.ollama_library_model_name}`}
</span>
);
}} }}
/> />
<SealColumn <SealColumn
@@ -369,7 +375,7 @@ const Models: React.FC<ModelsProps> = ({
}} }}
/> />
<SealColumn <SealColumn
span={5} span={4}
title={intl.formatMessage({ id: 'common.table.operation' })} title={intl.formatMessage({ id: 'common.table.operation' })}
key="operation" key="operation"
render={(text, record) => { render={(text, record) => {
+4 -1
View File
@@ -15,7 +15,10 @@ export const ollamaModelOptions = [
export const modelSourceMap: Record<string, string> = { export const modelSourceMap: Record<string, string> = {
huggingface: 'Hugging Face', huggingface: 'Hugging Face',
ollama_library: 'Ollama Library', ollama_library: 'Ollama Library',
s3: 'S3' s3: 'S3',
huggingface_value: 'huggingface',
ollama_library_value: 'ollama_library',
s3_value: 's3'
}; };
export const InstanceStatusMap = { export const InstanceStatusMap = {
+2
View File
@@ -2,6 +2,8 @@ export interface ListItem {
source: string; source: string;
huggingface_repo_id: string; huggingface_repo_id: string;
huggingface_file_name: string; huggingface_file_name: string;
huggingface_filename: string;
ollama_library_model_name: string;
s3Address: string; s3Address: string;
name: string; name: string;
description: string; description: string;
+2 -2
View File
@@ -24,7 +24,7 @@ const Models: React.FC = () => {
const [queryParams, setQueryParams] = useState({ const [queryParams, setQueryParams] = useState({
page: 1, page: 1,
perPage: 10, perPage: 10,
query: '' search: ''
}); });
// request data // request data
@@ -110,7 +110,7 @@ const Models: React.FC = () => {
(e: any) => { (e: any) => {
setQueryParams({ setQueryParams({
...queryParams, ...queryParams,
query: e.target.value search: e.target.value
}); });
}, },
[queryParams] [queryParams]
@@ -122,13 +122,16 @@ const MessageItem: React.FC<{
<div className="delete-btn"> <div className="delete-btn">
<Space size={5}> <Space size={5}>
{message.content && ( {message.content && (
<CopyButton text={message.content} size="small"></CopyButton> <CopyButton
text={message.content}
size="small"
shape="default"
type="default"
fontSize="12px"
></CopyButton>
)} )}
<Button <Button
type="text"
shape="circle"
size="small" size="small"
style={{ color: 'var(--ant-color-primary)' }}
onClick={handleDelete} onClick={handleDelete}
icon={<MinusCircleOutlined />} icon={<MinusCircleOutlined />}
></Button> ></Button>
@@ -66,7 +66,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const systemList = systemMessage const systemList = systemMessage
? [{ role: 'system', content: 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, ...parameters,
messages: [...systemList, ...messageList] messages: [...systemList, ...messageList]
@@ -92,7 +92,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const systemList = systemMessage const systemList = systemMessage
? [{ role: 'system', content: 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); setCodeValue(code);
} }
formatCode(); formatCode();
+2 -6
View File
@@ -4,18 +4,14 @@ import { GPUDeviceItem, ListItem } from '../config/types';
export const WORKERS_API = '/workers'; export const WORKERS_API = '/workers';
export const GPU_DEVICES_API = '/gpu-devices'; export const GPU_DEVICES_API = '/gpu-devices';
export async function queryWorkersList( export async function queryWorkersList(params: Global.SearchParams) {
params: Global.Pagination & { query?: string }
) {
return request<Global.PageResponse<ListItem>>(`${WORKERS_API}`, { return request<Global.PageResponse<ListItem>>(`${WORKERS_API}`, {
methos: 'GET', methos: 'GET',
params params
}); });
} }
export async function queryGpuDevicesList( export async function queryGpuDevicesList(params: Global.SearchParams) {
params: Global.Pagination & { query?: string }
) {
return request<Global.PageResponse<GPUDeviceItem>>(`${GPU_DEVICES_API}`, { return request<Global.PageResponse<GPUDeviceItem>>(`${GPU_DEVICES_API}`, {
methos: 'GET', methos: 'GET',
params params
+2 -2
View File
@@ -22,7 +22,7 @@ const GPUList: React.FC = () => {
const [queryParams, setQueryParams] = useState({ const [queryParams, setQueryParams] = useState({
page: 1, page: 1,
perPage: 10, perPage: 10,
query: '' search: ''
}); });
const handleShowSizeChange = (current: number, size: number) => { const handleShowSizeChange = (current: number, size: number) => {
setQueryParams({ setQueryParams({
@@ -67,7 +67,7 @@ const GPUList: React.FC = () => {
const handleNameChange = (e: any) => { const handleNameChange = (e: any) => {
setQueryParams({ setQueryParams({
...queryParams, ...queryParams,
query: e.target.value search: e.target.value
}); });
}; };
+2 -2
View File
@@ -27,7 +27,7 @@ const Resources: React.FC = () => {
const [queryParams, setQueryParams] = useState({ const [queryParams, setQueryParams] = useState({
page: 1, page: 1,
perPage: 10, perPage: 10,
query: '' search: ''
}); });
const fetchData = async () => { const fetchData = async () => {
@@ -73,7 +73,7 @@ const Resources: React.FC = () => {
const handleNameChange = (e: any) => { const handleNameChange = (e: any) => {
setQueryParams({ setQueryParams({
...queryParams, ...queryParams,
query: e.target.value search: e.target.value
}); });
}; };
+1 -3
View File
@@ -3,9 +3,7 @@ import { FormData, ListItem } from '../config/types';
export const USERS_API = '/users'; export const USERS_API = '/users';
export async function queryUsersList( export async function queryUsersList(params: Global.SearchParams) {
params: Global.Pagination & { query?: string }
) {
return request<Global.PageResponse<ListItem>>(`${USERS_API}`, { return request<Global.PageResponse<ListItem>>(`${USERS_API}`, {
methos: 'GET', methos: 'GET',
params params
+2 -2
View File
@@ -43,7 +43,7 @@ const Users: React.FC = () => {
const [queryParams, setQueryParams] = useState({ const [queryParams, setQueryParams] = useState({
page: 1, page: 1,
perPage: 10, perPage: 10,
query: '' search: ''
}); });
const ActionList = [ const ActionList = [
@@ -104,7 +104,7 @@ const Users: React.FC = () => {
const handleNameChange = (e: any) => { const handleNameChange = (e: any) => {
setQueryParams({ setQueryParams({
...queryParams, ...queryParams,
query: e.target.value search: e.target.value
}); });
}; };
+1 -1
View File
@@ -3,7 +3,7 @@ import { clearAtomStorage } from '@/atoms/utils';
import { RequestConfig, history } from '@umijs/max'; import { RequestConfig, history } from '@umijs/max';
import { message } from 'antd'; import { message } from 'antd';
const NoBaseURLAPIs = ['/auth', '/v1-openai']; const NoBaseURLAPIs = ['/auth', '/v1-openai', '/version'];
export const requestConfig: RequestConfig = { export const requestConfig: RequestConfig = {
errorConfig: { errorConfig: {
+6
View File
@@ -6,3 +6,9 @@ export async function queryCurrentUserState(opts?: Record<string, any>) {
...opts ...opts
}); });
} }
export async function queryVersionInfo() {
return request(`/version`, {
method: 'GET'
});
}