add model upload functionality; add launch tournament functionality; add model removal functionality
This commit is contained in:
parent
379edbb4f3
commit
a41f8c5fd8
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import Drawer from '@material-ui/core/Drawer';
|
import Drawer from '@material-ui/core/Drawer';
|
||||||
import List from '@material-ui/core/List';
|
import List from '@material-ui/core/List';
|
||||||
|
@ -10,6 +11,16 @@ import ExpandMore from '@material-ui/icons/ExpandMore';
|
||||||
import {makeStyles} from "@material-ui/core/styles";
|
import {makeStyles} from "@material-ui/core/styles";
|
||||||
import qs from 'query-string';
|
import qs from 'query-string';
|
||||||
import ListSubheader from "@material-ui/core/ListSubheader";
|
import ListSubheader from "@material-ui/core/ListSubheader";
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
import Dialog from "@material-ui/core/Dialog";
|
||||||
|
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||||
|
import DialogContent from "@material-ui/core/DialogContent";
|
||||||
|
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||||
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import DialogActions from "@material-ui/core/DialogActions";
|
||||||
|
|
||||||
|
import {Message, Upload} from 'element-react';
|
||||||
|
import {apiUrl} from "../utils/config";
|
||||||
|
|
||||||
const drawerWidth = 250;
|
const drawerWidth = 250;
|
||||||
|
|
||||||
|
@ -20,13 +31,30 @@ const useStyles = makeStyles((theme) => ({
|
||||||
flexShrink: 0
|
flexShrink: 0
|
||||||
},
|
},
|
||||||
drawerPaper: {
|
drawerPaper: {
|
||||||
|
height: 'calc(100% - 75px)',
|
||||||
zIndex: 1001,
|
zIndex: 1001,
|
||||||
width: drawerWidth,
|
width: drawerWidth,
|
||||||
top: 75
|
top: 75
|
||||||
},
|
},
|
||||||
|
button: {
|
||||||
|
width: 200,
|
||||||
|
position: 'fixed',
|
||||||
|
left: 25,
|
||||||
|
bottom: 25
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
height: 'calc(100% - 86px - 16px)',
|
||||||
|
overflowY: 'auto',
|
||||||
|
borderBottom: '1px solid #ccc',
|
||||||
|
paddingTop: '0'
|
||||||
|
},
|
||||||
nested: {
|
nested: {
|
||||||
paddingLeft: theme.spacing(4)
|
paddingLeft: theme.spacing(4)
|
||||||
},
|
},
|
||||||
|
nestedSubheader: {
|
||||||
|
paddingLeft: theme.spacing(4),
|
||||||
|
background: 'white'
|
||||||
|
},
|
||||||
menuLayer1: {
|
menuLayer1: {
|
||||||
'& span': {
|
'& span': {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
|
@ -52,6 +80,7 @@ function MenuBar (props) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const [open, setOpen] = React.useState({game: true, agent: true});
|
const [open, setOpen] = React.useState({game: true, agent: true});
|
||||||
|
|
||||||
const handleClickGame = () => {
|
const handleClickGame = () => {
|
||||||
setOpen({game: !open.game, agent: open.agent});
|
setOpen({game: !open.game, agent: open.agent});
|
||||||
};
|
};
|
||||||
|
@ -59,6 +88,46 @@ function MenuBar (props) {
|
||||||
setOpen({game: open.game, agent: !open.agent});
|
setOpen({game: open.game, agent: !open.agent});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [uploadDialogOpen, setUploadDialogOpen] = React.useState(false);
|
||||||
|
|
||||||
|
const openUploadDialog = () => {
|
||||||
|
setUploadDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUploadDialogClose = () => {
|
||||||
|
setUploadDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFormInitValue = {name: '', game: 'leduc-holdem', entry: ''};
|
||||||
|
const [uploadForm, setUploadForm] = React.useState({...uploadFormInitValue});
|
||||||
|
const handleUploadFormChange = (e, property) => {
|
||||||
|
let tempUploadForm = {...uploadForm};
|
||||||
|
tempUploadForm[property] = e.target.value;
|
||||||
|
setUploadForm(tempUploadForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
let uploadRef = React.createRef();
|
||||||
|
const handleSubmitUpload = () => {
|
||||||
|
console.log(uploadForm, uploadRef);
|
||||||
|
const bodyFormData = new FormData();
|
||||||
|
bodyFormData.append('name', uploadForm.name);
|
||||||
|
bodyFormData.append('entry', uploadForm.entry);
|
||||||
|
bodyFormData.append('game', uploadForm.game);
|
||||||
|
bodyFormData.append('model', uploadRef.current.state.fileList[0].raw);
|
||||||
|
|
||||||
|
axios.post(`${apiUrl}/tournament/upload_agent`, bodyFormData, {headers: {'Content-Type': 'multipart/form-data'}})
|
||||||
|
.then(res => {
|
||||||
|
Message({
|
||||||
|
message: "Successfully uploaded model",
|
||||||
|
type: "success",
|
||||||
|
showClose: true,
|
||||||
|
});
|
||||||
|
props.setReloadMenu(props.reloadMenu+1);
|
||||||
|
setUploadDialogOpen(false);
|
||||||
|
setUploadForm({...uploadFormInitValue});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const handleGameJump = (gameName) => {
|
const handleGameJump = (gameName) => {
|
||||||
history.push(`/leaderboard?type=game&name=${gameName}`);
|
history.push(`/leaderboard?type=game&name=${gameName}`);
|
||||||
|
@ -97,7 +166,7 @@ function MenuBar (props) {
|
||||||
<List
|
<List
|
||||||
component="nav"
|
component="nav"
|
||||||
aria-labelledby="nested-list-subheader"
|
aria-labelledby="nested-list-subheader"
|
||||||
className={classes.root}
|
className={classes.list}
|
||||||
>
|
>
|
||||||
<ListItem button onClick={handleClickAgent}>
|
<ListItem button onClick={handleClickAgent}>
|
||||||
<ListItemText primary="Game LeaderBoards" className={classes.menuLayer1}/>
|
<ListItemText primary="Game LeaderBoards" className={classes.menuLayer1}/>
|
||||||
|
@ -114,7 +183,7 @@ function MenuBar (props) {
|
||||||
{Object.keys(props.modelList).map(gameName => {
|
{Object.keys(props.modelList).map(gameName => {
|
||||||
return (
|
return (
|
||||||
<div key={`agentMenu-sublist-${gameName}`}>
|
<div key={`agentMenu-sublist-${gameName}`}>
|
||||||
<ListSubheader className={classes.nested}>{gameName}</ListSubheader>
|
<ListSubheader className={classes.nestedSubheader}>{gameName}</ListSubheader>
|
||||||
{generateAgentMenu(props.modelList[gameName])}
|
{generateAgentMenu(props.modelList[gameName])}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -122,6 +191,61 @@ function MenuBar (props) {
|
||||||
|
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</List>
|
</List>
|
||||||
|
<Button variant="contained" color="primary" onClick={openUploadDialog} className={classes.button}>
|
||||||
|
Upload Model
|
||||||
|
</Button>
|
||||||
|
<Dialog open={uploadDialogOpen} onClose={handleUploadDialogClose} aria-labelledby="form-dialog-title">
|
||||||
|
<DialogTitle id="form-dialog-title">Upload Model</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Upload
|
||||||
|
className="upload-demo"
|
||||||
|
drag
|
||||||
|
ref={uploadRef}
|
||||||
|
action="//placeholder/"
|
||||||
|
multiple
|
||||||
|
limit={1}
|
||||||
|
autoUpload={false}
|
||||||
|
>
|
||||||
|
<i className="el-icon-upload"/>
|
||||||
|
<div className="el-upload__text">Drag the file here, or <em>Click to upload</em></div>
|
||||||
|
</Upload>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
margin="dense"
|
||||||
|
id="name"
|
||||||
|
label="Model Name"
|
||||||
|
value={uploadForm.name}
|
||||||
|
onChange={(e) => handleUploadFormChange(e, 'name')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
margin="dense"
|
||||||
|
id="entry"
|
||||||
|
label="Entry"
|
||||||
|
value={uploadForm.entry}
|
||||||
|
onChange={(e) => handleUploadFormChange(e, 'entry')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
margin="dense"
|
||||||
|
id="game"
|
||||||
|
label="Game"
|
||||||
|
value={uploadForm.game}
|
||||||
|
onChange={(e) => handleUploadFormChange(e, 'game')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleUploadDialogClose} variant="contained" disableElevation>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSubmitUpload} color="primary" variant="contained" disableElevation>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,7 +209,6 @@ class LeducHoldemGameView extends React.Component {
|
||||||
message: "Empty move history",
|
message: "Empty move history",
|
||||||
type: "error",
|
type: "error",
|
||||||
showClose: true,
|
showClose: true,
|
||||||
duration: 0
|
|
||||||
});
|
});
|
||||||
this.setState({fullScreenLoading: false});
|
this.setState({fullScreenLoading: false});
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -19,6 +19,9 @@ import TablePagination from "@material-ui/core/TablePagination";
|
||||||
import Breadcrumbs from "@material-ui/core/Breadcrumbs";
|
import Breadcrumbs from "@material-ui/core/Breadcrumbs";
|
||||||
import withStyles from "@material-ui/core/styles/withStyles";
|
import withStyles from "@material-ui/core/styles/withStyles";
|
||||||
import PlayCircleOutlineIcon from '@material-ui/icons/PlayCircleOutline';
|
import PlayCircleOutlineIcon from '@material-ui/icons/PlayCircleOutline';
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
import {useHistory} from "react-router-dom";
|
||||||
|
import {Message} from "element-react";
|
||||||
|
|
||||||
const gameList = [
|
const gameList = [
|
||||||
{game: 'leduc-holdem', dispName: 'Leduc Hold\'em'},
|
{game: 'leduc-holdem', dispName: 'Leduc Hold\'em'},
|
||||||
|
@ -26,7 +29,6 @@ const gameList = [
|
||||||
];
|
];
|
||||||
|
|
||||||
// {'doudizhu': ['agent1', 'agent2', 'agent3']}
|
// {'doudizhu': ['agent1', 'agent2', 'agent3']}
|
||||||
const modelList = {};
|
|
||||||
|
|
||||||
function LeaderBoard () {
|
function LeaderBoard () {
|
||||||
const initRowsPerPage = 10;
|
const initRowsPerPage = 10;
|
||||||
|
@ -34,17 +36,31 @@ function LeaderBoard () {
|
||||||
const [page, setPage] = React.useState(0);
|
const [page, setPage] = React.useState(0);
|
||||||
const [rowsTotal, setRowsTotal] = React.useState(0);
|
const [rowsTotal, setRowsTotal] = React.useState(0);
|
||||||
const [rows, setRows] = React.useState([]);
|
const [rows, setRows] = React.useState([]);
|
||||||
|
const [modelList, setModelList] = React.useState({});
|
||||||
|
const [defaultModelList, setDefaultModelList] = React.useState([]);
|
||||||
|
const [reloadMenu, setReloadMenu] = React.useState(0);
|
||||||
|
|
||||||
// passing an empty array as second argument triggers the callback in useEffect
|
// passing an empty array as second argument triggers the callback in useEffect
|
||||||
// only after the initial render thus replicating `componentDidMount` lifecycle behaviour
|
// only after the initial render thus replicating `componentDidMount` lifecycle behaviour
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
gameList.forEach((game) => {
|
async function fetchModelData() {
|
||||||
axios.get(`${apiUrl}/tournament/list_baseline_agents?game=${game.game}`)
|
let tempModelList = {};
|
||||||
.then(res => {
|
let tempDefaultModelList = [];
|
||||||
modelList[game.game] = res.data.data;
|
for (const game of gameList) {
|
||||||
});
|
let res = await axios.get(`${apiUrl}/tournament/list_baseline_agents?game=${game.game}`);
|
||||||
});
|
console.log('agent', res);
|
||||||
}, []);
|
tempModelList[game.game] = res.data.data;
|
||||||
|
tempDefaultModelList = tempDefaultModelList.concat(res.data.data);
|
||||||
|
res = await axios.get(`${apiUrl}/tournament/list_uploaded_agents?game=${game.game}`);
|
||||||
|
res.data.forEach(agentInfo => {
|
||||||
|
tempModelList[game.game].push(agentInfo.fields.name);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setModelList(tempModelList);
|
||||||
|
setDefaultModelList(tempDefaultModelList);
|
||||||
|
}
|
||||||
|
fetchModelData();
|
||||||
|
}, [reloadMenu]);
|
||||||
|
|
||||||
const { type, name } = qs.parse(window.location.search);
|
const { type, name } = qs.parse(window.location.search);
|
||||||
let requestUrl = `${apiUrl}/tournament/`;
|
let requestUrl = `${apiUrl}/tournament/`;
|
||||||
|
@ -76,10 +92,11 @@ function LeaderBoard () {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<MenuBar gameList={gameList} modelList={modelList} />
|
<MenuBar gameList={gameList} modelList={modelList} reloadMenu={reloadMenu} setReloadMenu={setReloadMenu}/>
|
||||||
<div style={{marginLeft: '250px'}}>
|
<div style={{marginLeft: '250px'}}>
|
||||||
<div style={{padding: 20}}>
|
<div style={{padding: 20}}>
|
||||||
<EnhancedTable
|
<EnhancedTable
|
||||||
|
defaultModelList={defaultModelList}
|
||||||
tableRows={rows}
|
tableRows={rows}
|
||||||
routeInfo={{type, name}}
|
routeInfo={{type, name}}
|
||||||
page={page}
|
page={page}
|
||||||
|
@ -88,6 +105,8 @@ function LeaderBoard () {
|
||||||
setRowsPerPage={(q) => {setRowsPerPage(q)}}
|
setRowsPerPage={(q) => {setRowsPerPage(q)}}
|
||||||
rowsTotal={rowsTotal}
|
rowsTotal={rowsTotal}
|
||||||
type={type}
|
type={type}
|
||||||
|
reloadMenu={reloadMenu}
|
||||||
|
setReloadMenu={setReloadMenu}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -163,7 +182,9 @@ const useToolbarStyles = makeStyles((theme) => ({
|
||||||
paddingLeft: theme.spacing(2),
|
paddingLeft: theme.spacing(2),
|
||||||
paddingRight: theme.spacing(1),
|
paddingRight: theme.spacing(1),
|
||||||
minHeight: 52,
|
minHeight: 52,
|
||||||
|
justifyContent: 'space-between'
|
||||||
},
|
},
|
||||||
|
button: {},
|
||||||
highlight:
|
highlight:
|
||||||
theme.palette.type === 'light'
|
theme.palette.type === 'light'
|
||||||
? {
|
? {
|
||||||
|
@ -183,7 +204,8 @@ const useToolbarStyles = makeStyles((theme) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const EnhancedTableToolbar = (props) => {
|
const EnhancedTableToolbar = (props) => {
|
||||||
const { routeInfo } = props;
|
const { routeInfo, defaultModelList, reloadMenu, setReloadMenu } = props;
|
||||||
|
console.log('defaultModelList', defaultModelList)
|
||||||
const classes = useToolbarStyles();
|
const classes = useToolbarStyles();
|
||||||
|
|
||||||
let name = '';
|
let name = '';
|
||||||
|
@ -196,6 +218,54 @@ const EnhancedTableToolbar = (props) => {
|
||||||
name = routeInfo.name;
|
name = routeInfo.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const functionalButton = () => {
|
||||||
|
if (routeInfo.type === 'game'){
|
||||||
|
const handleLaunchTournament = (gameName) => {
|
||||||
|
// todo: customize eval num
|
||||||
|
// todo: add global loading when waiting for API response
|
||||||
|
axios.get(`${apiUrl}/tournament/launch?eval_num=200&name=${gameName}`)
|
||||||
|
.then(res => {
|
||||||
|
Message({
|
||||||
|
message: "Successfully launched tournament",
|
||||||
|
type: "success",
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={classes.button}>
|
||||||
|
<Button variant="contained" color="primary" onClick={() => handleLaunchTournament(routeInfo.name)}>
|
||||||
|
Launch Tournament
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if (routeInfo.type ==='agent') {
|
||||||
|
const delButtonDisabled = defaultModelList.includes(routeInfo.name);
|
||||||
|
const handleDelModel = (agentName) => {
|
||||||
|
axios.get(`${apiUrl}/tournament/delete_agent?name=${agentName}`)
|
||||||
|
.then(res => {
|
||||||
|
Message({
|
||||||
|
message: "Successfully deleted model",
|
||||||
|
type: "success",
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
setReloadMenu(reloadMenu+1);
|
||||||
|
history.push(`/leaderboard?type=game&name=leduc-holdem`);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={classes.button}>
|
||||||
|
<Button variant="contained" onClick={() => handleDelModel(routeInfo.name)} color="primary" disabled={delButtonDisabled}>
|
||||||
|
Delete Model
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toolbar
|
<Toolbar
|
||||||
className={classes.root}
|
className={classes.root}
|
||||||
|
@ -209,6 +279,9 @@ const EnhancedTableToolbar = (props) => {
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography color="textPrimary">{name}</Typography>
|
<Typography color="textPrimary">{name}</Typography>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
|
<div className={classes.button}>
|
||||||
|
{functionalButton()}
|
||||||
|
</div>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -330,7 +403,12 @@ const AgentTableContent = (props) => {
|
||||||
|
|
||||||
const EnhancedTable = (props) => {
|
const EnhancedTable = (props) => {
|
||||||
|
|
||||||
const { tableRows, routeInfo, rowsPerPage, page, setPage, setRowsPerPage, rowsTotal, type } = props;
|
const { tableRows, routeInfo,
|
||||||
|
rowsPerPage, page, setPage,
|
||||||
|
setRowsPerPage, rowsTotal,
|
||||||
|
type, defaultModelList,
|
||||||
|
reloadMenu, setReloadMenu
|
||||||
|
} = props;
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const handleChangePage = (event, newPage) => {
|
const handleChangePage = (event, newPage) => {
|
||||||
|
@ -352,7 +430,7 @@ const EnhancedTable = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<Paper className={classes.paper}>
|
<Paper className={classes.paper}>
|
||||||
<EnhancedTableToolbar routeInfo={routeInfo}/>
|
<EnhancedTableToolbar routeInfo={routeInfo} defaultModelList={defaultModelList} reloadMenu={reloadMenu} setReloadMenu={setReloadMenu}/>
|
||||||
{tableContent}
|
{tableContent}
|
||||||
<TablePagination
|
<TablePagination
|
||||||
// todo: remove testing page size option
|
// todo: remove testing page size option
|
||||||
|
|
Loading…
Reference in New Issue