chore: get start from deploying

This commit is contained in:
jialin
2025-02-24 11:34:40 +08:00
parent 1dd7037aa0
commit bcbefe29e7
11 changed files with 214 additions and 26 deletions
@@ -9,11 +9,21 @@ interface HeaderPrefixProps {
onSelectAll?: (e: any) => void; onSelectAll?: (e: any) => void;
indeterminate?: boolean; indeterminate?: boolean;
selectAll?: boolean; selectAll?: boolean;
hasColumns?: boolean;
} }
const HeaderPrefix: React.FC<HeaderPrefixProps> = (props) => { const HeaderPrefix: React.FC<HeaderPrefixProps> = (props) => {
const { expandable, enableSelection, onSelectAll, indeterminate, selectAll } = const {
props; hasColumns,
expandable,
enableSelection,
onSelectAll,
indeterminate,
selectAll
} = props;
if (!hasColumns) {
return null;
}
if (expandable && enableSelection) { if (expandable && enableSelection) {
return ( return (
<div className="header-row-prefix-wrapper"> <div className="header-row-prefix-wrapper">
@@ -18,6 +18,7 @@ interface TableBodyProps {
loadChildren?: any; loadChildren?: any;
loadChildrenAPI?: any; loadChildrenAPI?: any;
onCell?: any; onCell?: any;
empty?: React.ReactNode;
} }
const TableBody: React.FC<TableBodyProps> = ({ const TableBody: React.FC<TableBodyProps> = ({
@@ -34,12 +35,13 @@ const TableBody: React.FC<TableBodyProps> = ({
loadChildren, loadChildren,
loadChildrenAPI, loadChildrenAPI,
columns, columns,
onCell onCell,
empty
}) => { }) => {
if (!dataSource.length) { if (!dataSource.length) {
return ( return (
<div className="empty-wrapper"> <div className="empty-wrapper">
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> {empty || <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
</div> </div>
); );
} }
+3
View File
@@ -26,6 +26,7 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
watchChildren, watchChildren,
rowSelection, rowSelection,
pagination, pagination,
empty,
renderChildren, renderChildren,
loadChildren, loadChildren,
loadChildrenAPI loadChildrenAPI
@@ -116,11 +117,13 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
onSelectAll={handleSelectAllChange} onSelectAll={handleSelectAllChange}
expandable={expandable} expandable={expandable}
enableSelection={rowSelection?.enableSelection} enableSelection={rowSelection?.enableSelection}
hasColumns={parsedColumns.length > 0}
></HeaderPrefix> ></HeaderPrefix>
<Header onSort={onSort} columns={parsedColumns}></Header> <Header onSort={onSort} columns={parsedColumns}></Header>
</div> </div>
<Spin spinning={loading}> <Spin spinning={loading}>
<TableBody <TableBody
empty={empty}
dataSource={props.dataSource} dataSource={props.dataSource}
columns={parsedColumns} columns={parsedColumns}
rowSelection={rowSelection} rowSelection={rowSelection}
+97 -6
View File
@@ -18,6 +18,11 @@ import {
ListItem as WorkerListItem ListItem as WorkerListItem
} from '@/pages/resources/config/types'; } from '@/pages/resources/config/types';
import { handleBatchRequest } from '@/utils'; import { handleBatchRequest } from '@/utils';
import {
IS_FIRST_LOGIN,
readState,
writeState
} from '@/utils/localstore/index';
import { import {
DeleteOutlined, DeleteOutlined,
DownOutlined, DownOutlined,
@@ -28,7 +33,17 @@ import {
} from '@ant-design/icons'; } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { Access, useAccess, useIntl, useNavigate } from '@umijs/max'; import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
import { Button, Dropdown, Input, Select, Space, Tooltip, message } from 'antd'; import {
Button,
Dropdown,
Empty,
Input,
Select,
Space,
Tooltip,
Typography,
message
} from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import _ from 'lodash'; import _ from 'lodash';
@@ -45,6 +60,7 @@ import {
} from '../apis'; } from '../apis';
import { import {
InstanceRealtimeLogStatus, InstanceRealtimeLogStatus,
backendOptionsMap,
getSourceRepoConfigValue, getSourceRepoConfigValue,
modelCategories, modelCategories,
modelCategoriesMap, modelCategoriesMap,
@@ -76,6 +92,7 @@ interface ModelsProps {
deleteIds?: number[]; deleteIds?: number[];
gpuDeviceList: GPUDeviceItem[]; gpuDeviceList: GPUDeviceItem[];
workerList: WorkerListItem[]; workerList: WorkerListItem[];
catalogList?: any[];
dataSource: ListItem[]; dataSource: ListItem[];
loading: boolean; loading: boolean;
loadend: boolean; loadend: boolean;
@@ -158,12 +175,15 @@ const Models: React.FC<ModelsProps> = ({
dataSource, dataSource,
gpuDeviceList, gpuDeviceList,
workerList, workerList,
catalogList,
queryParams, queryParams,
loading, loading,
loadend, loadend,
total total
}) => { }) => {
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [isFirstLogin, setIsFirstLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom); const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom);
const access = useAccess(); const access = useAccess();
const intl = useIntl(); const intl = useIntl();
@@ -199,6 +219,17 @@ const Models: React.FC<ModelsProps> = ({
}); });
const modalRef = useRef<any>(null); const modalRef = useRef<any>(null);
useEffect(() => {
if (!catalogList?.length) {
return;
}
const getFirstLoginState = async () => {
const is_first_login = await readState(IS_FIRST_LOGIN);
setIsFirstLogin(is_first_login);
};
getFirstLoginState();
}, [catalogList?.length]);
useHotkeys( useHotkeys(
HotKeys.NEW1.join(','), HotKeys.NEW1.join(','),
() => { () => {
@@ -667,8 +698,11 @@ const Models: React.FC<ModelsProps> = ({
}); });
}; };
const columns: SealColumnProps[] = useMemo( const columns: SealColumnProps[] = useMemo(() => {
() => [ if (isFirstLogin) {
return [];
}
return [
{ {
title: intl.formatMessage({ id: 'common.table.name' }), title: intl.formatMessage({ id: 'common.table.name' }),
dataIndex: 'name', dataIndex: 'name',
@@ -744,13 +778,67 @@ const Models: React.FC<ModelsProps> = ({
/> />
) )
} }
], ];
[sortOrder, intl] }, [sortOrder, intl, isFirstLogin]);
);
const handleOnClick = async () => {
if (isLoading) {
return;
}
const data = catalogList?.find(
(item) => item.backend === backendOptionsMap.llamaBox && item.size === 1.5
);
console.log('catalogList=======', data);
try {
if (data) {
setIsLoading(true);
const modelData = await createModel({
data: data
});
writeState(IS_FIRST_LOGIN, false);
setIsFirstLogin(false);
setTimeout(() => {
updateExpandedRowKeys([modelData.id]);
}, 300);
message.success(intl.formatMessage({ id: 'common.message.success' }));
handleSearch?.();
}
} catch (error) {
// ingore
} finally {
setIsLoading(false);
}
};
const renderEmpty = useMemo(() => {
if (dataSource.length || !isFirstLogin) {
return null;
}
return (
<div
className="flex-column justify-center flex-center"
style={{ height: 300 }}
>
<Empty description=""></Empty>
<Typography.Title level={4} style={{ marginBottom: 30 }}>
No Models yet!
</Typography.Title>
<div>
<Button type="primary" onClick={handleOnClick} loading={isLoading}>
<span style={{ fontSize: 13 }}>Get started with</span>
<span style={{ fontSize: 14, fontWeight: 700 }}>
DeepSeek-R1-Distill-Qwen-1.5B
</span>
</Button>
</div>
</div>
);
}, [dataSource.length, isFirstLogin, isLoading]);
return ( return (
<> <>
<PageContainer <PageContainer
className="models-page-container"
ghost ghost
header={{ header={{
title: intl.formatMessage({ id: 'models.title' }), title: intl.formatMessage({ id: 'models.title' }),
@@ -760,6 +848,7 @@ const Models: React.FC<ModelsProps> = ({
> >
<PageTools <PageTools
marginBottom={22} marginBottom={22}
style={{ display: isFirstLogin ? 'none' : 'flex' }}
left={ left={
<Space> <Space>
<Input <Input
@@ -856,7 +945,9 @@ const Models: React.FC<ModelsProps> = ({
</Space> </Space>
} }
></PageTools> ></PageTools>
<SealTable <SealTable
empty={renderEmpty}
columns={columns} columns={columns}
dataSource={dataSource} dataSource={dataSource}
rowSelection={rowSelection} rowSelection={rowSelection}
+32
View File
@@ -6,12 +6,15 @@ import {
GPUDeviceItem, GPUDeviceItem,
ListItem as WokerListItem ListItem as WokerListItem
} from '@/pages/resources/config/types'; } from '@/pages/resources/config/types';
import { IS_FIRST_LOGIN, readState } from '@/utils/localstore';
import _ from 'lodash'; import _ from 'lodash';
import qs from 'query-string'; import qs from 'query-string';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { import {
MODELS_API, MODELS_API,
MODEL_INSTANCE_API, MODEL_INSTANCE_API,
queryCatalogItemSpec,
queryCatalogList,
queryModelsInstances, queryModelsInstances,
queryModelsList queryModelsList
} from './apis'; } from './apis';
@@ -37,6 +40,7 @@ const Models: React.FC = () => {
total: 0 total: 0
}); });
const [catalogList, setCatalogList] = useState<any[]>([]);
const [gpuDeviceList, setGpuDeviceList] = useState<GPUDeviceItem[]>([]); const [gpuDeviceList, setGpuDeviceList] = useState<GPUDeviceItem[]>([]);
const [workerList, setWorkerList] = useState<WokerListItem[]>([]); const [workerList, setWorkerList] = useState<WokerListItem[]>([]);
const chunkRequedtRef = useRef<any>(); const chunkRequedtRef = useRef<any>();
@@ -243,6 +247,33 @@ const Models: React.FC = () => {
}; };
}, [queryParams]); }, [queryParams]);
useEffect(() => {
const getCataLogList = async () => {
const isFirstLogin = readState(IS_FIRST_LOGIN);
if (!isFirstLogin) {
return;
}
try {
const res: any = await queryCatalogList({
search: 'DeepSeek R1',
page: 1
});
const name = _.toLower(res?.items[0]?.name).replace(/\s/g, '-') || '';
const catalogSpecs: any = await queryCatalogItemSpec({
id: res?.items[0]?.id
});
const list = catalogSpecs?.items?.map((item: any) => {
item.name = name;
return item;
});
setCatalogList(list || []);
} catch (error) {
// ignore
}
};
getCataLogList();
}, []);
useEffect(() => { useEffect(() => {
createModelsChunkRequest(); createModelsChunkRequest();
}, [createModelsChunkRequest]); }, [createModelsChunkRequest]);
@@ -311,6 +342,7 @@ const Models: React.FC = () => {
deleteIds={dataSource.deletedIds} deleteIds={dataSource.deletedIds}
gpuDeviceList={gpuDeviceList} gpuDeviceList={gpuDeviceList}
workerList={workerList} workerList={workerList}
catalogList={catalogList}
></TableList> ></TableList>
</TableContext.Provider> </TableContext.Provider>
); );
+6 -5
View File
@@ -8,13 +8,14 @@ import {
removeRememberMe removeRememberMe
} from '@/utils/localstore/index'; } from '@/utils/localstore/index';
import { LockOutlined, UserOutlined } from '@ant-design/icons'; import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { history, useIntl, useModel } from '@umijs/max'; import { useIntl, useModel } from '@umijs/max';
import { Button, Checkbox, Form } from 'antd'; import { Button, Checkbox, Form } from 'antd';
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { memo, useEffect, useMemo } from 'react'; import { memo, useEffect, useMemo } from 'react';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
import { login } from '../apis'; import { login } from '../apis';
import { checkDefaultPage } from '../utils';
const REMEMBER_ME_KEY = 'r_m'; const REMEMBER_ME_KEY = 'r_m';
const CRYPT_TEXT = 'seal'; const CRYPT_TEXT = 'seal';
@@ -50,11 +51,10 @@ const LoginForm = () => {
); );
}, [intl]); }, [intl]);
const gotoDefaultPage = (userInfo: any) => { const gotoDefaultPage = async (userInfo: any) => {
const pathname = checkDefaultPage(userInfo, true);
userInfo && userInfo?.is_admin ? '/dashboard' : '/playground';
history.push(pathname);
}; };
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
const userInfo = await initialState?.fetchUserInfo?.(); const userInfo = await initialState?.fetchUserInfo?.();
@@ -108,6 +108,7 @@ const LoginForm = () => {
username: values.username, username: values.username,
password: values.password password: values.password
}); });
const userInfo = await fetchUserInfo(); const userInfo = await fetchUserInfo();
setUserInfo(userInfo); setUserInfo(userInfo);
if (values.autoLogin) { if (values.autoLogin) {
+4 -5
View File
@@ -2,11 +2,12 @@ import { initialPasswordAtom, userAtom } from '@/atoms/user';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import { PasswordReg } from '@/config'; import { PasswordReg } from '@/config';
import { GlobalOutlined, LockOutlined } from '@ant-design/icons'; import { GlobalOutlined, LockOutlined } from '@ant-design/icons';
import { SelectLang, history, useIntl } from '@umijs/max'; import { SelectLang, useIntl } from '@umijs/max';
import { Button, Form, message } from 'antd'; import { Button, Form, message } from 'antd';
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { updatePassword } from '../apis'; import { updatePassword } from '../apis';
import { checkDefaultPage } from '../utils';
const CRYPT_TEXT = 'seal'; const CRYPT_TEXT = 'seal';
@@ -16,10 +17,8 @@ const PasswordForm: React.FC = () => {
const [userInfo, setUserInfo] = useAtom(userAtom); const [userInfo, setUserInfo] = useAtom(userAtom);
const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom); const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom);
const gotoDefaultPage = (userInfo: any) => { const gotoDefaultPage = async (userInfo: any) => {
const pathname = checkDefaultPage(userInfo, false);
userInfo && userInfo?.is_admin ? '/dashboard' : '/playground';
history.push(pathname);
}; };
const decryptPassword = (password: string) => { const decryptPassword = (password: string) => {
+5 -5
View File
@@ -1,29 +1,29 @@
import { userAtom } from '@/atoms/user'; import { userAtom } from '@/atoms/user';
import Footer from '@/components/footer'; import Footer from '@/components/footer';
import { history, useModel } from '@umijs/max'; import { useModel } from '@umijs/max';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useEffect } from 'react'; import { useEffect } from 'react';
import LoginForm from './components/login-form'; import LoginForm from './components/login-form';
import PasswordForm from './components/password-form'; import PasswordForm from './components/password-form';
import styles from './components/styles.less'; import styles from './components/styles.less';
import { checkDefaultPage } from './utils';
const Login = () => { const Login = () => {
const [userInfo, setUserInfo] = useAtom(userAtom); const [userInfo, setUserInfo] = useAtom(userAtom);
const { initialState, setInitialState } = useModel('@@initialState') || {}; const { initialState, setInitialState } = useModel('@@initialState') || {};
const gotoDefaultPage = (userInfo: any) => { const gotoDefaultPage = async (userInfo: any) => {
if (!userInfo || userInfo?.require_password_change) { if (!userInfo || userInfo?.require_password_change) {
return; return;
} }
checkDefaultPage(userInfo, true);
if (!initialState?.currentUser) { if (!initialState?.currentUser) {
setInitialState((s: any) => ({ setInitialState((s: any) => ({
...s, ...s,
currentUser: userInfo currentUser: userInfo
})); }));
} }
const pathname = userInfo?.is_admin ? '/dashboard' : '/playground';
history.push(pathname, { replace: true });
}; };
useEffect(() => { useEffect(() => {
gotoDefaultPage(userInfo); gotoDefaultPage(userInfo);
+24
View File
@@ -0,0 +1,24 @@
import { isOnline } from '@/utils';
import {
IS_FIRST_LOGIN,
readState,
writeState
} from '@/utils/localstore/index';
import { history } from '@umijs/max';
export const checkDefaultPage = async (userInfo: any, replace: boolean) => {
const isFirstLogin = await readState(IS_FIRST_LOGIN);
if (isFirstLogin === null && isOnline()) {
writeState(IS_FIRST_LOGIN, true);
const pathname =
userInfo && userInfo?.is_admin ? '/models/list' : '/playground';
history.push(pathname);
return;
}
await writeState(IS_FIRST_LOGIN, false);
const pathname = userInfo?.is_admin ? '/dashboard' : '/playground';
history.push(pathname, { replace: replace });
};
+5
View File
@@ -192,3 +192,8 @@ export const base64ToFile = (base64String: string, fileName: string) => {
return null; return null;
} }
}; };
// check onlinestatus
export const isOnline = () => {
return window.navigator.onLine;
};
+22 -1
View File
@@ -1,5 +1,7 @@
import localStore from './store'; import localStore from './store';
const IS_FIRST_LOGIN = 'is_first_login';
const store = localStore.createInstance({ name: '_xWXJKJ_S1Sna_' }); const store = localStore.createInstance({ name: '_xWXJKJ_S1Sna_' });
const rememberMe = (key: string, data: any) => { const rememberMe = (key: string, data: any) => {
@@ -20,6 +22,25 @@ const removeRememberMe = (key: string) => {
} }
}; };
export { getRememberMe, rememberMe, removeRememberMe }; const readState = (key: string) => {
if (store) {
return store.getItem(key);
}
};
const writeState = (key: string, data: any) => {
if (store) {
store.setItem(key, data);
}
};
export {
IS_FIRST_LOGIN,
getRememberMe,
readState,
rememberMe,
removeRememberMe,
writeState
};
export default store; export default store;