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 axios from 'axios';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Drawer from '@material-ui/core/Drawer';
|
||||
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 qs from 'query-string';
|
||||
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;
|
||||
|
||||
|
@ -20,13 +31,30 @@ const useStyles = makeStyles((theme) => ({
|
|||
flexShrink: 0
|
||||
},
|
||||
drawerPaper: {
|
||||
height: 'calc(100% - 75px)',
|
||||
zIndex: 1001,
|
||||
width: drawerWidth,
|
||||
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: {
|
||||
paddingLeft: theme.spacing(4)
|
||||
},
|
||||
nestedSubheader: {
|
||||
paddingLeft: theme.spacing(4),
|
||||
background: 'white'
|
||||
},
|
||||
menuLayer1: {
|
||||
'& span': {
|
||||
fontWeight: 600,
|
||||
|
@ -52,6 +80,7 @@ function MenuBar (props) {
|
|||
const classes = useStyles();
|
||||
|
||||
const [open, setOpen] = React.useState({game: true, agent: true});
|
||||
|
||||
const handleClickGame = () => {
|
||||
setOpen({game: !open.game, agent: open.agent});
|
||||
};
|
||||
|
@ -59,6 +88,46 @@ function MenuBar (props) {
|
|||
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 handleGameJump = (gameName) => {
|
||||
history.push(`/leaderboard?type=game&name=${gameName}`);
|
||||
|
@ -97,7 +166,7 @@ function MenuBar (props) {
|
|||
<List
|
||||
component="nav"
|
||||
aria-labelledby="nested-list-subheader"
|
||||
className={classes.root}
|
||||
className={classes.list}
|
||||
>
|
||||
<ListItem button onClick={handleClickAgent}>
|
||||
<ListItemText primary="Game LeaderBoards" className={classes.menuLayer1}/>
|
||||
|
@ -114,7 +183,7 @@ function MenuBar (props) {
|
|||
{Object.keys(props.modelList).map(gameName => {
|
||||
return (
|
||||
<div key={`agentMenu-sublist-${gameName}`}>
|
||||
<ListSubheader className={classes.nested}>{gameName}</ListSubheader>
|
||||
<ListSubheader className={classes.nestedSubheader}>{gameName}</ListSubheader>
|
||||
{generateAgentMenu(props.modelList[gameName])}
|
||||
</div>
|
||||
)
|
||||
|
@ -122,6 +191,61 @@ function MenuBar (props) {
|
|||
|
||||
</Collapse>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -209,7 +209,6 @@ class LeducHoldemGameView extends React.Component {
|
|||
message: "Empty move history",
|
||||
type: "error",
|
||||
showClose: true,
|
||||
duration: 0
|
||||
});
|
||||
this.setState({fullScreenLoading: false});
|
||||
return false;
|
||||
|
|
|
@ -19,6 +19,9 @@ import TablePagination from "@material-ui/core/TablePagination";
|
|||
import Breadcrumbs from "@material-ui/core/Breadcrumbs";
|
||||
import withStyles from "@material-ui/core/styles/withStyles";
|
||||
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 = [
|
||||
{game: 'leduc-holdem', dispName: 'Leduc Hold\'em'},
|
||||
|
@ -26,7 +29,6 @@ const gameList = [
|
|||
];
|
||||
|
||||
// {'doudizhu': ['agent1', 'agent2', 'agent3']}
|
||||
const modelList = {};
|
||||
|
||||
function LeaderBoard () {
|
||||
const initRowsPerPage = 10;
|
||||
|
@ -34,17 +36,31 @@ function LeaderBoard () {
|
|||
const [page, setPage] = React.useState(0);
|
||||
const [rowsTotal, setRowsTotal] = React.useState(0);
|
||||
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
|
||||
// only after the initial render thus replicating `componentDidMount` lifecycle behaviour
|
||||
useEffect(() => {
|
||||
gameList.forEach((game) => {
|
||||
axios.get(`${apiUrl}/tournament/list_baseline_agents?game=${game.game}`)
|
||||
.then(res => {
|
||||
modelList[game.game] = res.data.data;
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
async function fetchModelData() {
|
||||
let tempModelList = {};
|
||||
let tempDefaultModelList = [];
|
||||
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);
|
||||
let requestUrl = `${apiUrl}/tournament/`;
|
||||
|
@ -76,10 +92,11 @@ function LeaderBoard () {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<MenuBar gameList={gameList} modelList={modelList} />
|
||||
<MenuBar gameList={gameList} modelList={modelList} reloadMenu={reloadMenu} setReloadMenu={setReloadMenu}/>
|
||||
<div style={{marginLeft: '250px'}}>
|
||||
<div style={{padding: 20}}>
|
||||
<EnhancedTable
|
||||
defaultModelList={defaultModelList}
|
||||
tableRows={rows}
|
||||
routeInfo={{type, name}}
|
||||
page={page}
|
||||
|
@ -88,6 +105,8 @@ function LeaderBoard () {
|
|||
setRowsPerPage={(q) => {setRowsPerPage(q)}}
|
||||
rowsTotal={rowsTotal}
|
||||
type={type}
|
||||
reloadMenu={reloadMenu}
|
||||
setReloadMenu={setReloadMenu}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -163,7 +182,9 @@ const useToolbarStyles = makeStyles((theme) => ({
|
|||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1),
|
||||
minHeight: 52,
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
button: {},
|
||||
highlight:
|
||||
theme.palette.type === 'light'
|
||||
? {
|
||||
|
@ -183,7 +204,8 @@ const useToolbarStyles = makeStyles((theme) => ({
|
|||
}));
|
||||
|
||||
const EnhancedTableToolbar = (props) => {
|
||||
const { routeInfo } = props;
|
||||
const { routeInfo, defaultModelList, reloadMenu, setReloadMenu } = props;
|
||||
console.log('defaultModelList', defaultModelList)
|
||||
const classes = useToolbarStyles();
|
||||
|
||||
let name = '';
|
||||
|
@ -196,6 +218,54 @@ const EnhancedTableToolbar = (props) => {
|
|||
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 (
|
||||
<Toolbar
|
||||
className={classes.root}
|
||||
|
@ -209,6 +279,9 @@ const EnhancedTableToolbar = (props) => {
|
|||
</Typography>
|
||||
<Typography color="textPrimary">{name}</Typography>
|
||||
</Breadcrumbs>
|
||||
<div className={classes.button}>
|
||||
{functionalButton()}
|
||||
</div>
|
||||
</Toolbar>
|
||||
);
|
||||
};
|
||||
|
@ -330,7 +403,12 @@ const AgentTableContent = (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 handleChangePage = (event, newPage) => {
|
||||
|
@ -352,7 +430,7 @@ const EnhancedTable = (props) => {
|
|||
return (
|
||||
<div className={classes.root}>
|
||||
<Paper className={classes.paper}>
|
||||
<EnhancedTableToolbar routeInfo={routeInfo}/>
|
||||
<EnhancedTableToolbar routeInfo={routeInfo} defaultModelList={defaultModelList} reloadMenu={reloadMenu} setReloadMenu={setReloadMenu}/>
|
||||
{tableContent}
|
||||
<TablePagination
|
||||
// todo: remove testing page size option
|
||||
|
|
Loading…
Reference in New Issue