@@ -2,9 +2,8 @@ import React, { useEffect } from 'react';
2
2
import Button from '@mui/material/Button' ;
3
3
import DelIcon from '@mui/icons-material/Delete' ;
4
4
import { createDockerDesktopClient } from '@docker/extension-api-client' ;
5
- import { Divider , FormControlLabel , FormGroup , Grid , Icon , IconButton , Link , List , ListItem , ListItemButton , ListItemIcon , ListItemText , Modal , Paper , Stack , Switch , TextField , Tooltip , Typography } from '@mui/material' ;
5
+ import { Divider , FormControlLabel , FormGroup , Grid , Icon , IconButton , Link , List , ListItem , ListItemButton , ListItemIcon , ListItemText , Modal , Paper , Stack , Switch , TextareaAutosize , TextField , Tooltip , Typography } from '@mui/material' ;
6
6
import { getRunArgs } from './args' ;
7
- import { on } from 'events' ;
8
7
9
8
// Note: This line relies on Docker Desktop's presence as a host application.
10
9
// If you're running this React app in a browser, it won't work properly.
@@ -22,12 +21,12 @@ const debouncedToastSuccess = debounce(client.desktopUI.toast.success, 1000)
22
21
23
22
export function App ( ) {
24
23
const [ projects , setProjects ] = React . useState < string [ ] > ( localStorage . getItem ( 'projects' ) ? JSON . parse ( localStorage . getItem ( 'projects' ) ! ) : [ ] ) ;
25
- const [ selectedProject , setSelectedProject ] = React . useState < string | null > ( null ) ;
24
+ const [ selectedProject , setSelectedProject ] = React . useState < string | null > ( localStorage . getItem ( 'selectedProject' ) || null ) ;
26
25
27
26
const [ prompts , setPrompts ] = React . useState < string [ ] > ( localStorage . getItem ( 'prompts' ) ? JSON . parse ( localStorage . getItem ( 'prompts' ) ! ) : [ ] ) ;
28
- const [ selectedPrompt , setSelectedPrompt ] = React . useState < string | null > ( null ) ;
27
+ const [ selectedPrompt , setSelectedPrompt ] = React . useState < string | null > ( localStorage . getItem ( 'selectedPrompt' ) || null ) ;
29
28
30
- const [ openAIKey , setOpenAIKey ] = React . useState < string | null > ( null ) ;
29
+ const [ openAIKey , setOpenAIKey ] = React . useState < string | null > ( localStorage . getItem ( 'openAIKey' ) || '' ) ;
31
30
32
31
const [ promptInput , setPromptInput ] = React . useState < string > ( '' ) ;
33
32
@@ -46,6 +45,14 @@ export function App() {
46
45
localStorage . setItem ( 'openAIKey' , openAIKey || '' ) ;
47
46
} , [ openAIKey ] ) ;
48
47
48
+ useEffect ( ( ) => {
49
+ localStorage . setItem ( 'selectedProject' , selectedProject || '' ) ;
50
+ } , [ selectedProject ] ) ;
51
+
52
+ useEffect ( ( ) => {
53
+ localStorage . setItem ( 'selectedPrompt' , selectedPrompt || '' ) ;
54
+ } , [ selectedPrompt ] ) ;
55
+
49
56
50
57
useEffect ( ( ) => {
51
58
// URL format: https://github.com/<owner>/<repo>/tree/<branch>/<path>
@@ -63,6 +70,10 @@ export function App() {
63
70
}
64
71
} , [ promptInput ] ) ;
65
72
73
+ const appendToRunOut = ( msg : string ) => {
74
+ setRunOut ( runOut + '\n' + msg ) ;
75
+ }
76
+
66
77
const delim = client . host . platform === 'win32' ? '\\' : '/' ;
67
78
68
79
return (
@@ -72,7 +83,7 @@ export function App() {
72
83
< Paper sx = { { padding : 1 , pl : 2 } } >
73
84
< Stack direction = 'row' spacing = { 2 } alignItems = { 'center' } justifyContent = { 'space-between' } >
74
85
< Typography sx = { { flex : '1 1 30%' } } variant = 'h4' > OpenAI Key</ Typography >
75
- < TextField sx = { { flex : '1 1 70%' } } onChange = { e => setOpenAIKey ( e . target . value ) } value = { openAIKey } placeholder = 'Enter OpenAI API key' type = 'password' />
86
+ < TextField sx = { { flex : '1 1 70%' } } onChange = { e => setOpenAIKey ( e . target . value ) } value = { openAIKey || '' } placeholder = 'Enter OpenAI API key' type = 'password' />
76
87
</ Stack >
77
88
</ Paper >
78
89
</ Grid >
@@ -98,6 +109,7 @@ export function App() {
98
109
< List >
99
110
{ projects . map ( ( project ) => (
100
111
< ListItem
112
+ key = { project }
101
113
sx = { theme => ( { borderLeft : 'solid black 3px' , borderColor : selectedProject === project ? theme . palette . success . main : 'none' , my : 0.5 , padding : 0 } ) }
102
114
secondaryAction = {
103
115
< IconButton color = 'error' onClick = { ( ) => {
@@ -140,6 +152,7 @@ export function App() {
140
152
< List >
141
153
{ prompts . map ( ( prompt ) => (
142
154
< ListItem
155
+ key = { prompt }
143
156
sx = { theme => ( {
144
157
borderLeft : 'solid black 3px' ,
145
158
borderColor : selectedPrompt === prompt ? theme . palette . success . main : 'none' ,
@@ -169,49 +182,58 @@ export function App() {
169
182
</ Paper >
170
183
</ Grid >
171
184
{ /* Show row at bottom if selectProject AND selectedPrompt */ }
172
- { selectedProject && selectedPrompt ? (
185
+ { selectedProject && selectedPrompt && openAIKey ? (
173
186
< Grid item xs = { 12 } >
174
187
< Paper sx = { { padding : 1 } } >
175
188
< Typography variant = "h3" component = "h2" > Ready</ Typography >
176
- < Typography > < pre > PROJECT={ selectedProject } </ pre > </ Typography >
177
- < Typography > < pre > PROMPT={ selectedPrompt } </ pre > </ Typography >
189
+ < pre > PROJECT={ selectedProject } </ pre >
190
+ < pre > PROMPT={ selectedPrompt } </ pre >
178
191
< Button sx = { { mt : 1 , } } color = 'success' onClick = { async ( ) => {
179
192
// Write openai key to $HOME/.openai-api-key
180
- setRunOut ( 'Writing OpenAI key...' ) ;
181
- await client . extension . vm ?. cli . exec ( '/bin/sh' , [ '-c' , `echo ${ openAIKey } > $HOME/.openai-api-key` ] ) ;
182
- setRunOut ( 'Running...' ) ;
193
+ appendToRunOut ( 'Writing OpenAI key...' ) ;
194
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
195
+ // Write openai key to $HOME/.openai-api-key without shell operator
196
+ const result = await client . docker . cli . exec ( 'volume' , [ 'create' , 'openai-key' ] ) ;
197
+ appendToRunOut ( JSON . stringify ( result ) ) ;
198
+ // appendToRunOut(runOut + '\nRunning...');
183
199
client . docker . cli . exec ( 'run' , getRunArgs ( selectedPrompt , selectedProject , client . host . hostname , client . host . platform ) , {
184
200
stream : {
185
201
onError : ( err ) => {
186
- setRunOut ( runOut + '\n' + err . message ) ;
202
+ // appendToRunOut( err.message);
187
203
} ,
188
204
onOutput : ( { stdout, stderr } ) => {
189
- setRunOut ( runOut + '\n' + ( stdout || stderr ) ) ;
205
+ // appendToRunOut (stdout || stderr || '' );
190
206
}
191
- }
207
+ } ,
192
208
} )
193
209
} } >
194
210
< Typography variant = 'h3' > Run</ Typography >
195
211
</ Button >
196
212
</ Paper >
197
- </ Grid >
213
+ </ Grid >
198
214
) : (
199
215
< Grid item xs = { 12 } >
200
- < Paper >
201
- You must select a project and a prompt to run.
216
+ < Paper sx = { { padding : 1 } } >
217
+ < Typography variant = 'h3' > Missing:</ Typography >
218
+ { selectedProject ?. length ? null : < Typography variant = 'body1' > - Project</ Typography > }
219
+ { selectedPrompt ?. length ? null : < Typography variant = 'body1' > - Prompt</ Typography > }
220
+ { openAIKey ?. length ? null : < Typography variant = 'body1' > - OpenAI Key</ Typography > }
202
221
</ Paper >
203
222
</ Grid >
204
- ) }
223
+ )
224
+ }
205
225
{ /* Show run output */ }
206
- { runOut && (
207
- < Grid item xs = { 12 } >
208
- < Paper >
209
- < Typography variant = 'h3' > Run output</ Typography >
210
- < pre > { runOut } </ pre >
211
- </ Paper >
212
- </ Grid >
213
- ) }
214
- </ Grid >
226
+ {
227
+ runOut && (
228
+ < Grid item xs = { 12 } >
229
+ < Paper >
230
+ < Typography variant = 'h3' > Run output</ Typography >
231
+ < textarea readOnly style = { { width : '100%' , minHeight : '100px' } } value = { runOut } />
232
+ </ Paper >
233
+ </ Grid >
234
+ )
235
+ }
236
+ </ Grid >
215
237
</ >
216
238
) ;
217
239
}
0 commit comments