@@ -2,12 +2,18 @@ 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 { IconButton , Link , List , ListItem , ListItemButton , ListItemText , Paper , Stack , TextField , Typography } from '@mui/material' ;
5+ import { Chip , IconButton , Link , List , ListItem , ListItemButton , ListItemText , Paper , Stack , TextField , Typography } from '@mui/material' ;
66import { getRunArgs } from './args' ;
77import Convert from 'ansi-to-html' ;
88
99const convert = new Convert ( { newline : true } ) ;
1010
11+ type RPCMessage = {
12+ jsonrpc ?: string ;
13+ method : string ;
14+ params : any ;
15+ }
16+
1117// Note: This line relies on Docker Desktop's presence as a host application.
1218// If you're running this React app in a browser, it won't work properly.
1319const client = createDockerDesktopClient ( ) ;
@@ -33,10 +39,11 @@ export function App() {
3339
3440 const [ promptInput , setPromptInput ] = React . useState < string > ( '' ) ;
3541
36- const [ runOut , setRunOut ] = React . useState < string > ( '' ) ;
42+ const [ runOut , setRunOut ] = React . useState < RPCMessage [ ] > ( [ ] ) ;
3743
3844 const scrollRef = React . useRef < HTMLDivElement > ( null ) ;
3945
46+ const [ showDebug , setShowDebug ] = React . useState ( false ) ;
4047
4148 useEffect ( ( ) => {
4249 localStorage . setItem ( 'projects' , JSON . stringify ( projects ) ) ;
@@ -90,12 +97,38 @@ export function App() {
9097 const delim = client . host . platform === 'win32' ? '\\' : '/' ;
9198
9299 const startPrompt = async ( ) => {
93- let output = ""
94- const updateOutput = ( data : string ) => {
95- output += data ;
100+ let output : RPCMessage [ ] = [ ]
101+ const updateOutput = ( line : RPCMessage ) => {
102+ if ( line . method === 'functions' ) {
103+ const functions = line . params ;
104+ for ( const func of functions ) {
105+ const functionId = func . id ;
106+ const existingFunction = output . find ( o =>
107+ o . method === 'functions'
108+ &&
109+ o . params . find ( ( p : { id : string } ) => p . id === functionId )
110+ ) ;
111+ if ( existingFunction ) {
112+ const existingFunctionParamsIndex = existingFunction . params . findIndex ( ( p : { id : string } ) => p . id === functionId ) ;
113+ existingFunction . params [ existingFunctionParamsIndex ] = { ...existingFunction . params [ existingFunctionParamsIndex ] , ...func } ;
114+ output = output . map (
115+ o => o . method === 'functions'
116+ ?
117+ { ...o , params : o . params . map ( ( p : { id : string } ) => p . id === functionId ? { ...p , ...func } : p ) }
118+ :
119+ o
120+ ) ;
121+ } else {
122+ output = [ ...output , line ] ;
123+ }
124+ }
125+ }
126+ else {
127+ output = [ ...output , line ] ;
128+ }
96129 setRunOut ( output ) ;
97130 }
98- updateOutput ( " Pulling images\n" )
131+ updateOutput ( { method : 'message' , params : { debug : ' Pulling images' } } )
99132 try {
100133 const pullWriteFiles = await client . docker . cli . exec ( "pull" , [ "vonwig/function_write_files" ] ) ;
101134 const pullPrompts = await client . docker . cli . exec ( "pull" , [ "vonwig/prompts" ] ) ;
@@ -106,12 +139,12 @@ export function App() {
106139 "vonwig/function_write_files" ,
107140 `'` + JSON . stringify ( { files : [ { path : ".openai-api-key" , content : openAIKey , executable : false } ] } ) + `'`
108141 ] ) ;
109- updateOutput ( JSON . stringify ( { pullWriteFiles, pullPrompts, writeKey } ) ) ;
142+ updateOutput ( { method : 'message' , params : { debug : JSON . stringify ( { pullWriteFiles, pullPrompts, writeKey } ) } } ) ;
110143 }
111144 catch ( e ) {
112- updateOutput ( JSON . stringify ( e ) ) ;
145+ updateOutput ( { method : 'message' , params : { debug : JSON . stringify ( e ) } } ) ;
113146 }
114- updateOutput ( " Running prompts\n" )
147+ updateOutput ( { method : 'message' , params : { debug : ' Running prompts...' } } )
115148 const args = getRunArgs ( selectedPrompt ! , selectedProject ! , "" , client . host . platform )
116149
117150 client . docker . cli . exec ( "run" , args , {
@@ -120,24 +153,33 @@ export function App() {
120153 onOutput : ( { stdout, stderr } ) => {
121154 if ( stdout && stdout . startsWith ( '{' ) ) {
122155 let rpcMessage = stdout . split ( '}Content-Length:' ) [ 0 ]
123- if ( ! rpcMessage . endsWith ( '}' ) ) {
156+ if ( ! rpcMessage . endsWith ( '}} ' ) ) {
124157 rpcMessage += '}'
125158 }
126159 const json = JSON . parse ( rpcMessage )
127- if ( json . params . content ) {
128- output += json . params . content
129- }
160+ updateOutput ( json )
161+ // {
162+ // "jsonrpc": "2.0",
163+ // "method": "functions",
164+ // "params": [
165+ // {
166+ // "function": {
167+ // "name": "run-eslint",
168+ // "arguments": "{\n \""
169+ // },
170+ // "id": "call_53E2o4fq1QEmIHixWcKZmOqo"
171+ // }
172+ // ]
173+ // }
130174 }
131175 if ( stderr ) {
132- output += stderr
176+ updateOutput ( { method : 'message' , params : { debug : stderr } } ) ;
133177 }
134- setRunOut ( output ) ;
135178 } ,
136179 onError : ( err ) => {
137180 console . error ( err ) ;
138- output += err ;
139- setRunOut ( output ) ;
140- }
181+ updateOutput ( { method : 'message' , params : { debug : err } } ) ;
182+ } ,
141183 }
142184 } ) ;
143185 }
@@ -196,18 +238,31 @@ export function App() {
196238 { /* Prompts column */ }
197239 < Paper sx = { { padding : 1 } } >
198240 < Typography variant = "h3" > Prompts</ Typography >
199- < TextField
200- sx = { { width : '100%' , mt : 1 } }
201- placeholder = 'Enter GitHub ref or URL'
202- value = { promptInput }
203- onChange = { ( e ) => setPromptInput ( e . target . value ) }
204- />
205- { promptInput . length > 0 && (
241+ < Stack direction = 'row' spacing = { 1 } alignItems = { 'center' } justifyContent = { 'space-between' } >
242+ < TextField
243+ fullWidth
244+ placeholder = 'Enter GitHub ref or URL'
245+ value = { promptInput }
246+ onChange = { ( e ) => setPromptInput ( e . target . value ) }
247+ />
248+ { promptInput . length > 0 && (
249+ < Button onClick = { ( ) => {
250+ setPrompts ( [ ...prompts , promptInput ] ) ;
251+ setPromptInput ( '' ) ;
252+ } } > Add prompt</ Button >
253+ ) }
206254 < Button onClick = { ( ) => {
207- setPrompts ( [ ...prompts , promptInput ] ) ;
208- setPromptInput ( '' ) ;
209- } } > Add prompt</ Button >
210- ) }
255+ client . desktopUI . dialog . showOpenDialog ( {
256+ properties : [ 'openDirectory' , 'multiSelections' ]
257+ } ) . then ( ( result ) => {
258+ if ( result . canceled ) {
259+ return ;
260+ }
261+ setPrompts ( [ ...prompts , ...result . filePaths . map ( p => `local://${ p } ` ) ] ) ;
262+ } ) ;
263+ } } > Add local prompt</ Button >
264+ </ Stack >
265+
211266 < List >
212267 { prompts . map ( ( prompt ) => (
213268 < ListItem
@@ -232,8 +287,12 @@ export function App() {
232287 } >
233288 < ListItemButton sx = { { padding : 0 , pl : 1.5 } } onClick = { ( ) => {
234289 setSelectedPrompt ( prompt ) ;
235- } } >
236- < ListItemText primary = { prompt . split ( delim ) . pop ( ) } secondary = { prompt } />
290+ } } > {
291+ prompt . startsWith ( 'local://' ) ?
292+ < > < ListItemText primary = { < > { prompt . split ( delim ) . pop ( ) } < Chip sx = { { ml : 1 } } label = 'local' /> </ > } secondary = { prompt . replace ( 'local://' , '' ) } /> </ >
293+ :
294+ < ListItemText primary = { prompt . split ( '/' ) . pop ( ) } secondary = { prompt } />
295+ }
237296 </ ListItemButton >
238297 </ ListItem >
239298 ) ) }
@@ -259,10 +318,30 @@ export function App() {
259318 ) }
260319 { /* Show run output */ }
261320 {
262- runOut && (
321+ runOut . length > 0 && (
263322 < Paper sx = { { p : 1 } } >
264- < Typography variant = 'h3' > Run output</ Typography >
265- < div style = { { whiteSpace : 'pre-wrap' } } dangerouslySetInnerHTML = { { __html : convert . toHtml ( runOut ) } } />
323+ < Stack direction = 'row' spacing = { 1 } alignItems = { 'center' } justifyContent = { 'space-between' } >
324+ < Typography variant = 'h3' > Run output</ Typography >
325+ < Button onClick = { ( ) => setShowDebug ( ! showDebug ) } > { showDebug ? 'Hide' : 'Show' } debug</ Button >
326+ </ Stack >
327+
328+ < div style = { { overflow : 'auto' , maxHeight : '100vh' } } >
329+ { runOut . map ( ( line , i ) => {
330+ if ( line . method === 'message' ) {
331+ if ( line . params . debug ) {
332+ return showDebug ? < Typography key = { i } variant = 'body1' sx = { theme => ( { color : theme . palette . docker . grey [ 400 ] } ) } > { line . params . debug } </ Typography > : null ;
333+ }
334+ if ( line . params . role === 'assistant' ) {
335+ return < Typography key = { i } variant = 'body1' sx = { theme => ( { color : theme . palette . docker . blue [ 400 ] } ) } > { line . params . content } </ Typography >
336+ }
337+ return < pre key = { i } style = { { whiteSpace : 'pre-wrap' , display : 'inline' } } dangerouslySetInnerHTML = { { __html : convert . toHtml ( line . params . content ) } } />
338+ }
339+ if ( line . method === 'functions' ) {
340+ return < Typography key = { i } variant = 'body1' sx = { { whiteSpace : 'pre-wrap' } } > { JSON . stringify ( line . params , null , 2 ) } </ Typography >
341+ }
342+ return < Typography key = { i } variant = 'body1' > { JSON . stringify ( line ) } </ Typography >
343+ } ) }
344+ </ div >
266345 </ Paper >
267346 )
268347 }
0 commit comments