enzostvs HF Staff commited on
Commit
ba8865d
·
1 Parent(s): 96591d2

Add redesign stuffs

Browse files
server.js CHANGED
@@ -219,8 +219,14 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
219
  });
220
 
221
  app.post("/api/ask-ai", async (req, res) => {
222
- const { prompt, provider, model } = req.body;
223
- if (!prompt || !model) {
 
 
 
 
 
 
224
  return res.status(400).send({
225
  ok: false,
226
  message: "Missing required fields",
@@ -310,7 +316,9 @@ app.post("/api/ask-ai", async (req, res) => {
310
  },
311
  {
312
  role: "user",
313
- content: prompt,
 
 
314
  },
315
  ],
316
  max_tokens: selectedProvider.max_tokens,
@@ -675,6 +683,43 @@ app.get("/api/remix/:username/:repo", async (req, res) => {
675
  });
676
  }
677
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  app.get("*", (_req, res) => {
679
  res.sendFile(path.join(__dirname, "dist", "index.html"));
680
  });
 
219
  });
220
 
221
  app.post("/api/ask-ai", async (req, res) => {
222
+ const { prompt, provider, model, redesignMarkdown } = req.body;
223
+ if (!model) {
224
+ return res.status(400).send({
225
+ ok: false,
226
+ message: "Missing required fields",
227
+ });
228
+ }
229
+ if (!redesignMarkdown && !prompt) {
230
  return res.status(400).send({
231
  ok: false,
232
  message: "Missing required fields",
 
316
  },
317
  {
318
  role: "user",
319
+ content: redesignMarkdown
320
+ ? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.`
321
+ : prompt,
322
  },
323
  ],
324
  max_tokens: selectedProvider.max_tokens,
 
683
  });
684
  }
685
  });
686
+
687
+ app.post("/api/re-design", async (req, res) => {
688
+ const { url } = req.body;
689
+ if (!url) {
690
+ return res.status(400).send({
691
+ ok: false,
692
+ message: "Missing required fields",
693
+ });
694
+ }
695
+
696
+ // call the api https://r.jina.ai/{url} and return the response
697
+ try {
698
+ const response = await fetch(
699
+ `https://r.jina.ai/${encodeURIComponent(url)}`,
700
+ {
701
+ method: "POST",
702
+ }
703
+ );
704
+ if (!response.ok) {
705
+ return res.status(500).send({
706
+ ok: false,
707
+ message: "Failed to fetch redesign",
708
+ });
709
+ }
710
+ // return the html response
711
+ const markdown = await response.text();
712
+ return res.status(200).send({
713
+ ok: true,
714
+ markdown,
715
+ });
716
+ } catch (error) {
717
+ return res.status(500).send({
718
+ ok: false,
719
+ message: error.message,
720
+ });
721
+ }
722
+ });
723
  app.get("*", (_req, res) => {
724
  res.sendFile(path.join(__dirname, "dist", "index.html"));
725
  });
src/components/ask-ai/ask-ai-new.tsx DELETED
@@ -1,385 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { useState, useRef } from "react";
3
- import classNames from "classnames";
4
- import { toast } from "sonner";
5
- import { useLocalStorage, useUpdateEffect } from "react-use";
6
- import { ArrowUp, ChevronDown } from "lucide-react";
7
- import { FaStopCircle } from "react-icons/fa";
8
-
9
- import Login from "../login/login";
10
- import { defaultHTML } from "../../../utils/consts";
11
- import SuccessSound from "./../../assets/success.mp3";
12
- import Settings from "../settings/settings";
13
- import ProModal from "../pro-modal/pro-modal";
14
- import { Button } from "../ui/button";
15
- // @ts-expect-error not needed
16
- import { MODELS } from "../../../utils/providers";
17
- import Loading from "../loading/loading";
18
- import { HtmlHistory } from "../../../utils/types";
19
- import InviteFriends from "../invite-friends/invite-friends";
20
-
21
- function AskAI({
22
- html,
23
- setHtml,
24
- onScrollToBottom,
25
- isAiWorking,
26
- setisAiWorking,
27
- onNewPrompt,
28
- onSuccess,
29
- }: {
30
- html: string;
31
- setHtml: (html: string) => void;
32
- onScrollToBottom: () => void;
33
- isAiWorking: boolean;
34
- onNewPrompt: (prompt: string) => void;
35
- htmlHistory?: HtmlHistory[];
36
- setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
37
- onSuccess: (h: string, p: string, n?: number[][]) => void;
38
- }) {
39
- const refThink = useRef<HTMLDivElement | null>(null);
40
-
41
- const [open, setOpen] = useState(false);
42
- const [prompt, setPrompt] = useState("");
43
- const [hasAsked, setHasAsked] = useState(false);
44
- const [previousPrompt, setPreviousPrompt] = useState("");
45
- const [provider, setProvider] = useLocalStorage("provider", "auto");
46
- const [model, setModel] = useLocalStorage("model", MODELS[0].value);
47
- const [openProvider, setOpenProvider] = useState(false);
48
- const [providerError, setProviderError] = useState("");
49
- const [openProModal, setOpenProModal] = useState(false);
50
- const [think, setThink] = useState<string | undefined>(undefined);
51
- const [openThink, setOpenThink] = useState(false);
52
- const [isThinking, setIsThinking] = useState(true);
53
- const [controller, setController] = useState<AbortController | null>(null);
54
-
55
- const audio = new Audio(SuccessSound);
56
- audio.volume = 0.5;
57
-
58
- const callAi = async () => {
59
- if (isAiWorking || !prompt.trim()) return;
60
- setisAiWorking(true);
61
- setProviderError("");
62
- setThink("");
63
- setOpenThink(false);
64
- setIsThinking(true);
65
-
66
- let contentResponse = "";
67
- let thinkResponse = "";
68
- let lastRenderTime = 0;
69
-
70
- const isFollowUp = html !== defaultHTML;
71
- const abortController = new AbortController();
72
- setController(abortController);
73
- try {
74
- onNewPrompt(prompt);
75
- if (isFollowUp) {
76
- const request = await fetch("/api/ask-ai", {
77
- method: "PUT",
78
- body: JSON.stringify({
79
- prompt,
80
- provider,
81
- previousPrompt,
82
- html,
83
- }),
84
- headers: {
85
- "Content-Type": "application/json",
86
- },
87
- signal: abortController.signal,
88
- });
89
- if (request && request.body) {
90
- const res = await request.json();
91
- if (!request.ok) {
92
- if (res.openLogin) {
93
- setOpen(true);
94
- } else if (res.openSelectProvider) {
95
- setOpenProvider(true);
96
- setProviderError(res.message);
97
- } else if (res.openProModal) {
98
- setOpenProModal(true);
99
- } else {
100
- toast.error(res.message);
101
- }
102
- setisAiWorking(false);
103
- return;
104
- }
105
- setHtml(res.html);
106
- toast.success("AI responded successfully");
107
- setPreviousPrompt(prompt);
108
- setPrompt("");
109
- setisAiWorking(false);
110
- onSuccess(res.html, prompt, res.updatedLines);
111
- audio.play();
112
- }
113
- } else {
114
- const request = await fetch("/api/ask-ai", {
115
- method: "POST",
116
- body: JSON.stringify({
117
- prompt,
118
- provider,
119
- model,
120
- }),
121
- headers: {
122
- "Content-Type": "application/json",
123
- },
124
- signal: abortController.signal,
125
- });
126
- if (request && request.body) {
127
- if (!request.ok) {
128
- const res = await request.json();
129
- if (res.openLogin) {
130
- setOpen(true);
131
- } else if (res.openSelectProvider) {
132
- setOpenProvider(true);
133
- setProviderError(res.message);
134
- } else if (res.openProModal) {
135
- setOpenProModal(true);
136
- } else {
137
- toast.error(res.message);
138
- }
139
- setisAiWorking(false);
140
- return;
141
- }
142
- const reader = request.body.getReader();
143
- const decoder = new TextDecoder("utf-8");
144
- const selectedModel = MODELS.find(
145
- (m: { value: string }) => m.value === model
146
- );
147
- let contentThink: string | undefined = undefined;
148
- const read = async () => {
149
- const { done, value } = await reader.read();
150
- if (done) {
151
- toast.success("AI responded successfully");
152
- setPreviousPrompt(prompt);
153
- setPrompt("");
154
- setisAiWorking(false);
155
- setHasAsked(true);
156
- audio.play();
157
-
158
- // Now we have the complete HTML including </html>, so set it to be sure
159
- const finalDoc = contentResponse.match(
160
- /<!DOCTYPE html>[\s\S]*<\/html>/
161
- )?.[0];
162
- if (finalDoc) {
163
- setHtml(finalDoc);
164
- }
165
- onSuccess(finalDoc ?? contentResponse, prompt);
166
-
167
- return;
168
- }
169
-
170
- const chunk = decoder.decode(value, { stream: true });
171
- thinkResponse += chunk;
172
- if (selectedModel?.isThinker) {
173
- const thinkMatch = thinkResponse.match(/<think>[\s\S]*/)?.[0];
174
- if (thinkMatch && !thinkResponse?.includes("</think>")) {
175
- if ((contentThink?.length ?? 0) < 3) {
176
- setOpenThink(true);
177
- }
178
- setThink(thinkMatch.replace("<think>", "").trim());
179
- contentThink += chunk;
180
- return read();
181
- }
182
- }
183
-
184
- contentResponse += chunk;
185
-
186
- const newHtml = contentResponse.match(
187
- /<!DOCTYPE html>[\s\S]*/
188
- )?.[0];
189
- if (newHtml) {
190
- setIsThinking(false);
191
- let partialDoc = newHtml;
192
- if (
193
- partialDoc.includes("<head>") &&
194
- !partialDoc.includes("</head>")
195
- ) {
196
- partialDoc += "\n</head>";
197
- }
198
- if (
199
- partialDoc.includes("<body") &&
200
- !partialDoc.includes("</body>")
201
- ) {
202
- partialDoc += "\n</body>";
203
- }
204
- if (!partialDoc.includes("</html>")) {
205
- partialDoc += "\n</html>";
206
- }
207
-
208
- // Throttle the re-renders to avoid flashing/flicker
209
- const now = Date.now();
210
- if (now - lastRenderTime > 300) {
211
- setHtml(partialDoc);
212
- lastRenderTime = now;
213
- }
214
-
215
- if (partialDoc.length > 200) {
216
- onScrollToBottom();
217
- }
218
- }
219
- read();
220
- };
221
-
222
- read();
223
- }
224
- }
225
- } catch (error: any) {
226
- setisAiWorking(false);
227
- toast.error(error.message);
228
- if (error.openLogin) {
229
- setOpen(true);
230
- }
231
- }
232
- };
233
-
234
- const stopController = () => {
235
- if (controller) {
236
- controller.abort();
237
- setController(null);
238
- setisAiWorking(false);
239
- setThink("");
240
- setOpenThink(false);
241
- setIsThinking(false);
242
- }
243
- };
244
-
245
- useUpdateEffect(() => {
246
- if (refThink.current) {
247
- refThink.current.scrollTop = refThink.current.scrollHeight;
248
- }
249
- }, [think]);
250
-
251
- useUpdateEffect(() => {
252
- if (!isThinking) {
253
- setOpenThink(false);
254
- }
255
- }, [isThinking]);
256
-
257
- return (
258
- <div className="bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent z-10 absolute bottom-3 left-3 w-[calc(100%-20px)] group">
259
- {think && (
260
- <div className="w-full border-b border-neutral-700 relative overflow-hidden">
261
- <header
262
- className="flex items-center justify-between px-5 py-2.5 group hover:bg-neutral-600/20 transition-colors duration-200 cursor-pointer"
263
- onClick={() => {
264
- setOpenThink(!openThink);
265
- }}
266
- >
267
- <p className="text-sm font-medium text-neutral-300 group-hover:text-neutral-200 transition-colors duration-200">
268
- {isThinking ? "AI is thinking..." : "AI's plan"}
269
- </p>
270
- <ChevronDown
271
- className={classNames(
272
- "size-4 text-neutral-400 group-hover:text-neutral-300 transition-all duration-200",
273
- {
274
- "rotate-180": openThink,
275
- }
276
- )}
277
- />
278
- </header>
279
- <main
280
- ref={refThink}
281
- className={classNames(
282
- "overflow-y-auto transition-all duration-200 ease-in-out",
283
- {
284
- "max-h-[0px]": !openThink,
285
- "min-h-[250px] max-h-[250px] border-t border-neutral-700":
286
- openThink,
287
- }
288
- )}
289
- >
290
- <p className="text-[13px] text-neutral-400 whitespace-pre-line px-5 pb-4 pt-3">
291
- {think}
292
- </p>
293
- </main>
294
- </div>
295
- )}
296
- <div className="w-full relative flex items-center justify-between">
297
- {isAiWorking && (
298
- <div className="absolute bg-neutral-800 rounded-lg bottom-0 left-4 w-[calc(100%-30px)] h-full z-1 flex items-center justify-between max-lg:text-sm">
299
- <div className="flex items-center justify-start gap-2">
300
- <Loading overlay={false} className="!size-4" />
301
- <p className="text-neutral-400 text-sm">
302
- AI is {isThinking ? "thinking" : "coding"}...{" "}
303
- </p>
304
- </div>
305
- <div
306
- className="text-xs text-neutral-400 px-1 py-0.5 rounded-md border border-neutral-600 flex items-center justify-center gap-1.5 bg-neutral-800 hover:brightness-110 transition-all duration-200 cursor-pointer"
307
- onClick={stopController}
308
- >
309
- <FaStopCircle />
310
- Stop generation
311
- </div>
312
- </div>
313
- )}
314
- <input
315
- type="text"
316
- disabled={isAiWorking}
317
- className="w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4"
318
- placeholder={
319
- hasAsked ? "Ask DeepSite for edits" : "Ask DeepSite anything..."
320
- }
321
- value={prompt}
322
- onChange={(e) => setPrompt(e.target.value)}
323
- onKeyDown={(e) => {
324
- if (e.key === "Enter" && !e.shiftKey) {
325
- callAi();
326
- }
327
- }}
328
- />
329
- </div>
330
- <div className="flex items-center justify-between gap-2 px-4 pb-3">
331
- <div className="flex-1">
332
- <InviteFriends />
333
- </div>
334
- <div className="flex items-center justify-end gap-2">
335
- <Settings
336
- provider={provider as string}
337
- model={model as string}
338
- onChange={setProvider}
339
- onModelChange={setModel}
340
- open={openProvider}
341
- error={providerError}
342
- onClose={setOpenProvider}
343
- />
344
- <Button
345
- size="iconXs"
346
- disabled={isAiWorking || !prompt.trim()}
347
- onClick={callAi}
348
- >
349
- <ArrowUp className="size-4" />
350
- </Button>
351
- </div>
352
- </div>
353
- <div
354
- className={classNames(
355
- "h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
356
- {
357
- "opacity-0 pointer-events-none": !open,
358
- }
359
- )}
360
- onClick={() => setOpen(false)}
361
- ></div>
362
- <div
363
- className={classNames(
364
- "absolute top-0 -translate-y-[calc(100%+8px)] right-0 z-10 w-80 border border-neutral-800 !bg-neutral-900 rounded-lg shadow-lg transition-all duration-75 overflow-hidden",
365
- {
366
- "opacity-0 pointer-events-none": !open,
367
- }
368
- )}
369
- >
370
- <Login html={html}>
371
- <p className="text-gray-500 text-sm mb-3">
372
- You reached the limit of free AI usage. Please login to continue.
373
- </p>
374
- </Login>
375
- </div>
376
- <ProModal
377
- html={html}
378
- open={openProModal}
379
- onClose={() => setOpenProModal(false)}
380
- />
381
- </div>
382
- );
383
- }
384
-
385
- export default AskAI;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/ask-ai/ask-ai.tsx CHANGED
@@ -1,11 +1,10 @@
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
  import { useState, useRef } from "react";
3
- import { RiSparkling2Fill } from "react-icons/ri";
4
- import { GrSend } from "react-icons/gr";
5
  import classNames from "classnames";
6
  import { toast } from "sonner";
7
  import { useLocalStorage, useUpdateEffect } from "react-use";
8
- import { ChevronDown } from "lucide-react";
 
9
 
10
  import Login from "../login/login";
11
  import { defaultHTML } from "../../../utils/consts";
@@ -14,7 +13,11 @@ import Settings from "../settings/settings";
14
  import ProModal from "../pro-modal/pro-modal";
15
  import { Button } from "../ui/button";
16
  // @ts-expect-error not needed
17
- import { MODELS } from "./../../../utils/providers";
 
 
 
 
18
 
19
  function AskAI({
20
  html,
@@ -30,6 +33,7 @@ function AskAI({
30
  onScrollToBottom: () => void;
31
  isAiWorking: boolean;
32
  onNewPrompt: (prompt: string) => void;
 
33
  setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
34
  onSuccess: (h: string, p: string, n?: number[][]) => void;
35
  }) {
@@ -47,12 +51,14 @@ function AskAI({
47
  const [think, setThink] = useState<string | undefined>(undefined);
48
  const [openThink, setOpenThink] = useState(false);
49
  const [isThinking, setIsThinking] = useState(true);
 
50
 
51
  const audio = new Audio(SuccessSound);
52
  audio.volume = 0.5;
53
 
54
- const callAi = async () => {
55
- if (isAiWorking || !prompt.trim()) return;
 
56
  setisAiWorking(true);
57
  setProviderError("");
58
  setThink("");
@@ -64,9 +70,11 @@ function AskAI({
64
  let lastRenderTime = 0;
65
 
66
  const isFollowUp = html !== defaultHTML;
 
 
67
  try {
68
  onNewPrompt(prompt);
69
- if (isFollowUp) {
70
  const request = await fetch("/api/ask-ai", {
71
  method: "PUT",
72
  body: JSON.stringify({
@@ -78,6 +86,7 @@ function AskAI({
78
  headers: {
79
  "Content-Type": "application/json",
80
  },
 
81
  });
82
  if (request && request.body) {
83
  const res = await request.json();
@@ -110,10 +119,12 @@ function AskAI({
110
  prompt,
111
  provider,
112
  model,
 
113
  }),
114
  headers: {
115
  "Content-Type": "application/json",
116
  },
 
117
  });
118
  if (request && request.body) {
119
  if (!request.ok) {
@@ -223,6 +234,17 @@ function AskAI({
223
  }
224
  };
225
 
 
 
 
 
 
 
 
 
 
 
 
226
  useUpdateEffect(() => {
227
  if (refThink.current) {
228
  refThink.current.scrollTop = refThink.current.scrollHeight;
@@ -236,7 +258,7 @@ function AskAI({
236
  }, [isThinking]);
237
 
238
  return (
239
- <div className="bg-neutral-800 border border-neutral-700 rounded-lg ring-[5px] focus-within:ring-sky-500/50 ring-transparent z-10 absolute bottom-3 left-3 w-[calc(100%-20px)] group">
240
  {think && (
241
  <div className="w-full border-b border-neutral-700 relative overflow-hidden">
242
  <header
@@ -274,37 +296,46 @@ function AskAI({
274
  </main>
275
  </div>
276
  )}
277
- <div
278
- className={classNames(
279
- "w-full relative flex items-center justify-between pl-3.5 p-2 lg:p-2 lg:pl-4",
280
- {
281
- "animate-pulse": isAiWorking,
282
- }
283
- )}
284
- >
285
  {isAiWorking && (
286
- <div className="absolute bg-neutral-800 rounded-lg bottom-0 left-11 w-[calc(100%-92px)] h-full z-1 flex items-center justify-start max-lg:text-sm">
287
- <p className="text-neutral-400 font-code">
288
- AI is {isThinking ? "thinking" : "coding"}...
289
- </p>
 
 
 
 
 
 
 
 
 
 
290
  </div>
291
  )}
292
- <RiSparkling2Fill className="size-5 text-neutral-400 group-focus-within:text-sky-500" />
293
  <input
294
  type="text"
295
  disabled={isAiWorking}
296
- className="w-full bg-transparent max-lg:text-sm outline-none px-3 text-white placeholder:text-neutral-400 font-code"
297
- placeholder={hasAsked ? "Ask AI for edits" : "Ask AI anything..."}
 
 
298
  value={prompt}
299
  onChange={(e) => setPrompt(e.target.value)}
300
  onKeyDown={(e) => {
301
- if (e.key === "Enter") {
302
  callAi();
303
  }
304
  }}
305
  />
 
 
 
 
 
 
306
  <div className="flex items-center justify-end gap-2">
307
- {/* <SpeechPrompt setPrompt={setPrompt} /> */}
308
  <Settings
309
  provider={provider as string}
310
  model={model as string}
@@ -315,12 +346,11 @@ function AskAI({
315
  onClose={setOpenProvider}
316
  />
317
  <Button
318
- variant="pink"
319
- size="icon"
320
  disabled={isAiWorking || !prompt.trim()}
321
- onClick={callAi}
322
  >
323
- <GrSend className="-translate-x-[1px]" />
324
  </Button>
325
  </div>
326
  </div>
 
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
  import { useState, useRef } from "react";
 
 
3
  import classNames from "classnames";
4
  import { toast } from "sonner";
5
  import { useLocalStorage, useUpdateEffect } from "react-use";
6
+ import { ArrowUp, ChevronDown } from "lucide-react";
7
+ import { FaStopCircle } from "react-icons/fa";
8
 
9
  import Login from "../login/login";
10
  import { defaultHTML } from "../../../utils/consts";
 
13
  import ProModal from "../pro-modal/pro-modal";
14
  import { Button } from "../ui/button";
15
  // @ts-expect-error not needed
16
+ import { MODELS } from "../../../utils/providers";
17
+ import Loading from "../loading/loading";
18
+ import { HtmlHistory } from "../../../utils/types";
19
+ import InviteFriends from "../invite-friends/invite-friends";
20
+ import ReImagine from "../re-imagine/re-imagine";
21
 
22
  function AskAI({
23
  html,
 
33
  onScrollToBottom: () => void;
34
  isAiWorking: boolean;
35
  onNewPrompt: (prompt: string) => void;
36
+ htmlHistory?: HtmlHistory[];
37
  setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
38
  onSuccess: (h: string, p: string, n?: number[][]) => void;
39
  }) {
 
51
  const [think, setThink] = useState<string | undefined>(undefined);
52
  const [openThink, setOpenThink] = useState(false);
53
  const [isThinking, setIsThinking] = useState(true);
54
+ const [controller, setController] = useState<AbortController | null>(null);
55
 
56
  const audio = new Audio(SuccessSound);
57
  audio.volume = 0.5;
58
 
59
+ const callAi = async (redesignMarkdown?: string) => {
60
+ if (isAiWorking) return;
61
+ if (!redesignMarkdown && !prompt.trim()) return;
62
  setisAiWorking(true);
63
  setProviderError("");
64
  setThink("");
 
70
  let lastRenderTime = 0;
71
 
72
  const isFollowUp = html !== defaultHTML;
73
+ const abortController = new AbortController();
74
+ setController(abortController);
75
  try {
76
  onNewPrompt(prompt);
77
+ if (isFollowUp && !redesignMarkdown) {
78
  const request = await fetch("/api/ask-ai", {
79
  method: "PUT",
80
  body: JSON.stringify({
 
86
  headers: {
87
  "Content-Type": "application/json",
88
  },
89
+ signal: abortController.signal,
90
  });
91
  if (request && request.body) {
92
  const res = await request.json();
 
119
  prompt,
120
  provider,
121
  model,
122
+ redesignMarkdown,
123
  }),
124
  headers: {
125
  "Content-Type": "application/json",
126
  },
127
+ signal: abortController.signal,
128
  });
129
  if (request && request.body) {
130
  if (!request.ok) {
 
234
  }
235
  };
236
 
237
+ const stopController = () => {
238
+ if (controller) {
239
+ controller.abort();
240
+ setController(null);
241
+ setisAiWorking(false);
242
+ setThink("");
243
+ setOpenThink(false);
244
+ setIsThinking(false);
245
+ }
246
+ };
247
+
248
  useUpdateEffect(() => {
249
  if (refThink.current) {
250
  refThink.current.scrollTop = refThink.current.scrollHeight;
 
258
  }, [isThinking]);
259
 
260
  return (
261
+ <div className="bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent z-10 absolute bottom-3 left-3 w-[calc(100%-20px)] group">
262
  {think && (
263
  <div className="w-full border-b border-neutral-700 relative overflow-hidden">
264
  <header
 
296
  </main>
297
  </div>
298
  )}
299
+ <div className="w-full relative flex items-center justify-between">
 
 
 
 
 
 
 
300
  {isAiWorking && (
301
+ <div className="absolute bg-neutral-800 rounded-lg bottom-0 left-4 w-[calc(100%-30px)] h-full z-1 flex items-center justify-between max-lg:text-sm">
302
+ <div className="flex items-center justify-start gap-2">
303
+ <Loading overlay={false} className="!size-4" />
304
+ <p className="text-neutral-400 text-sm">
305
+ AI is {isThinking ? "thinking" : "coding"}...{" "}
306
+ </p>
307
+ </div>
308
+ <div
309
+ className="text-xs text-neutral-400 px-1 py-0.5 rounded-md border border-neutral-600 flex items-center justify-center gap-1.5 bg-neutral-800 hover:brightness-110 transition-all duration-200 cursor-pointer"
310
+ onClick={stopController}
311
+ >
312
+ <FaStopCircle />
313
+ Stop generation
314
+ </div>
315
  </div>
316
  )}
 
317
  <input
318
  type="text"
319
  disabled={isAiWorking}
320
+ className="w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4"
321
+ placeholder={
322
+ hasAsked ? "Ask DeepSite for edits" : "Ask DeepSite anything..."
323
+ }
324
  value={prompt}
325
  onChange={(e) => setPrompt(e.target.value)}
326
  onKeyDown={(e) => {
327
+ if (e.key === "Enter" && !e.shiftKey) {
328
  callAi();
329
  }
330
  }}
331
  />
332
+ </div>
333
+ <div className="flex items-center justify-between gap-2 px-4 pb-3">
334
+ <div className="flex-1 flex items-center justify-start gap-1.5">
335
+ <ReImagine onRedesign={(md) => callAi(md)} />
336
+ <InviteFriends />
337
+ </div>
338
  <div className="flex items-center justify-end gap-2">
 
339
  <Settings
340
  provider={provider as string}
341
  model={model as string}
 
346
  onClose={setOpenProvider}
347
  />
348
  <Button
349
+ size="iconXs"
 
350
  disabled={isAiWorking || !prompt.trim()}
351
+ onClick={() => callAi()}
352
  >
353
+ <ArrowUp className="size-4" />
354
  </Button>
355
  </div>
356
  </div>
src/components/deploy-button/deploy-button.tsx CHANGED
@@ -92,7 +92,7 @@ function DeployButton({
92
  </div>
93
  </PopoverTrigger>
94
  <PopoverContent
95
- className="p-0 overflow-hidden !bg-neutral-900"
96
  align="end"
97
  >
98
  {!auth ? (
 
92
  </div>
93
  </PopoverTrigger>
94
  <PopoverContent
95
+ className="!rounded-2xl p-0 overflow-hidden !bg-neutral-900"
96
  align="end"
97
  >
98
  {!auth ? (
src/components/history/history.tsx CHANGED
@@ -13,11 +13,11 @@ export default function History({
13
  <Popover>
14
  <PopoverTrigger asChild>
15
  <Button variant="ghost" size="sm" className="max-lg:hidden">
16
- {history?.length} edits
17
  </Button>
18
  </PopoverTrigger>
19
  <PopoverContent
20
- className="!p-0 overflow-hidden !bg-neutral-900"
21
  align="start"
22
  >
23
  <header className="text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
 
13
  <Popover>
14
  <PopoverTrigger asChild>
15
  <Button variant="ghost" size="sm" className="max-lg:hidden">
16
+ {history?.length} edit{history.length !== 1 ? "s" : ""}
17
  </Button>
18
  </PopoverTrigger>
19
  <PopoverContent
20
+ className="!rounded-2xl !p-0 overflow-hidden !bg-neutral-900"
21
  align="start"
22
  >
23
  <header className="text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
src/components/load-button/load-button.tsx CHANGED
@@ -64,7 +64,7 @@ function LoadButton({
64
  </p>
65
  </PopoverTrigger>
66
  <PopoverContent
67
- className="p-0 overflow-hidden !bg-neutral-900"
68
  align="end"
69
  >
70
  <header className="flex items-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
 
64
  </p>
65
  </PopoverTrigger>
66
  <PopoverContent
67
+ className="!rounded-2xl p-0 overflow-hidden !bg-neutral-900"
68
  align="end"
69
  >
70
  <header className="flex items-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
src/components/re-imagine/re-imagine.tsx ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import { Paintbrush } from "lucide-react";
3
+ import { toast } from "sonner";
4
+
5
+ import { Button } from "../ui/button";
6
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
7
+ import { Input } from "../ui/input";
8
+ import Loading from "../loading/loading";
9
+
10
+ export default function ReImagine({
11
+ onRedesign,
12
+ }: {
13
+ onRedesign: (md: string) => void;
14
+ }) {
15
+ const [url, setUrl] = useState<string>("");
16
+ const [open, setOpen] = useState(false);
17
+ const [isLoading, setIsLoading] = useState(false);
18
+
19
+ const checkIfUrlIsValid = (url: string) => {
20
+ const urlPattern = new RegExp(
21
+ /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/,
22
+ "i"
23
+ );
24
+ return urlPattern.test(url);
25
+ };
26
+
27
+ const handleClick = async () => {
28
+ if (!url) {
29
+ toast.error("Please enter a URL.");
30
+ return;
31
+ }
32
+ if (!checkIfUrlIsValid(url)) {
33
+ toast.error("Please enter a valid URL.");
34
+ return;
35
+ }
36
+ // Here you would typically handle the re-design logic
37
+ setIsLoading(true);
38
+ const request = await fetch("/api/re-design", {
39
+ method: "POST",
40
+ body: JSON.stringify({ url }),
41
+ headers: {
42
+ "Content-Type": "application/json",
43
+ },
44
+ });
45
+ const response = await request.json();
46
+ if (response.ok) {
47
+ setOpen(false);
48
+ setUrl("");
49
+ onRedesign(response.markdown);
50
+ toast.success("DeepSite is re-designing your site! Let him cook... 🔥");
51
+ } else {
52
+ toast.error(response.message || "Failed to re-design the site.");
53
+ }
54
+ setIsLoading(false);
55
+ };
56
+
57
+ return (
58
+ <Popover open={open} onOpenChange={setOpen}>
59
+ <form>
60
+ <PopoverTrigger asChild>
61
+ <Button
62
+ size="iconXs"
63
+ variant="outline"
64
+ className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
65
+ >
66
+ <Paintbrush className="size-4" />
67
+ </Button>
68
+ </PopoverTrigger>
69
+ <PopoverContent
70
+ align="start"
71
+ className="!rounded-2xl !p-5 !bg-white !border-neutral-100 min-w-xs text-center"
72
+ >
73
+ <header className="mb-5">
74
+ <p className="text-xl font-semibold text-neutral-950">
75
+ Re-Design your Site!
76
+ </p>
77
+ <p className="text-sm text-neutral-500 mt-1.5 mb-6">
78
+ Try our new Re-Design feature to give your site a fresh look.
79
+ </p>
80
+ <hr className="bg-neutral-200 max-w-44 mx-auto" />
81
+ </header>
82
+ <main className="space-y-4">
83
+ <div>
84
+ <p className="text-sm text-neutral-700 mb-2">
85
+ Enter your website URL to get started:
86
+ </p>
87
+ <Input
88
+ type="text"
89
+ placeholder="https://example.com"
90
+ value={url}
91
+ onChange={(e) => setUrl(e.target.value)}
92
+ onBlur={(e) => {
93
+ const inputUrl = e.target.value.trim();
94
+ if (!inputUrl) {
95
+ setUrl("");
96
+ return;
97
+ }
98
+ if (!checkIfUrlIsValid(inputUrl)) {
99
+ toast.error("Please enter a valid URL.");
100
+ return;
101
+ }
102
+ setUrl(inputUrl);
103
+ }}
104
+ className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
105
+ />
106
+ </div>
107
+ <div>
108
+ <p className="text-sm text-neutral-700 mb-2">
109
+ Then, let's re-design it!
110
+ </p>
111
+ <Button
112
+ variant="gray"
113
+ size="sm"
114
+ onClick={handleClick}
115
+ className="relative"
116
+ disabled={isLoading}
117
+ >
118
+ Re-Design <Paintbrush className="size-4" />
119
+ {isLoading && <Loading className="ml-2 size-4 animate-spin" />}
120
+ </Button>
121
+ </div>
122
+ </main>
123
+ </PopoverContent>
124
+ </form>
125
+ </Popover>
126
+ );
127
+ }
src/components/settings/settings.tsx CHANGED
@@ -62,7 +62,7 @@ function Settings({
62
  </Button>
63
  </PopoverTrigger>
64
  <PopoverContent
65
- className="p-0 !w-96 overflow-hidden !bg-neutral-900"
66
  align="center"
67
  >
68
  <header className="flex items-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
 
62
  </Button>
63
  </PopoverTrigger>
64
  <PopoverContent
65
+ className="!rounded-2xl p-0 !w-96 overflow-hidden !bg-neutral-900"
66
  align="center"
67
  >
68
  <header className="flex items-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
src/views/App.tsx CHANGED
@@ -21,7 +21,7 @@ import { defaultHTML } from "../../utils/consts";
21
  import DeployButton from "../components/deploy-button/deploy-button";
22
  import Preview from "../components/preview/preview";
23
  import Footer from "../components/footer/footer";
24
- import AskAI from "../components/ask-ai/ask-ai-new";
25
 
26
  export default function App() {
27
  const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
 
21
  import DeployButton from "../components/deploy-button/deploy-button";
22
  import Preview from "../components/preview/preview";
23
  import Footer from "../components/footer/footer";
24
+ import AskAI from "../components/ask-ai/ask-ai";
25
 
26
  export default function App() {
27
  const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");