Gregor Adams commited on
Commit
745163e
·
unverified ·
2 Parent(s): a86df80 17156f9

Merge pull request #1 from failfa-st/feat/100percent-browser

Browse files
.env.local.example CHANGED
@@ -1 +1 @@
1
- OPENAI_API_KEY=
 
1
+ OPENAI_API_KEY=
.gitignore CHANGED
@@ -70,7 +70,7 @@ typings/
70
 
71
  # dotenv environment variables file
72
  .env
73
- .env.test
74
 
75
  # parcel-bundler cache (https://parceljs.org/)
76
  .cache
 
70
 
71
  # dotenv environment variables file
72
  .env
73
+ .env.local
74
 
75
  # parcel-bundler cache (https://parceljs.org/)
76
  .cache
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -17,13 +17,26 @@
17
  "url": "https://github.com/pixelass/"
18
  },
19
  "scripts": {
20
- "dev": "concurrently 'npm:dev-*'",
21
- "dev-ui": "next dev",
22
- "dev-frame": "webpack serve --config ./config/webpack.config.dev.js",
23
  "prepare": "husky install",
24
  "spj": "npx sort-package-json",
25
  "toc": "npx markdown-toc README.md -i"
26
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  "dependencies": {
28
  "@emotion/cache": "11.10.7",
29
  "@emotion/react": "11.10.6",
@@ -32,6 +45,7 @@
32
  "@mui/icons-material": "5.11.16",
33
  "@mui/material": "5.12.0",
34
  "axios": "1.3.5",
 
35
  "eslint": "8.37.0",
36
  "eslint-config-next": "13.2.4",
37
  "jotai": "2.0.4",
@@ -45,15 +59,10 @@
45
  },
46
  "devDependencies": {
47
  "@semantic-release/git": "^10.0.1",
 
48
  "@types/react-syntax-highlighter": "^15.5.6",
49
- "concurrently": "^8.0.1",
50
- "css-loader": "^6.7.3",
51
- "html-webpack-plugin": "^5.5.0",
52
  "husky": "^8.0.3",
53
- "style-loader": "^3.3.2",
54
- "webpack": "^5.77.0",
55
- "webpack-cli": "^5.0.1",
56
- "webpack-dev-server": "^4.13.2"
57
  },
58
  "engines": {
59
  "node": ">= 18"
 
17
  "url": "https://github.com/pixelass/"
18
  },
19
  "scripts": {
20
+ "dev": "next dev",
 
 
21
  "prepare": "husky install",
22
  "spj": "npx sort-package-json",
23
  "toc": "npx markdown-toc README.md -i"
24
  },
25
+ "lint-staged": {
26
+ "*.js": [
27
+ "npx prettier --write"
28
+ ],
29
+ "package.json": [
30
+ "npx sort-package-json",
31
+ "npx prettier --write"
32
+ ],
33
+ "*.md": [
34
+ "npx prettier --write"
35
+ ],
36
+ "README.md": [
37
+ "npx markdown-toc -i"
38
+ ]
39
+ },
40
  "dependencies": {
41
  "@emotion/cache": "11.10.7",
42
  "@emotion/react": "11.10.6",
 
45
  "@mui/icons-material": "5.11.16",
46
  "@mui/material": "5.12.0",
47
  "axios": "1.3.5",
48
+ "esdeka": "0.1.18",
49
  "eslint": "8.37.0",
50
  "eslint-config-next": "13.2.4",
51
  "jotai": "2.0.4",
 
59
  },
60
  "devDependencies": {
61
  "@semantic-release/git": "^10.0.1",
62
+ "@types/node": "18.15.11",
63
  "@types/react-syntax-highlighter": "^15.5.6",
 
 
 
64
  "husky": "^8.0.3",
65
+ "prettier": "^2.8.7"
 
 
 
66
  },
67
  "engines": {
68
  "node": ">= 18"
project/public/index.html DELETED
@@ -1,15 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <title>fail3</title>
5
- <meta charset="utf-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
-
8
- <link rel="stylesheet" href="./style.css" />
9
- </head>
10
- <body>
11
- <canvas id="canvas"></canvas>
12
- <script src="utils.js"></script>
13
- <!-- WEBPACK will inject the bundle.js here -->
14
- </body>
15
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
project/public/style.css DELETED
@@ -1,10 +0,0 @@
1
- * {
2
- box-sizing: border-box;
3
- margin: 0;
4
- padding: 0;
5
- }
6
-
7
- html,
8
- body {
9
- overflow: hidden;
10
- }
 
 
 
 
 
 
 
 
 
 
 
project/public/utils.js DELETED
@@ -1,9 +0,0 @@
1
- const canvas = document.querySelector("#canvas");
2
- function handleResize() {
3
- requestAnimationFrame(() => {
4
- canvas.width = window.innerWidth;
5
- canvas.height = window.innerHeight;
6
- });
7
- }
8
- handleResize();
9
- window.addEventListener("resize", handleResize, { passive: true });
 
 
 
 
 
 
 
 
 
 
project/src/.gitkeep DELETED
File without changes
public/js/utils.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const canvas = document.querySelector("#canvas");
2
+ const ctx = canvas.getContext("2d");
3
+
4
+ function handleResize() {
5
+ requestAnimationFrame(() => {
6
+ canvas.width = window.innerWidth;
7
+ canvas.height = window.innerHeight;
8
+ });
9
+ }
10
+ handleResize();
11
+ window.addEventListener("resize", handleResize, { passive: true });
12
+
13
+ const clients = {
14
+ host: "__ESDEKA::host__",
15
+ guest: "__ESDEKA::guest__",
16
+ };
17
+
18
+ // Shared communicators
19
+
20
+ function subscribe(channel, callback) {
21
+ function handleMessage(event) {
22
+ if (
23
+ event.data.client &&
24
+ Object.values(clients).includes(event.data.client) &&
25
+ event.data.channel === channel
26
+ ) {
27
+ callback(event);
28
+ }
29
+ }
30
+ window.addEventListener("message", handleMessage);
31
+
32
+ return () => {
33
+ window.removeEventListener("message", handleMessage);
34
+ };
35
+ }
36
+ const host = {};
37
+ // Guest communicators
38
+
39
+ function answer(window_, channel, targetOrigin = "*") {
40
+ window_.postMessage(
41
+ {
42
+ client: clients.guest,
43
+ channel,
44
+ action: {
45
+ type: "answer",
46
+ },
47
+ },
48
+ targetOrigin
49
+ );
50
+ }
51
+
52
+ function handleTemplate(template) {
53
+ try {
54
+ Function("Template", `${template};`)();
55
+ } catch (error) {
56
+ console.log(error);
57
+ }
58
+ }
59
+
60
+ subscribe("fail4", event => {
61
+ const { action } = event.data;
62
+ switch (action.type) {
63
+ case "call":
64
+ host.current = event.source;
65
+ answer(event.source, "fail4");
66
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
67
+ handleTemplate(action.payload.template);
68
+ break;
69
+ case "broadcast":
70
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
71
+ handleTemplate(action.payload.template);
72
+ break;
73
+ default:
74
+ break;
75
+ }
76
+ });
src/lib/theme.ts CHANGED
@@ -12,11 +12,12 @@ export const roboto = Roboto({
12
  // Create a theme instance.
13
  const theme = createTheme({
14
  palette: {
 
15
  primary: {
16
- main: "#556cd6",
17
  },
18
  secondary: {
19
- main: "#19857b",
20
  },
21
  error: {
22
  main: red.A400,
 
12
  // Create a theme instance.
13
  const theme = createTheme({
14
  palette: {
15
+ mode: "dark",
16
  primary: {
17
+ main: "#00d720",
18
  },
19
  secondary: {
20
+ main: "#cc06ed",
21
  },
22
  error: {
23
  main: red.A400,
src/pages/_app.tsx CHANGED
@@ -1,7 +1,6 @@
1
  import Head from "next/head";
2
  import { AppProps } from "next/app";
3
  import { ThemeProvider } from "@mui/material/styles";
4
- import CssBaseline from "@mui/material/CssBaseline";
5
  import { CacheProvider, EmotionCache } from "@emotion/react";
6
  import theme from "@/lib/theme";
7
  import createEmotionCache from "@/lib/createEmotionCache";
@@ -22,7 +21,6 @@ export default function MyApp(props: MyAppProps) {
22
  </Head>
23
  <ThemeProvider theme={theme}>
24
  {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
25
- <CssBaseline />
26
  <Component {...pageProps} />
27
  </ThemeProvider>
28
  </CacheProvider>
 
1
  import Head from "next/head";
2
  import { AppProps } from "next/app";
3
  import { ThemeProvider } from "@mui/material/styles";
 
4
  import { CacheProvider, EmotionCache } from "@emotion/react";
5
  import theme from "@/lib/theme";
6
  import createEmotionCache from "@/lib/createEmotionCache";
 
21
  </Head>
22
  <ThemeProvider theme={theme}>
23
  {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
 
24
  <Component {...pageProps} />
25
  </ThemeProvider>
26
  </CacheProvider>
src/pages/api/gpt.ts CHANGED
@@ -9,9 +9,9 @@ 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).end();
13
  }
14
  default:
15
- return response.status(405).end();
16
  }
17
  }
 
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({});
16
  }
17
  }
src/pages/api/run.ts CHANGED
@@ -9,10 +9,10 @@ export default async function handler(request: NextApiRequest, response: NextApi
9
  case "POST":
10
  if (request.body.content) {
11
  await fs.writeFile(outputFile, request.body.content);
12
- return response.status(200).end();
13
  }
14
- return response.status(500).end();
15
  default:
16
- return response.status(405).end();
17
  }
18
  }
 
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/index.tsx CHANGED
@@ -1,7 +1,8 @@
1
  import { useEffect, useRef, useState } from "react";
2
 
3
- import Head from "next/head";
4
  import axios from "axios";
 
 
5
  import CheckIcon from "@mui/icons-material/Check";
6
  import ClearIcon from "@mui/icons-material/Clear";
7
  import ContentCopyIcon from "@mui/icons-material/ContentCopy";
@@ -9,7 +10,6 @@ import VisibilityIcon from "@mui/icons-material/Visibility";
9
  import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
10
  import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
11
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
12
- import HourglassTopIcon from "@mui/icons-material/HourglassTop";
13
  import PlayArrowIcon from "@mui/icons-material/PlayArrow";
14
  import ReplayIcon from "@mui/icons-material/Replay";
15
  import TextField from "@mui/material/TextField";
@@ -30,245 +30,373 @@ import Toolbar from "@mui/material/Toolbar";
30
  import ListItemIcon from "@mui/material/ListItemIcon";
31
  import ListItemButton from "@mui/material/ListItemButton";
32
  import ListItemText from "@mui/material/ListItemText";
 
 
 
 
 
 
33
 
34
  const base = {
35
- default: "/** 1.Base Script */const canvas=document.querySelector('canvas');",
 
 
 
 
 
 
 
 
 
 
 
36
  };
37
 
38
  const fontMono = Fira_Code({
39
  subsets: ["latin"],
40
  });
41
 
 
 
 
 
 
 
 
 
42
  export default function Home() {
43
  const ref = useRef<HTMLIFrameElement>(null);
44
  const [template, setTemplate] = useState(base.default);
45
  const [runningId, setRunningId] = useState("1");
46
  const [activeId, setActiveId] = useState("1");
47
- const [answers, setAnswers] = useState<{ id: string; content: string; task: string }[]>([
48
- {
49
- id: "1",
50
- content: base.default,
51
- task: "Base Script",
52
- },
53
- ]);
54
  const [loading, setLoading] = useState(false);
 
 
 
 
 
 
55
 
 
56
  useEffect(() => {
57
  const current = answers.find(({ id }) => id === runningId);
58
- if (current) {
59
- void axios.post("/api/run", {
60
- content: current.content,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  });
 
 
 
62
  }
63
- }, [runningId, answers]);
 
 
 
64
 
65
  const current = answers.find(({ id }) => id === activeId);
66
 
 
 
 
 
 
 
 
 
 
67
  return (
68
- <Stack
69
- sx={{
70
- ...fontMono.style,
71
- flexDirection: "row",
72
- height: "100%",
73
- }}
74
- >
75
- <Head>
76
- <style>{`html,body,#__next{margin:0;height:100%;overflow:hidden}`}</style>
77
- </Head>
78
- <Stack sx={{ width: "50%", flex: 1, gap: 2 }}>
79
- <Box
80
- component="form"
81
- onSubmit={async event => {
82
- event.preventDefault();
83
- const formData = new FormData(event.target as HTMLFormElement);
84
- const formObject = Object.fromEntries(formData);
85
- try {
86
- setLoading(true);
87
- const { data } = await axios.post("/api/gpt", formObject);
88
- const answer = data;
89
- setAnswers([answer, ...answers]);
90
- setRunningId(answer.id);
91
- } catch (error) {
92
- console.error(error);
93
- } finally {
94
- setLoading(false);
95
- }
96
- }}
97
- >
98
- <AppBar position="static" elevation={0}>
99
- <Toolbar>
100
- <IconButton
101
- type="submit"
102
- edge="start"
103
- color="inherit"
104
- aria-label={loading ? "Loading" : "Run"}
105
- disabled={loading}
106
- >
107
- {loading ? <HourglassTopIcon /> : <PlayArrowIcon />}
108
- </IconButton>
109
- <Typography sx={{ flex: 1 }}>
110
- {current?.task} - {current?.id ?? ""}
111
- </Typography>
112
- <IconButton
113
- edge="end"
114
- color="inherit"
115
- aria-label="Clear Prompt"
116
- onClick={async () => {
117
- await axios.post("/api/run", {
118
- content:
119
- "/** 1.Base Script */const canvas=document.querySelector('canvas');",
120
- });
121
- setActiveId("1");
122
- setTemplate(
123
- "/** 1.Base Script */const canvas=document.querySelector('canvas');"
124
- );
125
- }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  >
127
- <ClearIcon />
128
- </IconButton>
129
- </Toolbar>
130
- </AppBar>
131
- <Paper variant="outlined" sx={{ p: 0 }}>
132
- <Stack sx={{ p: 2, gap: 2 }}>
133
- <Typography>Based on: {current?.task ?? "Base Script"}</Typography>
134
- <TextField
135
- multiline
136
- fullWidth
137
- id="prompt"
138
- name="prompt"
139
- label="Prompt"
140
- placeholder="add a red box"
141
- maxRows={6}
142
- InputProps={{
143
- style: fontMono.style,
144
- }}
145
- />
146
- <TextField
147
- multiline
148
- fullWidth
149
- id="negativePrompt"
150
- name="negativePrompt"
151
- label="Negative Prompt"
152
- placeholder="images, audio files"
153
- maxRows={6}
154
- InputProps={{
155
- style: fontMono.style,
156
- }}
157
- />
158
- </Stack>
159
- </Paper>
160
- <Accordion>
161
- <AccordionSummary
162
- expandIcon={<ExpandMoreIcon />}
163
- aria-controls="panel1a-content"
164
- id="panel1a-header"
165
- >
166
- <Typography>Options</Typography>
167
- </AccordionSummary>
168
- <AccordionDetails>
169
- <TextField
170
- multiline
171
- fullWidth
172
- id="template"
173
- name="template"
174
- label="Template"
175
- placeholder={base.default}
176
- maxRows={6}
177
- value={template}
178
- InputProps={{
179
- style: { ...fontMono.style },
180
- }}
181
- onChange={event => {
182
- setTemplate(event.target.value);
183
- }}
184
- />
185
- </AccordionDetails>
186
- </Accordion>
187
- </Box>
188
 
189
- <List sx={{ flex: 1, overflow: "auto" }}>
190
- {answers.map(answer => {
191
- return (
192
- <ListItem
193
- key={answer.id}
194
- secondaryAction={
195
- <Stack sx={{ flexDirection: "row", gap: 1 }}>
196
- {answer.id === "1" ? undefined : (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  <IconButton
198
  edge="end"
199
- aria-label="Delete"
 
 
200
  onClick={() => {
201
- setAnswers(
202
- answers.filter(({ id }) => id !== answer.id)
203
- );
204
  }}
205
  >
206
- <DeleteForeverIcon />
 
 
 
 
207
  </IconButton>
208
- )}
209
- <IconButton
210
- edge="end"
211
- aria-label="Show"
212
- onClick={() => {
213
- setRunningId(answer.id);
214
- }}
215
- >
216
- {runningId === answer.id ? (
217
- <VisibilityIcon />
 
 
 
 
 
 
218
  ) : (
219
- <VisibilityOffIcon />
220
  )}
221
- </IconButton>
222
- </Stack>
223
- }
224
- disablePadding
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  >
226
- <ListItemButton
227
- dense
228
- selected={activeId === answer.id}
229
- role={undefined}
230
- onClick={() => {
231
- setActiveId(answer.id);
232
- setTemplate(answer.content);
233
- }}
234
- >
235
- <ListItemIcon>
236
- {activeId === answer.id ? (
237
- <CheckIcon />
238
- ) : (
239
- <ContentCopyIcon />
240
- )}
241
- </ListItemIcon>
242
- <ListItemText primary={`${answer.task} - ${answer.id}`} />
243
- </ListItemButton>
244
- </ListItem>
245
- );
246
- })}
247
- </List>
248
- </Stack>
249
- <Stack sx={{ flex: 1, width: "50%" }}>
250
- <AppBar position="static" elevation={0}>
251
- <Toolbar>
252
- <IconButton
253
- color="inherit"
254
- aria-label="Reload"
255
- onClick={() => {
256
- if (ref.current) {
257
- ref.current.src = `//localhost:8080?${nanoid()}`;
258
- }
259
  }}
260
  >
261
- <ReplayIcon />
262
- </IconButton>
263
- </Toolbar>
264
- </AppBar>
265
- <Box
266
- ref={ref}
267
- component="iframe"
268
- sx={{ width: "100%", flex: 1, m: 0, border: 0 }}
269
- src="//localhost:8080"
270
- />
 
 
 
 
 
 
 
271
  </Stack>
272
- </Stack>
273
  );
274
  }
 
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";
7
  import ClearIcon from "@mui/icons-material/Clear";
8
  import ContentCopyIcon from "@mui/icons-material/ContentCopy";
 
10
  import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
11
  import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
12
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
 
13
  import PlayArrowIcon from "@mui/icons-material/PlayArrow";
14
  import ReplayIcon from "@mui/icons-material/Replay";
15
  import TextField from "@mui/material/TextField";
 
30
  import ListItemIcon from "@mui/material/ListItemIcon";
31
  import ListItemButton from "@mui/material/ListItemButton";
32
  import ListItemText from "@mui/material/ListItemText";
33
+ import { useHost } from "esdeka/react";
34
+ import CircularProgress from "@mui/material/CircularProgress";
35
+ import CssBaseline from "@mui/material/CssBaseline";
36
+ import Slider from "@mui/material/Slider";
37
+ import { useAtom } from "jotai";
38
+ import { atomWithStorage } from "jotai/utils";
39
 
40
  const base = {
41
+ default: `/** CHANGELOG
42
+ * 1. Set up canvas
43
+ */
44
+ const canvas = document.querySelector('canvas');
45
+ const ctx = canvas.getContext('2d');
46
+ /*60FPS draw cycle*/
47
+ function draw(){
48
+ const FPS = 60;
49
+ setTimeout(requestAnimationFrame(draw),1000/FPS)
50
+ }
51
+ draw();
52
+ `.trim(),
53
  };
54
 
55
  const fontMono = Fira_Code({
56
  subsets: ["latin"],
57
  });
58
 
59
+ const answersAtom = atomWithStorage<{ id: string; content: string; task: string }[]>("fail4", [
60
+ {
61
+ id: "1",
62
+ content: base.default,
63
+ task: "Base Script",
64
+ },
65
+ ]);
66
+
67
  export default function Home() {
68
  const ref = useRef<HTMLIFrameElement>(null);
69
  const [template, setTemplate] = useState(base.default);
70
  const [runningId, setRunningId] = useState("1");
71
  const [activeId, setActiveId] = useState("1");
72
+ const [answers, setAnswers] = useAtom(answersAtom);
 
 
 
 
 
 
73
  const [loading, setLoading] = useState(false);
74
+ const [loadingLive, setLoadingLive] = useState(true);
75
+
76
+ const { broadcast, call, subscribe } = useHost(ref, "fail4");
77
+
78
+ const connection = useRef(false);
79
+ const [tries, setTries] = useState(1);
80
 
81
+ // Send a connection request
82
  useEffect(() => {
83
  const current = answers.find(({ id }) => id === runningId);
84
+ if (connection.current || tries <= 0) {
85
+ return () => {
86
+ /* Consistency */
87
+ };
88
+ }
89
+
90
+ const timeout = setTimeout(() => {
91
+ if (current) {
92
+ // call({ template: "" });
93
+
94
+ call({ template: current.content });
95
+ }
96
+
97
+ setTries(tries - 1);
98
+ }, 1_000);
99
+
100
+ return () => {
101
+ clearTimeout(timeout);
102
+ };
103
+ }, [call, tries, answers, runningId]);
104
+
105
+ useEffect(() => {
106
+ if (!connection.current && loadingLive) {
107
+ const unsubscribe = subscribe(event => {
108
+ const { action } = event.data;
109
+ switch (action.type) {
110
+ case "answer":
111
+ connection.current = true;
112
+ setLoadingLive(false);
113
+
114
+ console.log("connected");
115
+ break;
116
+ default:
117
+ break;
118
+ }
119
  });
120
+ return () => {
121
+ unsubscribe();
122
+ };
123
  }
124
+ return () => {
125
+ /* Consistency */
126
+ };
127
+ }, [subscribe, loadingLive]);
128
 
129
  const current = answers.find(({ id }) => id === activeId);
130
 
131
+ function reload() {
132
+ connection.current = false;
133
+ if (ref.current) {
134
+ ref.current.src = `/live?${nanoid()}`;
135
+ setLoadingLive(true);
136
+ setTries(1);
137
+ }
138
+ }
139
+
140
  return (
141
+ <>
142
+ <CssBaseline />
143
+
144
+ <Stack
145
+ sx={{
146
+ ...fontMono.style,
147
+ position: "absolute",
148
+ inset: 0,
149
+ overflow: "hidden",
150
+ flexDirection: "row",
151
+ height: "100%",
152
+ }}
153
+ >
154
+ <Stack sx={{ width: "50%", flex: 1, gap: 2 }}>
155
+ <Box
156
+ component="form"
157
+ onSubmit={async event => {
158
+ event.preventDefault();
159
+ const formData = new FormData(event.target as HTMLFormElement);
160
+ const formObject = Object.fromEntries(formData);
161
+ try {
162
+ setLoading(true);
163
+ const { data } = await axios.post("/api/gpt", formObject);
164
+ const answer = data;
165
+ setAnswers(previousAnswers => [answer, ...previousAnswers]);
166
+ setRunningId(answer.id);
167
+ reload();
168
+ } catch (error) {
169
+ console.error(error);
170
+ } finally {
171
+ setLoading(false);
172
+ }
173
+ }}
174
+ >
175
+ <AppBar position="static" elevation={0}>
176
+ <Toolbar>
177
+ <IconButton
178
+ type="submit"
179
+ edge="start"
180
+ color="inherit"
181
+ aria-label={loading ? "Loading" : "Run"}
182
+ aria-disabled={loading}
183
+ disabled={loading}
184
+ >
185
+ {loading ? <CircularProgress size={24} /> : <PlayArrowIcon />}
186
+ </IconButton>
187
+ <Typography sx={{ flex: 1 }}>
188
+ {loading
189
+ ? "Loading …"
190
+ : `${current?.task} - ${current?.id ?? ""}`}
191
+ </Typography>
192
+ <IconButton
193
+ edge="end"
194
+ color="inherit"
195
+ aria-label="Clear Prompt"
196
+ onClick={async () => {
197
+ // broadcast({ template: base.default });
198
+ setActiveId("1");
199
+ setTemplate(base.default);
200
+ reload();
201
+ }}
202
+ >
203
+ <ClearIcon />
204
+ </IconButton>
205
+ </Toolbar>
206
+ </AppBar>
207
+ <Paper variant="outlined" sx={{ p: 0 }}>
208
+ <Stack sx={{ p: 2, gap: 2 }}>
209
+ <Typography>
210
+ Based on: {current?.task ?? "Base Script"} - {activeId}
211
+ </Typography>
212
+ <TextField
213
+ multiline
214
+ fullWidth
215
+ id="prompt"
216
+ name="prompt"
217
+ label="Prompt"
218
+ placeholder="red heart"
219
+ defaultValue="red heart"
220
+ maxRows={6}
221
+ InputProps={{
222
+ style: fontMono.style,
223
+ }}
224
+ />
225
+ <TextField
226
+ multiline
227
+ fullWidth
228
+ id="negativePrompt"
229
+ name="negativePrompt"
230
+ label="Negative Prompt"
231
+ placeholder="images, audio files"
232
+ maxRows={6}
233
+ InputProps={{
234
+ style: fontMono.style,
235
+ }}
236
+ />
237
+ </Stack>
238
+ </Paper>
239
+ <Accordion>
240
+ <AccordionSummary
241
+ expandIcon={<ExpandMoreIcon />}
242
+ aria-controls="panel1a-content"
243
+ id="panel1a-header"
244
  >
245
+ <Typography>Options</Typography>
246
+ </AccordionSummary>
247
+ <AccordionDetails>
248
+ <Stack
249
+ spacing={2}
250
+ direction="row"
251
+ sx={{ mb: 2 }}
252
+ alignItems="center"
253
+ >
254
+ <AcUnitIcon />
255
+ <Slider
256
+ marks
257
+ id="temperature"
258
+ name="temperature"
259
+ min={0}
260
+ max={0.8}
261
+ defaultValue={0.2}
262
+ step={0.1}
263
+ valueLabelDisplay="auto"
264
+ aria-label="Temperature"
265
+ />
266
+ <LocalFireDepartmentIcon />
267
+ </Stack>
268
+ <TextField
269
+ multiline
270
+ fullWidth
271
+ id="template"
272
+ name="template"
273
+ label="Template"
274
+ placeholder={base.default}
275
+ maxRows={10}
276
+ value={template}
277
+ InputProps={{
278
+ style: { ...fontMono.style },
279
+ }}
280
+ onChange={event => {
281
+ setTemplate(event.target.value);
282
+ }}
283
+ />
284
+ </AccordionDetails>
285
+ </Accordion>
286
+ </Box>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
+ <List sx={{ flex: 1, overflow: "auto" }}>
289
+ {answers.map(answer => {
290
+ return (
291
+ <ListItem
292
+ key={answer.id}
293
+ secondaryAction={
294
+ <Stack sx={{ flexDirection: "row", gap: 1 }}>
295
+ {answer.id === "1" ? undefined : (
296
+ <IconButton
297
+ edge="end"
298
+ aria-label="Delete"
299
+ onClick={() => {
300
+ setAnswers(previousAnswers =>
301
+ previousAnswers.filter(
302
+ ({ id }) => id !== answer.id
303
+ )
304
+ );
305
+ if (runningId === answer.id) {
306
+ setRunningId("1");
307
+ setTemplate(base.default);
308
+ reload();
309
+ }
310
+ }}
311
+ >
312
+ <DeleteForeverIcon />
313
+ </IconButton>
314
+ )}
315
  <IconButton
316
  edge="end"
317
+ aria-label="Show"
318
+ aria-disabled={runningId === answer.id}
319
+ disabled={runningId === answer.id}
320
  onClick={() => {
321
+ setRunningId(answer.id);
322
+ reload();
 
323
  }}
324
  >
325
+ {runningId === answer.id ? (
326
+ <VisibilityIcon />
327
+ ) : (
328
+ <VisibilityOffIcon />
329
+ )}
330
  </IconButton>
331
+ </Stack>
332
+ }
333
+ disablePadding
334
+ >
335
+ <ListItemButton
336
+ dense
337
+ selected={activeId === answer.id}
338
+ role={undefined}
339
+ onClick={() => {
340
+ setActiveId(answer.id);
341
+ setTemplate(answer.content);
342
+ }}
343
+ >
344
+ <ListItemIcon>
345
+ {activeId === answer.id ? (
346
+ <CheckIcon />
347
  ) : (
348
+ <ContentCopyIcon />
349
  )}
350
+ </ListItemIcon>
351
+ <ListItemText primary={`${answer.task} - ${answer.id}`} />
352
+ </ListItemButton>
353
+ </ListItem>
354
+ );
355
+ })}
356
+ </List>
357
+ </Stack>
358
+ <Stack sx={{ flex: 1, width: "50%", position: "relative" }}>
359
+ <AppBar position="static" elevation={0}>
360
+ <Toolbar>
361
+ <IconButton
362
+ color="inherit"
363
+ aria-label="Reload"
364
+ onClick={() => {
365
+ reload();
366
+ }}
367
  >
368
+ <ReplayIcon />
369
+ </IconButton>
370
+ </Toolbar>
371
+ </AppBar>
372
+ {loadingLive && (
373
+ <Box
374
+ sx={{
375
+ position: "absolute",
376
+ zIndex: 100,
377
+ top: "50%",
378
+ left: "50%",
379
+ transform: "translate(-50%,-50%)",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  }}
381
  >
382
+ <CircularProgress />
383
+ </Box>
384
+ )}
385
+ <Box
386
+ ref={ref}
387
+ component="iframe"
388
+ sx={{
389
+ width: "100%",
390
+ flex: 1,
391
+ m: 0,
392
+ border: 0,
393
+ overflow: "hidden",
394
+ visibility: loadingLive ? "hidden" : undefined,
395
+ }}
396
+ src="/live"
397
+ />
398
+ </Stack>
399
  </Stack>
400
+ </>
401
  );
402
  }
src/pages/live.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Script from "next/script";
2
+ const styles = (
3
+ <style>
4
+ {`
5
+ * {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+ html, body {
11
+ height: 100%;
12
+ width: 100%;
13
+ overflow: hidden;
14
+ background: #a9a9a9;
15
+ }
16
+ #__next {
17
+ display: contents;
18
+ }
19
+ `}
20
+ </style>
21
+ );
22
+ export default function Page() {
23
+ return (
24
+ <>
25
+ {styles}
26
+ <canvas id="canvas" style={{}} />
27
+ <Script src="/js/utils.js" />
28
+ </>
29
+ );
30
+ }
src/services/api/index.ts CHANGED
@@ -1,5 +1,6 @@
1
  import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
2
  import { nanoid } from "nanoid";
 
3
 
4
  const configuration = new Configuration({
5
  apiKey: process.env.OPENAI_API_KEY,
@@ -26,35 +27,48 @@ function extractCode(string: string) {
26
  }
27
 
28
  export async function toOpenAI({
29
- prompt = "",
30
  negativePrompt = "",
31
  template = "",
32
- }: Record<string, string>) {
 
33
  const negativePrompt_ = negativePrompt.trim();
34
  const prompt_ = prompt.trim();
35
 
36
  const nextMessage: ChatCompletionRequestMessage = {
37
  role: "user",
38
  content: miniPrompt`
39
- DO: ${prompt_}
40
- ${negativePrompt_ ? `DONT: ${negativePrompt_}` : ""}
41
  INPUT: ${template.trim()}
42
  OUTPUT FORMAT: plain valid JavaScript
43
  `,
44
  };
45
  const task = `${prompt_}${negativePrompt_ ? ` | not(${negativePrompt_})` : ""}`;
46
- console.log(">>> NEXT MESSAGE CONTENT");
47
- console.log(nextMessage.content);
48
- console.log("<<<");
49
 
50
  try {
51
  const response = await openai.createChatCompletion({
52
  model: "gpt-3.5-turbo",
53
- temperature: 0.2,
54
  messages: [
55
  {
56
  role: "system",
57
- content: `You are a FULLSTACK DEVELOPER. You implement the "DO". You NEVER implement "DONT". You EXCLUSIVELY answer in the requested "OUTPUT FORMAT" and NOTHING ELSE. You ALWAYS follow the "DO", "DONT", "INPUT" and "OUTPUT FORMAT".`,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  },
59
  nextMessage,
60
  ],
 
1
  import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
2
  import { nanoid } from "nanoid";
3
+ import process from "node:process";
4
 
5
  const configuration = new Configuration({
6
  apiKey: process.env.OPENAI_API_KEY,
 
27
  }
28
 
29
  export async function toOpenAI({
30
+ prompt = "be creative",
31
  negativePrompt = "",
32
  template = "",
33
+ temperature = "0.2",
34
+ }) {
35
  const negativePrompt_ = negativePrompt.trim();
36
  const prompt_ = prompt.trim();
37
 
38
  const nextMessage: ChatCompletionRequestMessage = {
39
  role: "user",
40
  content: miniPrompt`
41
+ ADD: ${prompt_}
42
+ ${negativePrompt_ ? `REMOVE: ${negativePrompt_}` : ""}
43
  INPUT: ${template.trim()}
44
  OUTPUT FORMAT: plain valid JavaScript
45
  `,
46
  };
47
  const task = `${prompt_}${negativePrompt_ ? ` | not(${negativePrompt_})` : ""}`;
 
 
 
48
 
49
  try {
50
  const response = await openai.createChatCompletion({
51
  model: "gpt-3.5-turbo",
52
+ temperature: Number.parseFloat(temperature),
53
  messages: [
54
  {
55
  role: "system",
56
+ content: miniPrompt`
57
+ All UPPERCASE words are IMPORTANT, all "UPPERCASE" words in QUOTES (") indicate KEYWORDS.
58
+ You are: expert JavaScript Developer, creative, Canvas-2d expert, performance guru, interaction expert.
59
+ You strictly follow all "DOCS".
60
+ You extend "CHANGELOG" and the CODE.
61
+ You ALWAYS follow the "ADD", "REMOVE", "INPUT" and "OUTPUT FORMAT".
62
+ You NEVER explain anything.
63
+
64
+ DOCS:
65
+ "ADD" is a set of features that You write code for
66
+ "REMOVE" is a set of things that should be removed or changed to something else
67
+ "INPUT" is the code that should be EXTENDED, ADJUSTED or FIXED
68
+ "OUTPUT FORMAT" is always JavaScript. the output should always be just JavaScript and NOTHING ELSE
69
+
70
+ You EXCLUSIVELY answer in the requested "OUTPUT FORMAT" and NOTHING ELSE
71
+ `,
72
  },
73
  nextMessage,
74
  ],