@@ -2,9 +2,8 @@ import React, { useEffect } from 'react';
22import Button from '@mui/material/Button' ;
33import DelIcon from '@mui/icons-material/Delete' ;
44import { 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' ;
66import { getRunArgs } from './args' ;
7- import { on } from 'events' ;
87
98// Note: This line relies on Docker Desktop's presence as a host application.
109// 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)
2221
2322export function App ( ) {
2423 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 ) ;
2625
2726 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 ) ;
2928
30- const [ openAIKey , setOpenAIKey ] = React . useState < string | null > ( null ) ;
29+ const [ openAIKey , setOpenAIKey ] = React . useState < string | null > ( localStorage . getItem ( 'openAIKey' ) || '' ) ;
3130
3231 const [ promptInput , setPromptInput ] = React . useState < string > ( '' ) ;
3332
@@ -46,6 +45,14 @@ export function App() {
4645 localStorage . setItem ( 'openAIKey' , openAIKey || '' ) ;
4746 } , [ openAIKey ] ) ;
4847
48+ useEffect ( ( ) => {
49+ localStorage . setItem ( 'selectedProject' , selectedProject || '' ) ;
50+ } , [ selectedProject ] ) ;
51+
52+ useEffect ( ( ) => {
53+ localStorage . setItem ( 'selectedPrompt' , selectedPrompt || '' ) ;
54+ } , [ selectedPrompt ] ) ;
55+
4956
5057 useEffect ( ( ) => {
5158 // URL format: https://github.com/<owner>/<repo>/tree/<branch>/<path>
@@ -63,6 +70,10 @@ export function App() {
6370 }
6471 } , [ promptInput ] ) ;
6572
73+ const appendToRunOut = ( msg : string ) => {
74+ setRunOut ( runOut + '\n' + msg ) ;
75+ }
76+
6677 const delim = client . host . platform === 'win32' ? '\\' : '/' ;
6778
6879 return (
@@ -72,7 +83,7 @@ export function App() {
7283 < Paper sx = { { padding : 1 , pl : 2 } } >
7384 < Stack direction = 'row' spacing = { 2 } alignItems = { 'center' } justifyContent = { 'space-between' } >
7485 < 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' />
7687 </ Stack >
7788 </ Paper >
7889 </ Grid >
@@ -98,6 +109,7 @@ export function App() {
98109 < List >
99110 { projects . map ( ( project ) => (
100111 < ListItem
112+ key = { project }
101113 sx = { theme => ( { borderLeft : 'solid black 3px' , borderColor : selectedProject === project ? theme . palette . success . main : 'none' , my : 0.5 , padding : 0 } ) }
102114 secondaryAction = {
103115 < IconButton color = 'error' onClick = { ( ) => {
@@ -140,6 +152,7 @@ export function App() {
140152 < List >
141153 { prompts . map ( ( prompt ) => (
142154 < ListItem
155+ key = { prompt }
143156 sx = { theme => ( {
144157 borderLeft : 'solid black 3px' ,
145158 borderColor : selectedPrompt === prompt ? theme . palette . success . main : 'none' ,
@@ -169,49 +182,58 @@ export function App() {
169182 </ Paper >
170183 </ Grid >
171184 { /* Show row at bottom if selectProject AND selectedPrompt */ }
172- { selectedProject && selectedPrompt ? (
185+ { selectedProject && selectedPrompt && openAIKey ? (
173186 < Grid item xs = { 12 } >
174187 < Paper sx = { { padding : 1 } } >
175188 < 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 >
178191 < Button sx = { { mt : 1 , } } color = 'success' onClick = { async ( ) => {
179192 // 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...');
183199 client . docker . cli . exec ( 'run' , getRunArgs ( selectedPrompt , selectedProject , client . host . hostname , client . host . platform ) , {
184200 stream : {
185201 onError : ( err ) => {
186- setRunOut ( runOut + '\n' + err . message ) ;
202+ // appendToRunOut( err.message);
187203 } ,
188204 onOutput : ( { stdout, stderr } ) => {
189- setRunOut ( runOut + '\n' + ( stdout || stderr ) ) ;
205+ // appendToRunOut (stdout || stderr || '' );
190206 }
191- }
207+ } ,
192208 } )
193209 } } >
194210 < Typography variant = 'h3' > Run</ Typography >
195211 </ Button >
196212 </ Paper >
197- </ Grid >
213+ </ Grid >
198214 ) : (
199215 < 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 > }
202221 </ Paper >
203222 </ Grid >
204- ) }
223+ )
224+ }
205225 { /* 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 >
215237 </ >
216238 ) ;
217239}
0 commit comments