Skip to content

Commit 1b9fd67

Browse files
authored
Merge pull request #1194 from optuna/feat/smart-text-field
Add Support for triggering graph generation via Enter key press to `GraphByLLM`
2 parents a1b71d0 + 9efabbb commit 1b9fd67

File tree

3 files changed

+97
-78
lines changed

3 files changed

+97
-78
lines changed

optuna_dashboard/ts/components/GraphByLLM.tsx

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import ClearIcon from "@mui/icons-material/Clear"
21
import DeleteIcon from "@mui/icons-material/Delete"
32
import EditRoadIcon from "@mui/icons-material/EditRoad"
43
import MoreVertIcon from "@mui/icons-material/MoreVert"
@@ -10,12 +9,10 @@ import {
109
Card,
1110
CardContent,
1211
IconButton,
13-
InputAdornment,
1412
ListItemIcon,
1513
ListItemText,
1614
Menu,
1715
MenuItem,
18-
TextField,
1916
Typography,
2017
useTheme,
2118
} from "@mui/material"
@@ -26,6 +23,7 @@ import React, { FC, useEffect, useState } from "react"
2623
import { useGeneratePlotlyGraphQuery } from "../hooks/useGeneratePlotlyGraphQuery"
2724
import { usePlotlyColorTheme } from "../state"
2825
import { StudyDetail } from "../types/optuna"
26+
import { SmartTextField } from "./SmartTextField"
2927

3028
const plotDomIdPrefix = "graph-by-llm"
3129

