1
1
import React , { useEffect } from 'react' ;
2
- import Button from '@mui/material/Button' ;
3
- import DelIcon from '@mui/icons-material/Delete' ;
4
2
import { createDockerDesktopClient } from '@docker/extension-api-client' ;
5
- import { Chip , IconButton , Link , List , ListItem , ListItemButton , ListItemText , Paper , Stack , TextField , Typography } from '@mui/material' ;
3
+ import { Paper , Stack , Typography , Button } from '@mui/material' ;
6
4
import { getRunArgs } from './args' ;
7
- import Convert from 'ansi-to-html' ;
5
+ import OpenAIKey from './components/OpenAIKey' ;
6
+ import Projects from './components/Projects' ;
7
+ import Prompts from './components/Prompts' ;
8
+ import RunOutput from './components/RunOutput' ;
8
9
9
- const convert = new Convert ( { newline : true } ) ;
10
-
11
- type RPCMessage = {
12
- jsonrpc ?: string ;
13
- method : string ;
14
- params : any ;
15
- }
16
-
17
- // Note: This line relies on Docker Desktop's presence as a host application.
18
- // If you're running this React app in a browser, it won't work properly.
19
10
const client = createDockerDesktopClient ( ) ;
20
11
21
12
const track = ( event : string ) =>
@@ -42,7 +33,7 @@ export function App() {
42
33
43
34
const [ promptInput , setPromptInput ] = React . useState < string > ( '' ) ;
44
35
45
- const [ runOut , setRunOut ] = React . useState < RPCMessage [ ] > ( [ ] ) ;
36
+ const [ runOut , setRunOut ] = React . useState < any [ ] > ( [ ] ) ;
46
37
47
38
const scrollRef = React . useRef < HTMLDivElement > ( null ) ;
48
39
@@ -53,13 +44,19 @@ export function App() {
53
44
if ( ! selectedProject && projects . length > 0 ) {
54
45
setSelectedProject ( projects [ 0 ] ) ;
55
46
}
47
+ if ( selectedProject && projects . length === 0 ) {
48
+ setSelectedPrompt ( null ) ;
49
+ }
56
50
} , [ projects ] ) ;
57
51
58
52
useEffect ( ( ) => {
59
53
localStorage . setItem ( 'prompts' , JSON . stringify ( prompts ) ) ;
60
54
if ( ! selectedPrompt && prompts . length > 0 ) {
61
55
setSelectedPrompt ( prompts [ 0 ] ) ;
62
56
}
57
+ if ( selectedProject && prompts . length === 0 ) {
58
+ setSelectedPrompt ( null ) ;
59
+ }
63
60
} , [ prompts ] ) ;
64
61
65
62
useEffect ( ( ) => {
@@ -76,10 +73,7 @@ export function App() {
76
73
} , [ selectedPrompt ] ) ;
77
74
78
75
useEffect ( ( ) => {
79
- // URL format: https://github.com/<owner>/<repo>/tree/<branch>/<path>
80
- // REF format: github.com:<owner>/<repo>?ref=<branch>&path=<path>
81
76
if ( promptInput ?. startsWith ( 'http' ) ) {
82
- // Convert URL to REF
83
77
const url = new URL ( promptInput ) ;
84
78
const registry = url . hostname . split ( '.' ) . reverse ( ) . slice ( 1 ) . reverse ( ) . join ( '.' ) ;
85
79
const owner = url . pathname . split ( '/' ) [ 1 ] ;
@@ -97,12 +91,10 @@ export function App() {
97
91
}
98
92
} , [ runOut ] ) ;
99
93
100
- const delim = client . host . platform === 'win32' ? '\\' : '/' ;
101
-
102
94
const startPrompt = async ( ) => {
103
95
track ( 'start-prompt' ) ;
104
- let output : RPCMessage [ ] = [ ]
105
- const updateOutput = ( line : RPCMessage ) => {
96
+ let output : any [ ] = [ ]
97
+ const updateOutput = ( line : any ) => {
106
98
if ( line . method === 'functions' ) {
107
99
const functions = line . params ;
108
100
for ( const func of functions ) {
@@ -162,19 +154,6 @@ export function App() {
162
154
}
163
155
const json = JSON . parse ( rpcMessage )
164
156
updateOutput ( json )
165
- // {
166
- // "jsonrpc": "2.0",
167
- // "method": "functions",
168
- // "params": [
169
- // {
170
- // "function": {
171
- // "name": "run-eslint",
172
- // "arguments": "{\n \""
173
- // },
174
- // "id": "call_53E2o4fq1QEmIHixWcKZmOqo"
175
- // }
176
- // ]
177
- // }
178
157
}
179
158
if ( stderr ) {
180
159
updateOutput ( { method : 'message' , params : { debug : stderr } } ) ;
@@ -192,120 +171,9 @@ export function App() {
192
171
return (
193
172
< div style = { { overflow : 'auto' , maxHeight : '100vh' } } ref = { scrollRef } >
194
173
< Stack direction = "column" spacing = { 1 } >
195
- < Paper sx = { { padding : 1 } } >
196
- < Typography variant = 'h3' > OpenAI Key</ Typography >
197
- < TextField sx = { { mt : 1 , width : '100%' } } onChange = { e => setOpenAIKey ( e . target . value ) } value = { openAIKey || '' } placeholder = 'Enter OpenAI API key' type = 'password' />
198
- </ Paper >
199
- { /* Projects column */ }
200
- < Paper sx = { { padding : 1 } } >
201
- < Typography variant = 'h3' > Projects</ Typography >
202
- < Stack direction = 'row' spacing = { 1 } sx = { { mt : 1 } } alignItems = { 'center' } justifyContent = { 'space-between' } >
203
- < Button sx = { { padding : 1 } } onClick = { ( ) => {
204
- client . desktopUI . dialog . showOpenDialog ( {
205
- properties : [ 'openDirectory' , 'multiSelections' ]
206
- } ) . then ( ( result ) => {
207
- if ( result . canceled ) {
208
- return ;
209
- }
210
- const newProjects = result . filePaths
211
- setProjects ( [ ...projects , ...newProjects ] ) ;
212
- } ) ;
213
- } } >
214
- Add project
215
- </ Button >
216
- </ Stack >
217
- < List >
218
- { projects . map ( ( project ) => (
219
- < ListItem
220
- key = { project }
221
- sx = { theme => ( { borderLeft : 'solid black 3px' , borderColor : selectedProject === project ? theme . palette . success . main : 'none' , my : 0.5 , padding : 0 } ) }
222
- secondaryAction = {
223
- < IconButton color = 'error' onClick = { ( ) => {
224
- // Confirm
225
- const confirm = window . confirm ( `Are you sure you want to remove ${ project } ?` ) ;
226
- if ( ! confirm ) {
227
- return ;
228
- }
229
- setProjects ( projects . filter ( ( p ) => p !== project ) ) ;
230
- } } >
231
- < DelIcon />
232
- </ IconButton >
233
- } >
234
- < ListItemButton sx = { { padding : 0 , pl : 1.5 } } onClick = { ( ) => {
235
- setSelectedProject ( project ) ;
236
- } } >
237
- < ListItemText primary = { project . split ( delim ) . pop ( ) } secondary = { project } />
238
- </ ListItemButton >
239
- </ ListItem >
240
- ) ) }
241
- </ List >
242
- </ Paper >
243
- { /* Prompts column */ }
244
- < Paper sx = { { padding : 1 } } >
245
- < Typography variant = "h3" > Prompts</ Typography >
246
- < Stack direction = 'row' spacing = { 1 } alignItems = { 'center' } justifyContent = { 'space-between' } >
247
- < TextField
248
- fullWidth
249
- placeholder = 'Enter GitHub ref or URL'
250
- value = { promptInput }
251
- onChange = { ( e ) => setPromptInput ( e . target . value ) }
252
- />
253
- { promptInput . length > 0 && (
254
- < Button onClick = { ( ) => {
255
- setPrompts ( [ ...prompts , promptInput ] ) ;
256
- setPromptInput ( '' ) ;
257
- track ( 'add-prompt' ) ;
258
- } } > Add prompt</ Button >
259
- ) }
260
- < Button onClick = { ( ) => {
261
- client . desktopUI . dialog . showOpenDialog ( {
262
- properties : [ 'openDirectory' , 'multiSelections' ]
263
- } ) . then ( ( result ) => {
264
- if ( result . canceled ) {
265
- return ;
266
- }
267
- track ( 'add-local-prompt' ) ;
268
- setPrompts ( [ ...prompts , ...result . filePaths . map ( p => `local://${ p } ` ) ] ) ;
269
- } ) ;
270
- } } > Add local prompt</ Button >
271
- </ Stack >
272
-
273
- < List >
274
- { prompts . map ( ( prompt ) => (
275
- < ListItem
276
- key = { prompt }
277
- sx = { theme => ( {
278
- borderLeft : 'solid black 3px' ,
279
- borderColor : selectedPrompt === prompt ? theme . palette . success . main : 'none' ,
280
- my : 0.5 ,
281
- padding : 0
282
- } ) }
283
- secondaryAction = {
284
- < IconButton color = 'error' onClick = { ( ) => {
285
- // Confirm
286
- const confirm = window . confirm ( `Are you sure you want to remove ${ prompt } ?` ) ;
287
- if ( ! confirm ) {
288
- return ;
289
- }
290
- setPrompts ( prompts . filter ( ( p ) => p !== prompt ) ) ;
291
- } } >
292
- < DelIcon />
293
- </ IconButton >
294
- } >
295
- < ListItemButton sx = { { padding : 0 , pl : 1.5 } } onClick = { ( ) => {
296
- setSelectedPrompt ( prompt ) ;
297
- } } > {
298
- prompt . startsWith ( 'local://' ) ?
299
- < > < ListItemText primary = { < > { prompt . split ( delim ) . pop ( ) } < Chip sx = { { ml : 1 } } label = 'local' /> </ > } secondary = { prompt . replace ( 'local://' , '' ) } /> </ >
300
- :
301
- < ListItemText primary = { prompt . split ( '/' ) . pop ( ) } secondary = { prompt } />
302
- }
303
- </ ListItemButton >
304
- </ ListItem >
305
- ) ) }
306
- </ List >
307
- </ Paper >
308
- { /* Show row at bottom if selectProject AND selectedPrompt */ }
174
+ < OpenAIKey openAIKey = { openAIKey || '' } setOpenAIKey = { setOpenAIKey } />
175
+ < Projects projects = { projects } selectedProject = { selectedProject } setProjects = { setProjects } setSelectedProject = { setSelectedProject } />
176
+ < Prompts prompts = { prompts } selectedPrompt = { selectedPrompt } promptInput = { promptInput } setPrompts = { setPrompts } setSelectedPrompt = { setSelectedPrompt } setPromptInput = { setPromptInput } track = { track } />
309
177
{ selectedProject && selectedPrompt && openAIKey ? (
310
178
< Paper sx = { { padding : 1 } } >
311
179
< Typography variant = "h3" > Ready</ Typography >
@@ -323,35 +191,7 @@ export function App() {
323
191
{ openAIKey ?. length ? null : < Typography variant = 'body1' > - OpenAI Key</ Typography > }
324
192
</ Paper >
325
193
) }
326
- { /* Show run output */ }
327
- {
328
- runOut . length > 0 && (
329
- < Paper sx = { { p : 1 } } >
330
- < Stack direction = 'row' spacing = { 1 } alignItems = { 'center' } justifyContent = { 'space-between' } >
331
- < Typography variant = 'h3' > Run output</ Typography >
332
- < Button onClick = { ( ) => setShowDebug ( ! showDebug ) } > { showDebug ? 'Hide' : 'Show' } debug</ Button >
333
- </ Stack >
334
-
335
- < div style = { { overflow : 'auto' , maxHeight : '100vh' } } >
336
- { runOut . map ( ( line , i ) => {
337
- if ( line . method === 'message' ) {
338
- if ( line . params . debug ) {
339
- return showDebug ? < Typography key = { i } variant = 'body1' sx = { theme => ( { color : theme . palette . docker . grey [ 400 ] } ) } > { line . params . debug } </ Typography > : null ;
340
- }
341
- return < pre key = { i } style = { { whiteSpace : 'pre-wrap' , display : 'inline' } } dangerouslySetInnerHTML = { { __html : convert . toHtml ( line . params . content ) } } />
342
- }
343
- if ( line . method === 'functions' ) {
344
- return < Typography key = { i } variant = 'body1' sx = { theme => ( { whiteSpace : 'pre-wrap' , backgroundColor : theme . palette . docker . grey [ 300 ] , p : 1 } ) } > { JSON . stringify ( line . params , null , 2 ) } </ Typography >
345
- }
346
- if ( line . method === 'functions-done' ) {
347
- return showDebug ? < Typography key = { i } variant = 'body1' sx = { { whiteSpace : 'pre-wrap' } } > { JSON . stringify ( line . params , null , 2 ) } </ Typography > : null ;
348
- }
349
- return < Typography key = { i } variant = 'body1' > { JSON . stringify ( line ) } </ Typography >
350
- } ) }
351
- </ div >
352
- </ Paper >
353
- )
354
- }
194
+ < RunOutput runOut = { runOut } showDebug = { showDebug } setShowDebug = { setShowDebug } />
355
195
</ Stack >
356
196
</ div >
357
197
)
0 commit comments