Spaces:
Runtime error
Runtime error
feat: changed everything to create Canvas2D games
Browse files- README.md +1 -5
- package-lock.json +0 -0
- package.json +9 -1
- public/js/utils.js +2 -2
- src/components/Codepen.tsx +28 -0
- src/components/CodepenIcon.tsx +12 -0
- src/components/Codesandbox.tsx +25 -0
- src/components/EditTitle.tsx +2 -2
- src/components/ExampleButton.tsx +37 -0
- src/components/InfoMenu.tsx +72 -0
- src/components/SimpleSnackbar.tsx +37 -0
- src/constants/index.ts +8 -18
- src/lib/theme.ts +27 -4
- src/pages/_document.tsx +2 -2
- src/pages/api/{gpt.ts → generate.ts} +1 -1
- src/pages/api/run.ts +0 -18
- src/pages/api/url/codesandbox.ts +43 -0
- src/pages/api/url/types.d.ts +8 -0
- src/pages/index.tsx +379 -237
- src/pages/live.tsx +3 -2
- src/services/api/index.ts +35 -17
- src/store/atoms.ts +3 -3
- src/utils/index.ts +8 -4
- src/utils/share.tsx +110 -0
README.md
CHANGED
@@ -1,13 +1,9 @@
|
|
1 |
-
<h1 align="center"><big>
|
2 |
|
3 |
<p align="center"><img src="assets/logo.png" alt="logo" width="200"/></p>
|
4 |
|
5 |
[![Discord](https://img.shields.io/discord/1091306623819059300?color=7289da&label=Discord&logo=discord&logoColor=fff&style=for-the-badge)](https://discord.com/invite/m3TBB9XEkb)
|
6 |
|
7 |
-
> This project is built to fail
|
8 |
-
> (until it doesn't)
|
9 |
-
> Restart until it works
|
10 |
-
|
11 |
# Prompt-Driven WebUI for Creative Development
|
12 |
|
13 |
Discover a web-based user interface designed for prompt-driven development, enabling users to create
|
|
|
1 |
+
<h1 align="center"><big>2DGameGPT</big></h1>
|
2 |
|
3 |
<p align="center"><img src="assets/logo.png" alt="logo" width="200"/></p>
|
4 |
|
5 |
[![Discord](https://img.shields.io/discord/1091306623819059300?color=7289da&label=Discord&logo=discord&logoColor=fff&style=for-the-badge)](https://discord.com/invite/m3TBB9XEkb)
|
6 |
|
|
|
|
|
|
|
|
|
7 |
# Prompt-Driven WebUI for Creative Development
|
8 |
|
9 |
Discover a web-based user interface designed for prompt-driven development, enabling users to create
|
package-lock.json
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
package.json
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
{
|
2 |
-
"name": "
|
3 |
"version": "1.0.0",
|
4 |
"description": "JavaScript Canvas2D | 100% Prompt Driven Development (GPT) ",
|
5 |
"keywords": [
|
@@ -16,6 +16,12 @@
|
|
16 |
"name": "Gregor Adams",
|
17 |
"url": "https://github.com/pixelass/"
|
18 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
"scripts": {
|
20 |
"dev": "next dev",
|
21 |
"prepare": "husky install",
|
@@ -46,8 +52,10 @@
|
|
46 |
"@monaco-editor/react": "4.5.0",
|
47 |
"@mui/icons-material": "5.11.16",
|
48 |
"@mui/material": "5.12.0",
|
|
|
49 |
"@types/prettier": "^2.7.2",
|
50 |
"axios": "1.3.5",
|
|
|
51 |
"esdeka": "0.1.18",
|
52 |
"eslint": "8.37.0",
|
53 |
"eslint-config-next": "13.2.4",
|
|
|
1 |
{
|
2 |
+
"name": "2dgamegpt",
|
3 |
"version": "1.0.0",
|
4 |
"description": "JavaScript Canvas2D | 100% Prompt Driven Development (GPT) ",
|
5 |
"keywords": [
|
|
|
16 |
"name": "Gregor Adams",
|
17 |
"url": "https://github.com/pixelass/"
|
18 |
},
|
19 |
+
"contributors": [
|
20 |
+
{
|
21 |
+
"name": "Tim Pietrusky",
|
22 |
+
"url": "https://github.com/TimPietrusky"
|
23 |
+
}
|
24 |
+
],
|
25 |
"scripts": {
|
26 |
"dev": "next dev",
|
27 |
"prepare": "husky install",
|
|
|
52 |
"@monaco-editor/react": "4.5.0",
|
53 |
"@mui/icons-material": "5.11.16",
|
54 |
"@mui/material": "5.12.0",
|
55 |
+
"@tweenjs/tween.js": "^18.6.4",
|
56 |
"@types/prettier": "^2.7.2",
|
57 |
"axios": "1.3.5",
|
58 |
+
"codesandbox": "2.2.3",
|
59 |
"esdeka": "0.1.18",
|
60 |
"eslint": "8.37.0",
|
61 |
"eslint-config-next": "13.2.4",
|
public/js/utils.js
CHANGED
@@ -52,12 +52,12 @@ function handleTemplate(template) {
|
|
52 |
Function("Template", `${template};`)();
|
53 |
}
|
54 |
|
55 |
-
subscribe("
|
56 |
const { action } = event.data;
|
57 |
switch (action.type) {
|
58 |
case "call":
|
59 |
host.current = event.source;
|
60 |
-
answer(event.source, "
|
61 |
handleTemplate(action.payload.template);
|
62 |
break;
|
63 |
case "broadcast":
|
|
|
52 |
Function("Template", `${template};`)();
|
53 |
}
|
54 |
|
55 |
+
subscribe("2DGameGPT", event => {
|
56 |
const { action } = event.data;
|
57 |
switch (action.type) {
|
58 |
case "call":
|
59 |
host.current = event.source;
|
60 |
+
answer(event.source, "2DGameGPT");
|
61 |
handleTemplate(action.payload.template);
|
62 |
break;
|
63 |
case "broadcast":
|
src/components/Codepen.tsx
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import IconButton from "@mui/material/IconButton";
|
2 |
+
import { wrappers } from "@/utils/share";
|
3 |
+
import Tooltip from "@mui/material/Tooltip";
|
4 |
+
import { CodepenIcon } from "@/components/CodepenIcon";
|
5 |
+
import { ShareProps } from "../pages";
|
6 |
+
|
7 |
+
export function Codepen({ title, content }: ShareProps) {
|
8 |
+
return (
|
9 |
+
<form action="https://codepen.io/pen/define" method="POST" target="_blank">
|
10 |
+
<input
|
11 |
+
type="hidden"
|
12 |
+
name="data"
|
13 |
+
value={JSON.stringify({
|
14 |
+
title,
|
15 |
+
js: wrappers.js(content),
|
16 |
+
html: wrappers.miniHtml(title),
|
17 |
+
css: wrappers.css(),
|
18 |
+
})}
|
19 |
+
/>
|
20 |
+
|
21 |
+
<Tooltip title="Open in Codepen">
|
22 |
+
<IconButton color="primary" type="submit" aria-label="Codepen">
|
23 |
+
<CodepenIcon />
|
24 |
+
</IconButton>
|
25 |
+
</Tooltip>
|
26 |
+
</form>
|
27 |
+
);
|
28 |
+
}
|
src/components/CodepenIcon.tsx
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon";
|
2 |
+
|
3 |
+
export function CodepenIcon(props: SvgIconProps) {
|
4 |
+
return (
|
5 |
+
<SvgIcon {...props} viewBox="0 0 24 24">
|
6 |
+
<path
|
7 |
+
fill="currentColor"
|
8 |
+
d="M8.21 12L6.88 12.89V11.11L8.21 12M11.47 9.82V7.34L7.31 10.12L9.16 11.36L11.47 9.82M16.7 10.12L12.53 7.34V9.82L14.84 11.36L16.7 10.12M7.31 13.88L11.47 16.66V14.18L9.16 12.64L7.31 13.88M12.53 14.18V16.66L16.7 13.88L14.84 12.64L12.53 14.18M12 10.74L10.12 12L12 13.26L13.88 12L12 10.74M22 12C22 17.5 17.5 22 12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2C17.5 2 22 6.5 22 12M18.18 10.12C18.18 10.09 18.18 10.07 18.18 10.05L18.17 10L18.17 10L18.16 9.95C18.15 9.94 18.15 9.93 18.14 9.91L18.13 9.89L18.11 9.85L18.1 9.83L18.08 9.8L18.06 9.77L18.03 9.74L18 9.72L18 9.7L17.96 9.68L17.95 9.67L12.3 5.91C12.12 5.79 11.89 5.79 11.71 5.91L6.05 9.67L6.05 9.68L6 9.7C6 9.71 6 9.72 6 9.72L5.97 9.74L5.94 9.77L5.93 9.8L5.9 9.83L5.89 9.85L5.87 9.89L5.86 9.91L5.84 9.95L5.84 10L5.83 10L5.82 10.05C5.82 10.07 5.82 10.09 5.82 10.12V13.88C5.82 13.91 5.82 13.93 5.82 13.95L5.83 14L5.84 14L5.84 14.05C5.85 14.06 5.85 14.07 5.86 14.09L5.87 14.11L5.89 14.15L5.9 14.17L5.92 14.2L5.94 14.23C5.95 14.24 5.96 14.25 5.97 14.26L6 14.28L6 14.3L6.04 14.32L6.05 14.33L11.71 18.1C11.79 18.16 11.9 18.18 12 18.18C12.1 18.18 12.21 18.15 12.3 18.1L17.95 14.33L17.96 14.32L18 14.3L18 14.28L18.03 14.26L18.06 14.23L18.08 14.2L18.1 14.17L18.11 14.15L18.13 14.11L18.14 14.09L18.16 14.05L18.16 14L18.17 14L18.18 13.95C18.18 13.93 18.18 13.91 18.18 13.88V10.12M17.12 12.89V11.11L15.79 12L17.12 12.89Z"
|
9 |
+
/>
|
10 |
+
</SvgIcon>
|
11 |
+
);
|
12 |
+
}
|
src/components/Codesandbox.tsx
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import axios from "axios";
|
2 |
+
import CropSquareIcon from "@mui/icons-material/CropSquare";
|
3 |
+
import IconButton from "@mui/material/IconButton";
|
4 |
+
import Tooltip from "@mui/material/Tooltip";
|
5 |
+
import { ShareProps } from "../pages";
|
6 |
+
|
7 |
+
export function Codesandbox({ title, content }: ShareProps) {
|
8 |
+
return (
|
9 |
+
<Tooltip title="Open in Codesandbox">
|
10 |
+
<IconButton
|
11 |
+
color="primary"
|
12 |
+
aria-label="Codsandbox"
|
13 |
+
onClick={async () => {
|
14 |
+
const { data } = await axios.post<string>("/api/url/codesandbox", {
|
15 |
+
content,
|
16 |
+
title,
|
17 |
+
});
|
18 |
+
window.open(data, "_blank");
|
19 |
+
}}
|
20 |
+
>
|
21 |
+
<CropSquareIcon />
|
22 |
+
</IconButton>
|
23 |
+
</Tooltip>
|
24 |
+
);
|
25 |
+
}
|
src/components/EditTitle.tsx
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
import { useState } from "react";
|
2 |
import Box from "@mui/material/Box";
|
3 |
import InputBase from "@mui/material/InputBase";
|
4 |
-
import {
|
5 |
import IconButton from "@mui/material/IconButton";
|
6 |
import SaveIcon from "@mui/icons-material/Save";
|
7 |
|
@@ -24,7 +24,7 @@ export function EditTitle({ value, onSave }: { value: string; onSave(value: stri
|
|
24 |
sx={{
|
25 |
width: "100%",
|
26 |
fontSize: 16,
|
27 |
-
input: { ...
|
28 |
}}
|
29 |
onChange={event => {
|
30 |
setText(event.target.value);
|
|
|
1 |
import { useState } from "react";
|
2 |
import Box from "@mui/material/Box";
|
3 |
import InputBase from "@mui/material/InputBase";
|
4 |
+
import { poppins } from "@/lib/theme";
|
5 |
import IconButton from "@mui/material/IconButton";
|
6 |
import SaveIcon from "@mui/icons-material/Save";
|
7 |
|
|
|
24 |
sx={{
|
25 |
width: "100%",
|
26 |
fontSize: 16,
|
27 |
+
input: { ...poppins.style, p: 0, lineHeight: 1.5 },
|
28 |
}}
|
29 |
onChange={event => {
|
30 |
setText(event.target.value);
|
src/components/ExampleButton.tsx
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Button, Typography } from "@mui/material";
|
2 |
+
import { MouseEventHandler } from "react";
|
3 |
+
|
4 |
+
interface ExampleButtonProps {
|
5 |
+
text: string;
|
6 |
+
title?: string;
|
7 |
+
displayLength?: number;
|
8 |
+
onClick?: (text: string) => void;
|
9 |
+
}
|
10 |
+
|
11 |
+
/**
|
12 |
+
*
|
13 |
+
* A button that hosts an example "text" that can be used as the input
|
14 |
+
* to anything to get an inspiration on how to get started.
|
15 |
+
*
|
16 |
+
* @param props ExampleButtonProps
|
17 |
+
* @returns
|
18 |
+
*/
|
19 |
+
export default function ExampleButton(props: ExampleButtonProps) {
|
20 |
+
const { title, text, displayLength = 50, onClick } = props;
|
21 |
+
|
22 |
+
const displayText = text.slice(0, displayLength) + (text.length > displayLength ? "..." : "");
|
23 |
+
|
24 |
+
const handleClick: MouseEventHandler = event => {
|
25 |
+
event.preventDefault();
|
26 |
+
|
27 |
+
if (onClick) {
|
28 |
+
onClick(text);
|
29 |
+
}
|
30 |
+
};
|
31 |
+
|
32 |
+
return (
|
33 |
+
<Button onClick={handleClick} sx={{ textTransform: "none" }} variant="outlined">
|
34 |
+
<Typography>{title ?? displayText}</Typography>
|
35 |
+
</Button>
|
36 |
+
);
|
37 |
+
}
|
src/components/InfoMenu.tsx
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState, MouseEvent } from "react";
|
2 |
+
import IconButton from "@mui/material/IconButton";
|
3 |
+
import Menu from "@mui/material/Menu";
|
4 |
+
import MenuItem from "@mui/material/MenuItem";
|
5 |
+
import InfoIcon from "@mui/icons-material/Info";
|
6 |
+
import { useRouter } from "next/router";
|
7 |
+
|
8 |
+
export function InfoMenu() {
|
9 |
+
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
10 |
+
const open = Boolean(anchorEl);
|
11 |
+
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
12 |
+
setAnchorEl(event.currentTarget);
|
13 |
+
};
|
14 |
+
const router = useRouter();
|
15 |
+
const handleClose = () => {
|
16 |
+
setAnchorEl(null);
|
17 |
+
};
|
18 |
+
|
19 |
+
return (
|
20 |
+
<div>
|
21 |
+
<IconButton
|
22 |
+
aria-label="Info"
|
23 |
+
id="infoMenu-button"
|
24 |
+
aria-controls={open ? "infoMenu-menu" : undefined}
|
25 |
+
aria-expanded={open ? "true" : undefined}
|
26 |
+
aria-haspopup="true"
|
27 |
+
onClick={handleClick}
|
28 |
+
>
|
29 |
+
<InfoIcon />
|
30 |
+
</IconButton>
|
31 |
+
<Menu
|
32 |
+
id="infoMenu-menu"
|
33 |
+
MenuListProps={{
|
34 |
+
"aria-labelledby": "infoMenu-button",
|
35 |
+
}}
|
36 |
+
anchorEl={anchorEl}
|
37 |
+
open={open}
|
38 |
+
onClose={handleClose}
|
39 |
+
PaperProps={{
|
40 |
+
style: {
|
41 |
+
width: "20ch",
|
42 |
+
},
|
43 |
+
}}
|
44 |
+
>
|
45 |
+
<MenuItem
|
46 |
+
onClick={async () => {
|
47 |
+
await router.push("/legal/data-policy");
|
48 |
+
handleClose();
|
49 |
+
}}
|
50 |
+
>
|
51 |
+
Data Policy
|
52 |
+
</MenuItem>
|
53 |
+
<MenuItem
|
54 |
+
onClick={async () => {
|
55 |
+
await router.push("/legal/imprint");
|
56 |
+
handleClose();
|
57 |
+
}}
|
58 |
+
>
|
59 |
+
Imprint
|
60 |
+
</MenuItem>
|
61 |
+
<MenuItem
|
62 |
+
onClick={async () => {
|
63 |
+
await router.push("/legal/cookie-policy");
|
64 |
+
handleClose();
|
65 |
+
}}
|
66 |
+
>
|
67 |
+
Cookie Policy
|
68 |
+
</MenuItem>
|
69 |
+
</Menu>
|
70 |
+
</div>
|
71 |
+
);
|
72 |
+
}
|
src/components/SimpleSnackbar.tsx
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Button from "@mui/material/Button";
|
2 |
+
import Snackbar from "@mui/material/Snackbar";
|
3 |
+
import IconButton from "@mui/material/IconButton";
|
4 |
+
import CloseIcon from "@mui/icons-material/Close";
|
5 |
+
import { SyntheticEvent } from "react";
|
6 |
+
import { Alert, SnackbarContent } from "@mui/material";
|
7 |
+
|
8 |
+
interface SnackbarProps {
|
9 |
+
showError: boolean;
|
10 |
+
handleClose: (event: SyntheticEvent | Event, reason?: string) => void;
|
11 |
+
message: string;
|
12 |
+
}
|
13 |
+
|
14 |
+
export default function SimpleSnackbar({ showError, handleClose, message }: SnackbarProps) {
|
15 |
+
const action = (
|
16 |
+
<>
|
17 |
+
<Button color="secondary" size="small" onClick={handleClose}>
|
18 |
+
UNDO
|
19 |
+
</Button>
|
20 |
+
<IconButton size="small" aria-label="close" color="inherit" onClick={handleClose}>
|
21 |
+
<CloseIcon fontSize="small" />
|
22 |
+
</IconButton>
|
23 |
+
</>
|
24 |
+
);
|
25 |
+
|
26 |
+
return (
|
27 |
+
<Snackbar
|
28 |
+
open={showError}
|
29 |
+
autoHideDuration={6000}
|
30 |
+
onClose={handleClose}
|
31 |
+
action={action}
|
32 |
+
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
33 |
+
>
|
34 |
+
<Alert severity="error">{message}</Alert>
|
35 |
+
</Snackbar>
|
36 |
+
);
|
37 |
+
}
|
src/constants/index.ts
CHANGED
@@ -1,26 +1,16 @@
|
|
1 |
export const base = {
|
2 |
-
default:
|
3 |
-
|
4 |
-
*/
|
5 |
-
const canvas = document.querySelector('canvas');
|
6 |
const ctx = canvas.getContext('2d');
|
7 |
-
|
8 |
-
* Always use this random function
|
9 |
-
*/
|
10 |
-
function random() {
|
11 |
-
return Math.random()
|
12 |
-
}
|
13 |
-
/**
|
14 |
-
* The draw function is called every frame to update the canvas.
|
15 |
-
* To change the drawing logic, modify the code inside this function.
|
16 |
-
*/
|
17 |
function draw(){
|
18 |
-
// TODO: Add drawing logic here
|
19 |
-
// Set the desired FPS (frames per second) for the animation
|
20 |
const FPS = 60;
|
21 |
-
|
|
|
|
|
|
|
22 |
setTimeout(requestAnimationFrame(draw),1000/FPS)
|
23 |
}
|
24 |
draw();
|
25 |
-
|
26 |
};
|
|
|
1 |
export const base = {
|
2 |
+
default: `const canvas = document.querySelector('canvas');
|
3 |
+
canvas.style.backgroundColor = '#fff';
|
|
|
|
|
4 |
const ctx = canvas.getContext('2d');
|
5 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
function draw(){
|
|
|
|
|
7 |
const FPS = 60;
|
8 |
+
|
9 |
+
// TODO: Add drawing logic here
|
10 |
+
|
11 |
+
// NEVER stop the loop
|
12 |
setTimeout(requestAnimationFrame(draw),1000/FPS)
|
13 |
}
|
14 |
draw();
|
15 |
+
`.trim(),
|
16 |
};
|
src/lib/theme.ts
CHANGED
@@ -1,14 +1,13 @@
|
|
1 |
-
import { Fira_Code,
|
2 |
import { experimental_extendTheme as extendTheme } from "@mui/material/styles";
|
3 |
|
4 |
-
export const
|
5 |
weight: ["300", "400", "500", "700"],
|
6 |
subsets: ["latin"],
|
7 |
display: "swap",
|
8 |
fallback: ["Helvetica", "Arial", "sans-serif"],
|
9 |
});
|
10 |
|
11 |
-
// Create a theme instance.
|
12 |
const theme = extendTheme({
|
13 |
colorSchemes: {
|
14 |
light: {
|
@@ -33,7 +32,31 @@ const theme = extendTheme({
|
|
33 |
},
|
34 |
},
|
35 |
typography: {
|
36 |
-
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
},
|
38 |
});
|
39 |
|
|
|
1 |
+
import { Fira_Code, Poppins } from "next/font/google";
|
2 |
import { experimental_extendTheme as extendTheme } from "@mui/material/styles";
|
3 |
|
4 |
+
export const poppins = Poppins({
|
5 |
weight: ["300", "400", "500", "700"],
|
6 |
subsets: ["latin"],
|
7 |
display: "swap",
|
8 |
fallback: ["Helvetica", "Arial", "sans-serif"],
|
9 |
});
|
10 |
|
|
|
11 |
const theme = extendTheme({
|
12 |
colorSchemes: {
|
13 |
light: {
|
|
|
32 |
},
|
33 |
},
|
34 |
typography: {
|
35 |
+
...poppins.style,
|
36 |
+
h1: {
|
37 |
+
fontSize: "3em",
|
38 |
+
marginBottom: 24,
|
39 |
+
},
|
40 |
+
h2: {
|
41 |
+
fontSize: "1.9em",
|
42 |
+
marginBottom: 12,
|
43 |
+
},
|
44 |
+
h3: {
|
45 |
+
fontSize: "1.7em",
|
46 |
+
marginBottom: 12,
|
47 |
+
},
|
48 |
+
h4: {
|
49 |
+
fontSize: "1.25em",
|
50 |
+
marginBottom: 12,
|
51 |
+
},
|
52 |
+
h5: {
|
53 |
+
fontSize: "1em",
|
54 |
+
marginBottom: 12,
|
55 |
+
},
|
56 |
+
h6: {
|
57 |
+
fontSize: "0.8em",
|
58 |
+
marginBottom: 12,
|
59 |
+
},
|
60 |
},
|
61 |
});
|
62 |
|
src/pages/_document.tsx
CHANGED
@@ -9,7 +9,7 @@ import Document, {
|
|
9 |
} from "next/document";
|
10 |
import createEmotionServer from "@emotion/server/create-instance";
|
11 |
import { AppType } from "next/app";
|
12 |
-
import {
|
13 |
import createEmotionCache from "@/lib/createEmotionCache";
|
14 |
import { MyAppProps } from "./_app";
|
15 |
import { getInitColorSchemeScript } from "@mui/material/styles";
|
@@ -20,7 +20,7 @@ interface MyDocumentProps extends DocumentProps {
|
|
20 |
|
21 |
export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
|
22 |
return (
|
23 |
-
<Html lang="en" className={
|
24 |
<Head>
|
25 |
<link rel="shortcut icon" href="/favicon.ico" />
|
26 |
<meta name="emotion-insertion-point" content="" />
|
|
|
9 |
} from "next/document";
|
10 |
import createEmotionServer from "@emotion/server/create-instance";
|
11 |
import { AppType } from "next/app";
|
12 |
+
import { poppins } from "@/lib/theme";
|
13 |
import createEmotionCache from "@/lib/createEmotionCache";
|
14 |
import { MyAppProps } from "./_app";
|
15 |
import { getInitColorSchemeScript } from "@mui/material/styles";
|
|
|
20 |
|
21 |
export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
|
22 |
return (
|
23 |
+
<Html lang="en" className={poppins.className}>
|
24 |
<Head>
|
25 |
<link rel="shortcut icon" href="/favicon.ico" />
|
26 |
<meta name="emotion-insertion-point" content="" />
|
src/pages/api/{gpt.ts → generate.ts}
RENAMED
@@ -9,7 +9,7 @@ export default async function handler(request: NextApiRequest, response: NextApi
|
|
9 |
const answer = await toOpenAI(request.body);
|
10 |
return response.status(200).json(answer);
|
11 |
} catch (error) {
|
12 |
-
return response.status((error as AxiosError).status ?? 500).json(
|
13 |
}
|
14 |
default:
|
15 |
return response.status(405).json({});
|
|
|
9 |
const answer = await toOpenAI(request.body);
|
10 |
return response.status(200).json(answer);
|
11 |
} catch (error) {
|
12 |
+
return response.status((error as AxiosError).status ?? 500).json(error);
|
13 |
}
|
14 |
default:
|
15 |
return response.status(405).json({});
|
src/pages/api/run.ts
DELETED
@@ -1,18 +0,0 @@
|
|
1 |
-
import fs from "node:fs/promises";
|
2 |
-
import path from "node:path";
|
3 |
-
import { NextApiRequest, NextApiResponse } from "next";
|
4 |
-
|
5 |
-
const outputFile = path.join(__dirname, "../../../../project/src/index.js");
|
6 |
-
|
7 |
-
export default async function handler(request: NextApiRequest, response: NextApiResponse) {
|
8 |
-
switch (request.method) {
|
9 |
-
case "POST":
|
10 |
-
if (request.body.content) {
|
11 |
-
await fs.writeFile(outputFile, request.body.content);
|
12 |
-
return response.status(200).json({});
|
13 |
-
}
|
14 |
-
return response.status(500).json({});
|
15 |
-
default:
|
16 |
-
return response.status(405).json({});
|
17 |
-
}
|
18 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/pages/api/url/codesandbox.ts
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { getParameters } from "codesandbox/lib/api/define";
|
2 |
+
import { NextApiRequest, NextApiResponse } from "next";
|
3 |
+
import prettier from "prettier";
|
4 |
+
import parserHTML from "prettier/parser-html";
|
5 |
+
import parserCSS from "prettier/parser-postcss";
|
6 |
+
import parserBabel from "prettier/parser-babel";
|
7 |
+
import { wrappers } from "@/utils/share";
|
8 |
+
|
9 |
+
export default async function handler(request: NextApiRequest, response: NextApiResponse) {
|
10 |
+
const content = request.body.content as string;
|
11 |
+
const title = request.body.title as string;
|
12 |
+
|
13 |
+
const parameters = getParameters({
|
14 |
+
template: "static",
|
15 |
+
files: {
|
16 |
+
"index.html": {
|
17 |
+
content: prettier.format(wrappers.html(title), {
|
18 |
+
parser: "html",
|
19 |
+
plugins: [parserHTML],
|
20 |
+
}),
|
21 |
+
isBinary: false,
|
22 |
+
},
|
23 |
+
"style.css": {
|
24 |
+
content: prettier.format(wrappers.css(), {
|
25 |
+
parser: "css",
|
26 |
+
plugins: [parserCSS],
|
27 |
+
}),
|
28 |
+
isBinary: false,
|
29 |
+
},
|
30 |
+
"script.js": {
|
31 |
+
content: prettier.format(wrappers.js(content), {
|
32 |
+
parser: "babel",
|
33 |
+
plugins: [parserBabel],
|
34 |
+
}),
|
35 |
+
isBinary: false,
|
36 |
+
},
|
37 |
+
},
|
38 |
+
});
|
39 |
+
|
40 |
+
response
|
41 |
+
.status(200)
|
42 |
+
.json(`https://codesandbox.io/api/v1/sandboxes/define?parameters=${parameters}`);
|
43 |
+
}
|
src/pages/api/url/types.d.ts
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
declare module "codesandbox-import-utils/lib/api/define" {
|
2 |
+
interface IFiles {
|
3 |
+
[key: string]: {
|
4 |
+
content: string | Record<string, unknown>;
|
5 |
+
isBinary: boolean;
|
6 |
+
};
|
7 |
+
}
|
8 |
+
}
|
src/pages/index.tsx
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
-
import { useEffect, useRef, useState } from "react";
|
2 |
|
3 |
-
import axios from "axios";
|
4 |
import AcUnitIcon from "@mui/icons-material/AcUnit";
|
5 |
import LocalFireDepartmentIcon from "@mui/icons-material/LocalFireDepartment";
|
6 |
import CheckIcon from "@mui/icons-material/Check";
|
@@ -43,17 +43,29 @@ import FormControl from "@mui/material/FormControl";
|
|
43 |
import InputLabel from "@mui/material/InputLabel";
|
44 |
import Select from "@mui/material/Select";
|
45 |
import MenuItem from "@mui/material/MenuItem";
|
46 |
-
import { fontMono } from "@/lib/theme";
|
47 |
import { useColorScheme } from "@mui/material/styles";
|
48 |
import { getTheme, prettify } from "@/utils";
|
49 |
import { answersAtom, showCodeAtom } from "@/store/atoms";
|
50 |
import { base } from "@/constants";
|
51 |
import { EditTitle } from "@/components/EditTitle";
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
const MonacoEditor = dynamic(import("@monaco-editor/react"), { ssr: false });
|
54 |
|
|
|
|
|
|
|
|
|
|
|
55 |
export default function Home() {
|
56 |
const ref = useRef<HTMLIFrameElement>(null);
|
|
|
57 |
const [template, setTemplate] = useState(prettify(base.default));
|
58 |
const [runningId, setRunningId] = useState("1");
|
59 |
const [activeId, setActiveId] = useState("1");
|
@@ -62,9 +74,12 @@ export default function Home() {
|
|
62 |
const [showCode, setShowCode] = useAtom(showCodeAtom);
|
63 |
const [loading, setLoading] = useState(false);
|
64 |
const [loadingLive, setLoadingLive] = useState(true);
|
|
|
|
|
|
|
65 |
const { mode, systemMode } = useColorScheme();
|
66 |
|
67 |
-
const { call, subscribe } = useHost(ref, "
|
68 |
|
69 |
const connection = useRef(false);
|
70 |
const [tries, setTries] = useState(1);
|
@@ -99,8 +114,6 @@ export default function Home() {
|
|
99 |
case "answer":
|
100 |
connection.current = true;
|
101 |
setLoadingLive(false);
|
102 |
-
|
103 |
-
console.log("connected");
|
104 |
break;
|
105 |
default:
|
106 |
break;
|
@@ -117,6 +130,14 @@ export default function Home() {
|
|
117 |
|
118 |
const current = answers.find(({ id }) => id === activeId);
|
119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
function reload() {
|
121 |
connection.current = false;
|
122 |
if (ref.current) {
|
@@ -135,67 +156,23 @@ export default function Home() {
|
|
135 |
position: "absolute",
|
136 |
inset: 0,
|
137 |
overflow: "hidden",
|
138 |
-
flexDirection: "row",
|
139 |
height: "100%",
|
140 |
}}
|
141 |
>
|
142 |
-
<Stack
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
<AppBar position="static" elevation={0} color="default">
|
144 |
<Toolbar>
|
145 |
-
<
|
146 |
-
|
147 |
-
|
148 |
-
aria-label={loading ? "Loading" : "Run"}
|
149 |
-
aria-disabled={loading}
|
150 |
-
disabled={loading}
|
151 |
-
startIcon={
|
152 |
-
loading ? <CircularProgress size={20} /> : <PlayArrowIcon />
|
153 |
-
}
|
154 |
-
>
|
155 |
-
Run
|
156 |
-
</Button>
|
157 |
-
|
158 |
-
{current?.id === editingId ? (
|
159 |
-
<EditTitle
|
160 |
-
value={current.task}
|
161 |
-
onSave={value => {
|
162 |
-
setEditingId(null);
|
163 |
-
setAnswers(previousAnswers =>
|
164 |
-
previousAnswers.map(answer_ =>
|
165 |
-
current.id === answer_.id
|
166 |
-
? {
|
167 |
-
...answer_,
|
168 |
-
task: value,
|
169 |
-
}
|
170 |
-
: answer_
|
171 |
-
)
|
172 |
-
);
|
173 |
-
}}
|
174 |
-
/>
|
175 |
-
) : (
|
176 |
-
<>
|
177 |
-
<Typography
|
178 |
-
sx={{
|
179 |
-
flex: 1,
|
180 |
-
pl: 3,
|
181 |
-
overflow: "hidden",
|
182 |
-
textOverflow: "ellipsis",
|
183 |
-
whiteSpace: "nowrap",
|
184 |
-
}}
|
185 |
-
>
|
186 |
-
{current?.task}
|
187 |
-
</Typography>
|
188 |
-
<IconButton
|
189 |
-
onClick={() => {
|
190 |
-
if (current) {
|
191 |
-
setEditingId(current.id);
|
192 |
-
}
|
193 |
-
}}
|
194 |
-
>
|
195 |
-
<EditIcon />
|
196 |
-
</IconButton>
|
197 |
-
</>
|
198 |
-
)}
|
199 |
|
200 |
<IconButton
|
201 |
color="inherit"
|
@@ -229,12 +206,12 @@ export default function Home() {
|
|
229 |
event.preventDefault();
|
230 |
setAnswers(previousAnswers =>
|
231 |
previousAnswers.map(previousAnswer => {
|
232 |
-
console.log(previousAnswer.id, activeId);
|
233 |
return previousAnswer.id === activeId
|
234 |
? { ...previousAnswer, content: template }
|
235 |
: previousAnswer;
|
236 |
})
|
237 |
);
|
|
|
238 |
reload();
|
239 |
}
|
240 |
}}
|
@@ -247,25 +224,29 @@ export default function Home() {
|
|
247 |
fontSize: 14,
|
248 |
}}
|
249 |
onChange={async value => {
|
250 |
-
console.log(value);
|
251 |
setTemplate(value ?? "");
|
252 |
}}
|
253 |
/>
|
254 |
</Box>
|
255 |
)}
|
256 |
<Stack
|
257 |
-
sx={{
|
|
|
|
|
|
|
|
|
258 |
>
|
259 |
<Box
|
260 |
component="form"
|
261 |
id="gpt-form"
|
|
|
262 |
onSubmit={async event => {
|
263 |
event.preventDefault();
|
264 |
const formData = new FormData(event.target as HTMLFormElement);
|
265 |
const formObject = Object.fromEntries(formData);
|
266 |
try {
|
267 |
setLoading(true);
|
268 |
-
const { data } = await axios.post("/api/
|
269 |
const answer = data;
|
270 |
setAnswers(previousAnswers => [answer, ...previousAnswers]);
|
271 |
setRunningId(answer.id);
|
@@ -273,6 +254,8 @@ export default function Home() {
|
|
273 |
setTemplate(prettify(answer.content));
|
274 |
reload();
|
275 |
} catch (error) {
|
|
|
|
|
276 |
console.error(error);
|
277 |
} finally {
|
278 |
setLoading(false);
|
@@ -281,191 +264,314 @@ export default function Home() {
|
|
281 |
>
|
282 |
<Paper variant="outlined" sx={{ p: 0 }}>
|
283 |
<Stack sx={{ p: 2, gap: 2 }}>
|
284 |
-
<
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
multiline
|
299 |
-
fullWidth
|
300 |
-
id="negativePrompt"
|
301 |
-
name="negativePrompt"
|
302 |
-
label="Negative Prompt"
|
303 |
-
placeholder="images, audio files"
|
304 |
-
maxRows={6}
|
305 |
-
InputProps={{
|
306 |
-
style: fontMono.style,
|
307 |
-
}}
|
308 |
-
/>
|
309 |
-
</Stack>
|
310 |
-
</Paper>
|
311 |
-
<Accordion disableGutters square elevation={0}>
|
312 |
-
<AccordionSummary
|
313 |
-
expandIcon={<ExpandMoreIcon />}
|
314 |
-
aria-controls="gtp-options-content"
|
315 |
-
id="gtp-options-header"
|
316 |
-
sx={{
|
317 |
-
bgcolor: "background.paper",
|
318 |
-
color: "text.primary",
|
319 |
-
}}
|
320 |
-
>
|
321 |
-
<Typography>Options</Typography>
|
322 |
-
</AccordionSummary>
|
323 |
-
<AccordionDetails>
|
324 |
-
<FormControl fullWidth variant="outlined" sx={{ mb: 3 }}>
|
325 |
-
<InputLabel id="gpt-model-select-label">Model</InputLabel>
|
326 |
-
<Select
|
327 |
-
labelId="gpt-model-select-label"
|
328 |
-
id="gpt-model-select"
|
329 |
-
name="model"
|
330 |
-
defaultValue="gpt-3.5-turbo"
|
331 |
-
label="Model"
|
332 |
-
>
|
333 |
-
<MenuItem value="gpt-3.5-turbo">GPT 3.5 turbo</MenuItem>
|
334 |
-
<MenuItem value="gpt-4">GPT 4</MenuItem>
|
335 |
-
</Select>
|
336 |
-
</FormControl>
|
337 |
-
<Stack
|
338 |
-
spacing={2}
|
339 |
-
direction="row"
|
340 |
-
sx={{ mb: 2 }}
|
341 |
-
alignItems="center"
|
342 |
-
>
|
343 |
-
<AcUnitIcon />
|
344 |
-
<Slider
|
345 |
-
marks
|
346 |
-
id="temperature"
|
347 |
-
name="temperature"
|
348 |
-
min={0}
|
349 |
-
max={0.8}
|
350 |
-
defaultValue={0.2}
|
351 |
-
step={0.1}
|
352 |
-
valueLabelDisplay="auto"
|
353 |
-
aria-label="Temperature"
|
354 |
/>
|
355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
356 |
</Stack>
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
374 |
/>
|
375 |
-
<MoneyIcon />
|
376 |
</Stack>
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
|
|
|
|
|
|
|
|
|
|
384 |
}}
|
385 |
-
|
386 |
-
|
387 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
388 |
</Box>
|
389 |
|
390 |
-
<
|
391 |
-
{
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
416 |
}
|
417 |
-
}
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
disablePadding
|
426 |
-
>
|
427 |
-
<ListItemButton
|
428 |
-
dense
|
429 |
-
selected={activeId === answer.id}
|
430 |
-
disabled={activeId === answer.id}
|
431 |
-
role={undefined}
|
432 |
-
onClick={() => {
|
433 |
-
setActiveId(answer.id);
|
434 |
-
setRunningId(answer.id);
|
435 |
-
setTemplate(prettify(answer.content));
|
436 |
-
reload();
|
437 |
-
}}
|
438 |
>
|
439 |
-
<
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
)
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
primaryTypographyProps={{
|
450 |
-
sx: {
|
451 |
-
overflow: "hidden",
|
452 |
-
textOverflow: "ellipsis",
|
453 |
-
whiteSpace: "nowrap",
|
454 |
-
fontSize: 16,
|
455 |
-
},
|
456 |
}}
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
463 |
</Stack>
|
464 |
</Stack>
|
465 |
-
<Stack
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
466 |
<AppBar position="static" elevation={0} color="default">
|
467 |
<Toolbar>
|
468 |
<IconButton
|
|
|
469 |
color="inherit"
|
470 |
aria-label="Reload"
|
471 |
onClick={() => {
|
@@ -474,6 +580,28 @@ export default function Home() {
|
|
474 |
>
|
475 |
<ReplayIcon />
|
476 |
</IconButton>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
477 |
</Toolbar>
|
478 |
</AppBar>
|
479 |
{loadingLive && (
|
@@ -500,10 +628,24 @@ export default function Home() {
|
|
500 |
overflow: "hidden",
|
501 |
visibility: loadingLive ? "hidden" : undefined,
|
502 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
503 |
src="/live"
|
504 |
/>
|
505 |
</Stack>
|
506 |
</Stack>
|
|
|
|
|
|
|
|
|
|
|
|
|
507 |
</>
|
508 |
);
|
509 |
}
|
|
|
1 |
+
import { SyntheticEvent, useEffect, useRef, useState } from "react";
|
2 |
|
3 |
+
import axios, { AxiosError, AxiosError } from "axios";
|
4 |
import AcUnitIcon from "@mui/icons-material/AcUnit";
|
5 |
import LocalFireDepartmentIcon from "@mui/icons-material/LocalFireDepartment";
|
6 |
import CheckIcon from "@mui/icons-material/Check";
|
|
|
43 |
import InputLabel from "@mui/material/InputLabel";
|
44 |
import Select from "@mui/material/Select";
|
45 |
import MenuItem from "@mui/material/MenuItem";
|
|
|
46 |
import { useColorScheme } from "@mui/material/styles";
|
47 |
import { getTheme, prettify } from "@/utils";
|
48 |
import { answersAtom, showCodeAtom } from "@/store/atoms";
|
49 |
import { base } from "@/constants";
|
50 |
import { EditTitle } from "@/components/EditTitle";
|
51 |
+
import Link from "next/link";
|
52 |
+
import { fontMono } from "@/lib/theme";
|
53 |
+
import { Codesandbox } from "@/components/Codesandbox";
|
54 |
+
import { Codepen } from "@/components/Codepen";
|
55 |
+
import { InfoMenu } from "@/components/InfoMenu";
|
56 |
+
import SimpleSnackbar from "@/components/SimpleSnackbar";
|
57 |
+
import ExampleButton from "@/components/ExampleButton";
|
58 |
+
import { ListSubheader } from "@mui/material";
|
59 |
const MonacoEditor = dynamic(import("@monaco-editor/react"), { ssr: false });
|
60 |
|
61 |
+
export interface ShareProps {
|
62 |
+
title: string;
|
63 |
+
content: string;
|
64 |
+
}
|
65 |
+
|
66 |
export default function Home() {
|
67 |
const ref = useRef<HTMLIFrameElement>(null);
|
68 |
+
const [prompt, setPrompt] = useState("");
|
69 |
const [template, setTemplate] = useState(prettify(base.default));
|
70 |
const [runningId, setRunningId] = useState("1");
|
71 |
const [activeId, setActiveId] = useState("1");
|
|
|
74 |
const [showCode, setShowCode] = useAtom(showCodeAtom);
|
75 |
const [loading, setLoading] = useState(false);
|
76 |
const [loadingLive, setLoadingLive] = useState(true);
|
77 |
+
const [showError, setShowError] = useState(false);
|
78 |
+
const [errorMessage, setErrorMessage] = useState("");
|
79 |
+
|
80 |
const { mode, systemMode } = useColorScheme();
|
81 |
|
82 |
+
const { call, subscribe } = useHost(ref, "2DGameGPT");
|
83 |
|
84 |
const connection = useRef(false);
|
85 |
const [tries, setTries] = useState(1);
|
|
|
114 |
case "answer":
|
115 |
connection.current = true;
|
116 |
setLoadingLive(false);
|
|
|
|
|
117 |
break;
|
118 |
default:
|
119 |
break;
|
|
|
130 |
|
131 |
const current = answers.find(({ id }) => id === activeId);
|
132 |
|
133 |
+
const handleSnackbarClose = (event: SyntheticEvent | Event, reason?: string) => {
|
134 |
+
if (reason === "clickaway") {
|
135 |
+
return;
|
136 |
+
}
|
137 |
+
|
138 |
+
setShowError(false);
|
139 |
+
};
|
140 |
+
|
141 |
function reload() {
|
142 |
connection.current = false;
|
143 |
if (ref.current) {
|
|
|
156 |
position: "absolute",
|
157 |
inset: 0,
|
158 |
overflow: "hidden",
|
159 |
+
flexDirection: { md: "row" },
|
160 |
height: "100%",
|
161 |
}}
|
162 |
>
|
163 |
+
<Stack
|
164 |
+
sx={{
|
165 |
+
width: { md: "50%" },
|
166 |
+
height: { xs: "50%", md: "100%" },
|
167 |
+
flex: 1,
|
168 |
+
overflow: "hidden",
|
169 |
+
}}
|
170 |
+
>
|
171 |
<AppBar position="static" elevation={0} color="default">
|
172 |
<Toolbar>
|
173 |
+
<Typography variant="h3" component="h1" sx={{ flex: 1, m: 0 }}>
|
174 |
+
2D GameCreator-GPT
|
175 |
+
</Typography>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
|
177 |
<IconButton
|
178 |
color="inherit"
|
|
|
206 |
event.preventDefault();
|
207 |
setAnswers(previousAnswers =>
|
208 |
previousAnswers.map(previousAnswer => {
|
|
|
209 |
return previousAnswer.id === activeId
|
210 |
? { ...previousAnswer, content: template }
|
211 |
: previousAnswer;
|
212 |
})
|
213 |
);
|
214 |
+
setTemplate(previousState => prettify(previousState));
|
215 |
reload();
|
216 |
}
|
217 |
}}
|
|
|
224 |
fontSize: 14,
|
225 |
}}
|
226 |
onChange={async value => {
|
|
|
227 |
setTemplate(value ?? "");
|
228 |
}}
|
229 |
/>
|
230 |
</Box>
|
231 |
)}
|
232 |
<Stack
|
233 |
+
sx={{
|
234 |
+
flex: 1,
|
235 |
+
display: showCode ? "none" : undefined,
|
236 |
+
overflow: "hidden",
|
237 |
+
}}
|
238 |
>
|
239 |
<Box
|
240 |
component="form"
|
241 |
id="gpt-form"
|
242 |
+
sx={{ p: 1 }}
|
243 |
onSubmit={async event => {
|
244 |
event.preventDefault();
|
245 |
const formData = new FormData(event.target as HTMLFormElement);
|
246 |
const formObject = Object.fromEntries(formData);
|
247 |
try {
|
248 |
setLoading(true);
|
249 |
+
const { data } = await axios.post("/api/generate", formObject);
|
250 |
const answer = data;
|
251 |
setAnswers(previousAnswers => [answer, ...previousAnswers]);
|
252 |
setRunningId(answer.id);
|
|
|
254 |
setTemplate(prettify(answer.content));
|
255 |
reload();
|
256 |
} catch (error) {
|
257 |
+
setShowError(true);
|
258 |
+
setErrorMessage((error as AxiosError).message);
|
259 |
console.error(error);
|
260 |
} finally {
|
261 |
setLoading(false);
|
|
|
264 |
>
|
265 |
<Paper variant="outlined" sx={{ p: 0 }}>
|
266 |
<Stack sx={{ p: 2, gap: 2 }}>
|
267 |
+
<Stack direction="row" spacing={1}>
|
268 |
+
<TextField
|
269 |
+
multiline
|
270 |
+
fullWidth
|
271 |
+
required
|
272 |
+
id="prompt"
|
273 |
+
name="prompt"
|
274 |
+
label="Prompt"
|
275 |
+
value={prompt}
|
276 |
+
onChange={e => setPrompt(e.target.value)}
|
277 |
+
minRows={5}
|
278 |
+
InputProps={{
|
279 |
+
style: fontMono.style,
|
280 |
+
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
281 |
/>
|
282 |
+
|
283 |
+
<Stack spacing={1}>
|
284 |
+
<FormControl variant="outlined" sx={{ minWidth: 180 }}>
|
285 |
+
<InputLabel id="gpt-command-select-label">
|
286 |
+
Command
|
287 |
+
</InputLabel>
|
288 |
+
<Select
|
289 |
+
labelId="gpt-command-select-label"
|
290 |
+
id="gpt-command-select"
|
291 |
+
name="command"
|
292 |
+
defaultValue="CREATE_GAME"
|
293 |
+
label="Command"
|
294 |
+
>
|
295 |
+
<MenuItem value="CREATE_GAME">
|
296 |
+
create game
|
297 |
+
</MenuItem>
|
298 |
+
<MenuItem value="ADD_FEATURE">
|
299 |
+
add feature
|
300 |
+
</MenuItem>
|
301 |
+
<MenuItem value="REMOVE_FEATURE">
|
302 |
+
remove feature
|
303 |
+
</MenuItem>
|
304 |
+
<MenuItem value="UPDATE_FEATURE">
|
305 |
+
update feature
|
306 |
+
</MenuItem>
|
307 |
+
<MenuItem value="FIX_BUG">fix bug</MenuItem>
|
308 |
+
</Select>
|
309 |
+
</FormControl>
|
310 |
+
|
311 |
+
<Button
|
312 |
+
form="gpt-form"
|
313 |
+
type="submit"
|
314 |
+
variant="contained"
|
315 |
+
fullWidth
|
316 |
+
aria-label={loading ? "Loading" : "Run"}
|
317 |
+
aria-disabled={loading}
|
318 |
+
disabled={loading}
|
319 |
+
startIcon={
|
320 |
+
loading ? (
|
321 |
+
<CircularProgress size={20} />
|
322 |
+
) : (
|
323 |
+
<PlayArrowIcon />
|
324 |
+
)
|
325 |
+
}
|
326 |
+
sx={{ pl: 5, pr: 5, flexGrow: 1, overflow: "auto" }}
|
327 |
+
>
|
328 |
+
<Typography sx={{ fontWeight: "500" }}>
|
329 |
+
Run
|
330 |
+
</Typography>
|
331 |
+
</Button>
|
332 |
+
</Stack>
|
333 |
</Stack>
|
334 |
+
|
335 |
+
<Stack direction="row" spacing={1} alignItems="center">
|
336 |
+
<Typography>Examples</Typography>
|
337 |
+
|
338 |
+
<ExampleButton
|
339 |
+
title={"Space Invaders"}
|
340 |
+
text={"Retro Space Invaders"}
|
341 |
+
onClick={setPrompt}
|
342 |
+
/>
|
343 |
+
|
344 |
+
<ExampleButton
|
345 |
+
title="Jump & Run"
|
346 |
+
text={
|
347 |
+
"Jump & Run. Player collects coins to enter the next level. Various platform heights. Gras platform covers the whole ground. Space key for jumping, arrow keys for movement."
|
348 |
+
}
|
349 |
+
onClick={setPrompt}
|
350 |
+
/>
|
351 |
+
|
352 |
+
<ExampleButton
|
353 |
+
title="Flappy Bird"
|
354 |
+
text={
|
355 |
+
"Flappy Bird. Intro screen, start the game by pressing space key. Bird starts flying on the left center of the screen. Gradually falls slowly. Pressing the space key over and over lets the bird fly higher. Pipes move from the right of the screen to the left. Collision detection when the bird hits the ground or a pipe. When collision detected, then show intro screen. Player gets a point for each passed pipe without an collision. Score is shown in the top left while bird is flying. High score on intro screen."
|
356 |
+
}
|
357 |
+
onClick={setPrompt}
|
358 |
/>
|
|
|
359 |
</Stack>
|
360 |
+
</Stack>
|
361 |
+
</Paper>
|
362 |
+
|
363 |
+
<Paper variant="outlined" sx={{ mt: 2 }}>
|
364 |
+
<Accordion disableGutters square elevation={0}>
|
365 |
+
<AccordionSummary
|
366 |
+
expandIcon={<ExpandMoreIcon />}
|
367 |
+
aria-controls="gtp-options-content"
|
368 |
+
id="gtp-options-header"
|
369 |
+
sx={{
|
370 |
+
bgcolor: "background.paper",
|
371 |
+
color: "text.primary",
|
372 |
}}
|
373 |
+
>
|
374 |
+
<Typography>Options</Typography>
|
375 |
+
</AccordionSummary>
|
376 |
+
<AccordionDetails>
|
377 |
+
<Stack gap={2}>
|
378 |
+
<TextField
|
379 |
+
fullWidth
|
380 |
+
multiline
|
381 |
+
required={process.env.NODE_ENV === "production"}
|
382 |
+
id="openAPIKey"
|
383 |
+
name="openAPIKey"
|
384 |
+
label="OpenAI API Key"
|
385 |
+
minRows={1}
|
386 |
+
InputProps={{
|
387 |
+
style: fontMono.style,
|
388 |
+
}}
|
389 |
+
/>
|
390 |
+
<TextField
|
391 |
+
multiline
|
392 |
+
fullWidth
|
393 |
+
id="negativePrompt"
|
394 |
+
name="negativePrompt"
|
395 |
+
label="Negative Prompt"
|
396 |
+
placeholder="images, audio files"
|
397 |
+
maxRows={6}
|
398 |
+
InputProps={{
|
399 |
+
style: fontMono.style,
|
400 |
+
}}
|
401 |
+
/>
|
402 |
+
<FormControl
|
403 |
+
fullWidth
|
404 |
+
variant="outlined"
|
405 |
+
sx={{ mb: 3 }}
|
406 |
+
>
|
407 |
+
<InputLabel id="gpt-model-select-label">
|
408 |
+
Model
|
409 |
+
</InputLabel>
|
410 |
+
<Select
|
411 |
+
labelId="gpt-model-select-label"
|
412 |
+
id="gpt-model-select"
|
413 |
+
name="model"
|
414 |
+
defaultValue="gpt-3.5-turbo"
|
415 |
+
label="Model"
|
416 |
+
>
|
417 |
+
<MenuItem value="gpt-3.5-turbo">
|
418 |
+
GPT 3.5 turbo
|
419 |
+
</MenuItem>
|
420 |
+
<MenuItem value="gpt-4">GPT 4</MenuItem>
|
421 |
+
</Select>
|
422 |
+
</FormControl>
|
423 |
+
</Stack>
|
424 |
+
<Stack
|
425 |
+
spacing={2}
|
426 |
+
direction="row"
|
427 |
+
sx={{ mb: 2 }}
|
428 |
+
alignItems="center"
|
429 |
+
>
|
430 |
+
<AcUnitIcon />
|
431 |
+
<Slider
|
432 |
+
marks
|
433 |
+
id="temperature"
|
434 |
+
name="temperature"
|
435 |
+
min={0}
|
436 |
+
max={0.8}
|
437 |
+
defaultValue={0.2}
|
438 |
+
step={0.1}
|
439 |
+
valueLabelDisplay="auto"
|
440 |
+
aria-label="Temperature"
|
441 |
+
/>
|
442 |
+
<LocalFireDepartmentIcon />
|
443 |
+
</Stack>
|
444 |
+
<Stack
|
445 |
+
spacing={2}
|
446 |
+
direction="row"
|
447 |
+
sx={{ mb: 2 }}
|
448 |
+
alignItems="center"
|
449 |
+
>
|
450 |
+
<TollIcon />
|
451 |
+
<Slider
|
452 |
+
marks
|
453 |
+
id="maxTokens"
|
454 |
+
name="maxTokens"
|
455 |
+
min={1024}
|
456 |
+
max={8000}
|
457 |
+
defaultValue={2048}
|
458 |
+
step={256}
|
459 |
+
valueLabelDisplay="auto"
|
460 |
+
aria-label="Max Tokens"
|
461 |
+
/>
|
462 |
+
<MoneyIcon />
|
463 |
+
</Stack>
|
464 |
+
<input
|
465 |
+
id="template"
|
466 |
+
name="template"
|
467 |
+
type="hidden"
|
468 |
+
value={template}
|
469 |
+
onChange={event => {
|
470 |
+
setTemplate(event.target.value);
|
471 |
+
}}
|
472 |
+
/>
|
473 |
+
</AccordionDetails>
|
474 |
+
</Accordion>
|
475 |
+
</Paper>
|
476 |
</Box>
|
477 |
|
478 |
+
<Paper variant="elevation" sx={{ p: 1, overflow: "auto" }}>
|
479 |
+
<List sx={{ flex: 1, p: 0 }}>
|
480 |
+
<ListSubheader
|
481 |
+
sx={{ fontSize: "1em", fontWeight: "normal", color: "white" }}
|
482 |
+
>
|
483 |
+
Games
|
484 |
+
</ListSubheader>
|
485 |
+
{answers.map((answer, index) => {
|
486 |
+
return (
|
487 |
+
<ListItem
|
488 |
+
key={answer.id}
|
489 |
+
secondaryAction={
|
490 |
+
<Stack sx={{ flexDirection: "row", gap: 1 }}>
|
491 |
+
{answer.id === "1" ? undefined : (
|
492 |
+
<IconButton
|
493 |
+
edge="end"
|
494 |
+
aria-label="Delete"
|
495 |
+
onClick={() => {
|
496 |
+
setAnswers(previousAnswers =>
|
497 |
+
previousAnswers.filter(
|
498 |
+
({ id }) => id !== answer.id
|
499 |
+
)
|
500 |
+
);
|
501 |
+
if (runningId === answer.id) {
|
502 |
+
const previous =
|
503 |
+
answers[index + 1];
|
504 |
+
if (previous) {
|
505 |
+
setActiveId(previous.id);
|
506 |
+
setRunningId(previous.id);
|
507 |
+
setTemplate(
|
508 |
+
prettify(
|
509 |
+
previous.content
|
510 |
+
)
|
511 |
+
);
|
512 |
+
reload();
|
513 |
+
}
|
514 |
}
|
515 |
+
}}
|
516 |
+
>
|
517 |
+
<DeleteForeverIcon />
|
518 |
+
</IconButton>
|
519 |
+
)}
|
520 |
+
</Stack>
|
521 |
+
}
|
522 |
+
disablePadding
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
523 |
>
|
524 |
+
<ListItemButton
|
525 |
+
dense
|
526 |
+
selected={activeId === answer.id}
|
527 |
+
// disabled={activeId === answer.id}
|
528 |
+
role={undefined}
|
529 |
+
onClick={() => {
|
530 |
+
setActiveId(answer.id);
|
531 |
+
setRunningId(answer.id);
|
532 |
+
setTemplate(prettify(answer.content));
|
533 |
+
reload();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
534 |
}}
|
535 |
+
>
|
536 |
+
<ListItemIcon>
|
537 |
+
{runningId === answer.id ? (
|
538 |
+
<CheckIcon />
|
539 |
+
) : (
|
540 |
+
<VisibilityIcon />
|
541 |
+
)}
|
542 |
+
</ListItemIcon>
|
543 |
+
|
544 |
+
<ListItemText
|
545 |
+
primary={answer.task}
|
546 |
+
primaryTypographyProps={{
|
547 |
+
sx: {
|
548 |
+
overflow: "hidden",
|
549 |
+
textOverflow: "ellipsis",
|
550 |
+
whiteSpace: "nowrap",
|
551 |
+
fontSize: 16,
|
552 |
+
},
|
553 |
+
}}
|
554 |
+
/>
|
555 |
+
</ListItemButton>
|
556 |
+
</ListItem>
|
557 |
+
);
|
558 |
+
})}
|
559 |
+
</List>
|
560 |
+
</Paper>
|
561 |
</Stack>
|
562 |
</Stack>
|
563 |
+
<Stack
|
564 |
+
sx={{
|
565 |
+
flex: 1,
|
566 |
+
width: { md: "50%" },
|
567 |
+
height: { xs: "50%", md: "auto" },
|
568 |
+
position: "relative",
|
569 |
+
}}
|
570 |
+
>
|
571 |
<AppBar position="static" elevation={0} color="default">
|
572 |
<Toolbar>
|
573 |
<IconButton
|
574 |
+
edge="start"
|
575 |
color="inherit"
|
576 |
aria-label="Reload"
|
577 |
onClick={() => {
|
|
|
580 |
>
|
581 |
<ReplayIcon />
|
582 |
</IconButton>
|
583 |
+
{current && current.id !== "1" && (
|
584 |
+
<>
|
585 |
+
<Codepen title={current.task} content={current.content} />
|
586 |
+
<Codesandbox title={current.task} content={current.content} />
|
587 |
+
</>
|
588 |
+
)}
|
589 |
+
<Box sx={{ flex: 1 }} />
|
590 |
+
|
591 |
+
<InfoMenu />
|
592 |
+
|
593 |
+
<Link href="/" aria-label="home" style={{ color: "inherit" }}>
|
594 |
+
<Box
|
595 |
+
component="svg"
|
596 |
+
viewBox="0 0 24 24"
|
597 |
+
sx={{ fontSize: "2em", height: "1em", width: "1em" }}
|
598 |
+
>
|
599 |
+
<path
|
600 |
+
fill="currentColor"
|
601 |
+
d="m8,12c-.55,0-1-.45-1-1,0,0,0,0,0,0h-1v-1h4v1h-1s0,0,0,0c0,.55-.45,1-1,1Zm-4-2v4l.97,1.56-.02-4.03-.96-1.53Zm4,8h4v-1h-4v1Zm4,2v2s3,0,3,0l2.5-4h-4.3l-1.2,2Zm8-12v6l-1.28,2.05-4.33-.04.61-1.01h-3s0-13,0-13c7,0,8,6,8,6Zm-2,3l-2-2-2,2.01,2,1.99,2-2Zm-7,4h-1v1h1v-1Z"
|
602 |
+
/>
|
603 |
+
</Box>
|
604 |
+
</Link>
|
605 |
</Toolbar>
|
606 |
</AppBar>
|
607 |
{loadingLive && (
|
|
|
628 |
overflow: "hidden",
|
629 |
visibility: loadingLive ? "hidden" : undefined,
|
630 |
}}
|
631 |
+
onLoad={() => {
|
632 |
+
if (current) {
|
633 |
+
setLoadingLive(true);
|
634 |
+
setTries(1);
|
635 |
+
connection.current = false;
|
636 |
+
call({ template: current.content });
|
637 |
+
}
|
638 |
+
}}
|
639 |
src="/live"
|
640 |
/>
|
641 |
</Stack>
|
642 |
</Stack>
|
643 |
+
|
644 |
+
<SimpleSnackbar
|
645 |
+
handleClose={handleSnackbarClose}
|
646 |
+
showError={showError}
|
647 |
+
message={errorMessage}
|
648 |
+
/>
|
649 |
</>
|
650 |
);
|
651 |
}
|
src/pages/live.tsx
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import Script from "next/script";
|
|
|
2 |
|
3 |
const styles = (
|
4 |
<style>
|
@@ -12,7 +13,7 @@ const styles = (
|
|
12 |
height: 100%;
|
13 |
width: 100%;
|
14 |
overflow: hidden;
|
15 |
-
|
16 |
}
|
17 |
#__next {
|
18 |
display: contents;
|
@@ -24,7 +25,7 @@ export default function Page() {
|
|
24 |
return (
|
25 |
<>
|
26 |
{styles}
|
27 |
-
<canvas id="canvas"
|
28 |
<Script src="/js/utils.js" />
|
29 |
</>
|
30 |
);
|
|
|
1 |
import Script from "next/script";
|
2 |
+
import TWEEN from "@tweenjs/tween.js";
|
3 |
|
4 |
const styles = (
|
5 |
<style>
|
|
|
13 |
height: 100%;
|
14 |
width: 100%;
|
15 |
overflow: hidden;
|
16 |
+
background: #a9a9a9;
|
17 |
}
|
18 |
#__next {
|
19 |
display: contents;
|
|
|
25 |
return (
|
26 |
<>
|
27 |
{styles}
|
28 |
+
<canvas id="canvas" />
|
29 |
<Script src="/js/utils.js" />
|
30 |
</>
|
31 |
);
|
src/services/api/index.ts
CHANGED
@@ -1,14 +1,15 @@
|
|
1 |
import { ChatCompletionRequestMessage } from "openai";
|
2 |
import { nanoid } from "nanoid";
|
3 |
import { openai } from "@/services/api/openai";
|
4 |
-
import { extractCode, miniPrompt } from "@/utils";
|
5 |
|
6 |
export async function toOpenAI({
|
|
|
7 |
prompt = "extend the code",
|
8 |
negativePrompt = "",
|
|
|
9 |
template = "",
|
10 |
model = "gpt-3.5-turbo",
|
11 |
-
temperature = "0.2",
|
12 |
maxTokens = "2048",
|
13 |
}) {
|
14 |
const negativePrompt_ = negativePrompt.trim();
|
@@ -17,10 +18,9 @@ export async function toOpenAI({
|
|
17 |
const nextMessage: ChatCompletionRequestMessage = {
|
18 |
role: "user",
|
19 |
content: miniPrompt`
|
20 |
-
|
21 |
-
${negativePrompt_ ? `REMOVE: ${negativePrompt_}` : ""}
|
22 |
TEMPLATE:
|
23 |
-
\`\`\`
|
24 |
${template.trim().replace(/^\s+/gm, "").replace(/^\n+/g, "").replace(/\s+/, " ")}
|
25 |
\`\`\`
|
26 |
`,
|
@@ -31,35 +31,53 @@ export async function toOpenAI({
|
|
31 |
try {
|
32 |
const response = await openai.createChatCompletion({
|
33 |
model,
|
|
|
34 |
temperature: Number.parseFloat(temperature),
|
35 |
messages: [
|
36 |
{
|
37 |
role: "system",
|
38 |
content: miniPrompt`
|
39 |
-
|
40 |
-
|
41 |
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
},
|
51 |
nextMessage,
|
52 |
],
|
53 |
-
max_tokens: Number.parseInt(maxTokens, 10),
|
54 |
});
|
55 |
|
56 |
const { message } = response.data.choices[0];
|
57 |
|
58 |
if (message) {
|
|
|
|
|
59 |
return {
|
60 |
...message,
|
61 |
content: extractCode(message.content).replace(
|
62 |
-
/(
|
63 |
""
|
64 |
),
|
65 |
task,
|
|
|
1 |
import { ChatCompletionRequestMessage } from "openai";
|
2 |
import { nanoid } from "nanoid";
|
3 |
import { openai } from "@/services/api/openai";
|
4 |
+
import { extractCode, miniPrompt } from "@/utils/prompt";
|
5 |
|
6 |
export async function toOpenAI({
|
7 |
+
command = "CREATE_GAME",
|
8 |
prompt = "extend the code",
|
9 |
negativePrompt = "",
|
10 |
+
temperature = "0.2",
|
11 |
template = "",
|
12 |
model = "gpt-3.5-turbo",
|
|
|
13 |
maxTokens = "2048",
|
14 |
}) {
|
15 |
const negativePrompt_ = negativePrompt.trim();
|
|
|
18 |
const nextMessage: ChatCompletionRequestMessage = {
|
19 |
role: "user",
|
20 |
content: miniPrompt`
|
21 |
+
"${command}": ${prompt_}. Return the full source code of the game.
|
|
|
22 |
TEMPLATE:
|
23 |
+
\`\`\`javascript
|
24 |
${template.trim().replace(/^\s+/gm, "").replace(/^\n+/g, "").replace(/\s+/, " ")}
|
25 |
\`\`\`
|
26 |
`,
|
|
|
31 |
try {
|
32 |
const response = await openai.createChatCompletion({
|
33 |
model,
|
34 |
+
max_tokens: Number.parseInt(maxTokens),
|
35 |
temperature: Number.parseFloat(temperature),
|
36 |
messages: [
|
37 |
{
|
38 |
role: "system",
|
39 |
content: miniPrompt`
|
40 |
+
You are a skilled 2D game developer working with JavaScript on Canvas2D.
|
41 |
+
You understand and follow a set of "COMMANDS" to build and modify games.
|
42 |
|
43 |
+
- "CREATE_GAME": You initiate the development of a game. You consider the game type, environment, basic mechanics and extend the "TEMPLATE".
|
44 |
+
- "ADD_FEATURE": You add new features to the game like power-ups, enemies, or levels.
|
45 |
+
- "REMOVE_FEATURE": You can remove any existing feature from the game.
|
46 |
+
- "UPDATE_FEATURE": You can modify an existing feature in the game, altering its behavior or properties.
|
47 |
+
- "FIX_BUG": You debug and fix problems in the game, ensuring everything functions as intended.
|
48 |
+
|
49 |
+
You NEVER use any (external) assets: image, base64, sprite or audio.
|
50 |
+
You can use these globally available libraries without importing them: TWEEN.
|
51 |
+
Never use alert! Write your message on Canvas directly.
|
52 |
+
You aim for high performance.
|
53 |
+
Your "OUTPUT FORMAT" must be valid JavaScript code within a markdown code block.
|
54 |
+
It's crucial to follow these "COMMANDS" and "OUTPUT FORMAT" for the desired results.
|
55 |
+
`,
|
56 |
+
// content: miniPrompt`
|
57 |
+
// You are a 2D Game developer and use JavaScript to create full games on Canvas2D.
|
58 |
+
// You can choose to add highscore, levels, player life, power ups, enemies.
|
59 |
+
// You NEVER add assets like images or audio, everything you use is generated.
|
60 |
+
// You use space key for jumping or shooting; arrow left, bottom, right for movement
|
61 |
+
// You have a keen eye for performance optimization and are highly skilled in creating interactive experiences.
|
62 |
+
// When working on new features, you follow the "ADD" guidelines, and when necessary, remove or exclude elements using "REMOVE".
|
63 |
+
// You also pay close attention to "TEMPLATE" code, extending or fixing it as needed.
|
64 |
+
// Your "OUTPUT FORMAT" must be exclusively valid JavaScript in a markdown code block, which you achieve by using the provided "TEMPLATE".
|
65 |
+
// And remember, the "ADD", "REMOVE", "TEMPLATE", and "OUTPUT FORMAT" guidelines are crucial to follow for optimal results.
|
66 |
+
// `,
|
67 |
},
|
68 |
nextMessage,
|
69 |
],
|
|
|
70 |
});
|
71 |
|
72 |
const { message } = response.data.choices[0];
|
73 |
|
74 |
if (message) {
|
75 |
+
console.log("ORIGINAL OUTPUT");
|
76 |
+
console.log(message.content);
|
77 |
return {
|
78 |
...message,
|
79 |
content: extractCode(message.content).replace(
|
80 |
+
/(COMMANDS|CREATE_GAME|ADD_FEATURE|REMOVE_FEATURE|UPDATE_FEATURE|FIX_BUG|TEMPLATE|OUTPUT FORMAT).*\n/,
|
81 |
""
|
82 |
),
|
83 |
task,
|
src/store/atoms.ts
CHANGED
@@ -8,11 +8,11 @@ export const answersAtom = atomWithStorage<
|
|
8 |
content: string;
|
9 |
task: string;
|
10 |
}[]
|
11 |
-
>("
|
12 |
{
|
13 |
id: "1",
|
14 |
content: base.default,
|
15 |
-
task: "Base
|
16 |
},
|
17 |
]);
|
18 |
-
export const showCodeAtom = atomWithStorage("
|
|
|
8 |
content: string;
|
9 |
task: string;
|
10 |
}[]
|
11 |
+
>("2DGameGPT", [
|
12 |
{
|
13 |
id: "1",
|
14 |
content: base.default,
|
15 |
+
task: "Base Game",
|
16 |
},
|
17 |
]);
|
18 |
+
export const showCodeAtom = atomWithStorage("2DGameGPT-editor", false);
|
src/utils/index.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
import GPT3Tokenizer from "gpt3-tokenizer";
|
2 |
import prettier from "prettier";
|
|
|
3 |
|
4 |
export const tokenizer = new GPT3Tokenizer({ type: "gpt3" });
|
5 |
|
@@ -9,8 +10,13 @@ export function getTokens(text: string) {
|
|
9 |
|
10 |
export function prettify(code: string) {
|
11 |
try {
|
12 |
-
return prettier.format(code, {
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
14 |
return code;
|
15 |
}
|
16 |
}
|
@@ -24,5 +30,3 @@ export function getTheme(mode: string | undefined, systemMode: string | undefine
|
|
24 |
}
|
25 |
return undefined;
|
26 |
}
|
27 |
-
export { extractCode } from "@/utils/prompt";
|
28 |
-
export { miniPrompt } from "@/utils/prompt";
|
|
|
1 |
import GPT3Tokenizer from "gpt3-tokenizer";
|
2 |
import prettier from "prettier";
|
3 |
+
import parserBabel from "prettier/parser-babel";
|
4 |
|
5 |
export const tokenizer = new GPT3Tokenizer({ type: "gpt3" });
|
6 |
|
|
|
10 |
|
11 |
export function prettify(code: string) {
|
12 |
try {
|
13 |
+
return prettier.format(code, {
|
14 |
+
useTabs: true,
|
15 |
+
semi: true,
|
16 |
+
parser: "babel",
|
17 |
+
plugins: [parserBabel],
|
18 |
+
});
|
19 |
+
} catch (error) {
|
20 |
return code;
|
21 |
}
|
22 |
}
|
|
|
30 |
}
|
31 |
return undefined;
|
32 |
}
|
|
|
|
src/utils/share.tsx
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { renderToString } from "react-dom/server";
|
2 |
+
|
3 |
+
export const wrappers: Record<"js" | "html" | "css" | "miniHtml", (content?: string) => string> = {
|
4 |
+
html(content) {
|
5 |
+
return `<!DOCTYPE html>
|
6 |
+
<!-- generated with https://failfa.st -->
|
7 |
+
${renderToString(
|
8 |
+
<html lang="en">
|
9 |
+
<head>
|
10 |
+
<meta charSet="utf-8" />
|
11 |
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
12 |
+
<title>{content}</title>
|
13 |
+
<script defer src="/script.js" />
|
14 |
+
<link rel="stylesheet" href="/style.css" />
|
15 |
+
</head>
|
16 |
+
<body>
|
17 |
+
<canvas id="canvas" />
|
18 |
+
<a className="failfast" href="https://failfa.st" target="_blank">
|
19 |
+
AI generated with
|
20 |
+
<svg viewBox="0 0 24 24">
|
21 |
+
<path
|
22 |
+
fill="currentColor"
|
23 |
+
d="m8,12c-.55,0-1-.45-1-1,0,0,0,0,0,0h-1v-1h4v1h-1s0,0,0,0c0,.55-.45,1-1,1Zm-4-2v4l.97,1.56-.02-4.03-.96-1.53Zm4,8h4v-1h-4v1Zm4,2v2s3,0,3,0l2.5-4h-4.3l-1.2,2Zm8-12v6l-1.28,2.05-4.33-.04.61-1.01h-3s0-13,0-13c7,0,8,6,8,6Zm-2,3l-2-2-2,2.01,2,1.99,2-2Zm-7,4h-1v1h1v-1Z"
|
24 |
+
/>
|
25 |
+
</svg>
|
26 |
+
</a>
|
27 |
+
</body>
|
28 |
+
</html>
|
29 |
+
)}`;
|
30 |
+
},
|
31 |
+
miniHtml() {
|
32 |
+
return `
|
33 |
+
<!-- generated with https://failfa.st -->
|
34 |
+
${renderToString(
|
35 |
+
<>
|
36 |
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
37 |
+
|
38 |
+
<canvas id="canvas" />
|
39 |
+
<a className="failfast" href="https://failfa.st" target="_blank">
|
40 |
+
AI generated with
|
41 |
+
<svg viewBox="0 0 24 24">
|
42 |
+
<path
|
43 |
+
fill="currentColor"
|
44 |
+
d="m8,12c-.55,0-1-.45-1-1,0,0,0,0,0,0h-1v-1h4v1h-1s0,0,0,0c0,.55-.45,1-1,1Zm-4-2v4l.97,1.56-.02-4.03-.96-1.53Zm4,8h4v-1h-4v1Zm4,2v2s3,0,3,0l2.5-4h-4.3l-1.2,2Zm8-12v6l-1.28,2.05-4.33-.04.61-1.01h-3s0-13,0-13c7,0,8,6,8,6Zm-2,3l-2-2-2,2.01,2,1.99,2-2Zm-7,4h-1v1h1v-1Z"
|
45 |
+
/>
|
46 |
+
</svg>
|
47 |
+
</a>
|
48 |
+
</>
|
49 |
+
)}`;
|
50 |
+
},
|
51 |
+
css() {
|
52 |
+
return `/**
|
53 |
+
* generated with https://failfa.st
|
54 |
+
*/
|
55 |
+
|
56 |
+
* {
|
57 |
+
margin: 0;
|
58 |
+
padding: 0;
|
59 |
+
box-sizing: border-box;
|
60 |
+
}
|
61 |
+
html, body {
|
62 |
+
height: 100%;
|
63 |
+
width: 100%;
|
64 |
+
overflow: hidden;
|
65 |
+
background: #a9a9a9;
|
66 |
+
}
|
67 |
+
.failfast {
|
68 |
+
position: fixed;
|
69 |
+
display: "flex";
|
70 |
+
align-items: center;
|
71 |
+
align-content: center;
|
72 |
+
z-index: 1;
|
73 |
+
top: 0;
|
74 |
+
left: 0;
|
75 |
+
margin: 8px;
|
76 |
+
padding: 6px 16px;
|
77 |
+
background: black;
|
78 |
+
color: white;
|
79 |
+
text-decoration: none;
|
80 |
+
border-radius: 4px;
|
81 |
+
font-family: sans-serif;
|
82 |
+
}
|
83 |
+
.failfast svg {
|
84 |
+
height: 1em;
|
85 |
+
width: 1em;
|
86 |
+
font-size: 24px;
|
87 |
+
margin: 0 0 -4px 4px;
|
88 |
+
}
|
89 |
+
`;
|
90 |
+
},
|
91 |
+
js(content) {
|
92 |
+
return `
|
93 |
+
/**
|
94 |
+
* generated with https://failfa.st
|
95 |
+
*/
|
96 |
+
function __2DGameGPT__ResizeHelper(){
|
97 |
+
function handleResize() {
|
98 |
+
requestAnimationFrame(() => {
|
99 |
+
canvas.width = window.innerWidth;
|
100 |
+
canvas.height = window.innerHeight;
|
101 |
+
});
|
102 |
+
}
|
103 |
+
handleResize();
|
104 |
+
window.addEventListener("resize", handleResize, { passive: true });
|
105 |
+
}
|
106 |
+
__2DGameGPT__ResizeHelper()
|
107 |
+
${content}
|
108 |
+
`;
|
109 |
+
},
|
110 |
+
};
|