Spaces:
Running
Running
Add redesign stuffs
Browse files- server.js +48 -3
- src/components/ask-ai/ask-ai-new.tsx +0 -385
- src/components/ask-ai/ask-ai.tsx +59 -29
- src/components/deploy-button/deploy-button.tsx +1 -1
- src/components/history/history.tsx +2 -2
- src/components/load-button/load-button.tsx +1 -1
- src/components/re-imagine/re-imagine.tsx +127 -0
- src/components/settings/settings.tsx +1 -1
- src/views/App.tsx +1 -1
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 (!
|
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
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 "
|
|
|
|
|
|
|
|
|
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
|
|
|
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-
|
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-
|
287 |
-
<
|
288 |
-
|
289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
297 |
-
placeholder={
|
|
|
|
|
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 |
-
|
319 |
-
size="icon"
|
320 |
disabled={isAiWorking || !prompt.trim()}
|
321 |
-
onClick={callAi}
|
322 |
>
|
323 |
-
<
|
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}
|
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
|
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");
|