mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-21 13:10:53 +08:00
Merge pull request #331 from c-bata/improve-md-editor
Fix bug of markdown editor and add mathjax support.
This commit is contained in:
@@ -22,9 +22,8 @@ jobs:
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: |
|
||||
npm install
|
||||
npm run lint
|
||||
- run: npm install
|
||||
- run: npm run lint
|
||||
|
||||
build:
|
||||
name: JS build check
|
||||
|
||||
@@ -219,7 +219,6 @@ export const actionCreator = () => {
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.dir(err)
|
||||
if (err.response.status === 409) {
|
||||
const index = studyDetails[studyId].trials.findIndex(
|
||||
(t) => t.trial_id === trialId
|
||||
@@ -233,7 +232,7 @@ export const actionCreator = () => {
|
||||
)
|
||||
return
|
||||
}
|
||||
setTrialNote(studyId, index, note)
|
||||
setTrialNote(studyId, index, err.response.data.note)
|
||||
}
|
||||
const reason = err.response?.data.reason
|
||||
if (reason !== undefined) {
|
||||
|
||||
@@ -47,7 +47,6 @@ export const App: FC = () => {
|
||||
backgroundColor: colorMode === "dark" ? "#121212" : "#ffffff",
|
||||
width: "100%",
|
||||
minHeight: "100vh",
|
||||
paddingBottom: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import React, { FC } from "react"
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material"
|
||||
import { Link } from "react-router-dom"
|
||||
import LinkIcon from "@mui/icons-material/Link"
|
||||
|
||||
export const BestTrialsCard: FC<{
|
||||
studyDetail: StudyDetail | null
|
||||
}> = ({ studyDetail }) => {
|
||||
const theme = useTheme()
|
||||
|
||||
let header = "Best Trials"
|
||||
let content: React.ReactNode = null
|
||||
if (studyDetail !== null && studyDetail.best_trials.length === 1) {
|
||||
const bestTrial = studyDetail.best_trials[0]
|
||||
header = `Best Trial (number=${bestTrial.number})`
|
||||
content = (
|
||||
<>
|
||||
<Typography
|
||||
variant="h3"
|
||||
sx={{ fontWeight: 600, marginBottom: theme.spacing(2) }}
|
||||
color="secondary"
|
||||
>
|
||||
{bestTrial.values}
|
||||
</Typography>
|
||||
<Typography>
|
||||
Params = [
|
||||
{bestTrial.params.map((p) => `${p.name}: ${p.value}`).join(", ")}]
|
||||
</Typography>
|
||||
<Typography>
|
||||
Intermediate Values = [
|
||||
{studyDetail.best_trials[0].intermediate_values
|
||||
.map((p) => `${p.step}: ${p.value}`)
|
||||
.join(", ")}
|
||||
]
|
||||
</Typography>
|
||||
<Typography>
|
||||
User Attributes = [
|
||||
{studyDetail.best_trials[0].user_attrs
|
||||
.map((p) => `${p.key}: ${p.value}`)
|
||||
.join(", ")}
|
||||
]
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<LinkIcon />}
|
||||
component={Link}
|
||||
to={`${URL_PREFIX}/studies/${bestTrial.study_id}/trials/${bestTrial.number}/`}
|
||||
sx={{ margin: theme.spacing(1) }}
|
||||
>
|
||||
Details
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
} else if (studyDetail !== null && studyDetail.best_trials.length > 1) {
|
||||
const bestTrials = studyDetail.best_trials
|
||||
content = (
|
||||
<>
|
||||
<Divider
|
||||
sx={{ paddingBottom: theme.spacing(1) }}
|
||||
orientation="horizontal"
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "auto",
|
||||
height: "450px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{bestTrials.map((trial) => (
|
||||
<ListItem key={trial.number} disablePadding>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
to={
|
||||
URL_PREFIX +
|
||||
`/studies/${trial.study_id}/trials/${trial.number}`
|
||||
}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant="h5">Trial {trial.number}</Typography>
|
||||
}
|
||||
secondary={
|
||||
<>
|
||||
<Typography>
|
||||
Objective Values = [{trial.values?.join(", ")}]
|
||||
</Typography>
|
||||
<Typography>
|
||||
Params = [
|
||||
{trial.params
|
||||
.map((p) => `${p.name}: ${p.value}`)
|
||||
.join(", ")}
|
||||
]
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Card>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "inline-content",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ margin: "1em 0", fontWeight: 600 }}>
|
||||
{header}
|
||||
</Typography>
|
||||
{content}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -214,7 +214,9 @@ const plotContour = (
|
||||
const filteredTrials = trials.filter((t) => filterFunc(t, objectiveId))
|
||||
|
||||
if (filteredTrials.length === 0) {
|
||||
plotly.react(plotDomId, [])
|
||||
plotly.react(plotDomId, [], {
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,9 @@ const plotEdf = (study: StudyDetail, objectiveId: number, mode: string) => {
|
||||
const filteredTrials = trials.filter((t) => filterFunc(t, objectiveId))
|
||||
|
||||
if (filteredTrials.length === 0) {
|
||||
plotly.react(plotDomId, [])
|
||||
plotly.react(plotDomId, [], {
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ const plotHistory = (
|
||||
filteredTrials = filteredTrials.filter((t) => t.state !== "Pruned")
|
||||
}
|
||||
if (filteredTrials.length === 0) {
|
||||
plotly.react(plotDomId, [])
|
||||
plotly.react(plotDomId, [], layout)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ export const GraphHyperparameterImportanceBeta: FC<{
|
||||
<CardContent>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: 600, textAlign: "center" }}
|
||||
sx={{ margin: "1em 0", fontWeight: 600 }}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
@@ -4,6 +4,11 @@ import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
SxProps,
|
||||
TextField,
|
||||
@@ -13,21 +18,46 @@ import {
|
||||
import React, { FC, createRef, useState, useEffect } from "react"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import remarkMath from "remark-math"
|
||||
import rehypeMathjax from "rehype-mathjax"
|
||||
import LoadingButton from "@mui/lab/LoadingButton"
|
||||
import SaveIcon from "@mui/icons-material/Save"
|
||||
import EditIcon from "@mui/icons-material/Edit"
|
||||
import CloseIcon from "@mui/icons-material/Close"
|
||||
import EditIcon from "@mui/icons-material/Edit"
|
||||
import Divider from "@mui/material/Divider"
|
||||
import { Theme } from "@mui/material/styles"
|
||||
import {
|
||||
CodeComponent,
|
||||
ReactMarkdownNames,
|
||||
} from "react-markdown/lib/ast-to-react"
|
||||
import HtmlIcon from "@mui/icons-material/Html"
|
||||
import ModeEditIcon from "@mui/icons-material/ModeEdit"
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
|
||||
import { darcula } from "react-syntax-highlighter/dist/esm/styles/prism"
|
||||
|
||||
import { actionCreator } from "../action"
|
||||
|
||||
const placeholder = `## What is this feature for?
|
||||
|
||||
Here you can freely take a note in *(GitHub flavored) Markdown format*.
|
||||
In addition, **code blocks with syntax highlights** and **formula** are also supported here, as shown below.
|
||||
|
||||
### Code-block with Syntax Highlights
|
||||
|
||||
\`\`\`python
|
||||
def objective(trial):
|
||||
x = trial.suggest_float("x", -10, 10)
|
||||
y = trial.suggest_float("y", -10, 10)
|
||||
return (x - 5) ** 2 + (y + 5) ** 2
|
||||
\`\`\`
|
||||
|
||||
### Formula
|
||||
|
||||
$$
|
||||
L = \\frac{1}{2} \\rho v^2 S C_L
|
||||
$$
|
||||
`
|
||||
|
||||
const CodeBlock: CodeComponent | ReactMarkdownNames = ({
|
||||
inline,
|
||||
className,
|
||||
@@ -62,7 +92,6 @@ export const TrialNote: FC<{
|
||||
studyId={studyId}
|
||||
trialId={trialId}
|
||||
latestNote={latestNote}
|
||||
minRows={10}
|
||||
cardSx={cardSx}
|
||||
/>
|
||||
)
|
||||
@@ -71,35 +100,86 @@ export const TrialNote: FC<{
|
||||
export const StudyNote: FC<{
|
||||
studyId: number
|
||||
latestNote: Note
|
||||
minRows: number
|
||||
cardSx?: SxProps<Theme>
|
||||
}> = ({ studyId, latestNote, minRows, cardSx }) => {
|
||||
return (
|
||||
<NoteBase
|
||||
studyId={studyId}
|
||||
latestNote={latestNote}
|
||||
minRows={minRows}
|
||||
cardSx={cardSx}
|
||||
/>
|
||||
)
|
||||
}> = ({ studyId, latestNote, cardSx }) => {
|
||||
return <NoteBase studyId={studyId} latestNote={latestNote} cardSx={cardSx} />
|
||||
}
|
||||
|
||||
const NoteBase: FC<{
|
||||
const useConfirmCloseDialog = (
|
||||
handleClose: () => void
|
||||
): [() => void, () => React.ReactNode] => {
|
||||
const theme = useTheme()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const zIndex = theme.zIndex.snackbar - 1
|
||||
const openDialog = () => {
|
||||
setOpen(true)
|
||||
}
|
||||
const renderDialog = () => {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
sx={{
|
||||
zIndex: zIndex,
|
||||
}}
|
||||
>
|
||||
<DialogTitle>Unsaved changes</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Do you want to save or discard your changes?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Discard
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
Stay
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
return [openDialog, renderDialog]
|
||||
}
|
||||
|
||||
const MarkdownRenderer: FC<{ body: string }> = ({ body }) => (
|
||||
<ReactMarkdown
|
||||
children={body}
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
components={{ code: CodeBlock }}
|
||||
/>
|
||||
)
|
||||
|
||||
const MarkdownEditorModal: FC<{
|
||||
studyId: number
|
||||
trialId?: number
|
||||
latestNote: Note
|
||||
minRows: number
|
||||
cardSx?: SxProps<Theme>
|
||||
}> = ({ studyId, trialId, latestNote, minRows, cardSx }) => {
|
||||
setEditorUnmount: () => void
|
||||
}> = ({ studyId, trialId, latestNote, setEditorUnmount }) => {
|
||||
const theme = useTheme()
|
||||
const [renderMarkdown, setRenderMarkdown] = useState(true)
|
||||
const action = actionCreator()
|
||||
const [openConfirmCloseDialog, renderConfirmCloseDialog] =
|
||||
useConfirmCloseDialog(() => {
|
||||
setEditorUnmount()
|
||||
window.onbeforeunload = null
|
||||
})
|
||||
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [edited, setEdited] = useState(false)
|
||||
const [curNote, setCurNote] = useState({ version: 0, body: "" })
|
||||
const textAreaRef = createRef<HTMLTextAreaElement>()
|
||||
const action = actionCreator()
|
||||
const notLatest = latestNote.version > curNote.version
|
||||
|
||||
const [previewMarkdown, setPreviewMarkdown] = useState<string>("")
|
||||
const [preview, setPreview] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
setCurNote(latestNote)
|
||||
}, [])
|
||||
@@ -119,7 +199,6 @@ const NoteBase: FC<{
|
||||
body: textAreaRef.current ? textAreaRef.current.value : "",
|
||||
}
|
||||
setSaving(true)
|
||||
|
||||
let actionResponse: Promise<void>
|
||||
if (trialId === undefined) {
|
||||
actionResponse = action.saveStudyNote(studyId, newNote)
|
||||
@@ -129,8 +208,8 @@ const NoteBase: FC<{
|
||||
actionResponse
|
||||
.then(() => {
|
||||
setCurNote(newNote)
|
||||
setRenderMarkdown(true)
|
||||
window.onbeforeunload = null
|
||||
setEditorUnmount()
|
||||
})
|
||||
.finally(() => {
|
||||
setSaving(false)
|
||||
@@ -146,103 +225,173 @@ const NoteBase: FC<{
|
||||
window.onbeforeunload = null
|
||||
}
|
||||
|
||||
let content
|
||||
if (renderMarkdown) {
|
||||
const defaultBody =
|
||||
"*A markdown editor for taking a memo, related to the study. Click the 'Edit' button in the upper right corner to access the editor.*"
|
||||
content = (
|
||||
<ReactMarkdown
|
||||
children={latestNote.body || defaultBody}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{ code: CodeBlock }}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
content = (
|
||||
<>
|
||||
<TextField
|
||||
disabled={saving}
|
||||
minRows={minRows}
|
||||
multiline={true}
|
||||
placeholder={`Description about the ${
|
||||
trialId === undefined ? "study" : "trial"
|
||||
}...`}
|
||||
sx={{ width: "100%", margin: `${theme.spacing(1)} 0` }}
|
||||
inputProps={{ style: { resize: "vertical" } }}
|
||||
inputRef={textAreaRef}
|
||||
defaultValue={curNote.body}
|
||||
onChange={() => {
|
||||
const cur = textAreaRef.current ? textAreaRef.current.value : ""
|
||||
setEdited(cur !== curNote.body)
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{ display: "flex", flexDirection: "row", alignItems: "center" }}
|
||||
>
|
||||
{notLatest && !saving && (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.error.main,
|
||||
fontSize: "0.8rem",
|
||||
display: "inline",
|
||||
}}
|
||||
>
|
||||
The text you are editing has updated. Do you want to discard
|
||||
your changes and refresh the textarea?
|
||||
</Typography>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={handleRefresh}
|
||||
color="error"
|
||||
size="small"
|
||||
sx={{ textDecoration: "underline" }}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<LoadingButton
|
||||
onClick={handleSave}
|
||||
loading={saving}
|
||||
loadingPosition="start"
|
||||
startIcon={<SaveIcon />}
|
||||
variant="contained"
|
||||
disabled={!edited}
|
||||
>
|
||||
Save
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
// See https://github.com/iamhosseindhv/notistack/issues/231#issuecomment-825924840
|
||||
const zIndex = theme.zIndex.snackbar - 2
|
||||
|
||||
return (
|
||||
<Card sx={{ margin: theme.spacing(2), ...cardSx }}>
|
||||
<Card
|
||||
sx={{
|
||||
bottom: 0,
|
||||
height: "100%",
|
||||
left: 0,
|
||||
overflow: "hidden",
|
||||
position: "fixed",
|
||||
right: 0,
|
||||
top: 0,
|
||||
zIndex: zIndex,
|
||||
p: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setPreview(!preview)
|
||||
setPreviewMarkdown(
|
||||
textAreaRef.current ? textAreaRef.current.value : ""
|
||||
)
|
||||
}}
|
||||
>
|
||||
{preview ? (
|
||||
<ModeEditIcon color="primary" />
|
||||
) : (
|
||||
<HtmlIcon color="primary" />
|
||||
)}
|
||||
</IconButton>
|
||||
}
|
||||
title="Markdown Editor"
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
padding: theme.spacing(2),
|
||||
display: preview ? "default" : "none",
|
||||
overflow: "scroll",
|
||||
}}
|
||||
>
|
||||
<MarkdownRenderer body={previewMarkdown} />
|
||||
</Box>
|
||||
<TextField
|
||||
disabled={saving}
|
||||
multiline={true}
|
||||
placeholder={placeholder}
|
||||
sx={{
|
||||
position: "relative",
|
||||
resize: "none",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
margin: theme.spacing(1, 0),
|
||||
display: preview ? "none" : "default",
|
||||
"& .MuiInputBase-root": { height: "100%" },
|
||||
}}
|
||||
inputProps={{
|
||||
style: { resize: "none", overflow: "scroll", height: "100%" },
|
||||
}}
|
||||
inputRef={textAreaRef}
|
||||
defaultValue={latestNote.body}
|
||||
onChange={() => {
|
||||
const cur = textAreaRef.current ? textAreaRef.current.value : ""
|
||||
if (edited !== (cur !== curNote.body)) {
|
||||
setEdited(cur !== curNote.body)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ display: "flex", flexDirection: "row", alignItems: "center" }}>
|
||||
{notLatest && !saving && (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.error.main,
|
||||
fontSize: "0.8rem",
|
||||
display: "inline",
|
||||
}}
|
||||
>
|
||||
The text you are editing has updated. Do you want to discard your
|
||||
changes and refresh the textarea?
|
||||
</Typography>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={handleRefresh}
|
||||
color="error"
|
||||
size="small"
|
||||
sx={{ textDecoration: "underline" }}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
if (edited) {
|
||||
openConfirmCloseDialog()
|
||||
} else {
|
||||
setEditorUnmount()
|
||||
}
|
||||
}}
|
||||
startIcon={<CloseIcon />}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
<LoadingButton
|
||||
onClick={handleSave}
|
||||
loading={saving}
|
||||
loadingPosition="start"
|
||||
startIcon={<SaveIcon />}
|
||||
variant="contained"
|
||||
disabled={!edited || notLatest}
|
||||
sx={{ marginLeft: theme.spacing(1) }}
|
||||
>
|
||||
Save
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
{renderConfirmCloseDialog()}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const NoteBase: FC<{
|
||||
studyId: number
|
||||
trialId?: number
|
||||
latestNote: Note
|
||||
cardSx?: SxProps<Theme>
|
||||
}> = ({ studyId, trialId, latestNote, cardSx }) => {
|
||||
const theme = useTheme()
|
||||
const [editorMounted, setEditorMounted] = useState<boolean>(false)
|
||||
|
||||
const defaultBody = ""
|
||||
return (
|
||||
<Card sx={{ margin: theme.spacing(2), overflow: "scroll", ...cardSx }}>
|
||||
<CardHeader
|
||||
title="Note"
|
||||
action={
|
||||
!renderMarkdown ? (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setRenderMarkdown(true)
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton onClick={() => setRenderMarkdown(false)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
)
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setEditorMounted(true)
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{ paddingBottom: 0 }}
|
||||
/>
|
||||
<CardContent sx={{ paddingTop: theme.spacing(1) }}>
|
||||
<Divider />
|
||||
{content}
|
||||
<MarkdownRenderer body={latestNote.body || defaultBody} />
|
||||
</CardContent>
|
||||
{editorMounted && (
|
||||
<MarkdownEditorModal
|
||||
studyId={studyId}
|
||||
trialId={trialId}
|
||||
latestNote={latestNote}
|
||||
setEditorUnmount={() => {
|
||||
setEditorMounted(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -235,11 +235,7 @@ export const StudyDetail: FC<{
|
||||
<TrialTable studyDetail={studyDetail} />
|
||||
</Card>
|
||||
{studyDetail !== null ? (
|
||||
<StudyNote
|
||||
studyId={studyIdNumber}
|
||||
latestNote={studyDetail.note}
|
||||
minRows={5}
|
||||
/>
|
||||
<StudyNote studyId={studyIdNumber} latestNote={studyDetail.note} />
|
||||
) : null}
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -34,6 +34,7 @@ import { DataGrid, DataGridColumn } from "./DataGrid"
|
||||
import { GraphIntermediateValues } from "./GraphIntermediateValues"
|
||||
import { Edf } from "./GraphEdf"
|
||||
import { TrialList } from "./TrialList"
|
||||
import { BestTrialsCard } from "./BestTrialsCard"
|
||||
|
||||
interface ParamTypes {
|
||||
studyId: string
|
||||
@@ -123,103 +124,12 @@ export const StudyDetailBeta: FC<{
|
||||
graphHeight="450px"
|
||||
/>
|
||||
<Grid2 xs={6} spacing={2}>
|
||||
<Card>
|
||||
<CardContent
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{studyDetail !== null &&
|
||||
studyDetail.best_trials.length === 1 && (
|
||||
<>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: 600 }}
|
||||
>
|
||||
Best Trial
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h3"
|
||||
sx={{ fontWeight: 600, marginBottom: theme.spacing(2) }}
|
||||
color="secondary"
|
||||
>
|
||||
{studyDetail.best_trials[0].values}
|
||||
</Typography>
|
||||
<Typography>
|
||||
number={studyDetail.best_trials[0].number}
|
||||
</Typography>
|
||||
<Typography>
|
||||
trial_id={studyDetail.best_trials[0].trial_id}
|
||||
</Typography>
|
||||
<Typography>
|
||||
Params = [
|
||||
{studyDetail.best_trials[0].params
|
||||
.map((p) => `${p.name}: ${p.value}`)
|
||||
.join(", ")}
|
||||
]
|
||||
</Typography>
|
||||
<Typography>
|
||||
Intermediate Values = [
|
||||
{studyDetail.best_trials[0].intermediate_values
|
||||
.map((p) => `${p.step}: ${p.value}`)
|
||||
.join(", ")}
|
||||
]
|
||||
</Typography>
|
||||
<Typography>
|
||||
User Attributes = [
|
||||
{studyDetail.best_trials[0].user_attrs
|
||||
.map((p) => `${p.key}: ${p.value}`)
|
||||
.join(", ")}
|
||||
]
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
{studyDetail !== null && studyDetail.directions.length > 1 && (
|
||||
<>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: 600 }}
|
||||
>
|
||||
Best Trials ({studyDetail.best_trials.length} trials)
|
||||
</Typography>
|
||||
{studyDetail.best_trials.map((trial, i) => (
|
||||
<Card
|
||||
key={i}
|
||||
sx={{
|
||||
border: "1px solid rgba(128,128,128,0.5)",
|
||||
margin: theme.spacing(1, 0),
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography variant="h6">
|
||||
Trial number={trial.number} (trial_id=
|
||||
{trial.trial_id})
|
||||
</Typography>
|
||||
<Typography>
|
||||
Objective Values = [{trial.values?.join(", ")}]
|
||||
</Typography>
|
||||
<Typography>
|
||||
Params = [
|
||||
{trial.params
|
||||
.map((p) => `${p.name}: ${p.value}`)
|
||||
.join(", ")}
|
||||
]
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<BestTrialsCard studyDetail={studyDetail} />
|
||||
</Grid2>
|
||||
<Grid2 xs={6}>
|
||||
<Card>
|
||||
<CardContent
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
@@ -290,7 +200,6 @@ export const StudyDetailBeta: FC<{
|
||||
<StudyNote
|
||||
studyId={studyId}
|
||||
latestNote={studyDetail.note}
|
||||
minRows={30}
|
||||
cardSx={{ height: "90vh" }}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -144,7 +144,6 @@ export const StudyListBeta: FC<{
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<Refresh />}
|
||||
aria-haspopup="true"
|
||||
onClick={(e) => {
|
||||
action.updateStudySummaries("Success to reload")
|
||||
}}
|
||||
@@ -155,7 +154,6 @@ export const StudyListBeta: FC<{
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<AddBoxIcon />}
|
||||
aria-haspopup="true"
|
||||
onClick={(e) => {
|
||||
openCreateStudyDialog()
|
||||
}}
|
||||
|
||||
@@ -20,6 +20,30 @@ import { TrialNote } from "./Note"
|
||||
import { DataGrid, DataGridColumn } from "./DataGrid"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
type Color =
|
||||
| "default"
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "error"
|
||||
| "info"
|
||||
| "success"
|
||||
| "warning"
|
||||
|
||||
const getChipColor = (state: TrialState): Color => {
|
||||
if (state === "Complete") {
|
||||
return "success"
|
||||
} else if (state === "Running") {
|
||||
return "secondary"
|
||||
} else if (state === "Waiting") {
|
||||
return "secondary"
|
||||
} else if (state === "Pruned") {
|
||||
return "warning"
|
||||
} else if (state === "Fail") {
|
||||
return "error"
|
||||
}
|
||||
return "default"
|
||||
}
|
||||
|
||||
export const TrialList: FC<{
|
||||
studyDetail: StudyDetail | null
|
||||
trialNumber: number | null
|
||||
@@ -75,15 +99,17 @@ export const TrialList: FC<{
|
||||
<CardHeader
|
||||
title={`Trial ${trial.number} (trial_id=${trial.trial_id})`}
|
||||
/>
|
||||
<CardContent>
|
||||
{isBestTrial(trial.trial_id) ? (
|
||||
<CardContent sx={{ paddingTop: 0 }}>
|
||||
<Box sx={{ marginBottom: theme.spacing(1) }}>
|
||||
<Chip
|
||||
label={"Best Trial"}
|
||||
color="primary"
|
||||
sx={{ marginBottom: theme.spacing(1) }}
|
||||
size="small"
|
||||
color={getChipColor(trial.state)}
|
||||
label={trial.state}
|
||||
sx={{ marginRight: theme.spacing(1) }}
|
||||
/>
|
||||
) : null}
|
||||
{isBestTrial(trial.trial_id) ? (
|
||||
<Chip label={"Best Trial"} color="primary" />
|
||||
) : null}
|
||||
</Box>
|
||||
<Typography>
|
||||
Values:{" "}
|
||||
{trial.values?.map((v) => v.toString()).join(" ") || "None"}
|
||||
@@ -152,25 +178,6 @@ export const TrialList: FC<{
|
||||
studyDetail?.trials.length || 0
|
||||
} Trials`}</ListSubheader>
|
||||
{trials.map((trial, i) => {
|
||||
let color:
|
||||
| "default"
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "error"
|
||||
| "info"
|
||||
| "success"
|
||||
| "warning" = "default"
|
||||
if (trial.state === "Complete") {
|
||||
color = "success"
|
||||
} else if (trial.state === "Running") {
|
||||
color = "secondary"
|
||||
} else if (trial.state === "Waiting") {
|
||||
color = "secondary"
|
||||
} else if (trial.state === "Pruned") {
|
||||
color = "warning"
|
||||
} else if (trial.state === "Fail") {
|
||||
color = "error"
|
||||
}
|
||||
return (
|
||||
<ListItem key={trial.trial_id} disablePadding>
|
||||
<ListItemButton
|
||||
@@ -183,23 +190,29 @@ export const TrialList: FC<{
|
||||
setSelected(trial.number)
|
||||
}}
|
||||
selected={i === selected}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={`Trial ${trial.number}`}
|
||||
secondary={
|
||||
<Box sx={{ padding: theme.spacing(1, 0) }}>
|
||||
<Chip color={color} label={trial.state} size="small" />
|
||||
{isBestTrial(trial.trial_id) ? (
|
||||
<Chip
|
||||
label={"Best Trial"}
|
||||
color="primary"
|
||||
sx={{ marginLeft: theme.spacing(1) }}
|
||||
size="small"
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<ListItemText primary={`Trial ${trial.number}`} />
|
||||
<Box>
|
||||
<Chip
|
||||
color={getChipColor(trial.state)}
|
||||
label={trial.state}
|
||||
sx={{ margin: theme.spacing(1, 0) }}
|
||||
size="small"
|
||||
/>
|
||||
{isBestTrial(trial.trial_id) ? (
|
||||
<Chip
|
||||
label={"Best Trial"}
|
||||
color="primary"
|
||||
sx={{ marginLeft: theme.spacing(1) }}
|
||||
size="small"
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
)
|
||||
|
||||
Generated
+915
-115
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -30,7 +30,9 @@
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"recoil": "^0.7.6",
|
||||
"remark-gfm": "^3.0.1"
|
||||
"rehype-mathjax": "^4.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.3",
|
||||
|
||||
Reference in New Issue
Block a user