feat: 前端界面更新

This commit is contained in:
alwynzhou 2021-10-12 10:17:37 +08:00
parent 4a1182a69b
commit 40f2336cf4
16 changed files with 429 additions and 149 deletions

View File

@ -23,11 +23,11 @@ module.exports = {
}),
);
if (process.env.NODE_ENV !== 'production') {
config.plugins.push(
new BundleAnalyzerPlugin()
)
}
// if (process.env.NODE_ENV !== 'production') {
// config.plugins.push(
// new BundleAnalyzerPlugin()
// )
// }
return config;
},

View File

@ -7,27 +7,43 @@ const job_template_modelview = (): Promise<any> => {
return ajax.get('/job_template_modelview/api/');
};
// 获取项目组
const project_all = (): Promise<any> => {
return ajax.get('/project_modelview/api/');
};
// 获取 org 项目组
const project_modelview = (): Promise<any> => {
return ajax.get('/project_modelview/org/api/');
};
// 新增流水线
const pipeline_modelview_add = (data: IPipelineAdd): Promise<any> => {
return ajax.post({ url: '/pipeline_modelview/api/', data });
};
// 获取流水线列表
const pipeline_modelview_demo = (): Promise<any> => {
return ajax.get('/pipeline_modelview/demo/list/');
};
// 获取流水线列表
const pipeline_modelview_list = (): Promise<any> => {
return ajax.get('/pipeline_modelview/my/list/');
};
const pipeline_modelview_all = (filters: string): Promise<any> => {
return ajax.get(`/pipeline_modelview/api/?form_data=${filters}`);
};
// 获取流水线信息
const pipeline_modelview_detail = (pipelineId: number | string): Promise<any> => {
return ajax.get(`/pipeline_modelview/api/${pipelineId}`);
};
// 删除指定流水线
const pipeline_modelview_delete = (pipelineId: number | string): Promise<any> => {
return ajax.delete(`/pipeline_modelview/api/${pipelineId}`);
};
// 流水线编辑提交
const pipeline_modelview_edit = (pipelineId: number | string, data: IPipelineEdit): Promise<any> => {
return ajax.put({
@ -96,10 +112,14 @@ const task_modelview_edit = (pipelineId: string | number, taskId: string | numbe
const api = {
job_template_modelview,
project_all,
project_modelview,
pipeline_modelview_add,
pipeline_modelview_demo,
pipeline_modelview_list,
pipeline_modelview_all,
pipeline_modelview_detail,
pipeline_modelview_delete,
pipeline_modelview_edit,
pipeline_modelview_run,
pipeline_modelview_copy,

View File

@ -14,7 +14,7 @@ const DataSet: React.FC<NodeProps> = props => {
<div className={style.nodeConentTitleBar}>
<div className={style.nodeIconWrapper}>
<Icon iconName="Database" className={style.nodeIcon}></Icon>
<div className={style.nodeTitle}>{props?.data?.name}</div>
<div className={style.nodeTitle}>{props?.data?.label}</div>
</div>
</div>
</div>

View File

@ -151,10 +151,10 @@ const Model: React.FC<ModelProps> = props => {
}),
);
}
if (taskChanged?.name) {
if (taskChanged?.label) {
const res = elements.map(ele => {
if (ele.id === props.model.id) {
const data = { ...ele.data, ...{ name: taskChanged.name } };
const data = { ...ele.data, ...{ label: taskChanged.label } };
return { ...ele, ...{ data } };
}
return ele;
@ -223,24 +223,24 @@ const Model: React.FC<ModelProps> = props => {
<div className={style.splitLine}></div>
<TextField
label="名称"
description="英文名(字母、数字、- 组成)最长50个字符"
onChange={(event: FormEvent, value?: string) => {
handleOnChange('name', value ? value : '');
}}
value={task?.name || ''}
disabled
/>
<div className={style.splitLine}></div>
<TextField
label="标签"
description="中文名"
description="节点标签"
required
onChange={(event: FormEvent, value?: string) => {
handleOnChange('label', value ? value : '');
}}
value={task?.label || ''}
/>
{/* <div className={style.splitLine}></div>
<TextField
<div className={style.splitLine}></div>
{/* <TextField
label="挂载目录"
description="外部挂载,格式:$pvc_name1(pvc):/$container_path1,$hostpath1(hostpath):/$container_path2,注意pvc会自动挂载对应目录下的个人rtx子目录"
onChange={(event: FormEvent, value?: string) => {
@ -276,8 +276,8 @@ const Model: React.FC<ModelProps> = props => {
handleOnChange('node_selector', value ? value : '');
}}
value={task?.node_selector || ''}
/> */}
<div className={style.splitLine}></div>
/>
<div className={style.splitLine}></div> */}
<TextField
label="内存申请"
description="内存的资源使用限制示例1G10G 最大10G如需更多联系管理员"

View File

@ -142,6 +142,7 @@ const Setting: React.FC = () => {
handleOnChange('name', value ? value : '');
}}
value={current?.name || ''}
disabled
/>
<div className={style.splitLine}></div>
<TextField
@ -211,7 +212,7 @@ const Setting: React.FC = () => {
]}
selectedKey={`${current.depends_on_past}`}
onChange={(event: FormEvent, option?: IDropdownOption) => {
handleOnChange('depends_on_past', option?.data || true);
handleOnChange('depends_on_past', option?.data);
}}
/>
<div className={style.splitLine}></div>

View File

@ -61,15 +61,15 @@ const EditorBody: React.FC = () => {
if (pipelineId) {
dispatch(updateLoading(true));
// http://kubeflow.music.woa.com/job_template_modelview/api/_info
const taskName = `${modelInfo.name
.replace(/\.|[\u4e00-\u9fa5]/g, '')
.replace(/_|\s/g, '-')}-${Date.now()}`.substring(0, 49);
const taskName = `${
modelInfo.name.replace(/\.|[\u4e00-\u9fa5]/g, '').replace(/_|\s/g, '-') || 'task'
}-${Date.now()}`.substring(0, 49);
api
.task_modelview_add(pipelineId, {
job_template: modelInfo.id,
pipeline: +pipelineId,
name: taskName,
label: `新建 ${modelInfo.name} task`,
label: `新建 ${modelInfo.name} 任务`,
volume_mount: 'kubeflow-user-workspace(pvc):/mnt,kubeflow-archives(pvc):/archives',
image_pull_policy: 'Always',
working_dir: '',
@ -90,6 +90,7 @@ const EditorBody: React.FC = () => {
position,
data: {
name: taskName,
label: `新建 ${modelInfo.name} 任务`,
},
};
dispatch(updateEditing(true));
@ -166,6 +167,9 @@ const EditorBody: React.FC = () => {
if (isEdge(ele) && !ele?.arrowHeadType) {
return Object.assign(ele, { arrowHeadType: 'arrow' });
}
if (isNode(ele) && !ele.data.label) {
ele.data.label = ele.data.name;
}
return ele;
});

View File

@ -1,13 +1,12 @@
import React from 'react';
import { Stack, TooltipHost, IconButton, PrimaryButton, DefaultButton } from '@fluentui/react';
import moment from 'moment';
import ErrorTips from '../ErrorTips';
import { useAppSelector, useAppDispatch } from '../../../../models/hooks';
import { updateErrMsg } from '../../../../models/app';
import { selectPipelineId, selectInfo, savePipeline } from '../../../../models/pipeline';
import { saveTaskList } from '../../../../models/task';
import { toggle } from '../../../../models/setting';
import api from '../../../../api';
import { useAppSelector, useAppDispatch } from '@src/models/hooks';
import { updateErrMsg, selectUserName } from '@src/models/app';
import { selectPipelineId, selectInfo, savePipeline } from '@src/models/pipeline';
import { saveTaskList } from '@src/models/task';
import { toggle } from '@src/models/setting';
import api from '@src/api';
import style from './style';
const { Item } = Stack;
@ -15,13 +14,14 @@ const EditorHead: React.FC = () => {
const dispatch = useAppDispatch();
const pipelineId = useAppSelector(selectPipelineId);
const info = useAppSelector(selectInfo);
const userName = useAppSelector(selectUserName);
// 新建流水线
const handleNewPipeline = () => {
api
.pipeline_modelview_add({
describe: '新建流水线',
name: `pipeline-created-on-${moment().format('MM-DD-HHmmSSSS')}`,
describe: `新建流水线-${Date.now()}`,
name: `${userName}-pipeline-${Date.now()}`,
node_selector: 'cpu=true,train=true',
schedule_type: 'once',
image_pull_policy: 'Always',
@ -56,7 +56,7 @@ const EditorHead: React.FC = () => {
<Stack horizontal>
<Item>
{info.name ? (
<div className={style.headNameStyle}>{info.name}</div>
<div className={style.headNameStyle}>{info.describe}</div>
) : (
<PrimaryButton
styles={{

View File

@ -149,6 +149,21 @@ const EditorTool: React.FC = () => {
dispatch(updateElements(removeElements(selectedElements, elements)));
},
},
{
buttonStyles: style.commonButton,
iconOnly: true,
key: 'monitor',
iconProps: {
iconName: 'NetworkTower',
styles: style.commonIcon,
},
text: '监控',
onClick: () => {
if (pipeline?.id) {
window.open(`${window.location.origin}/pipeline_modelview/web/monitoring/${pipelineId}`);
}
},
},
];
// task 发生编辑行为时状态变更

View File

@ -0,0 +1,9 @@
import React from 'react';
import { Stack } from '@fluentui/react';
import style from './style';
const Pipeline: React.FC = () => {
return <Stack className={style.pipelineList}></Stack>;
};
export default Pipeline;

View File

@ -0,0 +1,8 @@
import { mergeStyles } from '@fluentui/merge-styles';
const pipelineList = mergeStyles({
marginTop: '16px !important',
padding: '0 10px 24px',
});
export default { pipelineList };

View File

@ -1,9 +1,8 @@
import React, { useEffect, useState } from 'react';
import { Icon, Stack, Modal } from '@fluentui/react';
import api from '@src/api';
import { updateErrMsg } from '@src/models/app';
import { useAppDispatch } from '@src/models/hooks';
import moment from 'moment';
import { updateErrMsg, selectUserName } from '@src/models/app';
import { useAppDispatch, useAppSelector } from '@src/models/hooks';
import Player from 'xgplayer';
import style from './style';
@ -19,12 +18,13 @@ const Section: React.FC<ISectionProps> = props => {
const [showModal, setShowModal] = useState<boolean>(false);
const [curVideo, setCurVideo] = useState<string>('');
const [curPlayer, setCurPlayer] = useState<Player | undefined>(undefined);
const userName = useAppSelector(selectUserName);
const handleNewPipeline = () => {
api
.pipeline_modelview_add({
describe: '新建流水线',
name: `pipeline-created-on-${moment().format('MM-DD-HHmmSSSS')}`,
describe: `new-pipeline-${Date.now()}`,
name: `${userName}-pipeline-${Date.now()}`,
node_selector: 'cpu=true,train=true',
schedule_type: 'once',
image_pull_policy: 'Always',
@ -58,7 +58,19 @@ const Section: React.FC<ISectionProps> = props => {
const handleClick = (item: any) => {
switch (item.type) {
case 'link':
window.location.href = item.url;
if (window.self === window.top) {
window.location.href = `${window.location.origin}${location.pathname}?pipeline_id=${item?.id}`;
} else {
window.parent.postMessage(
{
type: 'link',
message: {
pipelineId: item?.id,
},
},
`${window.location.origin}`,
);
}
break;
case 'outside':
window.open(item.url, '_blank');
@ -129,6 +141,7 @@ const Section: React.FC<ISectionProps> = props => {
) : null}
{/* sample card item */}
{props.data.map((item: any, key: number) => {
if (!item.img) return null;
return (
<div
className={style.sampleCardStyle}

View File

@ -6,11 +6,12 @@ const sectionStyles = mergeStyles({
borderBottom: '1px solid #dadada',
selectors: {
'.subtitle': {
marginBottom: 8,
marginBottom: 20,
height: 24,
lineHeight: '22px',
fontSize: 16,
fontWeight: 600,
lineHeight: '1.1',
fontSize: 20,
fontWeight: 'bold',
fontFamily: '"Raleway","Helvetica Neue",Helvetica,Arial,sans-serif;',
},
'.expand-button': {
color: '#005cd2',
@ -76,7 +77,6 @@ const sampleImgStyles = mergeStyles({
});
const cardTitleStyles = mergeStyles({
lineHeight: '20px',
marginTop: 16,
textAlign: 'center',
cursor: 'pointer',

View File

@ -1,12 +1,17 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import cookie from 'cookie';
import { RootState } from '../store';
const { myapp_username, t_uid, km_uid } = cookie.parse(document.cookie);
export interface AppState {
errMsg: any;
userName: string;
}
const initialState: AppState = {
errMsg: null,
userName: myapp_username || t_uid || km_uid,
};
const AppSlice = createSlice({
@ -22,5 +27,6 @@ const AppSlice = createSlice({
export const { updateErrMsg } = AppSlice.actions;
export const selectErrMsg = (state: RootState): AppState['errMsg'] => state.app.errMsg;
export const selectUserName = (state: RootState): AppState['userName'] => state.app.userName;
export default AppSlice.reducer;

View File

@ -12,6 +12,7 @@ export interface PipelineState {
changed: any;
editing: boolean;
pipelineList: any[];
all: any[] | undefined;
}
const initialState: PipelineState = {
@ -21,6 +22,7 @@ const initialState: PipelineState = {
changed: {},
editing: false,
pipelineList: [],
all: undefined,
};
const pipelineSlice = createSlice({
@ -45,11 +47,21 @@ const pipelineSlice = createSlice({
updatePipelineList: (state, action: PayloadAction<any[]>) => {
state.pipelineList = action.payload;
},
updateAll: (state, action: PayloadAction<any[] | undefined>) => {
state.all = action.payload;
},
},
});
export const { updatePipelineId, updateInfo, updateSaved, updateChanged, updateEditing, updatePipelineList } =
pipelineSlice.actions;
export const {
updatePipelineId,
updateInfo,
updateSaved,
updateChanged,
updateEditing,
updatePipelineList,
updateAll,
} = pipelineSlice.actions;
export const selectPipelineId = (state: RootState): PipelineState['pipelineId'] => state.pipeline.pipelineId;
export const selectInfo = (state: RootState): PipelineState['info'] => state.pipeline.info;
@ -57,23 +69,58 @@ export const selectSaved = (state: RootState): PipelineState['saved'] => state.p
export const selectChanged = (state: RootState): PipelineState['changed'] => state.pipeline.changed;
export const selectEditing = (state: RootState): PipelineState['editing'] => state.pipeline.editing;
export const selectPipelineList = (state: RootState): PipelineState['pipelineList'] => state.pipeline.pipelineList;
export const selectAll = (state: RootState): PipelineState['all'] => state.pipeline.all;
export const getPipelineList = (): AppThunk => dispatch => {
api.pipeline_modelview_list().then(res => {
if (res?.status === 0) {
const list = res.result.map((item: any) => {
const list: any[] = res.result.map((item: any) => {
return {
id: item.id,
name: item.name,
describe: item.describe,
changed_on: item.changed_on,
project_id: item.project_id,
};
});
dispatch(updatePipelineList(list));
dispatch(
updatePipelineList(list.sort((a, b) => new Date(b.changed_on).getTime() - new Date(a.changed_on).getTime())),
);
}
});
};
export const getAllList =
(data: unknown): AppThunk =>
dispatch => {
dispatch(updateAll(undefined));
api
.pipeline_modelview_all(JSON.stringify(data))
.then(res => {
if (res?.status === 0) {
const list: any[] = res.result.map((item: any) => {
return {
id: item.id,
name: item.name,
describe: item.describe,
changed_on: item.changed_on,
project_id: item.project.id,
};
});
dispatch(updateAll(list.sort((a, b) => new Date(b.changed_on).getTime() - new Date(a.changed_on).getTime())));
}
})
.catch(err => {
if (err.response) {
dispatch(
updateErrMsg({
msg: err?.response?.data?.message || '获取列表失败',
}),
);
}
});
};
// 获取当前流水线信息
export const getPipeline = (): AppThunk => (dispatch, getState) => {
const state = getState();

View File

@ -1,9 +1,28 @@
/* eslint-disable react/display-name */
import React, { useEffect, useState } from 'react';
import { DetailsList, DetailsListLayoutMode, IStackStyles, SelectionMode, Stack, IColumn } from '@fluentui/react';
import {
DetailsList,
DetailsListLayoutMode,
ShimmeredDetailsList,
IStackStyles,
SelectionMode,
Stack,
IColumn,
TooltipHost,
IconButton,
PrimaryButton,
Pivot,
PivotItem,
Dropdown,
IDropdownOption,
IDropdownStyles,
Text,
} from '@fluentui/react';
import api from '@src/api';
import Section from '@src/components/Home/Section';
import { pipelineDemo, videoDemo } from '@src/static/home';
import { videoDemo } from '@src/static/home';
import { useAppDispatch, useAppSelector } from '@src/models/hooks';
import { getPipelineList, selectPipelineList } from '@src/models/pipeline';
import { getPipelineList, selectPipelineList, selectAll, getAllList } from '@src/models/pipeline';
const homeContainerStyle: IStackStyles = {
root: {
@ -13,11 +32,39 @@ const homeContainerStyle: IStackStyles = {
},
};
const dropdownStyles: Partial<IDropdownStyles> = {
dropdown: { width: 100, marginLeft: 10, marginRight: 20 },
};
const options: IDropdownOption[] = [
{
key: '10',
text: '10',
},
{
key: '25',
text: '25',
},
{
key: '50',
text: '50',
},
{
key: '100',
text: '100',
},
];
const Home: React.FC = () => {
const dispatch = useAppDispatch();
const myPipeLine = useAppSelector(selectPipelineList);
const myAll = useAppSelector(selectAll);
const [videoList, setVideoList] = useState<any>([]);
const [pipelineList, setPipelineList] = useState<any>([]);
const [projectList, setProjectList] = useState<any>([]);
const [page, setPage] = useState<number>(0);
const [pageSize, setPageSize] = useState<number>(10);
const [hasProject, setHasProject] = useState<boolean>(false);
const [isEnd, setIsEnd] = useState<boolean>(false);
const column: IColumn[] = [
{
@ -35,7 +82,6 @@ const Home: React.FC = () => {
minWidth: 200,
maxWidth: 350,
data: 'string',
// eslint-disable-next-line react/display-name
onRender: (item: any) => (
<span
style={{
@ -67,8 +113,18 @@ const Home: React.FC = () => {
maxWidth: 300,
data: 'string',
},
{
key: 'project_id',
name: '项目组',
minWidth: 150,
maxWidth: 200,
onRender: (item: any) => {
return <div>{projectList[item.project_id].name}</div>;
},
},
];
// 跳转指定pipeline
const goPipeline = (item: any) => {
if (window.self === window.top) {
window.location.href = `${window.location.origin}${location.pathname}?pipeline_id=${item?.id}`;
@ -85,12 +141,68 @@ const Home: React.FC = () => {
}
};
// 删除指定pipeline
const deletePipeline = (item: any) => {
if (item?.id) {
api.pipeline_modelview_delete(item.id).then((res: any) => {
if (res.status === 0) {
dispatch(getPipelineList());
}
});
}
};
// 初始化
useEffect(() => {
setVideoList(videoDemo);
setPipelineList(pipelineDemo);
dispatch(getPipelineList());
api.pipeline_modelview_demo().then((res: any) => {
if (res.status === 0) {
const { result } = res;
const pipelineDemo = result.map((ele: any) => {
const cur = {
id: ele.id,
name: ele.describe,
img: JSON.parse(ele.parameter).img || '',
type: 'link',
};
return cur;
});
setPipelineList(pipelineDemo);
}
});
api.project_all().then((res: any) => {
if (res.status === 0) {
setHasProject(true);
const list: any = [];
res.result.forEach((ele: any) => {
if (ele.id) {
list[ele.id] = ele;
}
});
setProjectList(list);
dispatch(getPipelineList());
}
});
}, []);
useEffect(() => {
if (hasProject) {
dispatch(
getAllList({
page,
page_size: pageSize,
}),
);
}
}, [pageSize, hasProject, page]);
useEffect(() => {
if (myAll) {
setIsEnd(myAll.length < pageSize);
}
}, [myAll]);
return (
<Stack className="home-container" styles={homeContainerStyle}>
<Stack
@ -109,7 +221,6 @@ const Home: React.FC = () => {
<Stack
styles={{
root: {
width: '50%',
marginTop: '16px !important',
padding: '0 10px 24px',
},
@ -121,22 +232,149 @@ const Home: React.FC = () => {
style={{
marginBottom: 8,
height: 24,
lineHeight: '22px',
fontSize: 16,
fontWeight: 600,
lineHeight: '1.1',
fontSize: 20,
fontWeight: 'bold',
}}
>
线
线
</div>
</Stack>
<DetailsList
items={myPipeLine}
columns={column}
selectionMode={SelectionMode.none}
setKey="none"
layoutMode={DetailsListLayoutMode.justified}
isHeaderVisible={true}
/>
<Pivot aria-label="Basic Pivot Example" defaultSelectedKey="1">
<PivotItem
headerText="我的"
headerButtonProps={{
'data-order': 1,
'data-title': 'My Files Title',
}}
itemKey="1"
>
<div>
<DetailsList
items={myPipeLine}
columns={column.concat({
key: 'action',
name: '操作',
minWidth: 200,
maxWidth: 300,
onRender: (item: any) => {
return (
<div>
<TooltipHost content="删除">
<IconButton
onClick={() => {
deletePipeline(item);
}}
iconProps={{
iconName: 'Delete',
styles: {
root: {
color: 'red',
},
},
}}
></IconButton>
</TooltipHost>
</div>
);
},
})}
selectionMode={SelectionMode.none}
setKey="none"
layoutMode={DetailsListLayoutMode.fixedColumns}
isHeaderVisible={true}
compact={true}
styles={{
headerWrapper: {
'.ms-DetailsHeader': {
paddingTop: 0,
},
},
contentWrapper: {
lineHeight: '32px',
},
}}
/>
</div>
</PivotItem>
<PivotItem headerText="协作" itemKey="2">
<div>
<ShimmeredDetailsList
setKey="none"
isHeaderVisible={true}
items={myAll || []}
columns={column}
compact={!!myAll}
selectionMode={SelectionMode.none}
layoutMode={DetailsListLayoutMode.fixedColumns}
enableShimmer={!myAll}
detailsListStyles={{
headerWrapper: {
'.ms-DetailsHeader': {
paddingTop: 0,
},
},
contentWrapper: {
lineHeight: '32px',
},
}}
listProps={{
renderedWindowsAhead: 0,
renderedWindowsBehind: 0,
}}
></ShimmeredDetailsList>
<Stack
horizontal
reversed
verticalAlign="center"
styles={{
root: {
marginTop: 20,
},
}}
>
<PrimaryButton
text="下一页"
styles={{ root: { marginRight: 10 } }}
disabled={isEnd}
onClick={() => {
if (isEnd || !myAll) return;
setPage(page + 1);
}}
></PrimaryButton>
<PrimaryButton
text="上一页"
styles={{ root: { marginRight: 10 } }}
disabled={page === 0}
onClick={() => {
if (page === 0 || !myAll) return;
setPage(page - 1);
}}
></PrimaryButton>
<Dropdown
defaultSelectedKey={'10'}
placeholder="选择页数"
options={options}
styles={dropdownStyles}
onChange={(e, opt) => {
setPage(0);
opt?.key && setPageSize(+opt.key);
}}
/>
<Text
styles={{
root: {
fontSize: 14,
fontWeight: 600,
},
}}
>
</Text>
</Stack>
</div>
</PivotItem>
</Pivot>
</Stack>
</Stack>
</Stack>

View File

@ -1,89 +1,8 @@
export const pipelineDemo = [
{
name: 'nni超参搜索',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/nni.png',
url: 'http://kubeflow.tke.woa.com/nni_modelview/list/',
type: 'link',
},
{
name: '搭建私有集群',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/private.png',
url: 'http://tapd.oa.com/kubeflow/markdown_wikis/show/#1220424693001918725',
type: 'outside',
},
{
name: '创建在线vscode',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/vscode.png',
url: 'http://kubeflow.tke.woa.com/notebook_modelview/add',
type: 'link',
},
{
name: '问答搜索中心',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/search.png',
url: 'http://kubeflow.service.kfserving.woa.com/',
type: 'outside',
},
{
name: '图片视频卡通化',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/cart.png',
url: 'http://cartoonizer.service.kfserving.woa.com/',
type: 'outside',
},
{
name: '聊天机器人',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/chatbot.jpg',
url: 'http://chatbot.service.kfserving.woa.com/',
type: 'outside',
},
{
name: '人脸AI特效',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/face.jpg',
url: 'https://face-master.service.kfserving.woa.com/',
type: 'outside',
},
{
name: '人体姿态识别',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/openpose.jpg',
url: 'https://openpose-gpu.service.kfserving.woa.com/',
type: 'outside',
},
{
name: '机器学习pipeline流水线',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/dag.jpg',
url: 'http://kubeflow.tke.woa.com/pipeline_modelview/list/',
type: 'link',
},
{
name: '在线ide编辑器',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/ide.png',
url: 'http://kubeflow.tke.woa.com/notebook_modelview/list/',
type: 'link',
},
{
name: '超参搜索',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/automl.png',
url: 'http://kubeflow.tke.woa.com/hyperparameter_tuning_modelview/list/',
type: 'link',
},
{
name: '模型服务化',
img: 'http://kubeflow.tke.woa.com/static/assets/images/home/service.png',
url: 'http://kubeflow.tke.woa.com/service_modelview/list/',
type: 'link',
},
];
export const videoDemo = [
{
name: '新人制作一个pipeline',
img: 'http://kubeflow.tke.woa.com/static/assets/images/ad/video-cover2-thumb.png',
url: 'http://kubeflow.tke.woa.com/static/appbuilder/mnt/make_pipeline.mp4',
type: 'video',
},
{
name: '开发定制一个任务模板',
img: 'http://kubeflow.tke.woa.com/static/assets/images/ad/video-cover2-thumb.png',
url: 'http://kubeflow.tke.woa.com/static/appbuilder/mnt/make_job_template.mp4',
name: '',
img: '',
url: '',
type: 'video',
},
];