Spaces:
Runtime error
Runtime error
import { useEffect, useRef, useState } from "react"; | |
import axios from "axios"; | |
import AcUnitIcon from "@mui/icons-material/AcUnit"; | |
import LocalFireDepartmentIcon from "@mui/icons-material/LocalFireDepartment"; | |
import CheckIcon from "@mui/icons-material/Check"; | |
import ClearIcon from "@mui/icons-material/Clear"; | |
import CodeIcon from "@mui/icons-material/Code"; | |
import CodeOffIcon from "@mui/icons-material/CodeOff"; | |
import EditIcon from "@mui/icons-material/Edit"; | |
import VisibilityIcon from "@mui/icons-material/Visibility"; | |
import DeleteForeverIcon from "@mui/icons-material/DeleteForever"; | |
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; | |
import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | |
import ReplayIcon from "@mui/icons-material/Replay"; | |
import MoneyIcon from "@mui/icons-material/Money"; | |
import TollIcon from "@mui/icons-material/Toll"; | |
import TextField from "@mui/material/TextField"; | |
import Box from "@mui/material/Box"; | |
import Stack from "@mui/material/Stack"; | |
import Accordion from "@mui/material/Accordion"; | |
import Typography from "@mui/material/Typography"; | |
import AccordionSummary from "@mui/material/AccordionSummary"; | |
import AccordionDetails from "@mui/material/AccordionDetails"; | |
import Paper from "@mui/material/Paper"; | |
import IconButton from "@mui/material/IconButton"; | |
import List from "@mui/material/List"; | |
import ListItem from "@mui/material/ListItem"; | |
import { nanoid } from "nanoid"; | |
import AppBar from "@mui/material/AppBar"; | |
import Toolbar from "@mui/material/Toolbar"; | |
import ListItemIcon from "@mui/material/ListItemIcon"; | |
import ListItemButton from "@mui/material/ListItemButton"; | |
import ListItemText from "@mui/material/ListItemText"; | |
import { useHost } from "esdeka/react"; | |
import CircularProgress from "@mui/material/CircularProgress"; | |
import CssBaseline from "@mui/material/CssBaseline"; | |
import Slider from "@mui/material/Slider"; | |
import { useAtom } from "jotai"; | |
import Button from "@mui/material/Button"; | |
import dynamic from "next/dynamic"; | |
import FormControl from "@mui/material/FormControl"; | |
import InputLabel from "@mui/material/InputLabel"; | |
import Select from "@mui/material/Select"; | |
import MenuItem from "@mui/material/MenuItem"; | |
import { fontMono } from "@/lib/theme"; | |
import { useColorScheme } from "@mui/material/styles"; | |
import { getTheme, prettify } from "@/utils"; | |
import { answersAtom, showCodeAtom } from "@/store/atoms"; | |
import { base } from "@/constants"; | |
import { EditTitle } from "@/components/EditTitle"; | |
const MonacoEditor = dynamic(import("@monaco-editor/react"), { ssr: false }); | |
export default function Home() { | |
const ref = useRef<HTMLIFrameElement>(null); | |
const [template, setTemplate] = useState(prettify(base.default)); | |
const [runningId, setRunningId] = useState("1"); | |
const [activeId, setActiveId] = useState("1"); | |
const [editingId, setEditingId] = useState<string | null>(null); | |
const [answers, setAnswers] = useAtom(answersAtom); | |
const [showCode, setShowCode] = useAtom(showCodeAtom); | |
const [loading, setLoading] = useState(false); | |
const [loadingLive, setLoadingLive] = useState(true); | |
const { mode, systemMode } = useColorScheme(); | |
const { call, subscribe } = useHost(ref, "fail4"); | |
const connection = useRef(false); | |
const [tries, setTries] = useState(1); | |
// Send a connection request | |
useEffect(() => { | |
const current = answers.find(({ id }) => id === runningId); | |
if (connection.current || tries <= 0) { | |
return () => { | |
/* Consistency */ | |
}; | |
} | |
const timeout = setTimeout(() => { | |
if (current) { | |
call({ template: current.content }); | |
} | |
setTries(tries - 1); | |
}, 1_000); | |
return () => { | |
clearTimeout(timeout); | |
}; | |
}, [call, tries, answers, runningId]); | |
useEffect(() => { | |
if (!connection.current && loadingLive) { | |
const unsubscribe = subscribe(event => { | |
const { action } = event.data; | |
switch (action.type) { | |
case "answer": | |
connection.current = true; | |
setLoadingLive(false); | |
console.log("connected"); | |
break; | |
default: | |
break; | |
} | |
}); | |
return () => { | |
unsubscribe(); | |
}; | |
} | |
return () => { | |
/* Consistency */ | |
}; | |
}, [subscribe, loadingLive]); | |
const current = answers.find(({ id }) => id === activeId); | |
function reload() { | |
connection.current = false; | |
if (ref.current) { | |
ref.current.src = `/live?${nanoid()}`; | |
setLoadingLive(true); | |
setTries(1); | |
} | |
} | |
return ( | |
<> | |
<CssBaseline /> | |
<Stack | |
sx={{ | |
...fontMono.style, | |
position: "absolute", | |
inset: 0, | |
overflow: "hidden", | |
flexDirection: "row", | |
height: "100%", | |
}} | |
> | |
<Stack sx={{ width: "50%", flex: 1 }}> | |
<AppBar position="static" elevation={0} color="default"> | |
<Toolbar> | |
<Button | |
form="gpt-form" | |
type="submit" | |
aria-label={loading ? "Loading" : "Run"} | |
aria-disabled={loading} | |
disabled={loading} | |
startIcon={ | |
loading ? <CircularProgress size={20} /> : <PlayArrowIcon /> | |
} | |
> | |
Run | |
</Button> | |
{current?.id === editingId ? ( | |
<EditTitle | |
value={current.task} | |
onSave={value => { | |
setEditingId(null); | |
setAnswers(previousAnswers => | |
previousAnswers.map(answer_ => | |
current.id === answer_.id | |
? { | |
...answer_, | |
task: value, | |
} | |
: answer_ | |
) | |
); | |
}} | |
/> | |
) : ( | |
<> | |
<Typography | |
sx={{ | |
flex: 1, | |
pl: 3, | |
overflow: "hidden", | |
textOverflow: "ellipsis", | |
whiteSpace: "nowrap", | |
}} | |
> | |
{current?.task} | |
</Typography> | |
<IconButton | |
onClick={() => { | |
if (current) { | |
setEditingId(current.id); | |
} | |
}} | |
> | |
<EditIcon /> | |
</IconButton> | |
</> | |
)} | |
<IconButton | |
color="inherit" | |
aria-label={showCode ? "Hide Code" : "Show Code"} | |
onClick={() => { | |
setShowCode(previousState => !previousState); | |
}} | |
> | |
{showCode ? <CodeOffIcon /> : <CodeIcon />} | |
</IconButton> | |
<IconButton | |
edge="end" | |
color="inherit" | |
aria-label="Clear Prompt" | |
onClick={async () => { | |
setActiveId("1"); | |
setRunningId("1"); | |
setTemplate(prettify(base.default)); | |
reload(); | |
}} | |
> | |
<ClearIcon /> | |
</IconButton> | |
</Toolbar> | |
</AppBar> | |
{showCode && ( | |
<Box | |
sx={{ flex: 1 }} | |
onKeyDown={event => { | |
if (event.key === "s" && event.metaKey) { | |
event.preventDefault(); | |
setAnswers(previousAnswers => | |
previousAnswers.map(previousAnswer => { | |
console.log(previousAnswer.id, activeId); | |
return previousAnswer.id === activeId | |
? { ...previousAnswer, content: template } | |
: previousAnswer; | |
}) | |
); | |
reload(); | |
} | |
}} | |
> | |
<MonacoEditor | |
theme={getTheme(mode, systemMode)} | |
language="javascript" | |
value={template} | |
options={{ | |
fontSize: 14, | |
}} | |
onChange={async value => { | |
console.log(value); | |
setTemplate(value ?? ""); | |
}} | |
/> | |
</Box> | |
)} | |
<Stack | |
sx={{ flex: 1, display: showCode ? "none" : undefined, overflow: "hidden" }} | |
> | |
<Box | |
component="form" | |
id="gpt-form" | |
onSubmit={async event => { | |
event.preventDefault(); | |
const formData = new FormData(event.target as HTMLFormElement); | |
const formObject = Object.fromEntries(formData); | |
try { | |
setLoading(true); | |
const { data } = await axios.post("/api/gpt", formObject); | |
const answer = data; | |
setAnswers(previousAnswers => [answer, ...previousAnswers]); | |
setRunningId(answer.id); | |
setActiveId(answer.id); | |
setTemplate(prettify(answer.content)); | |
reload(); | |
} catch (error) { | |
console.error(error); | |
} finally { | |
setLoading(false); | |
} | |
}} | |
> | |
<Paper variant="outlined" sx={{ p: 0 }}> | |
<Stack sx={{ p: 2, gap: 2 }}> | |
<TextField | |
multiline | |
fullWidth | |
id="prompt" | |
name="prompt" | |
label="Prompt" | |
placeholder="matrix code" | |
defaultValue="matrix code" | |
maxRows={6} | |
InputProps={{ | |
style: fontMono.style, | |
}} | |
/> | |
<TextField | |
multiline | |
fullWidth | |
id="negativePrompt" | |
name="negativePrompt" | |
label="Negative Prompt" | |
placeholder="images, audio files" | |
maxRows={6} | |
InputProps={{ | |
style: fontMono.style, | |
}} | |
/> | |
</Stack> | |
</Paper> | |
<Accordion disableGutters square elevation={0}> | |
<AccordionSummary | |
expandIcon={<ExpandMoreIcon />} | |
aria-controls="gtp-options-content" | |
id="gtp-options-header" | |
sx={{ | |
bgcolor: "background.paper", | |
color: "text.primary", | |
}} | |
> | |
<Typography>Options</Typography> | |
</AccordionSummary> | |
<AccordionDetails> | |
<FormControl fullWidth variant="outlined" sx={{ mb: 3 }}> | |
<InputLabel id="gpt-model-select-label">Model</InputLabel> | |
<Select | |
labelId="gpt-model-select-label" | |
id="gpt-model-select" | |
name="model" | |
defaultValue="gpt-3.5-turbo" | |
label="Model" | |
> | |
<MenuItem value="gpt-3.5-turbo">GPT 3.5 turbo</MenuItem> | |
<MenuItem value="gpt-4">GPT 4</MenuItem> | |
</Select> | |
</FormControl> | |
<Stack | |
spacing={2} | |
direction="row" | |
sx={{ mb: 2 }} | |
alignItems="center" | |
> | |
<AcUnitIcon /> | |
<Slider | |
marks | |
id="temperature" | |
name="temperature" | |
min={0} | |
max={0.8} | |
defaultValue={0.2} | |
step={0.1} | |
valueLabelDisplay="auto" | |
aria-label="Temperature" | |
/> | |
<LocalFireDepartmentIcon /> | |
</Stack> | |
<Stack | |
spacing={2} | |
direction="row" | |
sx={{ mb: 2 }} | |
alignItems="center" | |
> | |
<TollIcon /> | |
<Slider | |
marks | |
id="maxTokens" | |
name="maxTokens" | |
min={1024} | |
max={4096} | |
defaultValue={2048} | |
step={256} | |
valueLabelDisplay="auto" | |
aria-label="Max Tokens" | |
/> | |
<MoneyIcon /> | |
</Stack> | |
<input | |
id="template" | |
name="template" | |
type="hidden" | |
value={template} | |
onChange={event => { | |
setTemplate(event.target.value); | |
}} | |
/> | |
</AccordionDetails> | |
</Accordion> | |
</Box> | |
<List sx={{ flex: 1, overflow: "auto" }}> | |
{answers.map((answer, index) => { | |
return ( | |
<ListItem | |
key={answer.id} | |
secondaryAction={ | |
<Stack sx={{ flexDirection: "row", gap: 1 }}> | |
{answer.id === "1" ? undefined : ( | |
<IconButton | |
edge="end" | |
aria-label="Delete" | |
onClick={() => { | |
setAnswers(previousAnswers => | |
previousAnswers.filter( | |
({ id }) => id !== answer.id | |
) | |
); | |
if (runningId === answer.id) { | |
const previous = answers[index + 1]; | |
if (previous) { | |
setActiveId(previous.id); | |
setRunningId(previous.id); | |
setTemplate( | |
prettify(previous.task) | |
); | |
reload(); | |
} | |
} | |
}} | |
> | |
<DeleteForeverIcon /> | |
</IconButton> | |
)} | |
</Stack> | |
} | |
disablePadding | |
> | |
<ListItemButton | |
dense | |
selected={activeId === answer.id} | |
disabled={activeId === answer.id} | |
role={undefined} | |
onClick={() => { | |
setActiveId(answer.id); | |
setRunningId(answer.id); | |
setTemplate(prettify(answer.content)); | |
reload(); | |
}} | |
> | |
<ListItemIcon> | |
{runningId === answer.id ? ( | |
<CheckIcon /> | |
) : ( | |
<VisibilityIcon /> | |
)} | |
</ListItemIcon> | |
<ListItemText | |
primary={answer.task} | |
primaryTypographyProps={{ | |
sx: { | |
overflow: "hidden", | |
textOverflow: "ellipsis", | |
whiteSpace: "nowrap", | |
fontSize: 16, | |
}, | |
}} | |
/> | |
</ListItemButton> | |
</ListItem> | |
); | |
})} | |
</List> | |
</Stack> | |
</Stack> | |
<Stack sx={{ flex: 1, width: "50%", position: "relative" }}> | |
<AppBar position="static" elevation={0} color="default"> | |
<Toolbar> | |
<IconButton | |
color="inherit" | |
aria-label="Reload" | |
onClick={() => { | |
reload(); | |
}} | |
> | |
<ReplayIcon /> | |
</IconButton> | |
</Toolbar> | |
</AppBar> | |
{loadingLive && ( | |
<Box | |
sx={{ | |
position: "absolute", | |
zIndex: 100, | |
top: "50%", | |
left: "50%", | |
transform: "translate(-50%,-50%)", | |
}} | |
> | |
<CircularProgress /> | |
</Box> | |
)} | |
<Box | |
ref={ref} | |
component="iframe" | |
sx={{ | |
width: "100%", | |
flex: 1, | |
m: 0, | |
border: 0, | |
overflow: "hidden", | |
visibility: loadingLive ? "hidden" : undefined, | |
}} | |
src="/live" | |
/> | |
</Stack> | |
</Stack> | |
</> | |
); | |
} | |