Skip to content

Commit 4b86fad

Browse files
authored
Merge branch 'main' into add/wandb-button-tasks-jobs
2 parents 774af28 + 754c6ca commit 4b86fad

File tree

5 files changed

+436
-16
lines changed

5 files changed

+436
-16
lines changed

src/renderer/components/Experiment/Tasks/EditTaskModal.tsx

Lines changed: 112 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,31 @@ import FormControl from '@mui/joy/FormControl';
88
import FormLabel from '@mui/joy/FormLabel';
99
import Input from '@mui/joy/Input';
1010
import Textarea from '@mui/joy/Textarea';
11-
import { ModalClose, ModalDialog, Sheet, Stack, Typography } from '@mui/joy';
11+
import {
12+
FormHelperText,
13+
ModalClose,
14+
ModalDialog,
15+
Sheet,
16+
Stack,
17+
Typography,
18+
} from '@mui/joy';
1219
import { FolderIcon } from 'lucide-react';
20+
import { Editor } from '@monaco-editor/react';
21+
import fairyflossTheme from '../../Shared/fairyfloss.tmTheme.js';
1322

1423
import * as chatAPI from 'renderer/lib/transformerlab-api-sdk';
1524
import { useNotification } from 'renderer/components/Shared/NotificationSystem';
1625
import { SafeJSONParse } from 'renderer/components/Shared/SafeJSONParse';
26+
import { useRef } from 'react';
27+
28+
const { parseTmTheme } = require('monaco-themes');
29+
30+
function setTheme(editor: any, monaco: any) {
31+
const themeData = parseTmTheme(fairyflossTheme);
32+
33+
monaco.editor.defineTheme('my-theme', themeData);
34+
monaco.editor.setTheme('my-theme');
35+
}
1736

