1- import { Box , Button , Grid , Stack } from "@mui/material" ;
2- import { lazy , useEffect , useState , useRef , useCallback } from "react" ;
3- import { useRecoilState , useRecoilValue } from "recoil" ;
1+ import { Liquid } from "liquidjs" ;
2+ import { Box , Button , Grid , Stack , Tab } from "@mui/material" ;
3+ import { TabContext , TabList , TabPanel } from "@mui/lab" ;
4+ import React , {
5+ lazy ,
6+ useEffect ,
7+ useState ,
8+ useRef ,
9+ useCallback ,
10+ useMemo ,
11+ } from "react" ;
12+ import { useRecoilState , useRecoilValue , useSetRecoilState } from "recoil" ;
413import {
514 apiBackendSelectedState ,
615 endpointConfigValueState ,
7- endpointSelectedState ,
816 inputValueState ,
917 isLoggedInState ,
10- templateValueState ,
18+ appRunDataState ,
1119} from "../data/atoms" ;
1220import {
1321 Messages ,
@@ -17,40 +25,88 @@ import {
1725import { Ws } from "../data/ws" ;
1826import { stitchObjects } from "../data/utils" ;
1927
28+ import { PromptlyAppWorkflowOutput } from "../components/apps/renderer/LayoutRenderer" ;
29+ import AceEditor from "react-ace" ;
30+ import "ace-builds/src-noconflict/mode-json" ;
31+ import "ace-builds/src-noconflict/theme-chrome" ;
32+
2033const ApiBackendSelector = lazy (
2134 ( ) => import ( "../components/ApiBackendSelector" ) ,
2235) ;
2336const ConfigForm = lazy ( ( ) => import ( "../components/ConfigForm" ) ) ;
2437const InputForm = lazy ( ( ) => import ( "../components/InputForm" ) ) ;
25- const Output = lazy ( ( ) => import ( "../components/Output" ) ) ;
2638const LoginDialog = lazy ( ( ) => import ( "../components/LoginDialog" ) ) ;
2739
40+ export function ThemedJsonEditor ( { data } ) {
41+ return (
42+ < AceEditor
43+ readOnly = { true }
44+ mode = "json"
45+ theme = "chrome"
46+ value = { JSON . stringify ( data , null , 2 ) }
47+ editorProps = { { $blockScrolling : true } }
48+ setOptions = { {
49+ useWorker : false ,
50+ showGutter : false ,
51+ } }
52+ />
53+ ) ;
54+ }
55+
56+ function Output ( props ) {
57+ const [ value , setValue ] = React . useState ( "form" ) ;
58+
59+ return (
60+ < Box sx = { { width : "100%" } } >
61+ < TabContext value = { value } >
62+ < Box sx = { { borderBottom : 1 , borderColor : "divider" } } >
63+ < TabList
64+ onChange = { ( event , newValue ) => {
65+ setValue ( newValue ) ;
66+ } }
67+ aria-label = "Output form tabs"
68+ >
69+ < Tab label = "Output" value = "form" />
70+ < Tab label = "JSON" value = "json" />
71+ </ TabList >
72+ </ Box >
73+ < TabPanel value = "form" sx = { { padding : "4px" } } >
74+ < PromptlyAppWorkflowOutput showHeader = { false } />
75+ </ TabPanel >
76+ < TabPanel value = "json" sx = { { padding : "4px" } } >
77+ < ThemedJsonEditor data = { props . jsonResult } />
78+ </ TabPanel >
79+ </ TabContext >
80+ </ Box >
81+ ) ;
82+ }
83+
2884export default function PlaygroundPage ( ) {
2985 const isLoggedIn = useRecoilValue ( isLoggedInState ) ;
3086 const [ input ] = useRecoilState ( inputValueState ) ;
3187 const appSessionId = useRef ( null ) ;
3288 const messagesRef = useRef ( new Messages ( ) ) ;
3389 const chunkedOutput = useRef ( { } ) ;
3490 const [ showLoginDialog , setShowLoginDialog ] = useState ( false ) ;
91+ const setAppRunData = useSetRecoilState ( appRunDataState ) ;
92+ const [ jsonResult , setJsonResult ] = useState ( { } ) ;
3593
36- const [ apiBackendSelected , setApiBackendSelected ] = useRecoilState (
37- apiBackendSelectedState ,
38- ) ;
39- const [ endpointSelected , setEndpointSelected ] = useRecoilState (
40- endpointSelectedState ,
41- ) ;
42- const [ paramValues , setParamValues ] = useRecoilState (
43- endpointConfigValueState ,
44- ) ;
45- const [ promptValues , setPromptValues ] = useRecoilState ( templateValueState ) ;
46- const [ output , setOutput ] = useState ( "" ) ;
47- const [ runError , setRunError ] = useState ( "" ) ;
48- const [ outputLoading , setOutputLoading ] = useState ( false ) ;
49- const [ tokenCount , setTokenCount ] = useState ( null ) ;
50- const [ processorResult , setProcessorResult ] = useState ( null ) ;
94+ const apiBackendSelected = useRecoilValue ( apiBackendSelectedState ) ;
95+ const templateEngine = useMemo ( ( ) => new Liquid ( ) , [ ] ) ;
96+ const [ outputTemplate , setOutputTemplate ] = useState ( null ) ;
97+
98+ const paramValues = useRecoilValue ( endpointConfigValueState ) ;
99+ useEffect ( ( ) => {
100+ if ( apiBackendSelected ) {
101+ setOutputTemplate (
102+ templateEngine . parse (
103+ apiBackendSelected . output_template || "{{ output }}" ,
104+ ) ,
105+ ) ;
106+ }
107+ } , [ templateEngine , apiBackendSelected , setOutputTemplate ] ) ;
51108
52109 const [ ws , setWs ] = useState ( null ) ;
53- const [ appRunData , setAppRunData ] = useState ( { } ) ;
54110
55111 const wsUrlPrefix = `${
56112 window . location . protocol === "https:" ? "wss" : "ws"
@@ -75,7 +131,7 @@ export default function PlaygroundPage() {
75131
76132 // Add messages from the session to the message list
77133 setAppRunData ( ( prevState ) => {
78- prevState ?. playground_messages ?. forEach ( ( message ) => {
134+ prevState ?. messages ?. forEach ( ( message ) => {
79135 messagesRef . current . add ( message ) ;
80136 } ) ;
81137
@@ -111,7 +167,7 @@ export default function PlaygroundPage() {
111167 isStreaming : false ,
112168 isRateLimited : true ,
113169 errors : [ "Rate limit exceeded" ] ,
114- playground_messages : messagesRef . current . get ( ) ,
170+ messages : messagesRef . current . get ( ) ,
115171 } ) ) ;
116172 }
117173
@@ -132,7 +188,7 @@ export default function PlaygroundPage() {
132188 isStreaming : false ,
133189 isUsageLimited : true ,
134190 errors : [ "Usage limit exceeded" ] ,
135- playground_messages : messagesRef . current . get ( ) ,
191+ messages : messagesRef . current . get ( ) ,
136192 } ) ) ;
137193
138194 // If the user is not logged in, show the login dialog
@@ -153,7 +209,7 @@ export default function PlaygroundPage() {
153209 isRunning : false ,
154210 isStreaming : false ,
155211 errors : message . errors ,
156- playground_messages : messagesRef . current . get ( ) ,
212+ messages : messagesRef . current . get ( ) ,
157213 } ) ) ;
158214 chunkedOutput . current = { } ;
159215 }
@@ -166,56 +222,32 @@ export default function PlaygroundPage() {
166222 }
167223
168224 if ( message . id && message . output ) {
169- const newMessage = message . output ;
170- messagesRef . current . add (
171- new AppMessage (
172- message . id ,
173- message . request_id ,
174- message . output ,
175- message . reply_to ,
176- ) ,
177- ) ;
178- setAppRunData ( ( prevState ) => ( {
179- ...prevState ,
180- playground_messages : messagesRef . current . get ( ) ,
181- isStreaming : newMessage . content !== null ,
182- } ) ) ;
225+ templateEngine
226+ . render ( outputTemplate , {
227+ output : JSON . stringify ( chunkedOutput . current ?. output ) || "{}" ,
228+ } )
229+ . then ( ( newMessage ) => {
230+ messagesRef . current . add (
231+ new AppMessage (
232+ message . id ,
233+ message . request_id ,
234+ newMessage ,
235+ message . reply_to ,
236+ ) ,
237+ ) ;
238+ setAppRunData ( ( prevState ) => ( {
239+ ...prevState ,
240+ messages : messagesRef . current . get ( ) ,
241+ isStreaming : newMessage . content !== null ,
242+ } ) ) ;
243+ } ) ;
244+ setJsonResult ( chunkedOutput . current ?. output ) ;
183245 }
184246 } ) ;
185247 }
186248
187- useEffect ( ( ) => {
188- if ( appRunData && ! appRunData ?. isRunning && ! appRunData ?. isStreaming ) {
189- if ( appRunData ?. playground_messages ) {
190- const lastMessage =
191- appRunData ?. playground_messages [
192- appRunData ?. playground_messages . length - 1
193- ] ;
194- if ( lastMessage ) {
195- setOutputLoading ( false ) ;
196- setProcessorResult ( lastMessage ?. content ?. output ) ;
197- if ( lastMessage ?. content ?. output ) {
198- if ( lastMessage ?. content ?. output ?. generations ) {
199- setOutput ( lastMessage ?. content ?. output ?. generations ) ;
200- } else if ( lastMessage ?. content ?. output ?. chat_completions ) {
201- setOutput ( lastMessage ?. content ?. output ?. chat_completions ) ;
202- } else {
203- setOutput ( [ lastMessage ?. content ?. output ] ) ;
204- }
205- }
206- if ( lastMessage ?. content ?. errors ) {
207- setRunError ( lastMessage ?. content ?. errors ) ;
208- }
209- }
210- }
211- }
212- } , [ appRunData ] ) ;
213-
214249 const runApp = useCallback (
215250 ( sessionId , input ) => {
216- setRunError ( "" ) ;
217- setOutputLoading ( true ) ;
218-
219251 chunkedOutput . current = { } ;
220252 const requestId = Math . random ( ) . toString ( 36 ) . substring ( 2 ) ;
221253
@@ -224,7 +256,7 @@ export default function PlaygroundPage() {
224256 isRunning : true ,
225257 isStreaming : false ,
226258 errors : null ,
227- playground_messages : messagesRef . current . get ( ) ,
259+ messages : messagesRef . current . get ( ) ,
228260 input,
229261 } ) ) ;
230262
@@ -260,18 +292,6 @@ export default function PlaygroundPage() {
260292 ) ;
261293 } ;
262294
263- useEffect ( ( ) => {
264- setTokenCount ( null ) ;
265- setOutput ( "" ) ;
266- } , [
267- setApiBackendSelected ,
268- setEndpointSelected ,
269- setParamValues ,
270- setPromptValues ,
271- ] ) ;
272-
273- useEffect ( ( ) => { } , [ paramValues , promptValues ] ) ;
274-
275295 return (
276296 < Box sx = { { margin : "10px 2px" } } >
277297 { showLoginDialog && (
@@ -314,17 +334,7 @@ export default function PlaygroundPage() {
314334 />
315335 </ Grid >
316336 < Grid item xs = { 12 } md = { 4 } >
317- < Output
318- result = { output }
319- endpoint = { endpointSelected }
320- loading = { outputLoading }
321- loadingTip = { "Running the input..." }
322- runError = { runError }
323- tokenCount = { tokenCount }
324- schema = { apiBackendSelected ?. output_schema || { } }
325- uiSchema = { apiBackendSelected ?. output_ui_schema || { } }
326- formData = { processorResult || { } }
327- />
337+ < Output jsonResult = { jsonResult } />
328338 </ Grid >
329339 </ Grid >
330340 </ Stack >
0 commit comments