Skip to content

Commit 91c74e9

Browse files
authored
Update Playground (#214)
1 parent f196a4e commit 91c74e9

File tree

4 files changed

+110
-264
lines changed

4 files changed

+110
-264
lines changed

llmstack/client/src/pages/playground.jsx

Lines changed: 104 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
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";
413
import {
514
apiBackendSelectedState,
615
endpointConfigValueState,
7-
endpointSelectedState,
816
inputValueState,
917
isLoggedInState,
10-
templateValueState,
18+
appRunDataState,
1119
} from "../data/atoms";
1220
import {
1321
Messages,
@@ -17,40 +25,88 @@ import {
1725
import { Ws } from "../data/ws";
1826
import { 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+
2033
const ApiBackendSelector = lazy(
2134
() => import("../components/ApiBackendSelector"),
2235
);
2336
const ConfigForm = lazy(() => import("../components/ConfigForm"));
2437
const InputForm = lazy(() => import("../components/InputForm"));
25-
const Output = lazy(() => import("../components/Output"));
2638
const 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+
2884
export 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

Comments
 (0)