1837
type EditTaskModalProps = {
1938
open: boolean;
@@ -40,6 +59,9 @@ export default function EditTaskModal({
4059
const [setup, setSetup] = React.useState('');
4160
const [saving, setSaving] = React.useState(false);
4261

62+
const setupEditorRef = useRef<any>(null);
63+
const commandEditorRef = useRef<any>(null);
64+
4365
React.useEffect(() => {
4466
if (!task) return;
4567
setTitle(task.name || '');
@@ -54,23 +76,48 @@ export default function EditTaskModal({
5476
setSetup(cfg.setup != null ? String(cfg.setup) : '');
5577
}, [task]);
5678

79+
// Keep Monaco editors in sync if the state changes after mount
80+
React.useEffect(() => {
81+
if (
82+
setupEditorRef.current &&
83+
typeof setupEditorRef.current.setValue === 'function'
84+
) {
85+
setupEditorRef.current.setValue(setup ?? '');
86+
}
87+
}, [setup]);
88+
89+
React.useEffect(() => {
90+
if (
91+
commandEditorRef.current &&
92+
typeof commandEditorRef.current.setValue === 'function'
93+
) {
94+
commandEditorRef.current.setValue(command ?? '');
95+
}
96+
}, [command]);
97+
5798
const handleSubmit = async (e: React.FormEvent) => {
5899
e.preventDefault();
100+
101+
const setupValue =
102+
setupEditorRef?.current?.getValue?.() ?? (setup || undefined);
103+
const commandValue =
104+
commandEditorRef?.current?.getValue?.() ?? (command || undefined);
105+
59106
if (!task) return;
60-
if (!command) {
107+
if (!commandValue) {
61108
addNotification({ type: 'warning', message: 'Command is required' });
62109
return;
63110
}
64111
setSaving(true);
65112
const config = {
66113
cluster_name: clusterName,
67-
command,
114+
command: commandValue,
68115
cpus: cpus || undefined,
69116
memory: memory || undefined,
70117
disk_space: diskSpace || undefined,
71118
accelerators: accelerators || undefined,
72119
num_nodes: numNodes ? parseInt(numNodes, 10) : undefined,
73-
setup: setup || undefined,
120+
setup: setupValue || undefined,
74121
} as any;
75122

76123
const body = {
@@ -115,6 +162,28 @@ export default function EditTaskModal({
115162
}
116163
};
117164

165+
function handleSetupEditorDidMount(editor: any, monaco: any) {
166+
setupEditorRef.current = editor;
167+
setTheme(editor, monaco);
168+
// initialize editor with current setup state
169+
try {
170+
editor.setValue(setup ?? '');
171+
} catch (e) {
172+
// ignore if setValue not available
173+
}
174+
}
175+
176+
function handleCommandEditorDidMount(editor: any, monaco: any) {
177+
commandEditorRef.current = editor;
178+
setTheme(editor, monaco);
179+
// initialize editor with current command state
180+
try {
181+
editor.setValue(command ?? '');
182+
} catch (e) {
183+
// ignore if setValue not available
184+
}
185+
}
186+
118187
return (
119188
<Modal open={open} onClose={onClose}>
120189
<ModalDialog
@@ -201,22 +270,59 @@ export default function EditTaskModal({
201270

202271
<FormControl sx={{ mt: 2 }}>
203272
<FormLabel>Setup Command</FormLabel>
204-
<Textarea
273+
{/* <Textarea
205274
minRows={2}
206275
value={setup}
207276
onChange={(e) => setSetup(e.target.value)}
208277
placeholder="Setup commands (optional) that runs before task is run. e.g. pip install -r requirements.txt"
278+
/> */}
279+
<Editor
280+
defaultLanguage="shell"
281+
theme="my-theme"
282+
defaultValue={setup}
283+
height="6rem"
284+
options={{
285+
minimap: {
286+
enabled: false,
287+
},
288+
fontSize: 18,
289+
cursorStyle: 'block',
290+
wordWrap: 'on',
291+
}}
292+
onMount={handleSetupEditorDidMount}
209293
/>
294+
<FormHelperText>
295+
e.g. <code>pip install -r requirements.txt</code>
296+
</FormHelperText>
210297
</FormControl>
211298

212299
<FormControl required sx={{ mt: 2 }}>
213300
<FormLabel>Command</FormLabel>
214-
<Textarea
301+
{/* <Textarea
215302
minRows={4}
216303
value={command}
217304
onChange={(e) => setCommand(e.target.value)}
218305
placeholder="e.g. python train.py --epochs 10"
306+
/> */}
307+
308+
<Editor
309+
defaultLanguage="shell"
310+
theme="my-theme"
311+
defaultValue={command}
312+
height="8rem"
313+
options={{
314+
minimap: {
315+
enabled: false,
316+
},
317+
fontSize: 18,
318+
cursorStyle: 'block',
319+
wordWrap: 'on',
320+
}}
321+
onMount={handleCommandEditorDidMount}
219322
/>
323+
<FormHelperText>
324+
e.g. <code>python train.py --epochs 10</code>
325+
</FormHelperText>
220326
</FormControl>
221327

222328
{/* Show uploaded directory indicator if present */}

src/renderer/components/Experiment/Tasks/NewTaskModal.tsx

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,22 @@ import FormControl from '@mui/joy/FormControl';
88
import FormLabel from '@mui/joy/FormLabel';
99
import Input from '@mui/joy/Input';
1010
import Textarea from '@mui/joy/Textarea';
11-
import { ModalClose, ModalDialog } from '@mui/joy';
11+
import { FormHelperText, ModalClose, ModalDialog } from '@mui/joy';
12+
import { Editor } from '@monaco-editor/react';
13+
import fairyflossTheme from '../../Shared/fairyfloss.tmTheme.js';
14+
1215
import DirectoryUpload from './DirectoryUpload';
16+
import { useRef } from 'react';
17+
import { useNotification } from 'renderer/components/Shared/NotificationSystem';
18+
19+
const { parseTmTheme } = require('monaco-themes');
20+
21+
function setTheme(editor: any, monaco: any) {
22+
const themeData = parseTmTheme(fairyflossTheme);
23+
24+
monaco.editor.defineTheme('my-theme', themeData);
25+
monaco.editor.setTheme('my-theme');
26+
}
1327

1428
type NewTaskModalProps = {
1529
open: boolean;
@@ -35,6 +49,8 @@ export default function NewTaskModal({
3549
onSubmit,
3650
isSubmitting = false,
3751
}: NewTaskModalProps) {
52+
const { addNotification } = useNotification();
53+
3854
const [title, setTitle] = React.useState('');
3955
const [clusterName, setClusterName] = React.useState('');
4056
const [command, setCommand] = React.useState('');
@@ -45,19 +61,33 @@ export default function NewTaskModal({
4561
const [numNodes, setNumNodes] = React.useState('');
4662
const [setup, setSetup] = React.useState('');
4763
const [uploadedDirPath, setUploadedDirPath] = React.useState('');
64+
// keep separate refs for the two Monaco editors
65+
const setupEditorRef = useRef<any>(null);
66+
const commandEditorRef = useRef<any>(null);
4867

4968
const handleSubmit = (e: React.FormEvent) => {
5069
e.preventDefault();
70+
// read editor values (fallback to state if editor not mounted)
71+
const setupValue =
72+
setupEditorRef?.current?.getValue?.() ?? (setup || undefined);
73+
const commandValue =
74+
commandEditorRef?.current?.getValue?.() ?? (command || undefined);
75+
76+
if (!commandValue) {
77+
addNotification({ type: 'warning', message: 'Command is required' });
78+
return;
79+
}
80+
5181
onSubmit({
5282
title,
5383
cluster_name: clusterName,
54-
command,
84+
command: commandValue,
5585
cpus: cpus || undefined,
5686
memory: memory || undefined,
5787
disk_space: diskSpace || undefined,
5888
accelerators: accelerators || undefined,
5989
num_nodes: numNodes ? parseInt(numNodes, 10) : undefined,
60-
setup: setup || undefined,
90+
setup: setupValue,
6191
uploaded_dir_path: uploadedDirPath || undefined,
6292
});
6393
// Reset all form fields
@@ -71,9 +101,26 @@ export default function NewTaskModal({
71101
setNumNodes('');
72102
setSetup('');
73103
setUploadedDirPath('');
104+
// clear editor contents if mounted
105+
try {
106+
setupEditorRef?.current?.setValue?.('');
107+
commandEditorRef?.current?.setValue?.('');
108+
} catch (err) {
109+
// ignore
110+
}
74111
onClose();
75112
};
76113

114+
function handleSetupEditorDidMount(editor: any, monaco: any) {
115+
setupEditorRef.current = editor;
116+
setTheme(editor, monaco);
117+
}
118+
119+
function handleCommandEditorDidMount(editor: any, monaco: any) {
120+
commandEditorRef.current = editor;
121+
setTheme(editor, monaco);
122+
}
123+
77124
return (
78125
<Modal open={open} onClose={onClose}>
79126
<ModalDialog
@@ -168,22 +215,58 @@ export default function NewTaskModal({
168215

169216
<FormControl sx={{ mt: 2 }}>
170217
<FormLabel>Setup Command</FormLabel>
171-
<Textarea
218+
{/* <Textarea
172219
minRows={2}
173220
value={setup}
174221
onChange={(e) => setSetup(e.target.value)}
175222
placeholder="Setup commands (optional) that runs before task is run. e.g. pip install -r requirements.txt"
223+
/> */}
224+
225+
<Editor
226+
defaultLanguage="shell"
227+
theme="my-theme"
228+
height="6rem"
229+
options={{
230+
minimap: {
231+
enabled: false,
232+
},
233+
fontSize: 18,
234+
cursorStyle: 'block',
235+
wordWrap: 'on',
236+
}}
237+
onMount={handleSetupEditorDidMount}
176238
/>
239+
<FormHelperText>
240+
e.g. <code>pip install -r requirements.txt</code>
241+
</FormHelperText>
177242
</FormControl>
178243

179-
<FormControl required sx={{ mt: 2 }}>
244+
<FormControl required sx={{ mt: 2, mb: 2 }}>
180245
<FormLabel>Command</FormLabel>
181-
<Textarea
246+
{/* <Textarea
182247
minRows={4}
183248
value={command}
184249
onChange={(e) => setCommand(e.target.value)}
185250
placeholder="e.g. python train.py --epochs 10"
251+
/> */}
252+
253+
<Editor
254+
defaultLanguage="shell"
255+
theme="my-theme"
256+
height="8rem"
257+
options={{
258+
minimap: {
259+
enabled: false,
260+
},
261+
fontSize: 18,
262+
cursorStyle: 'block',
263+
wordWrap: 'on',
264+
}}
265+
onMount={handleCommandEditorDidMount}
186266
/>
267+
<FormHelperText>
268+
e.g. <code>python train.py --epochs 10</code>
269+
</FormHelperText>
187270
</FormControl>
188271

189272
<DirectoryUpload

0 commit comments

Comments
 (0)