@@ -142,30 +140,17 @@ const GraphByLLMItem: FC<{
142140

143141
{isEditingGraph && (
144142
<Box sx={{ display: "flex", marginBottom: theme.spacing(2) }}>
145-
<TextField
143+
<SmartTextField
146144
id={`graph-by-llm-item-query-${id}`}
145+
value={queryInput}
146+
setValue={setQueryInput}
147147
variant="outlined"
148148
placeholder="Enter the part you want to edit in the graph"
149149
fullWidth
150150
size="small"
151-
value={queryInput}
152-
onChange={(e) => setQueryInput(e.target.value)}
153-
slotProps={{
154-
input: {
155-
endAdornment: queryInput && (
156-
<InputAdornment position="end">
157-
<IconButton
158-
aria-label="clear filter"
159-
onClick={() => setQueryInput("")}
160-
edge="end"
161-
size="small"
162-
disabled={isReGeneratingPlotlyGraph}
163-
>
164-
<ClearIcon />
165-
</IconButton>
166-
</InputAdornment>
167-
),
168-
},
151+
clearButtonDisabled={isReGeneratingPlotlyGraph}
152+
handleSubmit={() => {
153+
reGeneratePlotlyGraph(queryInput)
169154
}}
170155
/>
171156
<Button
@@ -231,6 +216,21 @@ export const GraphByLLM: FC<{
231216
>([])
232217
const [queryInput, setQueryInput] = useState("")
233218

219+
const handleSubmit = () => {
220+
if (study === null) return
221+
generatePlotlyGraph(study, queryInput).then((result) => {
222+
setGraphs((prev) => [
223+
...prev,
224+
{
225+
id: String(new Date().getTime()),
226+
functionStr: result.functionStr,
227+
title: result.graphTitle,
228+
plotData: result.plotData,
229+
},
230+
])
231+
})
232+
}
233+
234234
return (
235235
<Stack
236236
spacing={2}
@@ -280,51 +280,23 @@ export const GraphByLLM: FC<{
280280
/>
281281
))}
282282
<Box sx={{ display: "flex" }}>
283-
<TextField
283+
<SmartTextField
284284
id="graph-by-llm-query"
285285
variant="outlined"
286286
placeholder="Enter your query to generate a graph, e.g., 'Plot objective value vs trial number'"
287287
fullWidth
288288
size="small"
289289
value={queryInput}
290-
onChange={(e) => setQueryInput(e.target.value)}
291-
slotProps={{
292-
input: {
293-
endAdornment: queryInput && (
294-
<InputAdornment position="end">
295-
<IconButton
296-
aria-label="clear filter"
297-
onClick={() => setQueryInput("")}
298-
edge="end"
299-
size="small"
300-
disabled={isProcessing}
301-
>
302-
<ClearIcon />
303-
</IconButton>
304-
</InputAdornment>
305-
),
306-
},
307-
}}
290+
setValue={setQueryInput}
291+
clearButtonDisabled={isProcessing}
292+
handleSubmit={handleSubmit}
308293
/>
309294
<LoadingButton
310295
sx={{ marginLeft: theme.spacing(2) }}
311296
variant="contained"
312297
loading={isProcessing}
313298
disabled={queryInput.trim() === ""}
314-
onClick={() => {
315-
if (study === null) return
316-
generatePlotlyGraph(study, queryInput).then((result) => {
317-
setGraphs((prev) => [
318-
...prev,
319-
{
320-
id: String(new Date().getTime()),
321-
functionStr: result.functionStr,
322-
title: result.graphTitle,
323-
plotData: result.plotData,
324-
},
325-
])
326-
})
327-
}}
299+
onClick={handleSubmit}
328300
>
329301
Generate
330302
</LoadingButton>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import ClearIcon from "@mui/icons-material/Clear"
2+
import {
3+
IconButton,
4+
InputAdornment,
5+
TextField,
6+
TextFieldProps,
7+
} from "@mui/material"
8+
import React, { FC, useState } from "react"
9+
10+
export const SmartTextField: FC<
11+
{
12+
handleSubmit: () => void
13+
value: string
14+
setValue: React.Dispatch<React.SetStateAction<string>>
15+
clearButtonDisabled?: boolean
16+
} & Omit<
17+
TextFieldProps,
18+
"onCompositionStart" | "onCompositionEnd" | "value" | "onChange"
19+
>
20+
> = ({ handleSubmit, value, setValue, clearButtonDisabled, ...props }) => {
21+
const [isComposing, setIsComposing] = useState(false)
22+
23+
return (
24+
<TextField
25+
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
26+
if (e.key === "Enter" && !isComposing) {
27+
e.preventDefault()
28+
handleSubmit()
29+
}
30+
}}
31+
value={value}
32+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
33+
setValue(e.target.value)
34+
}}
35+
onCompositionStart={() => setIsComposing(true)}
36+
onCompositionEnd={() => setIsComposing(false)}
37+
slotProps={{
38+
input: {
39+
endAdornment: value && (
40+
<InputAdornment position="end">
41+
<IconButton
42+
aria-label="clear filter"
43+
onClick={() => {
44+
setValue("")
45+
}}
46+
edge="end"
47+
size="small"
48+
disabled={clearButtonDisabled}
49+
>
50+
<ClearIcon />
51+
</IconButton>
52+
</InputAdornment>
53+
),
54+
},
55+
}}
56+
{...props}
57+
/>
58+
)
59+
}

optuna_dashboard/ts/hooks/useSmartFilteringForm.tsx

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import FilterListIcon from "@mui/icons-material/FilterList"
2-
import { Button, CircularProgress, TextField } from "@mui/material"
3-
import React, { ReactNode, useState, useRef } from "react"
2+
import { Button, CircularProgress } from "@mui/material"
3+
import React, { ReactNode, useState } from "react"
4+
import { SmartTextField } from "../components/SmartTextField"
45
import { Trial } from "../types/optuna"
56
import { useTrialFilterQuery } from "./useTrialFilterQuery"
67

@@ -10,12 +11,9 @@ export const useSmartFilteringForm = (
1011
trialFilter: (trials: Trial[], filterQueryStr: string) => Promise<Trial[]>
1112
) => void
1213
): [() => ReactNode] => {
13-
const [isComposing, setIsComposing] = useState(false)
14-
const inputRef = useRef<HTMLInputElement>(null)
14+
const [value, setValue] = useState("")
1515
const handleClearFilter = () => {
16-
if (inputRef.current) {
17-
inputRef.current.value = ""
18-
}
16+
setValue("")
1917
}
2018
const [trialFilter, renderIframe, isProcessing] = useTrialFilterQuery({
2119
nRetry: 5,
@@ -29,31 +27,21 @@ export const useSmartFilteringForm = (
2927
if (isProcessing) {
3028
return
3129
}
32-
handleFilter(inputRef.current?.value ?? "", trialFilter)
30+
handleFilter(value, trialFilter)
3331
}
3432

3533
const render = () => {
3634
return (
3735
<>
38-
<TextField
39-
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
40-
if (e.key === "Enter" && !isComposing) {
41-
e.preventDefault()
42-
handleSubmit()
43-
}
44-
}}
36+
<SmartTextField
37+
handleSubmit={handleSubmit}
38+
value={value}
39+
setValue={setValue}
40+
clearButtonDisabled={isProcessing}
4541
placeholder="Enter filter query (e.g., trial number < 10)"
4642
fullWidth={true}
4743
size="small"
4844
disabled={isProcessing}
49-
type="search"
50-
onCompositionStart={() => {
51-
setIsComposing(true)
52-
}}
53-
onCompositionEnd={() => {
54-
setIsComposing(false)
55-
}}
56-
inputRef={inputRef}
5745
/>
5846
<Button
5947
variant="contained"

0 commit comments

Comments
 (0)