Spaces:
Running
Running
deepsite
#335
by
fsalmansour
- opened
- app/api/ask-ai/route.ts +86 -170
- app/api/me/projects/[namespace]/[repoId]/images/route.ts +0 -111
- app/api/me/projects/[namespace]/[repoId]/route.ts +16 -55
- app/api/me/projects/route.ts +12 -13
- app/layout.tsx +0 -4
- app/projects/[namespace]/[repoId]/page.tsx +3 -5
- app/projects/new/page.tsx +1 -1
- components/editor/ask-ai/index.tsx +220 -188
- components/editor/ask-ai/selected-files.tsx +0 -47
- components/editor/ask-ai/settings.tsx +5 -3
- components/editor/ask-ai/uploader.tsx +0 -203
- components/editor/deploy-button/content.tsx +0 -111
- components/editor/deploy-button/index.tsx +106 -12
- components/editor/footer/index.tsx +16 -39
- components/editor/history/index.tsx +4 -5
- components/editor/index.tsx +69 -119
- components/editor/pages/index.tsx +0 -30
- components/editor/pages/page.tsx +0 -82
- components/editor/preview/index.tsx +4 -59
- components/editor/save-button/index.tsx +5 -6
- components/iframe-detector.tsx +0 -75
- components/iframe-warning-modal.tsx +0 -61
- components/login-modal/index.tsx +5 -6
- components/pro-modal/index.tsx +5 -6
- components/ui/button.tsx +0 -1
- hooks/useCallAi.ts +0 -461
- hooks/useEditor.ts +6 -15
- lib/prompts.ts +16 -109
- lib/providers.ts +0 -7
- next.config.ts +0 -3
- package-lock.json +0 -542
- package.json +0 -1
- types/index.ts +1 -6
app/api/ask-ai/route.ts
CHANGED
@@ -10,15 +10,10 @@ import {
|
|
10 |
FOLLOW_UP_SYSTEM_PROMPT,
|
11 |
INITIAL_SYSTEM_PROMPT,
|
12 |
MAX_REQUESTS_PER_IP,
|
13 |
-
NEW_PAGE_END,
|
14 |
-
NEW_PAGE_START,
|
15 |
REPLACE_END,
|
16 |
SEARCH_START,
|
17 |
-
UPDATE_PAGE_START,
|
18 |
-
UPDATE_PAGE_END,
|
19 |
} from "@/lib/prompts";
|
20 |
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
21 |
-
import { Page } from "@/types";
|
22 |
|
23 |
const ipAddresses = new Map();
|
24 |
|
@@ -27,7 +22,7 @@ export async function POST(request: NextRequest) {
|
|
27 |
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
28 |
|
29 |
const body = await request.json();
|
30 |
-
const { prompt, provider, model, redesignMarkdown,
|
31 |
|
32 |
if (!model || (!prompt && !redesignMarkdown)) {
|
33 |
return NextResponse.json(
|
@@ -39,7 +34,6 @@ export async function POST(request: NextRequest) {
|
|
39 |
const selectedModel = MODELS.find(
|
40 |
(m) => m.value === model || m.label === model
|
41 |
);
|
42 |
-
|
43 |
if (!selectedModel) {
|
44 |
return NextResponse.json(
|
45 |
{ ok: false, error: "Invalid model selected" },
|
@@ -98,10 +92,12 @@ export async function POST(request: NextRequest) {
|
|
98 |
: PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
|
99 |
|
100 |
try {
|
|
|
101 |
const encoder = new TextEncoder();
|
102 |
const stream = new TransformStream();
|
103 |
const writer = stream.writable.getWriter();
|
104 |
|
|
|
105 |
const response = new NextResponse(stream.readable, {
|
106 |
headers: {
|
107 |
"Content-Type": "text/plain; charset=utf-8",
|
@@ -111,7 +107,7 @@ export async function POST(request: NextRequest) {
|
|
111 |
});
|
112 |
|
113 |
(async () => {
|
114 |
-
|
115 |
try {
|
116 |
const client = new InferenceClient(token);
|
117 |
const chatCompletion = client.chatCompletionStream(
|
@@ -123,14 +119,12 @@ export async function POST(request: NextRequest) {
|
|
123 |
role: "system",
|
124 |
content: INITIAL_SYSTEM_PROMPT,
|
125 |
},
|
126 |
-
...(pages?.length > 1 ? [{
|
127 |
-
role: "assistant",
|
128 |
-
content: `Here are the current pages:\n\n${pages.map((p: Page) => `- ${p.path} \n${p.html}`).join("\n")}\n\nNow, please create a new page based on this code. Also here are the previous prompts:\n\n${previousPrompts.map((p: string) => `- ${p}`).join("\n")}`
|
129 |
-
}] : []),
|
130 |
{
|
131 |
role: "user",
|
132 |
content: redesignMarkdown
|
133 |
? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.`
|
|
|
|
|
134 |
: prompt,
|
135 |
},
|
136 |
],
|
@@ -147,7 +141,39 @@ export async function POST(request: NextRequest) {
|
|
147 |
|
148 |
const chunk = value.choices[0]?.delta?.content;
|
149 |
if (chunk) {
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
}
|
152 |
}
|
153 |
} catch (error: any) {
|
@@ -197,10 +223,10 @@ export async function PUT(request: NextRequest) {
|
|
197 |
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
198 |
|
199 |
const body = await request.json();
|
200 |
-
const { prompt,
|
201 |
body;
|
202 |
|
203 |
-
if (!prompt ||
|
204 |
return NextResponse.json(
|
205 |
{ ok: false, error: "Missing required fields" },
|
206 |
{ status: 400 }
|
@@ -270,18 +296,18 @@ export async function PUT(request: NextRequest) {
|
|
270 |
},
|
271 |
{
|
272 |
role: "user",
|
273 |
-
content:
|
274 |
-
?
|
275 |
: "You are modifying the HTML file based on the user's request.",
|
276 |
},
|
277 |
{
|
278 |
role: "assistant",
|
279 |
|
280 |
-
content:
|
281 |
selectedElementHtml
|
282 |
? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\``
|
283 |
: ""
|
284 |
-
}
|
285 |
},
|
286 |
{
|
287 |
role: "user",
|
@@ -307,171 +333,61 @@ export async function PUT(request: NextRequest) {
|
|
307 |
|
308 |
if (chunk) {
|
309 |
const updatedLines: number[][] = [];
|
310 |
-
let newHtml =
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
const pageIndex = updatedPages.findIndex(p => p.path === pagePath);
|
320 |
-
if (pageIndex !== -1) {
|
321 |
-
let pageHtml = updatedPages[pageIndex].html;
|
322 |
-
|
323 |
-
let processedContent = pageContent;
|
324 |
-
const htmlMatch = pageContent.match(/```html\s*([\s\S]*?)\s*```/);
|
325 |
-
if (htmlMatch) {
|
326 |
-
processedContent = htmlMatch[1];
|
327 |
-
}
|
328 |
-
let position = 0;
|
329 |
-
let moreBlocks = true;
|
330 |
-
|
331 |
-
while (moreBlocks) {
|
332 |
-
const searchStartIndex = processedContent.indexOf(SEARCH_START, position);
|
333 |
-
if (searchStartIndex === -1) {
|
334 |
-
moreBlocks = false;
|
335 |
-
continue;
|
336 |
-
}
|
337 |
-
|
338 |
-
const dividerIndex = processedContent.indexOf(DIVIDER, searchStartIndex);
|
339 |
-
if (dividerIndex === -1) {
|
340 |
-
moreBlocks = false;
|
341 |
-
continue;
|
342 |
-
}
|
343 |
-
|
344 |
-
const replaceEndIndex = processedContent.indexOf(REPLACE_END, dividerIndex);
|
345 |
-
if (replaceEndIndex === -1) {
|
346 |
-
moreBlocks = false;
|
347 |
-
continue;
|
348 |
-
}
|
349 |
-
|
350 |
-
const searchBlock = processedContent.substring(
|
351 |
-
searchStartIndex + SEARCH_START.length,
|
352 |
-
dividerIndex
|
353 |
-
);
|
354 |
-
const replaceBlock = processedContent.substring(
|
355 |
-
dividerIndex + DIVIDER.length,
|
356 |
-
replaceEndIndex
|
357 |
-
);
|
358 |
-
|
359 |
-
if (searchBlock.trim() === "") {
|
360 |
-
pageHtml = `${replaceBlock}\n${pageHtml}`;
|
361 |
-
updatedLines.push([1, replaceBlock.split("\n").length]);
|
362 |
-
} else {
|
363 |
-
const blockPosition = pageHtml.indexOf(searchBlock);
|
364 |
-
if (blockPosition !== -1) {
|
365 |
-
const beforeText = pageHtml.substring(0, blockPosition);
|
366 |
-
const startLineNumber = beforeText.split("\n").length;
|
367 |
-
const replaceLines = replaceBlock.split("\n").length;
|
368 |
-
const endLineNumber = startLineNumber + replaceLines - 1;
|
369 |
-
|
370 |
-
updatedLines.push([startLineNumber, endLineNumber]);
|
371 |
-
pageHtml = pageHtml.replace(searchBlock, replaceBlock);
|
372 |
-
}
|
373 |
-
}
|
374 |
-
|
375 |
-
position = replaceEndIndex + REPLACE_END.length;
|
376 |
-
}
|
377 |
-
|
378 |
-
updatedPages[pageIndex].html = pageHtml;
|
379 |
-
|
380 |
-
if (pagePath === '/' || pagePath === '/index' || pagePath === 'index') {
|
381 |
-
newHtml = pageHtml;
|
382 |
-
}
|
383 |
}
|
384 |
-
}
|
385 |
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
const [, pagePath, pageContent] = newPageMatch;
|
391 |
-
|
392 |
-
let pageHtml = pageContent;
|
393 |
-
const htmlMatch = pageContent.match(/```html\s*([\s\S]*?)\s*```/);
|
394 |
-
if (htmlMatch) {
|
395 |
-
pageHtml = htmlMatch[1];
|
396 |
-
}
|
397 |
-
|
398 |
-
const existingPageIndex = updatedPages.findIndex(p => p.path === pagePath);
|
399 |
-
|
400 |
-
if (existingPageIndex !== -1) {
|
401 |
-
updatedPages[existingPageIndex] = {
|
402 |
-
path: pagePath,
|
403 |
-
html: pageHtml.trim()
|
404 |
-
};
|
405 |
-
} else {
|
406 |
-
updatedPages.push({
|
407 |
-
path: pagePath,
|
408 |
-
html: pageHtml.trim()
|
409 |
-
});
|
410 |
}
|
411 |
-
}
|
412 |
-
|
413 |
-
if (updatedPages.length === pages?.length && !chunk.includes(UPDATE_PAGE_START)) {
|
414 |
-
let position = 0;
|
415 |
-
let moreBlocks = true;
|
416 |
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
}
|
423 |
-
|
424 |
-
const dividerIndex = chunk.indexOf(DIVIDER, searchStartIndex);
|
425 |
-
if (dividerIndex === -1) {
|
426 |
-
moreBlocks = false;
|
427 |
-
continue;
|
428 |
-
}
|
429 |
-
|
430 |
-
const replaceEndIndex = chunk.indexOf(REPLACE_END, dividerIndex);
|
431 |
-
if (replaceEndIndex === -1) {
|
432 |
-
moreBlocks = false;
|
433 |
-
continue;
|
434 |
-
}
|
435 |
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
}
|
459 |
}
|
460 |
-
|
461 |
-
position = replaceEndIndex + REPLACE_END.length;
|
462 |
}
|
463 |
|
464 |
-
|
465 |
-
const mainPageIndex = updatedPages.findIndex(p => p.path === '/' || p.path === '/index' || p.path === 'index');
|
466 |
-
if (mainPageIndex !== -1) {
|
467 |
-
updatedPages[mainPageIndex].html = newHtml;
|
468 |
-
}
|
469 |
}
|
470 |
|
471 |
return NextResponse.json({
|
472 |
ok: true,
|
|
|
473 |
updatedLines,
|
474 |
-
pages: updatedPages,
|
475 |
});
|
476 |
} else {
|
477 |
return NextResponse.json(
|
|
|
10 |
FOLLOW_UP_SYSTEM_PROMPT,
|
11 |
INITIAL_SYSTEM_PROMPT,
|
12 |
MAX_REQUESTS_PER_IP,
|
|
|
|
|
13 |
REPLACE_END,
|
14 |
SEARCH_START,
|
|
|
|
|
15 |
} from "@/lib/prompts";
|
16 |
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
|
|
17 |
|
18 |
const ipAddresses = new Map();
|
19 |
|
|
|
22 |
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
23 |
|
24 |
const body = await request.json();
|
25 |
+
const { prompt, provider, model, redesignMarkdown, html } = body;
|
26 |
|
27 |
if (!model || (!prompt && !redesignMarkdown)) {
|
28 |
return NextResponse.json(
|
|
|
34 |
const selectedModel = MODELS.find(
|
35 |
(m) => m.value === model || m.label === model
|
36 |
);
|
|
|
37 |
if (!selectedModel) {
|
38 |
return NextResponse.json(
|
39 |
{ ok: false, error: "Invalid model selected" },
|
|
|
92 |
: PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
|
93 |
|
94 |
try {
|
95 |
+
// Create a stream response
|
96 |
const encoder = new TextEncoder();
|
97 |
const stream = new TransformStream();
|
98 |
const writer = stream.writable.getWriter();
|
99 |
|
100 |
+
// Start the response
|
101 |
const response = new NextResponse(stream.readable, {
|
102 |
headers: {
|
103 |
"Content-Type": "text/plain; charset=utf-8",
|
|
|
107 |
});
|
108 |
|
109 |
(async () => {
|
110 |
+
let completeResponse = "";
|
111 |
try {
|
112 |
const client = new InferenceClient(token);
|
113 |
const chatCompletion = client.chatCompletionStream(
|
|
|
119 |
role: "system",
|
120 |
content: INITIAL_SYSTEM_PROMPT,
|
121 |
},
|
|
|
|
|
|
|
|
|
122 |
{
|
123 |
role: "user",
|
124 |
content: redesignMarkdown
|
125 |
? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.`
|
126 |
+
: html
|
127 |
+
? `Here is my current HTML code:\n\n\`\`\`html\n${html}\n\`\`\`\n\nNow, please create a new design based on this HTML.`
|
128 |
: prompt,
|
129 |
},
|
130 |
],
|
|
|
141 |
|
142 |
const chunk = value.choices[0]?.delta?.content;
|
143 |
if (chunk) {
|
144 |
+
let newChunk = chunk;
|
145 |
+
if (!selectedModel?.isThinker) {
|
146 |
+
if (provider !== "sambanova") {
|
147 |
+
await writer.write(encoder.encode(chunk));
|
148 |
+
completeResponse += chunk;
|
149 |
+
|
150 |
+
if (completeResponse.includes("</html>")) {
|
151 |
+
break;
|
152 |
+
}
|
153 |
+
} else {
|
154 |
+
if (chunk.includes("</html>")) {
|
155 |
+
newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
|
156 |
+
}
|
157 |
+
completeResponse += newChunk;
|
158 |
+
await writer.write(encoder.encode(newChunk));
|
159 |
+
if (newChunk.includes("</html>")) {
|
160 |
+
break;
|
161 |
+
}
|
162 |
+
}
|
163 |
+
} else {
|
164 |
+
const lastThinkTagIndex =
|
165 |
+
completeResponse.lastIndexOf("</think>");
|
166 |
+
completeResponse += newChunk;
|
167 |
+
await writer.write(encoder.encode(newChunk));
|
168 |
+
if (lastThinkTagIndex !== -1) {
|
169 |
+
const afterLastThinkTag = completeResponse.slice(
|
170 |
+
lastThinkTagIndex + "</think>".length
|
171 |
+
);
|
172 |
+
if (afterLastThinkTag.includes("</html>")) {
|
173 |
+
break;
|
174 |
+
}
|
175 |
+
}
|
176 |
+
}
|
177 |
}
|
178 |
}
|
179 |
} catch (error: any) {
|
|
|
223 |
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
224 |
|
225 |
const body = await request.json();
|
226 |
+
const { prompt, html, previousPrompt, provider, selectedElementHtml, model } =
|
227 |
body;
|
228 |
|
229 |
+
if (!prompt || !html) {
|
230 |
return NextResponse.json(
|
231 |
{ ok: false, error: "Missing required fields" },
|
232 |
{ status: 400 }
|
|
|
296 |
},
|
297 |
{
|
298 |
role: "user",
|
299 |
+
content: previousPrompt
|
300 |
+
? previousPrompt
|
301 |
: "You are modifying the HTML file based on the user's request.",
|
302 |
},
|
303 |
{
|
304 |
role: "assistant",
|
305 |
|
306 |
+
content: `The current code is: \n\`\`\`html\n${html}\n\`\`\` ${
|
307 |
selectedElementHtml
|
308 |
? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\``
|
309 |
: ""
|
310 |
+
}`,
|
311 |
},
|
312 |
{
|
313 |
role: "user",
|
|
|
333 |
|
334 |
if (chunk) {
|
335 |
const updatedLines: number[][] = [];
|
336 |
+
let newHtml = html;
|
337 |
+
let position = 0;
|
338 |
+
let moreBlocks = true;
|
339 |
+
|
340 |
+
while (moreBlocks) {
|
341 |
+
const searchStartIndex = chunk.indexOf(SEARCH_START, position);
|
342 |
+
if (searchStartIndex === -1) {
|
343 |
+
moreBlocks = false;
|
344 |
+
continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
345 |
}
|
|
|
346 |
|
347 |
+
const dividerIndex = chunk.indexOf(DIVIDER, searchStartIndex);
|
348 |
+
if (dividerIndex === -1) {
|
349 |
+
moreBlocks = false;
|
350 |
+
continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
351 |
}
|
|
|
|
|
|
|
|
|
|
|
352 |
|
353 |
+
const replaceEndIndex = chunk.indexOf(REPLACE_END, dividerIndex);
|
354 |
+
if (replaceEndIndex === -1) {
|
355 |
+
moreBlocks = false;
|
356 |
+
continue;
|
357 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
358 |
|
359 |
+
const searchBlock = chunk.substring(
|
360 |
+
searchStartIndex + SEARCH_START.length,
|
361 |
+
dividerIndex
|
362 |
+
);
|
363 |
+
const replaceBlock = chunk.substring(
|
364 |
+
dividerIndex + DIVIDER.length,
|
365 |
+
replaceEndIndex
|
366 |
+
);
|
367 |
|
368 |
+
if (searchBlock.trim() === "") {
|
369 |
+
newHtml = `${replaceBlock}\n${newHtml}`;
|
370 |
+
updatedLines.push([1, replaceBlock.split("\n").length]);
|
371 |
+
} else {
|
372 |
+
const blockPosition = newHtml.indexOf(searchBlock);
|
373 |
+
if (blockPosition !== -1) {
|
374 |
+
const beforeText = newHtml.substring(0, blockPosition);
|
375 |
+
const startLineNumber = beforeText.split("\n").length;
|
376 |
+
const replaceLines = replaceBlock.split("\n").length;
|
377 |
+
const endLineNumber = startLineNumber + replaceLines - 1;
|
378 |
+
|
379 |
+
updatedLines.push([startLineNumber, endLineNumber]);
|
380 |
+
newHtml = newHtml.replace(searchBlock, replaceBlock);
|
|
|
381 |
}
|
|
|
|
|
382 |
}
|
383 |
|
384 |
+
position = replaceEndIndex + REPLACE_END.length;
|
|
|
|
|
|
|
|
|
385 |
}
|
386 |
|
387 |
return NextResponse.json({
|
388 |
ok: true,
|
389 |
+
html: newHtml,
|
390 |
updatedLines,
|
|
|
391 |
});
|
392 |
} else {
|
393 |
return NextResponse.json(
|
app/api/me/projects/[namespace]/[repoId]/images/route.ts
DELETED
@@ -1,111 +0,0 @@
|
|
1 |
-
import { NextRequest, NextResponse } from "next/server";
|
2 |
-
import { RepoDesignation, uploadFiles } from "@huggingface/hub";
|
3 |
-
|
4 |
-
import { isAuthenticated } from "@/lib/auth";
|
5 |
-
import Project from "@/models/Project";
|
6 |
-
import dbConnect from "@/lib/mongodb";
|
7 |
-
|
8 |
-
// No longer need the ImageUpload interface since we're handling FormData with File objects
|
9 |
-
|
10 |
-
export async function POST(
|
11 |
-
req: NextRequest,
|
12 |
-
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
13 |
-
) {
|
14 |
-
try {
|
15 |
-
const user = await isAuthenticated();
|
16 |
-
|
17 |
-
if (user instanceof NextResponse || !user) {
|
18 |
-
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
19 |
-
}
|
20 |
-
|
21 |
-
await dbConnect();
|
22 |
-
const param = await params;
|
23 |
-
const { namespace, repoId } = param;
|
24 |
-
|
25 |
-
const project = await Project.findOne({
|
26 |
-
user_id: user.id,
|
27 |
-
space_id: `${namespace}/${repoId}`,
|
28 |
-
}).lean();
|
29 |
-
|
30 |
-
if (!project) {
|
31 |
-
return NextResponse.json(
|
32 |
-
{
|
33 |
-
ok: false,
|
34 |
-
error: "Project not found",
|
35 |
-
},
|
36 |
-
{ status: 404 }
|
37 |
-
);
|
38 |
-
}
|
39 |
-
|
40 |
-
// Parse the FormData to get the images
|
41 |
-
const formData = await req.formData();
|
42 |
-
const imageFiles = formData.getAll("images") as File[];
|
43 |
-
|
44 |
-
if (!imageFiles || imageFiles.length === 0) {
|
45 |
-
return NextResponse.json(
|
46 |
-
{
|
47 |
-
ok: false,
|
48 |
-
error: "At least one image file is required under the 'images' key",
|
49 |
-
},
|
50 |
-
{ status: 400 }
|
51 |
-
);
|
52 |
-
}
|
53 |
-
|
54 |
-
const files: File[] = [];
|
55 |
-
for (const file of imageFiles) {
|
56 |
-
if (!(file instanceof File)) {
|
57 |
-
return NextResponse.json(
|
58 |
-
{
|
59 |
-
ok: false,
|
60 |
-
error: "Invalid file format - all items under 'images' key must be files",
|
61 |
-
},
|
62 |
-
{ status: 400 }
|
63 |
-
);
|
64 |
-
}
|
65 |
-
|
66 |
-
if (!file.type.startsWith('image/')) {
|
67 |
-
return NextResponse.json(
|
68 |
-
{
|
69 |
-
ok: false,
|
70 |
-
error: `File ${file.name} is not an image`,
|
71 |
-
},
|
72 |
-
{ status: 400 }
|
73 |
-
);
|
74 |
-
}
|
75 |
-
|
76 |
-
// Create File object with images/ folder prefix
|
77 |
-
const fileName = `images/${file.name}`;
|
78 |
-
const processedFile = new File([file], fileName, { type: file.type });
|
79 |
-
files.push(processedFile);
|
80 |
-
}
|
81 |
-
|
82 |
-
// Upload files to HuggingFace space
|
83 |
-
const repo: RepoDesignation = {
|
84 |
-
type: "space",
|
85 |
-
name: `${namespace}/${repoId}`,
|
86 |
-
};
|
87 |
-
|
88 |
-
await uploadFiles({
|
89 |
-
repo,
|
90 |
-
files,
|
91 |
-
accessToken: user.token as string,
|
92 |
-
commitTitle: `Upload ${files.length} image(s)`,
|
93 |
-
});
|
94 |
-
|
95 |
-
return NextResponse.json({
|
96 |
-
ok: true,
|
97 |
-
message: `Successfully uploaded ${files.length} image(s) to ${namespace}/${repoId}/images/`,
|
98 |
-
uploadedFiles: files.map((file) => `https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${file.name}`),
|
99 |
-
}, { status: 200 });
|
100 |
-
|
101 |
-
} catch (error) {
|
102 |
-
console.error('Error uploading images:', error);
|
103 |
-
return NextResponse.json(
|
104 |
-
{
|
105 |
-
ok: false,
|
106 |
-
error: "Failed to upload images",
|
107 |
-
},
|
108 |
-
{ status: 500 }
|
109 |
-
);
|
110 |
-
}
|
111 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/me/projects/[namespace]/[repoId]/route.ts
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
import { NextRequest, NextResponse } from "next/server";
|
2 |
-
import { RepoDesignation, spaceInfo,
|
3 |
|
4 |
import { isAuthenticated } from "@/lib/auth";
|
5 |
import Project from "@/models/Project";
|
6 |
import dbConnect from "@/lib/mongodb";
|
7 |
-
import {
|
8 |
|
9 |
export async function GET(
|
10 |
req: NextRequest,
|
@@ -33,6 +33,7 @@ export async function GET(
|
|
33 |
{ status: 404 }
|
34 |
);
|
35 |
}
|
|
|
36 |
try {
|
37 |
const space = await spaceInfo({
|
38 |
name: namespace + "/" + repoId,
|
@@ -59,59 +60,25 @@ export async function GET(
|
|
59 |
);
|
60 |
}
|
61 |
|
62 |
-
const
|
63 |
-
|
64 |
-
name: `${namespace}/${repoId}`,
|
65 |
-
};
|
66 |
-
|
67 |
-
const htmlFiles: Page[] = [];
|
68 |
-
const images: string[] = [];
|
69 |
-
|
70 |
-
const allowedImagesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif"];
|
71 |
-
|
72 |
-
for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) {
|
73 |
-
if (fileInfo.path.endsWith(".html")) {
|
74 |
-
const res = await fetch(`https://huggingface.co/spaces/${namespace}/${repoId}/raw/main/${fileInfo.path}`);
|
75 |
-
if (res.ok) {
|
76 |
-
const html = await res.text();
|
77 |
-
if (fileInfo.path === "index.html") {
|
78 |
-
htmlFiles.unshift({
|
79 |
-
path: fileInfo.path,
|
80 |
-
html,
|
81 |
-
});
|
82 |
-
} else {
|
83 |
-
htmlFiles.push({
|
84 |
-
path: fileInfo.path,
|
85 |
-
html,
|
86 |
-
});
|
87 |
-
}
|
88 |
-
}
|
89 |
-
}
|
90 |
-
if (fileInfo.type === "directory" && fileInfo.path === "images") {
|
91 |
-
for await (const imageInfo of listFiles({repo, accessToken: user.token as string, path: fileInfo.path})) {
|
92 |
-
if (allowedImagesExtensions.includes(imageInfo.path.split(".").pop() || "")) {
|
93 |
-
images.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${imageInfo.path}`);
|
94 |
-
}
|
95 |
-
}
|
96 |
-
}
|
97 |
-
}
|
98 |
-
|
99 |
-
if (htmlFiles.length === 0) {
|
100 |
return NextResponse.json(
|
101 |
{
|
102 |
ok: false,
|
103 |
-
error: "
|
104 |
},
|
105 |
{ status: 404 }
|
106 |
);
|
107 |
}
|
|
|
|
|
|
|
108 |
|
109 |
return NextResponse.json(
|
110 |
{
|
111 |
project: {
|
112 |
...project,
|
113 |
-
|
114 |
-
images,
|
115 |
},
|
116 |
ok: true,
|
117 |
},
|
@@ -150,7 +117,7 @@ export async function PUT(
|
|
150 |
await dbConnect();
|
151 |
const param = await params;
|
152 |
const { namespace, repoId } = param;
|
153 |
-
const {
|
154 |
|
155 |
const project = await Project.findOne({
|
156 |
user_id: user.id,
|
@@ -171,18 +138,11 @@ export async function PUT(
|
|
171 |
name: `${namespace}/${repoId}`,
|
172 |
};
|
173 |
|
174 |
-
const
|
175 |
-
const
|
176 |
-
|
177 |
-
});
|
178 |
-
files.push(promptsFile);
|
179 |
-
pages.forEach((page: Page) => {
|
180 |
-
const file = new File([page.html], page.path, { type: "text/html" });
|
181 |
-
files.push(file);
|
182 |
-
});
|
183 |
-
await uploadFiles({
|
184 |
repo,
|
185 |
-
|
186 |
accessToken: user.token as string,
|
187 |
commitTitle: `${prompts[prompts.length - 1]} - Follow Up Deployment`,
|
188 |
});
|
@@ -192,6 +152,7 @@ export async function PUT(
|
|
192 |
{
|
193 |
$set: {
|
194 |
prompts: [
|
|
|
195 |
...prompts,
|
196 |
],
|
197 |
},
|
|
|
1 |
import { NextRequest, NextResponse } from "next/server";
|
2 |
+
import { RepoDesignation, spaceInfo, uploadFile } from "@huggingface/hub";
|
3 |
|
4 |
import { isAuthenticated } from "@/lib/auth";
|
5 |
import Project from "@/models/Project";
|
6 |
import dbConnect from "@/lib/mongodb";
|
7 |
+
import { getPTag } from "@/lib/utils";
|
8 |
|
9 |
export async function GET(
|
10 |
req: NextRequest,
|
|
|
33 |
{ status: 404 }
|
34 |
);
|
35 |
}
|
36 |
+
const space_url = `https://huggingface.co/spaces/${namespace}/${repoId}/raw/main/index.html`;
|
37 |
try {
|
38 |
const space = await spaceInfo({
|
39 |
name: namespace + "/" + repoId,
|
|
|
60 |
);
|
61 |
}
|
62 |
|
63 |
+
const response = await fetch(space_url);
|
64 |
+
if (!response.ok) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
return NextResponse.json(
|
66 |
{
|
67 |
ok: false,
|
68 |
+
error: "Failed to fetch space HTML",
|
69 |
},
|
70 |
{ status: 404 }
|
71 |
);
|
72 |
}
|
73 |
+
let html = await response.text();
|
74 |
+
// remove the last p tag including this url https://enzostvs-deepsite.hf.space
|
75 |
+
html = html.replace(getPTag(namespace + "/" + repoId), "");
|
76 |
|
77 |
return NextResponse.json(
|
78 |
{
|
79 |
project: {
|
80 |
...project,
|
81 |
+
html,
|
|
|
82 |
},
|
83 |
ok: true,
|
84 |
},
|
|
|
117 |
await dbConnect();
|
118 |
const param = await params;
|
119 |
const { namespace, repoId } = param;
|
120 |
+
const { html, prompts } = await req.json();
|
121 |
|
122 |
const project = await Project.findOne({
|
123 |
user_id: user.id,
|
|
|
138 |
name: `${namespace}/${repoId}`,
|
139 |
};
|
140 |
|
141 |
+
const newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);
|
142 |
+
const file = new File([newHtml], "index.html", { type: "text/html" });
|
143 |
+
await uploadFile({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
repo,
|
145 |
+
file,
|
146 |
accessToken: user.token as string,
|
147 |
commitTitle: `${prompts[prompts.length - 1]} - Follow Up Deployment`,
|
148 |
});
|
|
|
152 |
{
|
153 |
$set: {
|
154 |
prompts: [
|
155 |
+
...(project && "prompts" in project ? project.prompts : []),
|
156 |
...prompts,
|
157 |
],
|
158 |
},
|
app/api/me/projects/route.ts
CHANGED
@@ -4,9 +4,8 @@ import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub";
|
|
4 |
import { isAuthenticated } from "@/lib/auth";
|
5 |
import Project from "@/models/Project";
|
6 |
import dbConnect from "@/lib/mongodb";
|
7 |
-
import { COLORS } from "@/lib/utils";
|
8 |
-
import
|
9 |
-
|
10 |
export async function GET() {
|
11 |
const user = await isAuthenticated();
|
12 |
|
@@ -40,6 +39,10 @@ export async function GET() {
|
|
40 |
);
|
41 |
}
|
42 |
|
|
|
|
|
|
|
|
|
43 |
export async function POST(request: NextRequest) {
|
44 |
const user = await isAuthenticated();
|
45 |
|
@@ -47,9 +50,9 @@ export async function POST(request: NextRequest) {
|
|
47 |
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
48 |
}
|
49 |
|
50 |
-
const { title,
|
51 |
|
52 |
-
if (!title || !
|
53 |
return NextResponse.json(
|
54 |
{ message: "Title and HTML content are required.", ok: false },
|
55 |
{ status: 400 }
|
@@ -60,6 +63,7 @@ export async function POST(request: NextRequest) {
|
|
60 |
|
61 |
try {
|
62 |
let readme = "";
|
|
|
63 |
|
64 |
const newTitle = title
|
65 |
.toLowerCase()
|
@@ -93,17 +97,12 @@ tags:
|
|
93 |
|
94 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`;
|
95 |
|
|
|
|
|
96 |
const readmeFile = new File([readme], "README.md", {
|
97 |
type: "text/markdown",
|
98 |
});
|
99 |
-
const
|
100 |
-
type: "text/plain",
|
101 |
-
});
|
102 |
-
const files = [readmeFile, promptsFile];
|
103 |
-
pages.forEach((page: Page) => {
|
104 |
-
const file = new File([page.html], page.path, { type: "text/html" });
|
105 |
-
files.push(file);
|
106 |
-
});
|
107 |
await uploadFiles({
|
108 |
repo,
|
109 |
files,
|
|
|
4 |
import { isAuthenticated } from "@/lib/auth";
|
5 |
import Project from "@/models/Project";
|
6 |
import dbConnect from "@/lib/mongodb";
|
7 |
+
import { COLORS, getPTag } from "@/lib/utils";
|
8 |
+
// import type user
|
|
|
9 |
export async function GET() {
|
10 |
const user = await isAuthenticated();
|
11 |
|
|
|
39 |
);
|
40 |
}
|
41 |
|
42 |
+
/**
|
43 |
+
* This API route creates a new project in Hugging Face Spaces.
|
44 |
+
* It requires an Authorization header with a valid token and a JSON body with the project details.
|
45 |
+
*/
|
46 |
export async function POST(request: NextRequest) {
|
47 |
const user = await isAuthenticated();
|
48 |
|
|
|
50 |
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
51 |
}
|
52 |
|
53 |
+
const { title, html, prompts } = await request.json();
|
54 |
|
55 |
+
if (!title || !html) {
|
56 |
return NextResponse.json(
|
57 |
{ message: "Title and HTML content are required.", ok: false },
|
58 |
{ status: 400 }
|
|
|
63 |
|
64 |
try {
|
65 |
let readme = "";
|
66 |
+
let newHtml = html;
|
67 |
|
68 |
const newTitle = title
|
69 |
.toLowerCase()
|
|
|
97 |
|
98 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`;
|
99 |
|
100 |
+
newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);
|
101 |
+
const file = new File([newHtml], "index.html", { type: "text/html" });
|
102 |
const readmeFile = new File([readme], "README.md", {
|
103 |
type: "text/markdown",
|
104 |
});
|
105 |
+
const files = [file, readmeFile];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
await uploadFiles({
|
107 |
repo,
|
108 |
files,
|
app/layout.tsx
CHANGED
@@ -10,7 +10,6 @@ import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
|
10 |
import { apiServer } from "@/lib/api";
|
11 |
import AppContext from "@/components/contexts/app-context";
|
12 |
import Script from "next/script";
|
13 |
-
import IframeDetector from "@/components/iframe-detector";
|
14 |
|
15 |
const inter = Inter({
|
16 |
variable: "--font-inter-sans",
|
@@ -83,8 +82,6 @@ async function getMe() {
|
|
83 |
}
|
84 |
}
|
85 |
|
86 |
-
// if domain isn't deepsite.hf.co or enzostvs-deepsite.hf.space redirect to deepsite.hf.co
|
87 |
-
|
88 |
export default async function RootLayout({
|
89 |
children,
|
90 |
}: Readonly<{
|
@@ -101,7 +98,6 @@ export default async function RootLayout({
|
|
101 |
<body
|
102 |
className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
|
103 |
>
|
104 |
-
<IframeDetector />
|
105 |
<Toaster richColors position="bottom-center" />
|
106 |
<TanstackProvider>
|
107 |
<AppContext me={data}>{children}</AppContext>
|
|
|
10 |
import { apiServer } from "@/lib/api";
|
11 |
import AppContext from "@/components/contexts/app-context";
|
12 |
import Script from "next/script";
|
|
|
13 |
|
14 |
const inter = Inter({
|
15 |
variable: "--font-inter-sans",
|
|
|
82 |
}
|
83 |
}
|
84 |
|
|
|
|
|
85 |
export default async function RootLayout({
|
86 |
children,
|
87 |
}: Readonly<{
|
|
|
98 |
<body
|
99 |
className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
|
100 |
>
|
|
|
101 |
<Toaster richColors position="bottom-center" />
|
102 |
<TanstackProvider>
|
103 |
<AppContext me={data}>{children}</AppContext>
|
app/projects/[namespace]/[repoId]/page.tsx
CHANGED
@@ -32,11 +32,9 @@ export default async function ProjectNamespacePage({
|
|
32 |
params: Promise<{ namespace: string; repoId: string }>;
|
33 |
}) {
|
34 |
const { namespace, repoId } = await params;
|
35 |
-
const
|
36 |
-
if (!
|
37 |
redirect("/projects");
|
38 |
}
|
39 |
-
return
|
40 |
-
<AppEditor project={data} pages={data.pages} images={data.images ?? []} />
|
41 |
-
);
|
42 |
}
|
|
|
32 |
params: Promise<{ namespace: string; repoId: string }>;
|
33 |
}) {
|
34 |
const { namespace, repoId } = await params;
|
35 |
+
const project = await getProject(namespace, repoId);
|
36 |
+
if (!project?.html) {
|
37 |
redirect("/projects");
|
38 |
}
|
39 |
+
return <AppEditor project={project} />;
|
|
|
|
|
40 |
}
|
app/projects/new/page.tsx
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
import { AppEditor } from "@/components/editor";
|
2 |
|
3 |
export default function ProjectsNewPage() {
|
4 |
-
return <AppEditor
|
5 |
}
|
|
|
1 |
import { AppEditor } from "@/components/editor";
|
2 |
|
3 |
export default function ProjectsNewPage() {
|
4 |
+
return <AppEditor />;
|
5 |
}
|
components/editor/ask-ai/index.tsx
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
"use client";
|
2 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
3 |
-
import { useState,
|
4 |
import classNames from "classnames";
|
5 |
import { toast } from "sonner";
|
6 |
import { useLocalStorage, useUpdateEffect } from "react-use";
|
@@ -10,8 +10,8 @@ import { FaStopCircle } from "react-icons/fa";
|
|
10 |
import ProModal from "@/components/pro-modal";
|
11 |
import { Button } from "@/components/ui/button";
|
12 |
import { MODELS } from "@/lib/providers";
|
13 |
-
import { HtmlHistory
|
14 |
-
|
15 |
import { Settings } from "@/components/editor/ask-ai/settings";
|
16 |
import { LoginModal } from "@/components/login-modal";
|
17 |
import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
|
@@ -22,86 +22,50 @@ import { TooltipContent } from "@radix-ui/react-tooltip";
|
|
22 |
import { SelectedHtmlElement } from "./selected-html-element";
|
23 |
import { FollowUpTooltip } from "./follow-up-tooltip";
|
24 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
25 |
-
import { useCallAi } from "@/hooks/useCallAi";
|
26 |
-
import { SelectedFiles } from "./selected-files";
|
27 |
-
import { Uploader } from "./uploader";
|
28 |
|
29 |
export function AskAI({
|
30 |
-
|
31 |
-
|
32 |
-
images,
|
33 |
-
currentPage,
|
34 |
-
previousPrompts,
|
35 |
onScrollToBottom,
|
36 |
isAiWorking,
|
37 |
setisAiWorking,
|
38 |
isEditableModeEnabled = false,
|
39 |
-
pages,
|
40 |
-
htmlHistory,
|
41 |
selectedElement,
|
42 |
setSelectedElement,
|
43 |
-
selectedFiles,
|
44 |
-
setSelectedFiles,
|
45 |
setIsEditableModeEnabled,
|
46 |
onNewPrompt,
|
47 |
onSuccess,
|
48 |
-
setPages,
|
49 |
-
setCurrentPage,
|
50 |
}: {
|
51 |
-
|
52 |
-
|
53 |
-
images?: string[];
|
54 |
-
pages: Page[];
|
55 |
onScrollToBottom: () => void;
|
56 |
-
previousPrompts: string[];
|
57 |
isAiWorking: boolean;
|
58 |
onNewPrompt: (prompt: string) => void;
|
59 |
htmlHistory?: HtmlHistory[];
|
60 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
61 |
-
|
62 |
-
onSuccess: (page: Page[], p: string, n?: number[][]) => void;
|
63 |
isEditableModeEnabled: boolean;
|
64 |
setIsEditableModeEnabled: React.Dispatch<React.SetStateAction<boolean>>;
|
65 |
selectedElement?: HTMLElement | null;
|
66 |
setSelectedElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
|
67 |
-
selectedFiles: string[];
|
68 |
-
setSelectedFiles: React.Dispatch<React.SetStateAction<string[]>>;
|
69 |
-
setPages: React.Dispatch<React.SetStateAction<Page[]>>;
|
70 |
-
setCurrentPage: React.Dispatch<React.SetStateAction<string>>;
|
71 |
}) {
|
72 |
const refThink = useRef<HTMLDivElement | null>(null);
|
|
|
73 |
|
74 |
const [open, setOpen] = useState(false);
|
75 |
const [prompt, setPrompt] = useState("");
|
|
|
|
|
76 |
const [provider, setProvider] = useLocalStorage("provider", "auto");
|
77 |
const [model, setModel] = useLocalStorage("model", MODELS[0].value);
|
78 |
const [openProvider, setOpenProvider] = useState(false);
|
79 |
const [providerError, setProviderError] = useState("");
|
80 |
const [openProModal, setOpenProModal] = useState(false);
|
|
|
81 |
const [openThink, setOpenThink] = useState(false);
|
82 |
const [isThinking, setIsThinking] = useState(true);
|
83 |
-
const [
|
84 |
const [isFollowUp, setIsFollowUp] = useState(true);
|
85 |
-
const [isUploading, setIsUploading] = useState(false);
|
86 |
-
const [files, setFiles] = useState<string[]>(images ?? []);
|
87 |
-
|
88 |
-
const {
|
89 |
-
callAiNewProject,
|
90 |
-
callAiFollowUp,
|
91 |
-
callAiNewPage,
|
92 |
-
stopController,
|
93 |
-
audio: hookAudio,
|
94 |
-
} = useCallAi({
|
95 |
-
onNewPrompt,
|
96 |
-
onSuccess,
|
97 |
-
onScrollToBottom,
|
98 |
-
setPages,
|
99 |
-
setCurrentPage,
|
100 |
-
currentPage,
|
101 |
-
pages,
|
102 |
-
isAiWorking,
|
103 |
-
setisAiWorking,
|
104 |
-
});
|
105 |
|
106 |
const selectedModel = useMemo(() => {
|
107 |
return MODELS.find((m: { value: string }) => m.value === model);
|
@@ -110,101 +74,203 @@ export function AskAI({
|
|
110 |
const callAi = async (redesignMarkdown?: string) => {
|
111 |
if (isAiWorking) return;
|
112 |
if (!redesignMarkdown && !prompt.trim()) return;
|
|
|
|
|
|
|
|
|
|
|
113 |
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
? selectedElement.outerHTML
|
118 |
-
: "";
|
119 |
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
selectedElementHtml
|
126 |
-
|
127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
|
|
|
|
|
|
|
|
|
|
133 |
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
currentPage.path,
|
143 |
-
[
|
144 |
-
...(previousPrompts ?? []),
|
145 |
-
...(htmlHistory?.map((h) => h.prompt) ?? []),
|
146 |
-
]
|
147 |
-
);
|
148 |
-
if (result?.error) {
|
149 |
-
handleError(result.error, result.message);
|
150 |
-
return;
|
151 |
-
}
|
152 |
|
153 |
-
|
154 |
-
|
155 |
-
}
|
156 |
-
} else {
|
157 |
-
const result = await callAiNewProject(
|
158 |
-
prompt,
|
159 |
-
model,
|
160 |
-
provider,
|
161 |
-
redesignMarkdown,
|
162 |
-
handleThink,
|
163 |
-
() => {
|
164 |
-
setIsThinking(false);
|
165 |
-
}
|
166 |
-
);
|
167 |
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
|
173 |
-
|
174 |
-
setPrompt("");
|
175 |
-
if (selectedModel?.isThinker) {
|
176 |
-
setModel(MODELS[0].value);
|
177 |
}
|
178 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
}
|
180 |
};
|
181 |
|
182 |
-
const
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
case "login_required":
|
191 |
-
setOpen(true);
|
192 |
-
break;
|
193 |
-
case "provider_required":
|
194 |
-
setOpenProvider(true);
|
195 |
-
setProviderError(message || "");
|
196 |
-
break;
|
197 |
-
case "pro_required":
|
198 |
-
setOpenProModal(true);
|
199 |
-
break;
|
200 |
-
case "api_error":
|
201 |
-
toast.error(message || "An error occurred");
|
202 |
-
break;
|
203 |
-
case "network_error":
|
204 |
-
toast.error(message || "Network error occurred");
|
205 |
-
break;
|
206 |
-
default:
|
207 |
-
toast.error("An unexpected error occurred");
|
208 |
}
|
209 |
};
|
210 |
|
@@ -221,8 +287,8 @@ export function AskAI({
|
|
221 |
}, [isThinking]);
|
222 |
|
223 |
const isSameHtml = useMemo(() => {
|
224 |
-
return isTheSameHtml(
|
225 |
-
}, [
|
226 |
|
227 |
return (
|
228 |
<div className="px-3">
|
@@ -264,13 +330,6 @@ export function AskAI({
|
|
264 |
</main>
|
265 |
</div>
|
266 |
)}
|
267 |
-
<SelectedFiles
|
268 |
-
files={selectedFiles}
|
269 |
-
isAiWorking={isAiWorking}
|
270 |
-
onDelete={(file) =>
|
271 |
-
setSelectedFiles((prev) => prev.filter((f) => f !== file))
|
272 |
-
}
|
273 |
-
/>
|
274 |
{selectedElement && (
|
275 |
<div className="px-4 pt-3">
|
276 |
<SelectedHtmlElement
|
@@ -281,29 +340,28 @@ export function AskAI({
|
|
281 |
</div>
|
282 |
)}
|
283 |
<div className="w-full relative flex items-center justify-between">
|
284 |
-
{
|
285 |
-
<div className="absolute bg-neutral-800
|
286 |
<div className="flex items-center justify-start gap-2">
|
287 |
<Loading overlay={false} className="!size-4" />
|
288 |
<p className="text-neutral-400 text-sm">
|
289 |
-
{
|
290 |
</p>
|
291 |
</div>
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
</div>
|
300 |
-
)}
|
301 |
</div>
|
302 |
)}
|
303 |
-
<
|
|
|
304 |
disabled={isAiWorking}
|
305 |
className={classNames(
|
306 |
-
"w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4
|
307 |
{
|
308 |
"!pt-2.5": selectedElement && !isAiWorking,
|
309 |
}
|
@@ -311,7 +369,7 @@ export function AskAI({
|
|
311 |
placeholder={
|
312 |
selectedElement
|
313 |
? `Ask DeepSite about ${selectedElement.tagName.toLowerCase()}...`
|
314 |
-
:
|
315 |
? "Ask DeepSite for edits"
|
316 |
: "Ask DeepSite anything..."
|
317 |
}
|
@@ -324,25 +382,9 @@ export function AskAI({
|
|
324 |
}}
|
325 |
/>
|
326 |
</div>
|
327 |
-
<div className="flex items-center justify-between gap-2 px-4 pb-3
|
328 |
<div className="flex-1 flex items-center justify-start gap-1.5">
|
329 |
-
<
|
330 |
-
pages={pages}
|
331 |
-
onLoading={setIsUploading}
|
332 |
-
isLoading={isUploading}
|
333 |
-
onFiles={setFiles}
|
334 |
-
onSelectFile={(file) => {
|
335 |
-
if (selectedFiles.includes(file)) {
|
336 |
-
setSelectedFiles((prev) => prev.filter((f) => f !== file));
|
337 |
-
} else {
|
338 |
-
setSelectedFiles((prev) => [...prev, file]);
|
339 |
-
}
|
340 |
-
}}
|
341 |
-
files={files}
|
342 |
-
selectedFiles={selectedFiles}
|
343 |
-
project={project}
|
344 |
-
/>
|
345 |
-
{isNew && <ReImagine onRedesign={(md) => callAi(md)} />}
|
346 |
{!isSameHtml && (
|
347 |
<Tooltip>
|
348 |
<TooltipTrigger asChild>
|
@@ -370,7 +412,7 @@ export function AskAI({
|
|
370 |
</TooltipContent>
|
371 |
</Tooltip>
|
372 |
)}
|
373 |
-
|
374 |
</div>
|
375 |
<div className="flex items-center justify-end gap-2">
|
376 |
<Settings
|
@@ -392,22 +434,12 @@ export function AskAI({
|
|
392 |
</Button>
|
393 |
</div>
|
394 |
</div>
|
395 |
-
<LoginModal open={open} onClose={() => setOpen(false)}
|
396 |
<ProModal
|
397 |
-
|
398 |
open={openProModal}
|
399 |
onClose={() => setOpenProModal(false)}
|
400 |
/>
|
401 |
-
{pages.length === 1 && (
|
402 |
-
<div className="border border-sky-500/20 bg-sky-500/40 hover:bg-sky-600 transition-all duration-200 text-sky-500 pl-2 pr-4 py-1.5 text-xs rounded-full absolute top-0 -translate-y-[calc(100%+8px)] left-0 max-w-max flex items-center justify-start gap-2">
|
403 |
-
<span className="rounded-full text-[10px] font-semibold bg-white text-neutral-900 px-1.5 py-0.5">
|
404 |
-
NEW
|
405 |
-
</span>
|
406 |
-
<p className="text-sm text-neutral-100">
|
407 |
-
DeepSite can now create multiple pages at once. Try it!
|
408 |
-
</p>
|
409 |
-
</div>
|
410 |
-
)}
|
411 |
{!isSameHtml && (
|
412 |
<div className="absolute top-0 right-0 -translate-y-[calc(100%+8px)] select-none text-xs text-neutral-400 flex items-center justify-center gap-2 bg-neutral-800 border border-neutral-700 rounded-md p-1 pr-2.5">
|
413 |
<label
|
@@ -430,7 +462,7 @@ export function AskAI({
|
|
430 |
</div>
|
431 |
)}
|
432 |
</div>
|
433 |
-
<audio ref={
|
434 |
<source src="/success.mp3" type="audio/mpeg" />
|
435 |
Your browser does not support the audio element.
|
436 |
</audio>
|
|
|
1 |
"use client";
|
2 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
3 |
+
import { useState, useRef, useMemo } from "react";
|
4 |
import classNames from "classnames";
|
5 |
import { toast } from "sonner";
|
6 |
import { useLocalStorage, useUpdateEffect } from "react-use";
|
|
|
10 |
import ProModal from "@/components/pro-modal";
|
11 |
import { Button } from "@/components/ui/button";
|
12 |
import { MODELS } from "@/lib/providers";
|
13 |
+
import { HtmlHistory } from "@/types";
|
14 |
+
import { InviteFriends } from "@/components/invite-friends";
|
15 |
import { Settings } from "@/components/editor/ask-ai/settings";
|
16 |
import { LoginModal } from "@/components/login-modal";
|
17 |
import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
|
|
|
22 |
import { SelectedHtmlElement } from "./selected-html-element";
|
23 |
import { FollowUpTooltip } from "./follow-up-tooltip";
|
24 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
|
|
|
|
|
|
25 |
|
26 |
export function AskAI({
|
27 |
+
html,
|
28 |
+
setHtml,
|
|
|
|
|
|
|
29 |
onScrollToBottom,
|
30 |
isAiWorking,
|
31 |
setisAiWorking,
|
32 |
isEditableModeEnabled = false,
|
|
|
|
|
33 |
selectedElement,
|
34 |
setSelectedElement,
|
|
|
|
|
35 |
setIsEditableModeEnabled,
|
36 |
onNewPrompt,
|
37 |
onSuccess,
|
|
|
|
|
38 |
}: {
|
39 |
+
html: string;
|
40 |
+
setHtml: (html: string) => void;
|
|
|
|
|
41 |
onScrollToBottom: () => void;
|
|
|
42 |
isAiWorking: boolean;
|
43 |
onNewPrompt: (prompt: string) => void;
|
44 |
htmlHistory?: HtmlHistory[];
|
45 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
46 |
+
onSuccess: (h: string, p: string, n?: number[][]) => void;
|
|
|
47 |
isEditableModeEnabled: boolean;
|
48 |
setIsEditableModeEnabled: React.Dispatch<React.SetStateAction<boolean>>;
|
49 |
selectedElement?: HTMLElement | null;
|
50 |
setSelectedElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
|
|
|
|
|
|
|
|
|
51 |
}) {
|
52 |
const refThink = useRef<HTMLDivElement | null>(null);
|
53 |
+
const audio = useRef<HTMLAudioElement | null>(null);
|
54 |
|
55 |
const [open, setOpen] = useState(false);
|
56 |
const [prompt, setPrompt] = useState("");
|
57 |
+
const [hasAsked, setHasAsked] = useState(false);
|
58 |
+
const [previousPrompt, setPreviousPrompt] = useState("");
|
59 |
const [provider, setProvider] = useLocalStorage("provider", "auto");
|
60 |
const [model, setModel] = useLocalStorage("model", MODELS[0].value);
|
61 |
const [openProvider, setOpenProvider] = useState(false);
|
62 |
const [providerError, setProviderError] = useState("");
|
63 |
const [openProModal, setOpenProModal] = useState(false);
|
64 |
+
const [think, setThink] = useState<string | undefined>(undefined);
|
65 |
const [openThink, setOpenThink] = useState(false);
|
66 |
const [isThinking, setIsThinking] = useState(true);
|
67 |
+
const [controller, setController] = useState<AbortController | null>(null);
|
68 |
const [isFollowUp, setIsFollowUp] = useState(true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
70 |
const selectedModel = useMemo(() => {
|
71 |
return MODELS.find((m: { value: string }) => m.value === model);
|
|
|
74 |
const callAi = async (redesignMarkdown?: string) => {
|
75 |
if (isAiWorking) return;
|
76 |
if (!redesignMarkdown && !prompt.trim()) return;
|
77 |
+
setisAiWorking(true);
|
78 |
+
setProviderError("");
|
79 |
+
setThink("");
|
80 |
+
setOpenThink(false);
|
81 |
+
setIsThinking(true);
|
82 |
|
83 |
+
let contentResponse = "";
|
84 |
+
let thinkResponse = "";
|
85 |
+
let lastRenderTime = 0;
|
|
|
|
|
86 |
|
87 |
+
const abortController = new AbortController();
|
88 |
+
setController(abortController);
|
89 |
+
try {
|
90 |
+
onNewPrompt(prompt);
|
91 |
+
if (isFollowUp && !redesignMarkdown && !isSameHtml) {
|
92 |
+
const selectedElementHtml = selectedElement
|
93 |
+
? selectedElement.outerHTML
|
94 |
+
: "";
|
95 |
+
const request = await fetch("/api/ask-ai", {
|
96 |
+
method: "PUT",
|
97 |
+
body: JSON.stringify({
|
98 |
+
prompt,
|
99 |
+
provider,
|
100 |
+
previousPrompt,
|
101 |
+
model,
|
102 |
+
html,
|
103 |
+
selectedElementHtml,
|
104 |
+
}),
|
105 |
+
headers: {
|
106 |
+
"Content-Type": "application/json",
|
107 |
+
"x-forwarded-for": window.location.hostname,
|
108 |
+
},
|
109 |
+
signal: abortController.signal,
|
110 |
+
});
|
111 |
+
if (request && request.body) {
|
112 |
+
const res = await request.json();
|
113 |
+
if (!request.ok) {
|
114 |
+
if (res.openLogin) {
|
115 |
+
setOpen(true);
|
116 |
+
} else if (res.openSelectProvider) {
|
117 |
+
setOpenProvider(true);
|
118 |
+
setProviderError(res.message);
|
119 |
+
} else if (res.openProModal) {
|
120 |
+
setOpenProModal(true);
|
121 |
+
} else {
|
122 |
+
toast.error(res.message);
|
123 |
+
}
|
124 |
+
setisAiWorking(false);
|
125 |
+
return;
|
126 |
+
}
|
127 |
+
setHtml(res.html);
|
128 |
+
toast.success("AI responded successfully");
|
129 |
+
setPreviousPrompt(prompt);
|
130 |
+
setPrompt("");
|
131 |
+
setisAiWorking(false);
|
132 |
+
onSuccess(res.html, prompt, res.updatedLines);
|
133 |
+
if (audio.current) audio.current.play();
|
134 |
+
}
|
135 |
+
} else {
|
136 |
+
const request = await fetch("/api/ask-ai", {
|
137 |
+
method: "POST",
|
138 |
+
body: JSON.stringify({
|
139 |
+
prompt,
|
140 |
+
provider,
|
141 |
+
model,
|
142 |
+
html: isSameHtml ? "" : html,
|
143 |
+
redesignMarkdown,
|
144 |
+
}),
|
145 |
+
headers: {
|
146 |
+
"Content-Type": "application/json",
|
147 |
+
"x-forwarded-for": window.location.hostname,
|
148 |
+
},
|
149 |
+
signal: abortController.signal,
|
150 |
+
});
|
151 |
+
if (request && request.body) {
|
152 |
+
const reader = request.body.getReader();
|
153 |
+
const decoder = new TextDecoder("utf-8");
|
154 |
+
const selectedModel = MODELS.find(
|
155 |
+
(m: { value: string }) => m.value === model
|
156 |
+
);
|
157 |
+
let contentThink: string | undefined = undefined;
|
158 |
+
const read = async () => {
|
159 |
+
const { done, value } = await reader.read();
|
160 |
+
if (done) {
|
161 |
+
const isJson =
|
162 |
+
contentResponse.trim().startsWith("{") &&
|
163 |
+
contentResponse.trim().endsWith("}");
|
164 |
+
const jsonResponse = isJson ? JSON.parse(contentResponse) : null;
|
165 |
+
if (jsonResponse && !jsonResponse.ok) {
|
166 |
+
if (jsonResponse.openLogin) {
|
167 |
+
setOpen(true);
|
168 |
+
} else if (jsonResponse.openSelectProvider) {
|
169 |
+
setOpenProvider(true);
|
170 |
+
setProviderError(jsonResponse.message);
|
171 |
+
} else if (jsonResponse.openProModal) {
|
172 |
+
setOpenProModal(true);
|
173 |
+
} else {
|
174 |
+
toast.error(jsonResponse.message);
|
175 |
+
}
|
176 |
+
setisAiWorking(false);
|
177 |
+
return;
|
178 |
+
}
|
179 |
|
180 |
+
toast.success("AI responded successfully");
|
181 |
+
setPreviousPrompt(prompt);
|
182 |
+
setPrompt("");
|
183 |
+
setisAiWorking(false);
|
184 |
+
setHasAsked(true);
|
185 |
+
if (selectedModel?.isThinker) {
|
186 |
+
setModel(MODELS[0].value);
|
187 |
+
}
|
188 |
+
if (audio.current) audio.current.play();
|
189 |
|
190 |
+
// Now we have the complete HTML including </html>, so set it to be sure
|
191 |
+
const finalDoc = contentResponse.match(
|
192 |
+
/<!DOCTYPE html>[\s\S]*<\/html>/
|
193 |
+
)?.[0];
|
194 |
+
if (finalDoc) {
|
195 |
+
setHtml(finalDoc);
|
196 |
+
}
|
197 |
+
onSuccess(finalDoc ?? contentResponse, prompt);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
|
199 |
+
return;
|
200 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
|
202 |
+
const chunk = decoder.decode(value, { stream: true });
|
203 |
+
thinkResponse += chunk;
|
204 |
+
if (selectedModel?.isThinker) {
|
205 |
+
const thinkMatch = thinkResponse.match(/<think>[\s\S]*/)?.[0];
|
206 |
+
if (thinkMatch && !thinkResponse?.includes("</think>")) {
|
207 |
+
if ((contentThink?.length ?? 0) < 3) {
|
208 |
+
setOpenThink(true);
|
209 |
+
}
|
210 |
+
setThink(thinkMatch.replace("<think>", "").trim());
|
211 |
+
contentThink += chunk;
|
212 |
+
return read();
|
213 |
+
}
|
214 |
+
}
|
215 |
+
|
216 |
+
contentResponse += chunk;
|
217 |
+
|
218 |
+
const newHtml = contentResponse.match(
|
219 |
+
/<!DOCTYPE html>[\s\S]*/
|
220 |
+
)?.[0];
|
221 |
+
if (newHtml) {
|
222 |
+
setIsThinking(false);
|
223 |
+
let partialDoc = newHtml;
|
224 |
+
if (
|
225 |
+
partialDoc.includes("<head>") &&
|
226 |
+
!partialDoc.includes("</head>")
|
227 |
+
) {
|
228 |
+
partialDoc += "\n</head>";
|
229 |
+
}
|
230 |
+
if (
|
231 |
+
partialDoc.includes("<body") &&
|
232 |
+
!partialDoc.includes("</body>")
|
233 |
+
) {
|
234 |
+
partialDoc += "\n</body>";
|
235 |
+
}
|
236 |
+
if (!partialDoc.includes("</html>")) {
|
237 |
+
partialDoc += "\n</html>";
|
238 |
+
}
|
239 |
+
|
240 |
+
// Throttle the re-renders to avoid flashing/flicker
|
241 |
+
const now = Date.now();
|
242 |
+
if (now - lastRenderTime > 300) {
|
243 |
+
setHtml(partialDoc);
|
244 |
+
lastRenderTime = now;
|
245 |
+
}
|
246 |
+
|
247 |
+
if (partialDoc.length > 200) {
|
248 |
+
onScrollToBottom();
|
249 |
+
}
|
250 |
+
}
|
251 |
+
read();
|
252 |
+
};
|
253 |
|
254 |
+
read();
|
|
|
|
|
|
|
255 |
}
|
256 |
}
|
257 |
+
} catch (error: any) {
|
258 |
+
setisAiWorking(false);
|
259 |
+
toast.error(error.message);
|
260 |
+
if (error.openLogin) {
|
261 |
+
setOpen(true);
|
262 |
+
}
|
263 |
}
|
264 |
};
|
265 |
|
266 |
+
const stopController = () => {
|
267 |
+
if (controller) {
|
268 |
+
controller.abort();
|
269 |
+
setController(null);
|
270 |
+
setisAiWorking(false);
|
271 |
+
setThink("");
|
272 |
+
setOpenThink(false);
|
273 |
+
setIsThinking(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
274 |
}
|
275 |
};
|
276 |
|
|
|
287 |
}, [isThinking]);
|
288 |
|
289 |
const isSameHtml = useMemo(() => {
|
290 |
+
return isTheSameHtml(html);
|
291 |
+
}, [html]);
|
292 |
|
293 |
return (
|
294 |
<div className="px-3">
|
|
|
330 |
</main>
|
331 |
</div>
|
332 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
333 |
{selectedElement && (
|
334 |
<div className="px-4 pt-3">
|
335 |
<SelectedHtmlElement
|
|
|
340 |
</div>
|
341 |
)}
|
342 |
<div className="w-full relative flex items-center justify-between">
|
343 |
+
{isAiWorking && (
|
344 |
+
<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">
|
345 |
<div className="flex items-center justify-start gap-2">
|
346 |
<Loading overlay={false} className="!size-4" />
|
347 |
<p className="text-neutral-400 text-sm">
|
348 |
+
AI is {isThinking ? "thinking" : "coding"}...{" "}
|
349 |
</p>
|
350 |
</div>
|
351 |
+
<div
|
352 |
+
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"
|
353 |
+
onClick={stopController}
|
354 |
+
>
|
355 |
+
<FaStopCircle />
|
356 |
+
Stop generation
|
357 |
+
</div>
|
|
|
|
|
358 |
</div>
|
359 |
)}
|
360 |
+
<input
|
361 |
+
type="text"
|
362 |
disabled={isAiWorking}
|
363 |
className={classNames(
|
364 |
+
"w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4",
|
365 |
{
|
366 |
"!pt-2.5": selectedElement && !isAiWorking,
|
367 |
}
|
|
|
369 |
placeholder={
|
370 |
selectedElement
|
371 |
? `Ask DeepSite about ${selectedElement.tagName.toLowerCase()}...`
|
372 |
+
: hasAsked
|
373 |
? "Ask DeepSite for edits"
|
374 |
: "Ask DeepSite anything..."
|
375 |
}
|
|
|
382 |
}}
|
383 |
/>
|
384 |
</div>
|
385 |
+
<div className="flex items-center justify-between gap-2 px-4 pb-3">
|
386 |
<div className="flex-1 flex items-center justify-start gap-1.5">
|
387 |
+
<ReImagine onRedesign={(md) => callAi(md)} />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
388 |
{!isSameHtml && (
|
389 |
<Tooltip>
|
390 |
<TooltipTrigger asChild>
|
|
|
412 |
</TooltipContent>
|
413 |
</Tooltip>
|
414 |
)}
|
415 |
+
<InviteFriends />
|
416 |
</div>
|
417 |
<div className="flex items-center justify-end gap-2">
|
418 |
<Settings
|
|
|
434 |
</Button>
|
435 |
</div>
|
436 |
</div>
|
437 |
+
<LoginModal open={open} onClose={() => setOpen(false)} html={html} />
|
438 |
<ProModal
|
439 |
+
html={html}
|
440 |
open={openProModal}
|
441 |
onClose={() => setOpenProModal(false)}
|
442 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
443 |
{!isSameHtml && (
|
444 |
<div className="absolute top-0 right-0 -translate-y-[calc(100%+8px)] select-none text-xs text-neutral-400 flex items-center justify-center gap-2 bg-neutral-800 border border-neutral-700 rounded-md p-1 pr-2.5">
|
445 |
<label
|
|
|
462 |
</div>
|
463 |
)}
|
464 |
</div>
|
465 |
+
<audio ref={audio} id="audio" className="hidden">
|
466 |
<source src="/success.mp3" type="audio/mpeg" />
|
467 |
Your browser does not support the audio element.
|
468 |
</audio>
|
components/editor/ask-ai/selected-files.tsx
DELETED
@@ -1,47 +0,0 @@
|
|
1 |
-
import Image from "next/image";
|
2 |
-
|
3 |
-
import { Button } from "@/components/ui/button";
|
4 |
-
import { Minus } from "lucide-react";
|
5 |
-
|
6 |
-
export const SelectedFiles = ({
|
7 |
-
files,
|
8 |
-
isAiWorking,
|
9 |
-
onDelete,
|
10 |
-
}: {
|
11 |
-
files: string[];
|
12 |
-
isAiWorking: boolean;
|
13 |
-
onDelete: (file: string) => void;
|
14 |
-
}) => {
|
15 |
-
if (files.length === 0) return null;
|
16 |
-
return (
|
17 |
-
<div className="px-4 pt-3">
|
18 |
-
<div className="flex items-center justify-start gap-2">
|
19 |
-
{files.map((file) => (
|
20 |
-
<div
|
21 |
-
key={file}
|
22 |
-
className="flex items-center relative justify-start gap-2 p-1 bg-neutral-700 rounded-md"
|
23 |
-
>
|
24 |
-
<Image
|
25 |
-
src={file}
|
26 |
-
alt="uploaded image"
|
27 |
-
className="size-12 rounded-md object-cover"
|
28 |
-
width={40}
|
29 |
-
height={40}
|
30 |
-
/>
|
31 |
-
<Button
|
32 |
-
size="iconXsss"
|
33 |
-
variant="secondary"
|
34 |
-
className={`absolute top-0.5 right-0.5 ${
|
35 |
-
isAiWorking ? "opacity-50 !cursor-not-allowed" : ""
|
36 |
-
}`}
|
37 |
-
disabled={isAiWorking}
|
38 |
-
onClick={() => onDelete(file)}
|
39 |
-
>
|
40 |
-
<Minus className="size-4" />
|
41 |
-
</Button>
|
42 |
-
</div>
|
43 |
-
))}
|
44 |
-
</div>
|
45 |
-
</div>
|
46 |
-
);
|
47 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/editor/ask-ai/settings.tsx
CHANGED
@@ -80,14 +80,16 @@ export function Settings({
|
|
80 |
</p>
|
81 |
)}
|
82 |
<label className="block">
|
83 |
-
<p className="text-neutral-300 text-sm mb-2.5">
|
|
|
|
|
84 |
<Select defaultValue={model} onValueChange={onModelChange}>
|
85 |
<SelectTrigger className="w-full">
|
86 |
-
<SelectValue placeholder="Select a model" />
|
87 |
</SelectTrigger>
|
88 |
<SelectContent>
|
89 |
<SelectGroup>
|
90 |
-
<SelectLabel>
|
91 |
{MODELS.map(
|
92 |
({
|
93 |
value,
|
|
|
80 |
</p>
|
81 |
)}
|
82 |
<label className="block">
|
83 |
+
<p className="text-neutral-300 text-sm mb-2.5">
|
84 |
+
Choose a DeepSeek model
|
85 |
+
</p>
|
86 |
<Select defaultValue={model} onValueChange={onModelChange}>
|
87 |
<SelectTrigger className="w-full">
|
88 |
+
<SelectValue placeholder="Select a DeepSeek model" />
|
89 |
</SelectTrigger>
|
90 |
<SelectContent>
|
91 |
<SelectGroup>
|
92 |
+
<SelectLabel>DeepSeek models</SelectLabel>
|
93 |
{MODELS.map(
|
94 |
({
|
95 |
value,
|
components/editor/ask-ai/uploader.tsx
DELETED
@@ -1,203 +0,0 @@
|
|
1 |
-
import { useRef, useState } from "react";
|
2 |
-
import { Images, Upload } from "lucide-react";
|
3 |
-
import Image from "next/image";
|
4 |
-
|
5 |
-
import {
|
6 |
-
Popover,
|
7 |
-
PopoverContent,
|
8 |
-
PopoverTrigger,
|
9 |
-
} from "@/components/ui/popover";
|
10 |
-
import { Button } from "@/components/ui/button";
|
11 |
-
import { Page, Project } from "@/types";
|
12 |
-
import Loading from "@/components/loading";
|
13 |
-
import { RiCheckboxCircleFill } from "react-icons/ri";
|
14 |
-
import { useUser } from "@/hooks/useUser";
|
15 |
-
import { LoginModal } from "@/components/login-modal";
|
16 |
-
import { DeployButtonContent } from "../deploy-button/content";
|
17 |
-
|
18 |
-
export const Uploader = ({
|
19 |
-
pages,
|
20 |
-
onLoading,
|
21 |
-
isLoading,
|
22 |
-
onFiles,
|
23 |
-
onSelectFile,
|
24 |
-
selectedFiles,
|
25 |
-
files,
|
26 |
-
project,
|
27 |
-
}: {
|
28 |
-
pages: Page[];
|
29 |
-
onLoading: (isLoading: boolean) => void;
|
30 |
-
isLoading: boolean;
|
31 |
-
files: string[];
|
32 |
-
onFiles: React.Dispatch<React.SetStateAction<string[]>>;
|
33 |
-
onSelectFile: (file: string) => void;
|
34 |
-
selectedFiles: string[];
|
35 |
-
project?: Project | null;
|
36 |
-
}) => {
|
37 |
-
const { user } = useUser();
|
38 |
-
|
39 |
-
const [open, setOpen] = useState(false);
|
40 |
-
const fileInputRef = useRef<HTMLInputElement>(null);
|
41 |
-
|
42 |
-
const uploadFiles = async (files: FileList | null) => {
|
43 |
-
if (!files) return;
|
44 |
-
if (!project) return;
|
45 |
-
|
46 |
-
onLoading(true);
|
47 |
-
|
48 |
-
const images = Array.from(files).filter((file) => {
|
49 |
-
return file.type.startsWith("image/");
|
50 |
-
});
|
51 |
-
|
52 |
-
const data = new FormData();
|
53 |
-
images.forEach((image) => {
|
54 |
-
data.append("images", image);
|
55 |
-
});
|
56 |
-
|
57 |
-
const response = await fetch(
|
58 |
-
`/api/me/projects/${project.space_id}/images`,
|
59 |
-
{
|
60 |
-
method: "POST",
|
61 |
-
body: data,
|
62 |
-
}
|
63 |
-
);
|
64 |
-
if (response.ok) {
|
65 |
-
const data = await response.json();
|
66 |
-
onFiles((prev) => [...prev, ...data.uploadedFiles]);
|
67 |
-
}
|
68 |
-
onLoading(false);
|
69 |
-
};
|
70 |
-
|
71 |
-
// TODO FIRST PUBLISH YOUR PROJECT TO UPLOAD IMAGES.
|
72 |
-
return user?.id ? (
|
73 |
-
<Popover open={open} onOpenChange={setOpen}>
|
74 |
-
<form>
|
75 |
-
<PopoverTrigger asChild>
|
76 |
-
<Button
|
77 |
-
size="iconXs"
|
78 |
-
variant="outline"
|
79 |
-
className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
|
80 |
-
>
|
81 |
-
<Images className="size-4" />
|
82 |
-
</Button>
|
83 |
-
</PopoverTrigger>
|
84 |
-
<PopoverContent
|
85 |
-
align="start"
|
86 |
-
className="!rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden"
|
87 |
-
>
|
88 |
-
{project?.space_id ? (
|
89 |
-
<>
|
90 |
-
<header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
|
91 |
-
<div className="flex items-center justify-center -space-x-4 mb-3">
|
92 |
-
<div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
93 |
-
🎨
|
94 |
-
</div>
|
95 |
-
<div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
|
96 |
-
🖼️
|
97 |
-
</div>
|
98 |
-
<div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
99 |
-
💻
|
100 |
-
</div>
|
101 |
-
</div>
|
102 |
-
<p className="text-xl font-semibold text-neutral-950">
|
103 |
-
Add Custom Images
|
104 |
-
</p>
|
105 |
-
<p className="text-sm text-neutral-500 mt-1.5">
|
106 |
-
Upload images to your project and use them with DeepSite!
|
107 |
-
</p>
|
108 |
-
</header>
|
109 |
-
<main className="space-y-4 p-5">
|
110 |
-
<div>
|
111 |
-
<p className="text-xs text-left text-neutral-700 mb-2">
|
112 |
-
Uploaded Images
|
113 |
-
</p>
|
114 |
-
<div className="grid grid-cols-4 gap-1 flex-wrap max-h-40 overflow-y-auto">
|
115 |
-
{files.map((file) => (
|
116 |
-
<div
|
117 |
-
key={file}
|
118 |
-
className="select-none relative cursor-pointer bg-white rounded-md border-[2px] border-white hover:shadow-2xl transition-all duration-300"
|
119 |
-
onClick={() => onSelectFile(file)}
|
120 |
-
>
|
121 |
-
<Image
|
122 |
-
src={file}
|
123 |
-
alt="uploaded image"
|
124 |
-
width={56}
|
125 |
-
height={56}
|
126 |
-
className="object-cover w-full rounded-sm aspect-square"
|
127 |
-
/>
|
128 |
-
{selectedFiles.includes(file) && (
|
129 |
-
<div className="absolute top-0 right-0 h-full w-full flex items-center justify-center bg-black/50 rounded-md">
|
130 |
-
<RiCheckboxCircleFill className="size-6 text-neutral-100" />
|
131 |
-
</div>
|
132 |
-
)}
|
133 |
-
</div>
|
134 |
-
))}
|
135 |
-
</div>
|
136 |
-
</div>
|
137 |
-
<div>
|
138 |
-
<p className="text-xs text-left text-neutral-700 mb-2">
|
139 |
-
Or import images from your computer
|
140 |
-
</p>
|
141 |
-
<Button
|
142 |
-
variant="black"
|
143 |
-
onClick={() => fileInputRef.current?.click()}
|
144 |
-
className="relative w-full"
|
145 |
-
>
|
146 |
-
{isLoading ? (
|
147 |
-
<>
|
148 |
-
<Loading
|
149 |
-
overlay={false}
|
150 |
-
className="ml-2 size-4 animate-spin"
|
151 |
-
/>
|
152 |
-
Uploading image(s)...
|
153 |
-
</>
|
154 |
-
) : (
|
155 |
-
<>
|
156 |
-
<Upload className="size-4" />
|
157 |
-
Upload Images
|
158 |
-
</>
|
159 |
-
)}
|
160 |
-
</Button>
|
161 |
-
<input
|
162 |
-
ref={fileInputRef}
|
163 |
-
type="file"
|
164 |
-
className="hidden"
|
165 |
-
multiple
|
166 |
-
accept="image/*"
|
167 |
-
onChange={(e) => uploadFiles(e.target.files)}
|
168 |
-
/>
|
169 |
-
</div>
|
170 |
-
</main>
|
171 |
-
</>
|
172 |
-
) : (
|
173 |
-
<DeployButtonContent
|
174 |
-
pages={pages}
|
175 |
-
prompts={[]}
|
176 |
-
options={{
|
177 |
-
description: "Publish your project first to add custom images.",
|
178 |
-
}}
|
179 |
-
/>
|
180 |
-
)}
|
181 |
-
</PopoverContent>
|
182 |
-
</form>
|
183 |
-
</Popover>
|
184 |
-
) : (
|
185 |
-
<>
|
186 |
-
<Button
|
187 |
-
size="iconXs"
|
188 |
-
variant="outline"
|
189 |
-
className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
|
190 |
-
onClick={() => setOpen(true)}
|
191 |
-
>
|
192 |
-
<Images className="size-4" />
|
193 |
-
</Button>
|
194 |
-
<LoginModal
|
195 |
-
open={open}
|
196 |
-
onClose={() => setOpen(false)}
|
197 |
-
pages={pages}
|
198 |
-
title="Log In to add Custom Images"
|
199 |
-
description="Log In through your Hugging Face account to publish your project and increase your monthly free limit."
|
200 |
-
/>
|
201 |
-
</>
|
202 |
-
);
|
203 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/editor/deploy-button/content.tsx
DELETED
@@ -1,111 +0,0 @@
|
|
1 |
-
import { Rocket } from "lucide-react";
|
2 |
-
import Image from "next/image";
|
3 |
-
|
4 |
-
import Loading from "@/components/loading";
|
5 |
-
import { Button } from "@/components/ui/button";
|
6 |
-
import { Input } from "@/components/ui/input";
|
7 |
-
import SpaceIcon from "@/assets/space.svg";
|
8 |
-
import { Page } from "@/types";
|
9 |
-
import { api } from "@/lib/api";
|
10 |
-
import { toast } from "sonner";
|
11 |
-
import { useState } from "react";
|
12 |
-
import { useRouter } from "next/navigation";
|
13 |
-
|
14 |
-
export const DeployButtonContent = ({
|
15 |
-
pages,
|
16 |
-
options,
|
17 |
-
prompts,
|
18 |
-
}: {
|
19 |
-
pages: Page[];
|
20 |
-
options?: {
|
21 |
-
title?: string;
|
22 |
-
description?: string;
|
23 |
-
};
|
24 |
-
prompts: string[];
|
25 |
-
}) => {
|
26 |
-
const router = useRouter();
|
27 |
-
const [loading, setLoading] = useState(false);
|
28 |
-
|
29 |
-
const [config, setConfig] = useState({
|
30 |
-
title: "",
|
31 |
-
});
|
32 |
-
|
33 |
-
const createSpace = async () => {
|
34 |
-
if (!config.title) {
|
35 |
-
toast.error("Please enter a title for your space.");
|
36 |
-
return;
|
37 |
-
}
|
38 |
-
setLoading(true);
|
39 |
-
|
40 |
-
try {
|
41 |
-
const res = await api.post("/me/projects", {
|
42 |
-
title: config.title,
|
43 |
-
pages,
|
44 |
-
prompts,
|
45 |
-
});
|
46 |
-
if (res.data.ok) {
|
47 |
-
router.push(`/projects/${res.data.path}?deploy=true`);
|
48 |
-
} else {
|
49 |
-
toast.error(res?.data?.error || "Failed to create space");
|
50 |
-
}
|
51 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
52 |
-
} catch (err: any) {
|
53 |
-
toast.error(err.response?.data?.error || err.message);
|
54 |
-
} finally {
|
55 |
-
setLoading(false);
|
56 |
-
}
|
57 |
-
};
|
58 |
-
|
59 |
-
return (
|
60 |
-
<>
|
61 |
-
<header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
|
62 |
-
<div className="flex items-center justify-center -space-x-4 mb-3">
|
63 |
-
<div className="size-9 rounded-full bg-amber-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
64 |
-
🚀
|
65 |
-
</div>
|
66 |
-
<div className="size-11 rounded-full bg-red-200 shadow-2xl flex items-center justify-center z-2">
|
67 |
-
<Image src={SpaceIcon} alt="Space Icon" className="size-7" />
|
68 |
-
</div>
|
69 |
-
<div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
70 |
-
👻
|
71 |
-
</div>
|
72 |
-
</div>
|
73 |
-
<p className="text-xl font-semibold text-neutral-950">
|
74 |
-
Publish as Space!
|
75 |
-
</p>
|
76 |
-
<p className="text-sm text-neutral-500 mt-1.5">
|
77 |
-
{options?.description ??
|
78 |
-
"Save and Publish your project to a Space on the Hub. Spaces are a way to share your project with the world."}
|
79 |
-
</p>
|
80 |
-
</header>
|
81 |
-
<main className="space-y-4 p-6">
|
82 |
-
<div>
|
83 |
-
<p className="text-sm text-neutral-700 mb-2">
|
84 |
-
Choose a title for your space:
|
85 |
-
</p>
|
86 |
-
<Input
|
87 |
-
type="text"
|
88 |
-
placeholder="My Awesome Website"
|
89 |
-
value={config.title}
|
90 |
-
onChange={(e) => setConfig({ ...config, title: e.target.value })}
|
91 |
-
className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
|
92 |
-
/>
|
93 |
-
</div>
|
94 |
-
<div>
|
95 |
-
<p className="text-sm text-neutral-700 mb-2">
|
96 |
-
Then, let's publish it!
|
97 |
-
</p>
|
98 |
-
<Button
|
99 |
-
variant="black"
|
100 |
-
onClick={createSpace}
|
101 |
-
className="relative w-full"
|
102 |
-
disabled={loading}
|
103 |
-
>
|
104 |
-
Publish Space <Rocket className="size-4" />
|
105 |
-
{loading && <Loading className="ml-2 size-4 animate-spin" />}
|
106 |
-
</Button>
|
107 |
-
</div>
|
108 |
-
</main>
|
109 |
-
</>
|
110 |
-
);
|
111 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/editor/deploy-button/index.tsx
CHANGED
@@ -1,28 +1,67 @@
|
|
1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2 |
import { useState } from "react";
|
|
|
|
|
|
|
3 |
import { MdSave } from "react-icons/md";
|
|
|
4 |
|
|
|
|
|
5 |
import { Button } from "@/components/ui/button";
|
6 |
import {
|
7 |
Popover,
|
8 |
PopoverContent,
|
9 |
PopoverTrigger,
|
10 |
} from "@/components/ui/popover";
|
|
|
|
|
11 |
import { LoginModal } from "@/components/login-modal";
|
12 |
import { useUser } from "@/hooks/useUser";
|
13 |
-
import { Page } from "@/types";
|
14 |
-
import { DeployButtonContent } from "./content";
|
15 |
|
16 |
export function DeployButton({
|
17 |
-
|
18 |
prompts,
|
19 |
}: {
|
20 |
-
|
21 |
prompts: string[];
|
22 |
}) {
|
|
|
23 |
const { user } = useUser();
|
|
|
24 |
const [open, setOpen] = useState(false);
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
return (
|
27 |
<div className="flex items-center justify-end gap-5">
|
28 |
<div className="relative flex items-center justify-end">
|
@@ -32,10 +71,10 @@ export function DeployButton({
|
|
32 |
<div>
|
33 |
<Button variant="default" className="max-lg:hidden !px-4">
|
34 |
<MdSave className="size-4" />
|
35 |
-
|
36 |
</Button>
|
37 |
<Button variant="default" size="sm" className="lg:hidden">
|
38 |
-
|
39 |
</Button>
|
40 |
</div>
|
41 |
</PopoverTrigger>
|
@@ -43,7 +82,62 @@ export function DeployButton({
|
|
43 |
className="!rounded-2xl !p-0 !bg-white !border-neutral-200 min-w-xs text-center overflow-hidden"
|
44 |
align="end"
|
45 |
>
|
46 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
</PopoverContent>
|
48 |
</Popover>
|
49 |
) : (
|
@@ -54,7 +148,7 @@ export function DeployButton({
|
|
54 |
onClick={() => setOpen(true)}
|
55 |
>
|
56 |
<MdSave className="size-4" />
|
57 |
-
|
58 |
</Button>
|
59 |
<Button
|
60 |
variant="default"
|
@@ -62,16 +156,16 @@ export function DeployButton({
|
|
62 |
className="lg:hidden"
|
63 |
onClick={() => setOpen(true)}
|
64 |
>
|
65 |
-
|
66 |
</Button>
|
67 |
</>
|
68 |
)}
|
69 |
<LoginModal
|
70 |
open={open}
|
71 |
onClose={() => setOpen(false)}
|
72 |
-
|
73 |
-
title="Log In to
|
74 |
-
description="Log In through your Hugging Face account to
|
75 |
/>
|
76 |
</div>
|
77 |
</div>
|
|
|
1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2 |
import { useState } from "react";
|
3 |
+
import { toast } from "sonner";
|
4 |
+
import Image from "next/image";
|
5 |
+
import { useRouter } from "next/navigation";
|
6 |
import { MdSave } from "react-icons/md";
|
7 |
+
import { Rocket } from "lucide-react";
|
8 |
|
9 |
+
import SpaceIcon from "@/assets/space.svg";
|
10 |
+
import Loading from "@/components/loading";
|
11 |
import { Button } from "@/components/ui/button";
|
12 |
import {
|
13 |
Popover,
|
14 |
PopoverContent,
|
15 |
PopoverTrigger,
|
16 |
} from "@/components/ui/popover";
|
17 |
+
import { Input } from "@/components/ui/input";
|
18 |
+
import { api } from "@/lib/api";
|
19 |
import { LoginModal } from "@/components/login-modal";
|
20 |
import { useUser } from "@/hooks/useUser";
|
|
|
|
|
21 |
|
22 |
export function DeployButton({
|
23 |
+
html,
|
24 |
prompts,
|
25 |
}: {
|
26 |
+
html: string;
|
27 |
prompts: string[];
|
28 |
}) {
|
29 |
+
const router = useRouter();
|
30 |
const { user } = useUser();
|
31 |
+
const [loading, setLoading] = useState(false);
|
32 |
const [open, setOpen] = useState(false);
|
33 |
|
34 |
+
const [config, setConfig] = useState({
|
35 |
+
title: "",
|
36 |
+
});
|
37 |
+
|
38 |
+
const createSpace = async () => {
|
39 |
+
if (!config.title) {
|
40 |
+
toast.error("Please enter a title for your space.");
|
41 |
+
return;
|
42 |
+
}
|
43 |
+
setLoading(true);
|
44 |
+
|
45 |
+
try {
|
46 |
+
const res = await api.post("/me/projects", {
|
47 |
+
title: config.title,
|
48 |
+
html,
|
49 |
+
prompts,
|
50 |
+
});
|
51 |
+
if (res.data.ok) {
|
52 |
+
router.push(`/projects/${res.data.path}?deploy=true`);
|
53 |
+
} else {
|
54 |
+
toast.error(res?.data?.error || "Failed to create space");
|
55 |
+
}
|
56 |
+
} catch (err: any) {
|
57 |
+
toast.error(err.response?.data?.error || err.message);
|
58 |
+
} finally {
|
59 |
+
setLoading(false);
|
60 |
+
}
|
61 |
+
};
|
62 |
+
|
63 |
+
// TODO add a way to do not allow people to deploy if the html is broken.
|
64 |
+
|
65 |
return (
|
66 |
<div className="flex items-center justify-end gap-5">
|
67 |
<div className="relative flex items-center justify-end">
|
|
|
71 |
<div>
|
72 |
<Button variant="default" className="max-lg:hidden !px-4">
|
73 |
<MdSave className="size-4" />
|
74 |
+
Deploy your Project
|
75 |
</Button>
|
76 |
<Button variant="default" size="sm" className="lg:hidden">
|
77 |
+
Deploy
|
78 |
</Button>
|
79 |
</div>
|
80 |
</PopoverTrigger>
|
|
|
82 |
className="!rounded-2xl !p-0 !bg-white !border-neutral-200 min-w-xs text-center overflow-hidden"
|
83 |
align="end"
|
84 |
>
|
85 |
+
<header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
|
86 |
+
<div className="flex items-center justify-center -space-x-4 mb-3">
|
87 |
+
<div className="size-9 rounded-full bg-amber-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
88 |
+
🚀
|
89 |
+
</div>
|
90 |
+
<div className="size-11 rounded-full bg-red-200 shadow-2xl flex items-center justify-center z-2">
|
91 |
+
<Image
|
92 |
+
src={SpaceIcon}
|
93 |
+
alt="Space Icon"
|
94 |
+
className="size-7"
|
95 |
+
/>
|
96 |
+
</div>
|
97 |
+
<div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
98 |
+
👻
|
99 |
+
</div>
|
100 |
+
</div>
|
101 |
+
<p className="text-xl font-semibold text-neutral-950">
|
102 |
+
Deploy as Space!
|
103 |
+
</p>
|
104 |
+
<p className="text-sm text-neutral-500 mt-1.5">
|
105 |
+
Save and Deploy your project to a Space on the Hub. Spaces are
|
106 |
+
a way to share your project with the world.
|
107 |
+
</p>
|
108 |
+
</header>
|
109 |
+
<main className="space-y-4 p-6">
|
110 |
+
<div>
|
111 |
+
<p className="text-sm text-neutral-700 mb-2">
|
112 |
+
Choose a title for your space:
|
113 |
+
</p>
|
114 |
+
<Input
|
115 |
+
type="text"
|
116 |
+
placeholder="My Awesome Website"
|
117 |
+
value={config.title}
|
118 |
+
onChange={(e) =>
|
119 |
+
setConfig({ ...config, title: e.target.value })
|
120 |
+
}
|
121 |
+
className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
|
122 |
+
/>
|
123 |
+
</div>
|
124 |
+
<div>
|
125 |
+
<p className="text-sm text-neutral-700 mb-2">
|
126 |
+
Then, let's deploy it!
|
127 |
+
</p>
|
128 |
+
<Button
|
129 |
+
variant="black"
|
130 |
+
onClick={createSpace}
|
131 |
+
className="relative w-full"
|
132 |
+
disabled={loading}
|
133 |
+
>
|
134 |
+
Deploy Space <Rocket className="size-4" />
|
135 |
+
{loading && (
|
136 |
+
<Loading className="ml-2 size-4 animate-spin" />
|
137 |
+
)}
|
138 |
+
</Button>
|
139 |
+
</div>
|
140 |
+
</main>
|
141 |
</PopoverContent>
|
142 |
</Popover>
|
143 |
) : (
|
|
|
148 |
onClick={() => setOpen(true)}
|
149 |
>
|
150 |
<MdSave className="size-4" />
|
151 |
+
Save your Project
|
152 |
</Button>
|
153 |
<Button
|
154 |
variant="default"
|
|
|
156 |
className="lg:hidden"
|
157 |
onClick={() => setOpen(true)}
|
158 |
>
|
159 |
+
Save
|
160 |
</Button>
|
161 |
</>
|
162 |
)}
|
163 |
<LoginModal
|
164 |
open={open}
|
165 |
onClose={() => setOpen(false)}
|
166 |
+
html={html}
|
167 |
+
title="Log In to save your Project"
|
168 |
+
description="Log In through your Hugging Face account to save your project and increase your monthly free limit."
|
169 |
/>
|
170 |
</div>
|
171 |
</div>
|
components/editor/footer/index.tsx
CHANGED
@@ -1,16 +1,13 @@
|
|
1 |
import classNames from "classnames";
|
2 |
import { FaMobileAlt } from "react-icons/fa";
|
3 |
-
import { HelpCircle,
|
4 |
import { FaLaptopCode } from "react-icons/fa6";
|
5 |
-
import { HtmlHistory
|
6 |
import { Button } from "@/components/ui/button";
|
7 |
import { MdAdd } from "react-icons/md";
|
8 |
import { History } from "@/components/editor/history";
|
9 |
import { UserMenu } from "@/components/user-menu";
|
10 |
import { useUser } from "@/hooks/useUser";
|
11 |
-
import Link from "next/link";
|
12 |
-
import { useLocalStorage } from "react-use";
|
13 |
-
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
14 |
|
15 |
const DEVICES = [
|
16 |
{
|
@@ -24,23 +21,21 @@ const DEVICES = [
|
|
24 |
];
|
25 |
|
26 |
export function Footer({
|
27 |
-
|
28 |
-
isNew = false,
|
29 |
htmlHistory,
|
30 |
-
|
31 |
device,
|
32 |
setDevice,
|
33 |
iframeRef,
|
34 |
}: {
|
35 |
-
|
36 |
-
isNew?: boolean;
|
37 |
htmlHistory?: HtmlHistory[];
|
38 |
device: "desktop" | "mobile";
|
39 |
-
|
40 |
iframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
41 |
setDevice: React.Dispatch<React.SetStateAction<"desktop" | "mobile">>;
|
42 |
}) {
|
43 |
-
const { user
|
44 |
|
45 |
const handleRefreshIframe = () => {
|
46 |
if (iframeRef?.current) {
|
@@ -53,19 +48,11 @@ export function Footer({
|
|
53 |
}
|
54 |
};
|
55 |
|
56 |
-
const [, setStorage] = useLocalStorage("pages");
|
57 |
-
const handleClick = async () => {
|
58 |
-
if (pages && !isTheSameHtml(pages[0].html)) {
|
59 |
-
setStorage(pages);
|
60 |
-
}
|
61 |
-
openLoginWindow();
|
62 |
-
};
|
63 |
-
|
64 |
return (
|
65 |
<footer className="border-t bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 py-2 flex items-center justify-between sticky bottom-0 z-20">
|
66 |
<div className="flex items-center gap-2">
|
67 |
-
{user
|
68 |
-
user?.isLocalUse ? (
|
69 |
<>
|
70 |
<div className="max-w-max bg-amber-500/10 rounded-full px-3 py-1 text-amber-500 border border-amber-500/20 text-sm font-semibold">
|
71 |
Local Usage
|
@@ -73,26 +60,16 @@ export function Footer({
|
|
73 |
</>
|
74 |
) : (
|
75 |
<UserMenu className="!p-1 !pr-3 !h-auto" />
|
76 |
-
)
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
)}
|
83 |
-
{user && !isNew && <p className="text-neutral-700">|</p>}
|
84 |
-
{!isNew && (
|
85 |
-
<Link href="/projects/new">
|
86 |
-
<Button size="sm" variant="secondary">
|
87 |
-
<MdAdd className="text-sm" />
|
88 |
-
New <span className="max-lg:hidden">Project</span>
|
89 |
-
</Button>
|
90 |
-
</Link>
|
91 |
-
)}
|
92 |
{htmlHistory && htmlHistory.length > 0 && (
|
93 |
<>
|
94 |
<p className="text-neutral-700">|</p>
|
95 |
-
<History history={htmlHistory}
|
96 |
</>
|
97 |
)}
|
98 |
</div>
|
|
|
1 |
import classNames from "classnames";
|
2 |
import { FaMobileAlt } from "react-icons/fa";
|
3 |
+
import { HelpCircle, RefreshCcw, SparkleIcon } from "lucide-react";
|
4 |
import { FaLaptopCode } from "react-icons/fa6";
|
5 |
+
import { HtmlHistory } from "@/types";
|
6 |
import { Button } from "@/components/ui/button";
|
7 |
import { MdAdd } from "react-icons/md";
|
8 |
import { History } from "@/components/editor/history";
|
9 |
import { UserMenu } from "@/components/user-menu";
|
10 |
import { useUser } from "@/hooks/useUser";
|
|
|
|
|
|
|
11 |
|
12 |
const DEVICES = [
|
13 |
{
|
|
|
21 |
];
|
22 |
|
23 |
export function Footer({
|
24 |
+
onReset,
|
|
|
25 |
htmlHistory,
|
26 |
+
setHtml,
|
27 |
device,
|
28 |
setDevice,
|
29 |
iframeRef,
|
30 |
}: {
|
31 |
+
onReset: () => void;
|
|
|
32 |
htmlHistory?: HtmlHistory[];
|
33 |
device: "desktop" | "mobile";
|
34 |
+
setHtml: (html: string) => void;
|
35 |
iframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
36 |
setDevice: React.Dispatch<React.SetStateAction<"desktop" | "mobile">>;
|
37 |
}) {
|
38 |
+
const { user } = useUser();
|
39 |
|
40 |
const handleRefreshIframe = () => {
|
41 |
if (iframeRef?.current) {
|
|
|
48 |
}
|
49 |
};
|
50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
return (
|
52 |
<footer className="border-t bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 py-2 flex items-center justify-between sticky bottom-0 z-20">
|
53 |
<div className="flex items-center gap-2">
|
54 |
+
{user &&
|
55 |
+
(user?.isLocalUse ? (
|
56 |
<>
|
57 |
<div className="max-w-max bg-amber-500/10 rounded-full px-3 py-1 text-amber-500 border border-amber-500/20 text-sm font-semibold">
|
58 |
Local Usage
|
|
|
60 |
</>
|
61 |
) : (
|
62 |
<UserMenu className="!p-1 !pr-3 !h-auto" />
|
63 |
+
))}
|
64 |
+
{user && <p className="text-neutral-700">|</p>}
|
65 |
+
<Button size="sm" variant="secondary" onClick={onReset}>
|
66 |
+
<MdAdd className="text-sm" />
|
67 |
+
New <span className="max-lg:hidden">Project</span>
|
68 |
+
</Button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
{htmlHistory && htmlHistory.length > 0 && (
|
70 |
<>
|
71 |
<p className="text-neutral-700">|</p>
|
72 |
+
<History history={htmlHistory} setHtml={setHtml} />
|
73 |
</>
|
74 |
)}
|
75 |
</div>
|
components/editor/history/index.tsx
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
import { History as HistoryIcon } from "lucide-react";
|
2 |
-
import { HtmlHistory
|
3 |
import {
|
4 |
Popover,
|
5 |
PopoverContent,
|
@@ -9,10 +9,10 @@ import { Button } from "@/components/ui/button";
|
|
9 |
|
10 |
export function History({
|
11 |
history,
|
12 |
-
|
13 |
}: {
|
14 |
history: HtmlHistory[];
|
15 |
-
|
16 |
}) {
|
17 |
return (
|
18 |
<Popover>
|
@@ -57,8 +57,7 @@ export function History({
|
|
57 |
variant="sky"
|
58 |
size="xs"
|
59 |
onClick={() => {
|
60 |
-
|
61 |
-
setPages(item.pages);
|
62 |
}}
|
63 |
>
|
64 |
Select
|
|
|
1 |
import { History as HistoryIcon } from "lucide-react";
|
2 |
+
import { HtmlHistory } from "@/types";
|
3 |
import {
|
4 |
Popover,
|
5 |
PopoverContent,
|
|
|
9 |
|
10 |
export function History({
|
11 |
history,
|
12 |
+
setHtml,
|
13 |
}: {
|
14 |
history: HtmlHistory[];
|
15 |
+
setHtml: (html: string) => void;
|
16 |
}) {
|
17 |
return (
|
18 |
<Popover>
|
|
|
57 |
variant="sky"
|
58 |
size="xs"
|
59 |
onClick={() => {
|
60 |
+
setHtml(item.html);
|
|
|
61 |
}}
|
62 |
>
|
63 |
Select
|
components/editor/index.tsx
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
"use client";
|
2 |
-
import {
|
3 |
import { toast } from "sonner";
|
4 |
import { editor } from "monaco-editor";
|
5 |
import Editor from "@monaco-editor/react";
|
@@ -22,32 +22,17 @@ import { Preview } from "@/components/editor/preview";
|
|
22 |
import { useEditor } from "@/hooks/useEditor";
|
23 |
import { AskAI } from "@/components/editor/ask-ai";
|
24 |
import { DeployButton } from "./deploy-button";
|
25 |
-
import {
|
26 |
import { SaveButton } from "./save-button";
|
27 |
import { LoadProject } from "../my-projects/load-project";
|
28 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
29 |
-
import { ListPages } from "./pages";
|
30 |
|
31 |
-
export const AppEditor = ({
|
32 |
-
|
33 |
-
pages: initialPages,
|
34 |
-
images,
|
35 |
-
isNew,
|
36 |
-
}: {
|
37 |
-
project?: Project | null;
|
38 |
-
pages?: Page[];
|
39 |
-
images?: string[];
|
40 |
-
isNew?: boolean;
|
41 |
-
}) => {
|
42 |
-
const [htmlStorage, , removeHtmlStorage] = useLocalStorage("pages");
|
43 |
const [, copyToClipboard] = useCopyToClipboard();
|
44 |
-
const { htmlHistory, setHtmlHistory, prompts, setPrompts
|
45 |
-
useEditor(
|
46 |
-
|
47 |
-
project?.prompts ?? [],
|
48 |
-
typeof htmlStorage === "string" ? htmlStorage : undefined
|
49 |
-
);
|
50 |
-
|
51 |
const searchParams = useSearchParams();
|
52 |
const router = useRouter();
|
53 |
const deploy = searchParams.get("deploy") === "true";
|
@@ -61,7 +46,6 @@ export const AppEditor = ({
|
|
61 |
const monacoRef = useRef<any>(null);
|
62 |
|
63 |
const [currentTab, setCurrentTab] = useState("chat");
|
64 |
-
const [currentPage, setCurrentPage] = useState("index.html");
|
65 |
const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
|
66 |
const [isResizing, setIsResizing] = useState(false);
|
67 |
const [isAiWorking, setIsAiWorking] = useState(false);
|
@@ -69,8 +53,12 @@ export const AppEditor = ({
|
|
69 |
const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(
|
70 |
null
|
71 |
);
|
72 |
-
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
73 |
|
|
|
|
|
|
|
|
|
|
|
74 |
const resetLayout = () => {
|
75 |
if (!editor.current || !preview.current) return;
|
76 |
|
@@ -90,6 +78,10 @@ export const AppEditor = ({
|
|
90 |
}
|
91 |
};
|
92 |
|
|
|
|
|
|
|
|
|
93 |
const handleResize = (e: MouseEvent) => {
|
94 |
if (!editor.current || !preview.current || !resizer.current) return;
|
95 |
|
@@ -157,7 +149,7 @@ export const AppEditor = ({
|
|
157 |
|
158 |
// Prevent accidental navigation away when AI is working or content has changed
|
159 |
useEvent("beforeunload", (e) => {
|
160 |
-
if (isAiWorking || !isTheSameHtml(
|
161 |
e.preventDefault();
|
162 |
return "";
|
163 |
}
|
@@ -183,15 +175,6 @@ export const AppEditor = ({
|
|
183 |
console.log("Editor validation markers:", markers);
|
184 |
};
|
185 |
|
186 |
-
const currentPageData = useMemo(() => {
|
187 |
-
return (
|
188 |
-
pages.find((page) => page.path === currentPage) ?? {
|
189 |
-
path: "index.html",
|
190 |
-
html: defaultHTML,
|
191 |
-
}
|
192 |
-
);
|
193 |
-
}, [pages, currentPage]);
|
194 |
-
|
195 |
return (
|
196 |
<section className="h-[100dvh] bg-neutral-950 flex flex-col">
|
197 |
<Header tab={currentTab} onNewTab={setCurrentTab}>
|
@@ -200,11 +183,10 @@ export const AppEditor = ({
|
|
200 |
router.push(`/projects/${project.space_id}`);
|
201 |
}}
|
202 |
/>
|
203 |
-
{/* for these buttons pass the whole pages */}
|
204 |
{project?._id ? (
|
205 |
-
<SaveButton
|
206 |
) : (
|
207 |
-
<DeployButton
|
208 |
)}
|
209 |
</Header>
|
210 |
<main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full max-lg:h-[calc(100%-82px)] relative">
|
@@ -214,43 +196,10 @@ export const AppEditor = ({
|
|
214 |
ref={editor}
|
215 |
className="bg-neutral-900 relative flex-1 overflow-hidden h-full flex flex-col gap-2 pb-3"
|
216 |
>
|
217 |
-
<ListPages
|
218 |
-
pages={pages}
|
219 |
-
currentPage={currentPage}
|
220 |
-
onSelectPage={(path, newPath) => {
|
221 |
-
if (newPath) {
|
222 |
-
setPages((prev) =>
|
223 |
-
prev.map((page) =>
|
224 |
-
page.path === path ? { ...page, path: newPath } : page
|
225 |
-
)
|
226 |
-
);
|
227 |
-
setCurrentPage(newPath);
|
228 |
-
} else {
|
229 |
-
setCurrentPage(path);
|
230 |
-
}
|
231 |
-
}}
|
232 |
-
onDeletePage={(path) => {
|
233 |
-
const newPages = pages.filter((page) => page.path !== path);
|
234 |
-
setPages(newPages);
|
235 |
-
if (currentPage === path) {
|
236 |
-
setCurrentPage(newPages[0]?.path ?? "index.html");
|
237 |
-
}
|
238 |
-
}}
|
239 |
-
onNewPage={() => {
|
240 |
-
setPages((prev) => [
|
241 |
-
...prev,
|
242 |
-
{
|
243 |
-
path: `page-${prev.length + 1}.html`,
|
244 |
-
html: defaultHTML,
|
245 |
-
},
|
246 |
-
]);
|
247 |
-
setCurrentPage(`page-${pages.length + 1}.html`);
|
248 |
-
}}
|
249 |
-
/>
|
250 |
<CopyIcon
|
251 |
-
className="size-4 absolute top-
|
252 |
onClick={() => {
|
253 |
-
copyToClipboard(
|
254 |
toast.success("HTML copied to clipboard!");
|
255 |
}}
|
256 |
/>
|
@@ -273,17 +222,10 @@ export const AppEditor = ({
|
|
273 |
},
|
274 |
wordWrap: "on",
|
275 |
}}
|
276 |
-
value={
|
277 |
onChange={(value) => {
|
278 |
const newValue = value ?? "";
|
279 |
-
|
280 |
-
setPages((prev) =>
|
281 |
-
prev.map((page) =>
|
282 |
-
page.path === currentPageData.path
|
283 |
-
? { ...page, html: newValue }
|
284 |
-
: page
|
285 |
-
)
|
286 |
-
);
|
287 |
}}
|
288 |
onMount={(editor, monaco) => {
|
289 |
editorRef.current = editor;
|
@@ -292,49 +234,49 @@ export const AppEditor = ({
|
|
292 |
onValidate={handleEditorValidation}
|
293 |
/>
|
294 |
<AskAI
|
295 |
-
|
296 |
-
|
297 |
-
|
|
|
298 |
htmlHistory={htmlHistory}
|
299 |
-
|
300 |
-
|
|
|
|
|
|
|
301 |
const currentHistory = [...htmlHistory];
|
302 |
currentHistory.unshift({
|
303 |
-
|
304 |
createdAt: new Date(),
|
305 |
prompt: p,
|
306 |
});
|
307 |
setHtmlHistory(currentHistory);
|
308 |
setSelectedElement(null);
|
309 |
-
setSelectedFiles([]);
|
310 |
// if xs or sm
|
311 |
if (window.innerWidth <= 1024) {
|
312 |
setCurrentTab("preview");
|
313 |
}
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
}}
|
335 |
-
setPages={setPages}
|
336 |
-
pages={pages}
|
337 |
-
setCurrentPage={setCurrentPage}
|
338 |
isAiWorking={isAiWorking}
|
339 |
setisAiWorking={setIsAiWorking}
|
340 |
onNewPrompt={(prompt: string) => {
|
@@ -345,13 +287,10 @@ export const AppEditor = ({
|
|
345 |
editorRef.current?.getModel()?.getLineCount() ?? 0
|
346 |
);
|
347 |
}}
|
348 |
-
isNew={isNew}
|
349 |
isEditableModeEnabled={isEditableModeEnabled}
|
350 |
setIsEditableModeEnabled={setIsEditableModeEnabled}
|
351 |
selectedElement={selectedElement}
|
352 |
setSelectedElement={setSelectedElement}
|
353 |
-
setSelectedFiles={setSelectedFiles}
|
354 |
-
selectedFiles={selectedFiles}
|
355 |
/>
|
356 |
</div>
|
357 |
<div
|
@@ -361,13 +300,11 @@ export const AppEditor = ({
|
|
361 |
</>
|
362 |
)}
|
363 |
<Preview
|
364 |
-
html={
|
365 |
isResizing={isResizing}
|
366 |
isAiWorking={isAiWorking}
|
367 |
ref={preview}
|
368 |
device={device}
|
369 |
-
pages={pages}
|
370 |
-
setCurrentPage={setCurrentPage}
|
371 |
currentTab={currentTab}
|
372 |
isEditableModeEnabled={isEditableModeEnabled}
|
373 |
iframeRef={iframeRef}
|
@@ -379,12 +316,25 @@ export const AppEditor = ({
|
|
379 |
/>
|
380 |
</main>
|
381 |
<Footer
|
382 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
383 |
htmlHistory={htmlHistory}
|
384 |
-
|
385 |
iframeRef={iframeRef}
|
386 |
device={device}
|
387 |
-
isNew={isNew}
|
388 |
setDevice={setDevice}
|
389 |
/>
|
390 |
</section>
|
|
|
1 |
"use client";
|
2 |
+
import { useRef, useState } from "react";
|
3 |
import { toast } from "sonner";
|
4 |
import { editor } from "monaco-editor";
|
5 |
import Editor from "@monaco-editor/react";
|
|
|
22 |
import { useEditor } from "@/hooks/useEditor";
|
23 |
import { AskAI } from "@/components/editor/ask-ai";
|
24 |
import { DeployButton } from "./deploy-button";
|
25 |
+
import { Project } from "@/types";
|
26 |
import { SaveButton } from "./save-button";
|
27 |
import { LoadProject } from "../my-projects/load-project";
|
28 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
|
|
29 |
|
30 |
+
export const AppEditor = ({ project }: { project?: Project | null }) => {
|
31 |
+
const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
const [, copyToClipboard] = useCopyToClipboard();
|
33 |
+
const { html, setHtml, htmlHistory, setHtmlHistory, prompts, setPrompts } =
|
34 |
+
useEditor(project?.html ?? (htmlStorage as string) ?? defaultHTML);
|
35 |
+
// get query params from URL
|
|
|
|
|
|
|
|
|
36 |
const searchParams = useSearchParams();
|
37 |
const router = useRouter();
|
38 |
const deploy = searchParams.get("deploy") === "true";
|
|
|
46 |
const monacoRef = useRef<any>(null);
|
47 |
|
48 |
const [currentTab, setCurrentTab] = useState("chat");
|
|
|
49 |
const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
|
50 |
const [isResizing, setIsResizing] = useState(false);
|
51 |
const [isAiWorking, setIsAiWorking] = useState(false);
|
|
|
53 |
const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(
|
54 |
null
|
55 |
);
|
|
|
56 |
|
57 |
+
/**
|
58 |
+
* Resets the layout based on screen size
|
59 |
+
* - For desktop: Sets editor to 1/3 width and preview to 2/3
|
60 |
+
* - For mobile: Removes inline styles to let CSS handle it
|
61 |
+
*/
|
62 |
const resetLayout = () => {
|
63 |
if (!editor.current || !preview.current) return;
|
64 |
|
|
|
78 |
}
|
79 |
};
|
80 |
|
81 |
+
/**
|
82 |
+
* Handles resizing when the user drags the resizer
|
83 |
+
* Ensures minimum widths are maintained for both panels
|
84 |
+
*/
|
85 |
const handleResize = (e: MouseEvent) => {
|
86 |
if (!editor.current || !preview.current || !resizer.current) return;
|
87 |
|
|
|
149 |
|
150 |
// Prevent accidental navigation away when AI is working or content has changed
|
151 |
useEvent("beforeunload", (e) => {
|
152 |
+
if (isAiWorking || !isTheSameHtml(html)) {
|
153 |
e.preventDefault();
|
154 |
return "";
|
155 |
}
|
|
|
175 |
console.log("Editor validation markers:", markers);
|
176 |
};
|
177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
178 |
return (
|
179 |
<section className="h-[100dvh] bg-neutral-950 flex flex-col">
|
180 |
<Header tab={currentTab} onNewTab={setCurrentTab}>
|
|
|
183 |
router.push(`/projects/${project.space_id}`);
|
184 |
}}
|
185 |
/>
|
|
|
186 |
{project?._id ? (
|
187 |
+
<SaveButton html={html} prompts={prompts} />
|
188 |
) : (
|
189 |
+
<DeployButton html={html} prompts={prompts} />
|
190 |
)}
|
191 |
</Header>
|
192 |
<main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full max-lg:h-[calc(100%-82px)] relative">
|
|
|
196 |
ref={editor}
|
197 |
className="bg-neutral-900 relative flex-1 overflow-hidden h-full flex flex-col gap-2 pb-3"
|
198 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
<CopyIcon
|
200 |
+
className="size-4 absolute top-2 right-5 text-neutral-500 hover:text-neutral-300 z-2 cursor-pointer"
|
201 |
onClick={() => {
|
202 |
+
copyToClipboard(html);
|
203 |
toast.success("HTML copied to clipboard!");
|
204 |
}}
|
205 |
/>
|
|
|
222 |
},
|
223 |
wordWrap: "on",
|
224 |
}}
|
225 |
+
value={html}
|
226 |
onChange={(value) => {
|
227 |
const newValue = value ?? "";
|
228 |
+
setHtml(newValue);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
}}
|
230 |
onMount={(editor, monaco) => {
|
231 |
editorRef.current = editor;
|
|
|
234 |
onValidate={handleEditorValidation}
|
235 |
/>
|
236 |
<AskAI
|
237 |
+
html={html}
|
238 |
+
setHtml={(newHtml: string) => {
|
239 |
+
setHtml(newHtml);
|
240 |
+
}}
|
241 |
htmlHistory={htmlHistory}
|
242 |
+
onSuccess={(
|
243 |
+
finalHtml: string,
|
244 |
+
p: string,
|
245 |
+
updatedLines?: number[][]
|
246 |
+
) => {
|
247 |
const currentHistory = [...htmlHistory];
|
248 |
currentHistory.unshift({
|
249 |
+
html: finalHtml,
|
250 |
createdAt: new Date(),
|
251 |
prompt: p,
|
252 |
});
|
253 |
setHtmlHistory(currentHistory);
|
254 |
setSelectedElement(null);
|
|
|
255 |
// if xs or sm
|
256 |
if (window.innerWidth <= 1024) {
|
257 |
setCurrentTab("preview");
|
258 |
}
|
259 |
+
if (updatedLines && updatedLines?.length > 0) {
|
260 |
+
const decorations = updatedLines.map((line) => ({
|
261 |
+
range: new monacoRef.current.Range(
|
262 |
+
line[0],
|
263 |
+
1,
|
264 |
+
line[1],
|
265 |
+
1
|
266 |
+
),
|
267 |
+
options: {
|
268 |
+
inlineClassName: "matched-line",
|
269 |
+
},
|
270 |
+
}));
|
271 |
+
setTimeout(() => {
|
272 |
+
editorRef?.current
|
273 |
+
?.getModel()
|
274 |
+
?.deltaDecorations([], decorations);
|
275 |
|
276 |
+
editorRef.current?.revealLine(updatedLines[0][0]);
|
277 |
+
}, 100);
|
278 |
+
}
|
279 |
}}
|
|
|
|
|
|
|
280 |
isAiWorking={isAiWorking}
|
281 |
setisAiWorking={setIsAiWorking}
|
282 |
onNewPrompt={(prompt: string) => {
|
|
|
287 |
editorRef.current?.getModel()?.getLineCount() ?? 0
|
288 |
);
|
289 |
}}
|
|
|
290 |
isEditableModeEnabled={isEditableModeEnabled}
|
291 |
setIsEditableModeEnabled={setIsEditableModeEnabled}
|
292 |
selectedElement={selectedElement}
|
293 |
setSelectedElement={setSelectedElement}
|
|
|
|
|
294 |
/>
|
295 |
</div>
|
296 |
<div
|
|
|
300 |
</>
|
301 |
)}
|
302 |
<Preview
|
303 |
+
html={html}
|
304 |
isResizing={isResizing}
|
305 |
isAiWorking={isAiWorking}
|
306 |
ref={preview}
|
307 |
device={device}
|
|
|
|
|
308 |
currentTab={currentTab}
|
309 |
isEditableModeEnabled={isEditableModeEnabled}
|
310 |
iframeRef={iframeRef}
|
|
|
316 |
/>
|
317 |
</main>
|
318 |
<Footer
|
319 |
+
onReset={() => {
|
320 |
+
if (isAiWorking) {
|
321 |
+
toast.warning("Please wait for the AI to finish working.");
|
322 |
+
return;
|
323 |
+
}
|
324 |
+
if (
|
325 |
+
window.confirm("You're about to reset the editor. Are you sure?")
|
326 |
+
) {
|
327 |
+
setHtml(defaultHTML);
|
328 |
+
removeHtmlStorage();
|
329 |
+
editorRef.current?.revealLine(
|
330 |
+
editorRef.current?.getModel()?.getLineCount() ?? 0
|
331 |
+
);
|
332 |
+
}
|
333 |
+
}}
|
334 |
htmlHistory={htmlHistory}
|
335 |
+
setHtml={setHtml}
|
336 |
iframeRef={iframeRef}
|
337 |
device={device}
|
|
|
338 |
setDevice={setDevice}
|
339 |
/>
|
340 |
</section>
|
components/editor/pages/index.tsx
DELETED
@@ -1,30 +0,0 @@
|
|
1 |
-
import { Page } from "@/types";
|
2 |
-
import { ListPagesItem } from "./page";
|
3 |
-
|
4 |
-
export function ListPages({
|
5 |
-
pages,
|
6 |
-
currentPage,
|
7 |
-
onSelectPage,
|
8 |
-
onDeletePage,
|
9 |
-
}: {
|
10 |
-
pages: Array<Page>;
|
11 |
-
currentPage: string;
|
12 |
-
onSelectPage: (path: string, newPath?: string) => void;
|
13 |
-
onNewPage: () => void;
|
14 |
-
onDeletePage: (path: string) => void;
|
15 |
-
}) {
|
16 |
-
return (
|
17 |
-
<div className="w-full flex items-center justify-start bg-neutral-950 overflow-auto flex-nowrap min-h-[44px]">
|
18 |
-
{pages.map((page, i) => (
|
19 |
-
<ListPagesItem
|
20 |
-
key={i}
|
21 |
-
page={page}
|
22 |
-
currentPage={currentPage}
|
23 |
-
onSelectPage={onSelectPage}
|
24 |
-
onDeletePage={onDeletePage}
|
25 |
-
index={i}
|
26 |
-
/>
|
27 |
-
))}
|
28 |
-
</div>
|
29 |
-
);
|
30 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/editor/pages/page.tsx
DELETED
@@ -1,82 +0,0 @@
|
|
1 |
-
import classNames from "classnames";
|
2 |
-
import { XIcon } from "lucide-react";
|
3 |
-
|
4 |
-
import { Button } from "@/components/ui/button";
|
5 |
-
import { Page } from "@/types";
|
6 |
-
|
7 |
-
export function ListPagesItem({
|
8 |
-
page,
|
9 |
-
currentPage,
|
10 |
-
onSelectPage,
|
11 |
-
onDeletePage,
|
12 |
-
index,
|
13 |
-
}: {
|
14 |
-
page: Page;
|
15 |
-
currentPage: string;
|
16 |
-
onSelectPage: (path: string, newPath?: string) => void;
|
17 |
-
onDeletePage: (path: string) => void;
|
18 |
-
index: number;
|
19 |
-
}) {
|
20 |
-
return (
|
21 |
-
<div
|
22 |
-
key={index}
|
23 |
-
className={classNames(
|
24 |
-
"pl-6 pr-1 py-3 text-neutral-400 cursor-pointer text-sm hover:bg-neutral-900 flex items-center justify-center gap-1 group text-nowrap border-r border-neutral-800",
|
25 |
-
{
|
26 |
-
"bg-neutral-900 !text-white": currentPage === page.path,
|
27 |
-
"!pr-6": index === 0, // Ensure the first item has padding on the right
|
28 |
-
}
|
29 |
-
)}
|
30 |
-
onClick={() => onSelectPage(page.path)}
|
31 |
-
title={page.path}
|
32 |
-
>
|
33 |
-
{/* {index > 0 && (
|
34 |
-
<Button
|
35 |
-
size="iconXsss"
|
36 |
-
variant="ghost"
|
37 |
-
onClick={(e) => {
|
38 |
-
e.stopPropagation();
|
39 |
-
// open the window modal to edit the name page
|
40 |
-
let newName = window.prompt(
|
41 |
-
"Enter new name for the page:",
|
42 |
-
page.path
|
43 |
-
);
|
44 |
-
if (newName && newName.trim() !== "") {
|
45 |
-
newName = newName.toLowerCase();
|
46 |
-
if (!newName.endsWith(".html")) {
|
47 |
-
newName = newName.replace(/\.[^/.]+$/, "");
|
48 |
-
newName = newName.replace(/\s+/g, "-");
|
49 |
-
newName += ".html";
|
50 |
-
}
|
51 |
-
onSelectPage(page.path, newName);
|
52 |
-
} else {
|
53 |
-
window.alert("Page name cannot be empty.");
|
54 |
-
}
|
55 |
-
}}
|
56 |
-
>
|
57 |
-
<EditIcon className="!h-3.5 text-neutral-400 cursor-pointer hover:text-neutral-300" />
|
58 |
-
</Button>
|
59 |
-
)} */}
|
60 |
-
{page.path}
|
61 |
-
{index > 0 && (
|
62 |
-
<Button
|
63 |
-
size="iconXsss"
|
64 |
-
variant="ghost"
|
65 |
-
className="group-hover:opacity-100 opacity-0"
|
66 |
-
onClick={(e) => {
|
67 |
-
e.stopPropagation();
|
68 |
-
if (
|
69 |
-
window.confirm(
|
70 |
-
"Are you sure you want to delete this page? This action cannot be undone."
|
71 |
-
)
|
72 |
-
) {
|
73 |
-
onDeletePage(page.path);
|
74 |
-
}
|
75 |
-
}}
|
76 |
-
>
|
77 |
-
<XIcon className="h-3 text-neutral-400 cursor-pointer hover:text-neutral-300" />
|
78 |
-
</Button>
|
79 |
-
)}
|
80 |
-
</div>
|
81 |
-
);
|
82 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/editor/preview/index.tsx
CHANGED
@@ -3,12 +3,10 @@ import { useUpdateEffect } from "react-use";
|
|
3 |
import { useMemo, useState } from "react";
|
4 |
import classNames from "classnames";
|
5 |
import { toast } from "sonner";
|
6 |
-
import { useThrottleFn } from "react-use";
|
7 |
|
8 |
import { cn } from "@/lib/utils";
|
9 |
import { GridPattern } from "@/components/magic-ui/grid-pattern";
|
10 |
import { htmlTagToText } from "@/lib/html-tag-to-text";
|
11 |
-
import { Page } from "@/types";
|
12 |
|
13 |
export const Preview = ({
|
14 |
html,
|
@@ -18,16 +16,12 @@ export const Preview = ({
|
|
18 |
device,
|
19 |
currentTab,
|
20 |
iframeRef,
|
21 |
-
pages,
|
22 |
-
setCurrentPage,
|
23 |
isEditableModeEnabled,
|
24 |
onClickElement,
|
25 |
}: {
|
26 |
html: string;
|
27 |
isResizing: boolean;
|
28 |
isAiWorking: boolean;
|
29 |
-
pages: Page[];
|
30 |
-
setCurrentPage: React.Dispatch<React.SetStateAction<string>>;
|
31 |
ref: React.RefObject<HTMLDivElement | null>;
|
32 |
iframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
33 |
device: "desktop" | "mobile";
|
@@ -39,6 +33,7 @@ export const Preview = ({
|
|
39 |
null
|
40 |
);
|
41 |
|
|
|
42 |
const handleMouseOver = (event: MouseEvent) => {
|
43 |
if (iframeRef?.current) {
|
44 |
const iframeDocument = iframeRef.current.contentDocument;
|
@@ -70,48 +65,6 @@ export const Preview = ({
|
|
70 |
}
|
71 |
}
|
72 |
};
|
73 |
-
const handleCustomNavigation = (event: MouseEvent) => {
|
74 |
-
if (iframeRef?.current) {
|
75 |
-
const iframeDocument = iframeRef.current.contentDocument;
|
76 |
-
if (iframeDocument) {
|
77 |
-
const findClosestAnchor = (
|
78 |
-
element: HTMLElement
|
79 |
-
): HTMLAnchorElement | null => {
|
80 |
-
let current = element;
|
81 |
-
while (current && current !== iframeDocument.body) {
|
82 |
-
if (current.tagName === "A") {
|
83 |
-
return current as HTMLAnchorElement;
|
84 |
-
}
|
85 |
-
current = current.parentElement as HTMLElement;
|
86 |
-
}
|
87 |
-
return null;
|
88 |
-
};
|
89 |
-
|
90 |
-
const anchorElement = findClosestAnchor(event.target as HTMLElement);
|
91 |
-
if (anchorElement) {
|
92 |
-
let href = anchorElement.getAttribute("href");
|
93 |
-
if (href) {
|
94 |
-
event.stopPropagation();
|
95 |
-
event.preventDefault();
|
96 |
-
|
97 |
-
if (href.includes("#") && !href.includes(".html")) {
|
98 |
-
const targetElement = iframeDocument.querySelector(href);
|
99 |
-
if (targetElement) {
|
100 |
-
targetElement.scrollIntoView({ behavior: "smooth" });
|
101 |
-
}
|
102 |
-
return;
|
103 |
-
}
|
104 |
-
|
105 |
-
href = href.split(".html")[0] + ".html";
|
106 |
-
const isPageExist = pages.some((page) => page.path === href);
|
107 |
-
if (isPageExist) {
|
108 |
-
setCurrentPage(href);
|
109 |
-
}
|
110 |
-
}
|
111 |
-
}
|
112 |
-
}
|
113 |
-
}
|
114 |
-
};
|
115 |
|
116 |
useUpdateEffect(() => {
|
117 |
const cleanupListeners = () => {
|
@@ -126,6 +79,7 @@ export const Preview = ({
|
|
126 |
if (iframeRef?.current) {
|
127 |
const iframeDocument = iframeRef.current.contentDocument;
|
128 |
if (iframeDocument) {
|
|
|
129 |
cleanupListeners();
|
130 |
|
131 |
if (isEditableModeEnabled) {
|
@@ -136,6 +90,7 @@ export const Preview = ({
|
|
136 |
}
|
137 |
}
|
138 |
|
|
|
139 |
return cleanupListeners;
|
140 |
}, [iframeRef, isEditableModeEnabled]);
|
141 |
|
@@ -145,8 +100,6 @@ export const Preview = ({
|
|
145 |
return hoveredElement;
|
146 |
}, [hoveredElement, isEditableModeEnabled]);
|
147 |
|
148 |
-
const throttledHtml = useThrottleFn((html) => html, 1000, [html]);
|
149 |
-
|
150 |
return (
|
151 |
<div
|
152 |
ref={ref}
|
@@ -207,7 +160,7 @@ export const Preview = ({
|
|
207 |
currentTab !== "preview" && device === "desktop",
|
208 |
}
|
209 |
)}
|
210 |
-
srcDoc={
|
211 |
onLoad={() => {
|
212 |
if (iframeRef?.current?.contentWindow?.document?.body) {
|
213 |
iframeRef.current.contentWindow.document.body.scrollIntoView({
|
@@ -216,14 +169,6 @@ export const Preview = ({
|
|
216 |
behavior: isAiWorking ? "instant" : "smooth",
|
217 |
});
|
218 |
}
|
219 |
-
// add event listener to all links in the iframe to handle navigation
|
220 |
-
if (iframeRef?.current?.contentWindow?.document) {
|
221 |
-
const links =
|
222 |
-
iframeRef.current.contentWindow.document.querySelectorAll("a");
|
223 |
-
links.forEach((link) => {
|
224 |
-
link.addEventListener("click", handleCustomNavigation);
|
225 |
-
});
|
226 |
-
}
|
227 |
}}
|
228 |
/>
|
229 |
</div>
|
|
|
3 |
import { useMemo, useState } from "react";
|
4 |
import classNames from "classnames";
|
5 |
import { toast } from "sonner";
|
|
|
6 |
|
7 |
import { cn } from "@/lib/utils";
|
8 |
import { GridPattern } from "@/components/magic-ui/grid-pattern";
|
9 |
import { htmlTagToText } from "@/lib/html-tag-to-text";
|
|
|
10 |
|
11 |
export const Preview = ({
|
12 |
html,
|
|
|
16 |
device,
|
17 |
currentTab,
|
18 |
iframeRef,
|
|
|
|
|
19 |
isEditableModeEnabled,
|
20 |
onClickElement,
|
21 |
}: {
|
22 |
html: string;
|
23 |
isResizing: boolean;
|
24 |
isAiWorking: boolean;
|
|
|
|
|
25 |
ref: React.RefObject<HTMLDivElement | null>;
|
26 |
iframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
27 |
device: "desktop" | "mobile";
|
|
|
33 |
null
|
34 |
);
|
35 |
|
36 |
+
// add event listener to the iframe to track hovered elements
|
37 |
const handleMouseOver = (event: MouseEvent) => {
|
38 |
if (iframeRef?.current) {
|
39 |
const iframeDocument = iframeRef.current.contentDocument;
|
|
|
65 |
}
|
66 |
}
|
67 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
useUpdateEffect(() => {
|
70 |
const cleanupListeners = () => {
|
|
|
79 |
if (iframeRef?.current) {
|
80 |
const iframeDocument = iframeRef.current.contentDocument;
|
81 |
if (iframeDocument) {
|
82 |
+
// Clean up existing listeners first
|
83 |
cleanupListeners();
|
84 |
|
85 |
if (isEditableModeEnabled) {
|
|
|
90 |
}
|
91 |
}
|
92 |
|
93 |
+
// Clean up when component unmounts or dependencies change
|
94 |
return cleanupListeners;
|
95 |
}, [iframeRef, isEditableModeEnabled]);
|
96 |
|
|
|
100 |
return hoveredElement;
|
101 |
}, [hoveredElement, isEditableModeEnabled]);
|
102 |
|
|
|
|
|
103 |
return (
|
104 |
<div
|
105 |
ref={ref}
|
|
|
160 |
currentTab !== "preview" && device === "desktop",
|
161 |
}
|
162 |
)}
|
163 |
+
srcDoc={html}
|
164 |
onLoad={() => {
|
165 |
if (iframeRef?.current?.contentWindow?.document?.body) {
|
166 |
iframeRef.current.contentWindow.document.body.scrollIntoView({
|
|
|
169 |
behavior: isAiWorking ? "instant" : "smooth",
|
170 |
});
|
171 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
}}
|
173 |
/>
|
174 |
</div>
|
components/editor/save-button/index.tsx
CHANGED
@@ -7,13 +7,12 @@ import { useParams } from "next/navigation";
|
|
7 |
import Loading from "@/components/loading";
|
8 |
import { Button } from "@/components/ui/button";
|
9 |
import { api } from "@/lib/api";
|
10 |
-
import { Page } from "@/types";
|
11 |
|
12 |
export function SaveButton({
|
13 |
-
|
14 |
prompts,
|
15 |
}: {
|
16 |
-
|
17 |
prompts: string[];
|
18 |
}) {
|
19 |
// get params from URL
|
@@ -28,7 +27,7 @@ export function SaveButton({
|
|
28 |
|
29 |
try {
|
30 |
const res = await api.put(`/me/projects/${namespace}/${repoId}`, {
|
31 |
-
|
32 |
prompts,
|
33 |
});
|
34 |
if (res.data.ok) {
|
@@ -60,7 +59,7 @@ export function SaveButton({
|
|
60 |
onClick={updateSpace}
|
61 |
>
|
62 |
<MdSave className="size-4" />
|
63 |
-
|
64 |
{loading && <Loading className="ml-2 size-4 animate-spin" />}
|
65 |
</Button>
|
66 |
<Button
|
@@ -69,7 +68,7 @@ export function SaveButton({
|
|
69 |
className="lg:hidden relative"
|
70 |
onClick={updateSpace}
|
71 |
>
|
72 |
-
|
73 |
</Button>
|
74 |
</>
|
75 |
);
|
|
|
7 |
import Loading from "@/components/loading";
|
8 |
import { Button } from "@/components/ui/button";
|
9 |
import { api } from "@/lib/api";
|
|
|
10 |
|
11 |
export function SaveButton({
|
12 |
+
html,
|
13 |
prompts,
|
14 |
}: {
|
15 |
+
html: string;
|
16 |
prompts: string[];
|
17 |
}) {
|
18 |
// get params from URL
|
|
|
27 |
|
28 |
try {
|
29 |
const res = await api.put(`/me/projects/${namespace}/${repoId}`, {
|
30 |
+
html,
|
31 |
prompts,
|
32 |
});
|
33 |
if (res.data.ok) {
|
|
|
59 |
onClick={updateSpace}
|
60 |
>
|
61 |
<MdSave className="size-4" />
|
62 |
+
Deploy your Project{" "}
|
63 |
{loading && <Loading className="ml-2 size-4 animate-spin" />}
|
64 |
</Button>
|
65 |
<Button
|
|
|
68 |
className="lg:hidden relative"
|
69 |
onClick={updateSpace}
|
70 |
>
|
71 |
+
Deploy {loading && <Loading className="ml-2 size-4 animate-spin" />}
|
72 |
</Button>
|
73 |
</>
|
74 |
);
|
components/iframe-detector.tsx
DELETED
@@ -1,75 +0,0 @@
|
|
1 |
-
"use client";
|
2 |
-
|
3 |
-
import { useEffect, useState } from "react";
|
4 |
-
import IframeWarningModal from "./iframe-warning-modal";
|
5 |
-
|
6 |
-
export default function IframeDetector() {
|
7 |
-
const [showWarning, setShowWarning] = useState(false);
|
8 |
-
|
9 |
-
useEffect(() => {
|
10 |
-
// Helper function to check if a hostname is from allowed domains
|
11 |
-
const isAllowedDomain = (hostname: string) => {
|
12 |
-
const host = hostname.toLowerCase();
|
13 |
-
return (
|
14 |
-
host.endsWith(".huggingface.co") ||
|
15 |
-
host.endsWith(".hf.co") ||
|
16 |
-
host === "huggingface.co" ||
|
17 |
-
host === "hf.co"
|
18 |
-
);
|
19 |
-
};
|
20 |
-
|
21 |
-
// Check if the current window is in an iframe
|
22 |
-
const isInIframe = () => {
|
23 |
-
try {
|
24 |
-
return window.self !== window.top;
|
25 |
-
} catch {
|
26 |
-
// If we can't access window.top due to cross-origin restrictions,
|
27 |
-
// we're likely in an iframe
|
28 |
-
return true;
|
29 |
-
}
|
30 |
-
};
|
31 |
-
|
32 |
-
// Additional check: compare window location with parent location
|
33 |
-
const isEmbedded = () => {
|
34 |
-
try {
|
35 |
-
return window.location !== window.parent.location;
|
36 |
-
} catch {
|
37 |
-
// Cross-origin iframe
|
38 |
-
return true;
|
39 |
-
}
|
40 |
-
};
|
41 |
-
|
42 |
-
// Check if we're in an iframe from a non-allowed domain
|
43 |
-
const shouldShowWarning = () => {
|
44 |
-
if (!isInIframe() && !isEmbedded()) {
|
45 |
-
return false; // Not in an iframe
|
46 |
-
}
|
47 |
-
|
48 |
-
try {
|
49 |
-
// Try to get the parent's hostname
|
50 |
-
const parentHostname = window.parent.location.hostname;
|
51 |
-
return !isAllowedDomain(parentHostname);
|
52 |
-
} catch {
|
53 |
-
// Cross-origin iframe - try to get referrer instead
|
54 |
-
try {
|
55 |
-
if (document.referrer) {
|
56 |
-
const referrerUrl = new URL(document.referrer);
|
57 |
-
return !isAllowedDomain(referrerUrl.hostname);
|
58 |
-
}
|
59 |
-
} catch {
|
60 |
-
// If we can't determine the parent domain, assume it's not allowed
|
61 |
-
}
|
62 |
-
return true;
|
63 |
-
}
|
64 |
-
};
|
65 |
-
|
66 |
-
if (shouldShowWarning()) {
|
67 |
-
// Show warning modal instead of redirecting immediately
|
68 |
-
setShowWarning(true);
|
69 |
-
}
|
70 |
-
}, []);
|
71 |
-
|
72 |
-
return (
|
73 |
-
<IframeWarningModal isOpen={showWarning} onOpenChange={setShowWarning} />
|
74 |
-
);
|
75 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/iframe-warning-modal.tsx
DELETED
@@ -1,61 +0,0 @@
|
|
1 |
-
"use client";
|
2 |
-
|
3 |
-
import {
|
4 |
-
Dialog,
|
5 |
-
DialogContent,
|
6 |
-
DialogDescription,
|
7 |
-
DialogFooter,
|
8 |
-
DialogHeader,
|
9 |
-
DialogTitle,
|
10 |
-
} from "@/components/ui/dialog";
|
11 |
-
import { Button } from "@/components/ui/button";
|
12 |
-
import { ExternalLink, AlertTriangle } from "lucide-react";
|
13 |
-
|
14 |
-
interface IframeWarningModalProps {
|
15 |
-
isOpen: boolean;
|
16 |
-
onOpenChange: (open: boolean) => void;
|
17 |
-
}
|
18 |
-
|
19 |
-
export default function IframeWarningModal({
|
20 |
-
isOpen,
|
21 |
-
}: // onOpenChange,
|
22 |
-
IframeWarningModalProps) {
|
23 |
-
const handleVisitSite = () => {
|
24 |
-
window.open("https://deepsite.hf.co", "_blank");
|
25 |
-
};
|
26 |
-
|
27 |
-
return (
|
28 |
-
<Dialog open={isOpen} onOpenChange={() => {}}>
|
29 |
-
<DialogContent className="sm:max-w-md">
|
30 |
-
<DialogHeader>
|
31 |
-
<div className="flex items-center gap-2">
|
32 |
-
<AlertTriangle className="h-5 w-5 text-red-500" />
|
33 |
-
<DialogTitle>Unauthorized Embedding</DialogTitle>
|
34 |
-
</div>
|
35 |
-
<DialogDescription className="text-left">
|
36 |
-
You're viewing DeepSite through an unauthorized iframe. For the
|
37 |
-
best experience and security, please visit the official website
|
38 |
-
directly.
|
39 |
-
</DialogDescription>
|
40 |
-
</DialogHeader>
|
41 |
-
|
42 |
-
<div className="bg-muted/50 rounded-lg p-4 space-y-2">
|
43 |
-
<p className="text-sm font-medium">Why visit the official site?</p>
|
44 |
-
<ul className="text-sm text-muted-foreground space-y-1">
|
45 |
-
<li>• Better performance and security</li>
|
46 |
-
<li>• Full functionality access</li>
|
47 |
-
<li>• Latest features and updates</li>
|
48 |
-
<li>• Proper authentication support</li>
|
49 |
-
</ul>
|
50 |
-
</div>
|
51 |
-
|
52 |
-
<DialogFooter className="flex-col sm:flex-row gap-2">
|
53 |
-
<Button onClick={handleVisitSite} className="w-full sm:w-auto">
|
54 |
-
<ExternalLink className="mr-2 h-4 w-4" />
|
55 |
-
Visit Deepsite.hf.co
|
56 |
-
</Button>
|
57 |
-
</DialogFooter>
|
58 |
-
</DialogContent>
|
59 |
-
</Dialog>
|
60 |
-
);
|
61 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/login-modal/index.tsx
CHANGED
@@ -3,26 +3,25 @@ import { Button } from "@/components/ui/button";
|
|
3 |
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
4 |
import { useUser } from "@/hooks/useUser";
|
5 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
6 |
-
import { Page } from "@/types";
|
7 |
|
8 |
export const LoginModal = ({
|
9 |
open,
|
10 |
-
|
11 |
onClose,
|
12 |
title = "Log In to use DeepSite for free",
|
13 |
description = "Log In through your Hugging Face account to continue using DeepSite and increase your monthly free limit.",
|
14 |
}: {
|
15 |
open: boolean;
|
16 |
-
|
17 |
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
18 |
title?: string;
|
19 |
description?: string;
|
20 |
}) => {
|
21 |
const { openLoginWindow } = useUser();
|
22 |
-
const [, setStorage] = useLocalStorage("
|
23 |
const handleClick = async () => {
|
24 |
-
if (
|
25 |
-
setStorage(
|
26 |
}
|
27 |
openLoginWindow();
|
28 |
onClose(false);
|
|
|
3 |
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
4 |
import { useUser } from "@/hooks/useUser";
|
5 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
|
|
6 |
|
7 |
export const LoginModal = ({
|
8 |
open,
|
9 |
+
html,
|
10 |
onClose,
|
11 |
title = "Log In to use DeepSite for free",
|
12 |
description = "Log In through your Hugging Face account to continue using DeepSite and increase your monthly free limit.",
|
13 |
}: {
|
14 |
open: boolean;
|
15 |
+
html?: string;
|
16 |
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
17 |
title?: string;
|
18 |
description?: string;
|
19 |
}) => {
|
20 |
const { openLoginWindow } = useUser();
|
21 |
+
const [, setStorage] = useLocalStorage("html_content");
|
22 |
const handleClick = async () => {
|
23 |
+
if (html && !isTheSameHtml(html)) {
|
24 |
+
setStorage(html);
|
25 |
}
|
26 |
openLoginWindow();
|
27 |
onClose(false);
|
components/pro-modal/index.tsx
CHANGED
@@ -3,21 +3,20 @@ import { Button } from "@/components/ui/button";
|
|
3 |
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
4 |
import { CheckCheck } from "lucide-react";
|
5 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
6 |
-
import { Page } from "@/types";
|
7 |
|
8 |
export const ProModal = ({
|
9 |
open,
|
10 |
-
|
11 |
onClose,
|
12 |
}: {
|
13 |
open: boolean;
|
14 |
-
|
15 |
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
16 |
}) => {
|
17 |
-
const [, setStorage] = useLocalStorage("
|
18 |
const handleProClick = () => {
|
19 |
-
if (
|
20 |
-
setStorage(
|
21 |
}
|
22 |
window.open("https://huggingface.co/subscribe/pro?from=DeepSite", "_blank");
|
23 |
onClose(false);
|
|
|
3 |
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
4 |
import { CheckCheck } from "lucide-react";
|
5 |
import { isTheSameHtml } from "@/lib/compare-html-diff";
|
|
|
6 |
|
7 |
export const ProModal = ({
|
8 |
open,
|
9 |
+
html,
|
10 |
onClose,
|
11 |
}: {
|
12 |
open: boolean;
|
13 |
+
html: string;
|
14 |
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
15 |
}) => {
|
16 |
+
const [, setStorage] = useLocalStorage("html_content");
|
17 |
const handleProClick = () => {
|
18 |
+
if (!isTheSameHtml(html)) {
|
19 |
+
setStorage(html);
|
20 |
}
|
21 |
window.open("https://huggingface.co/subscribe/pro?from=DeepSite", "_blank");
|
22 |
onClose(false);
|
components/ui/button.tsx
CHANGED
@@ -33,7 +33,6 @@ const buttonVariants = cva(
|
|
33 |
icon: "size-9",
|
34 |
iconXs: "size-7",
|
35 |
iconXss: "size-6",
|
36 |
-
iconXsss: "size-5",
|
37 |
xs: "h-6 text-xs rounded-full pl-2 pr-2 gap-1",
|
38 |
},
|
39 |
},
|
|
|
33 |
icon: "size-9",
|
34 |
iconXs: "size-7",
|
35 |
iconXss: "size-6",
|
|
|
36 |
xs: "h-6 text-xs rounded-full pl-2 pr-2 gap-1",
|
37 |
},
|
38 |
},
|
hooks/useCallAi.ts
DELETED
@@ -1,461 +0,0 @@
|
|
1 |
-
import { useState, useRef } from "react";
|
2 |
-
import { toast } from "sonner";
|
3 |
-
import { MODELS } from "@/lib/providers";
|
4 |
-
import { Page } from "@/types";
|
5 |
-
|
6 |
-
interface UseCallAiProps {
|
7 |
-
onNewPrompt: (prompt: string) => void;
|
8 |
-
onSuccess: (page: Page[], p: string, n?: number[][]) => void;
|
9 |
-
onScrollToBottom: () => void;
|
10 |
-
setPages: React.Dispatch<React.SetStateAction<Page[]>>;
|
11 |
-
setCurrentPage: React.Dispatch<React.SetStateAction<string>>;
|
12 |
-
currentPage: Page;
|
13 |
-
pages: Page[];
|
14 |
-
isAiWorking: boolean;
|
15 |
-
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
16 |
-
}
|
17 |
-
|
18 |
-
export const useCallAi = ({
|
19 |
-
onNewPrompt,
|
20 |
-
onSuccess,
|
21 |
-
onScrollToBottom,
|
22 |
-
setPages,
|
23 |
-
setCurrentPage,
|
24 |
-
pages,
|
25 |
-
isAiWorking,
|
26 |
-
setisAiWorking,
|
27 |
-
}: UseCallAiProps) => {
|
28 |
-
const audio = useRef<HTMLAudioElement | null>(null);
|
29 |
-
const [controller, setController] = useState<AbortController | null>(null);
|
30 |
-
|
31 |
-
const callAiNewProject = async (prompt: string, model: string | undefined, provider: string | undefined, redesignMarkdown?: string, handleThink?: (think: string) => void, onFinishThink?: () => void) => {
|
32 |
-
if (isAiWorking) return;
|
33 |
-
if (!redesignMarkdown && !prompt.trim()) return;
|
34 |
-
|
35 |
-
setisAiWorking(true);
|
36 |
-
|
37 |
-
const abortController = new AbortController();
|
38 |
-
setController(abortController);
|
39 |
-
|
40 |
-
try {
|
41 |
-
onNewPrompt(prompt);
|
42 |
-
|
43 |
-
const request = await fetch("/api/ask-ai", {
|
44 |
-
method: "POST",
|
45 |
-
body: JSON.stringify({
|
46 |
-
prompt,
|
47 |
-
provider,
|
48 |
-
model,
|
49 |
-
redesignMarkdown,
|
50 |
-
}),
|
51 |
-
headers: {
|
52 |
-
"Content-Type": "application/json",
|
53 |
-
"x-forwarded-for": window.location.hostname,
|
54 |
-
},
|
55 |
-
signal: abortController.signal,
|
56 |
-
});
|
57 |
-
|
58 |
-
if (request && request.body) {
|
59 |
-
const reader = request.body.getReader();
|
60 |
-
const decoder = new TextDecoder("utf-8");
|
61 |
-
const selectedModel = MODELS.find(
|
62 |
-
(m: { value: string }) => m.value === model
|
63 |
-
);
|
64 |
-
let contentResponse = "";
|
65 |
-
|
66 |
-
const read = async () => {
|
67 |
-
const { done, value } = await reader.read();
|
68 |
-
if (done) {
|
69 |
-
const isJson =
|
70 |
-
contentResponse.trim().startsWith("{") &&
|
71 |
-
contentResponse.trim().endsWith("}");
|
72 |
-
const jsonResponse = isJson ? JSON.parse(contentResponse) : null;
|
73 |
-
|
74 |
-
if (jsonResponse && !jsonResponse.ok) {
|
75 |
-
if (jsonResponse.openLogin) {
|
76 |
-
// Handle login required
|
77 |
-
return { error: "login_required" };
|
78 |
-
} else if (jsonResponse.openSelectProvider) {
|
79 |
-
// Handle provider selection required
|
80 |
-
return { error: "provider_required", message: jsonResponse.message };
|
81 |
-
} else if (jsonResponse.openProModal) {
|
82 |
-
// Handle pro modal required
|
83 |
-
return { error: "pro_required" };
|
84 |
-
} else {
|
85 |
-
toast.error(jsonResponse.message);
|
86 |
-
setisAiWorking(false);
|
87 |
-
return { error: "api_error", message: jsonResponse.message };
|
88 |
-
}
|
89 |
-
}
|
90 |
-
|
91 |
-
toast.success("AI responded successfully");
|
92 |
-
setisAiWorking(false);
|
93 |
-
|
94 |
-
if (audio.current) audio.current.play();
|
95 |
-
|
96 |
-
const newPages = formatPages(contentResponse);
|
97 |
-
onSuccess(newPages, prompt);
|
98 |
-
|
99 |
-
return { success: true, pages: newPages };
|
100 |
-
}
|
101 |
-
|
102 |
-
const chunk = decoder.decode(value, { stream: true });
|
103 |
-
contentResponse += chunk;
|
104 |
-
|
105 |
-
if (selectedModel?.isThinker) {
|
106 |
-
const thinkMatch = contentResponse.match(/<think>[\s\S]*/)?.[0];
|
107 |
-
if (thinkMatch && !contentResponse?.includes("</think>")) {
|
108 |
-
handleThink?.(thinkMatch.replace("<think>", "").trim());
|
109 |
-
return read();
|
110 |
-
}
|
111 |
-
}
|
112 |
-
|
113 |
-
if (contentResponse.includes("</think>")) {
|
114 |
-
onFinishThink?.();
|
115 |
-
}
|
116 |
-
|
117 |
-
formatPages(contentResponse);
|
118 |
-
return read();
|
119 |
-
};
|
120 |
-
|
121 |
-
return await read();
|
122 |
-
}
|
123 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
124 |
-
} catch (error: any) {
|
125 |
-
setisAiWorking(false);
|
126 |
-
toast.error(error.message);
|
127 |
-
if (error.openLogin) {
|
128 |
-
return { error: "login_required" };
|
129 |
-
}
|
130 |
-
return { error: "network_error", message: error.message };
|
131 |
-
}
|
132 |
-
};
|
133 |
-
|
134 |
-
const callAiNewPage = async (prompt: string, model: string | undefined, provider: string | undefined, currentPagePath: string, previousPrompts?: string[]) => {
|
135 |
-
if (isAiWorking) return;
|
136 |
-
if (!prompt.trim()) return;
|
137 |
-
|
138 |
-
setisAiWorking(true);
|
139 |
-
|
140 |
-
const abortController = new AbortController();
|
141 |
-
setController(abortController);
|
142 |
-
|
143 |
-
try {
|
144 |
-
onNewPrompt(prompt);
|
145 |
-
|
146 |
-
const request = await fetch("/api/ask-ai", {
|
147 |
-
method: "POST",
|
148 |
-
body: JSON.stringify({
|
149 |
-
prompt,
|
150 |
-
provider,
|
151 |
-
model,
|
152 |
-
pages,
|
153 |
-
previousPrompts,
|
154 |
-
}),
|
155 |
-
headers: {
|
156 |
-
"Content-Type": "application/json",
|
157 |
-
"x-forwarded-for": window.location.hostname,
|
158 |
-
},
|
159 |
-
signal: abortController.signal,
|
160 |
-
});
|
161 |
-
|
162 |
-
if (request && request.body) {
|
163 |
-
const reader = request.body.getReader();
|
164 |
-
const decoder = new TextDecoder("utf-8");
|
165 |
-
const selectedModel = MODELS.find(
|
166 |
-
(m: { value: string }) => m.value === model
|
167 |
-
);
|
168 |
-
let contentResponse = "";
|
169 |
-
|
170 |
-
const read = async () => {
|
171 |
-
const { done, value } = await reader.read();
|
172 |
-
if (done) {
|
173 |
-
const isJson =
|
174 |
-
contentResponse.trim().startsWith("{") &&
|
175 |
-
contentResponse.trim().endsWith("}");
|
176 |
-
const jsonResponse = isJson ? JSON.parse(contentResponse) : null;
|
177 |
-
|
178 |
-
if (jsonResponse && !jsonResponse.ok) {
|
179 |
-
if (jsonResponse.openLogin) {
|
180 |
-
// Handle login required
|
181 |
-
return { error: "login_required" };
|
182 |
-
} else if (jsonResponse.openSelectProvider) {
|
183 |
-
// Handle provider selection required
|
184 |
-
return { error: "provider_required", message: jsonResponse.message };
|
185 |
-
} else if (jsonResponse.openProModal) {
|
186 |
-
// Handle pro modal required
|
187 |
-
return { error: "pro_required" };
|
188 |
-
} else {
|
189 |
-
toast.error(jsonResponse.message);
|
190 |
-
setisAiWorking(false);
|
191 |
-
return { error: "api_error", message: jsonResponse.message };
|
192 |
-
}
|
193 |
-
}
|
194 |
-
|
195 |
-
toast.success("AI responded successfully");
|
196 |
-
setisAiWorking(false);
|
197 |
-
|
198 |
-
if (selectedModel?.isThinker) {
|
199 |
-
// Reset to default model if using thinker model
|
200 |
-
// Note: You might want to add a callback for this
|
201 |
-
}
|
202 |
-
|
203 |
-
if (audio.current) audio.current.play();
|
204 |
-
|
205 |
-
const newPage = formatPage(contentResponse, currentPagePath);
|
206 |
-
if (!newPage) { return { error: "api_error", message: "Failed to format page" } }
|
207 |
-
onSuccess([...pages, newPage], prompt);
|
208 |
-
|
209 |
-
return { success: true, pages: [...pages, newPage] };
|
210 |
-
}
|
211 |
-
|
212 |
-
const chunk = decoder.decode(value, { stream: true });
|
213 |
-
contentResponse += chunk;
|
214 |
-
|
215 |
-
if (selectedModel?.isThinker) {
|
216 |
-
const thinkMatch = contentResponse.match(/<think>[\s\S]*/)?.[0];
|
217 |
-
if (thinkMatch && !contentResponse?.includes("</think>")) {
|
218 |
-
// contentThink += chunk;
|
219 |
-
return read();
|
220 |
-
}
|
221 |
-
}
|
222 |
-
|
223 |
-
formatPage(contentResponse, currentPagePath);
|
224 |
-
return read();
|
225 |
-
};
|
226 |
-
|
227 |
-
return await read();
|
228 |
-
}
|
229 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
230 |
-
} catch (error: any) {
|
231 |
-
setisAiWorking(false);
|
232 |
-
toast.error(error.message);
|
233 |
-
if (error.openLogin) {
|
234 |
-
return { error: "login_required" };
|
235 |
-
}
|
236 |
-
return { error: "network_error", message: error.message };
|
237 |
-
}
|
238 |
-
};
|
239 |
-
|
240 |
-
const callAiFollowUp = async (prompt: string, model: string | undefined, provider: string | undefined, previousPrompts: string[], selectedElementHtml?: string, files?: string[]) => {
|
241 |
-
if (isAiWorking) return;
|
242 |
-
if (!prompt.trim()) return;
|
243 |
-
|
244 |
-
setisAiWorking(true);
|
245 |
-
|
246 |
-
const abortController = new AbortController();
|
247 |
-
setController(abortController);
|
248 |
-
|
249 |
-
try {
|
250 |
-
onNewPrompt(prompt);
|
251 |
-
|
252 |
-
const request = await fetch("/api/ask-ai", {
|
253 |
-
method: "PUT",
|
254 |
-
body: JSON.stringify({
|
255 |
-
prompt,
|
256 |
-
provider,
|
257 |
-
previousPrompts,
|
258 |
-
model,
|
259 |
-
pages,
|
260 |
-
selectedElementHtml,
|
261 |
-
files,
|
262 |
-
}),
|
263 |
-
headers: {
|
264 |
-
"Content-Type": "application/json",
|
265 |
-
"x-forwarded-for": window.location.hostname,
|
266 |
-
},
|
267 |
-
signal: abortController.signal,
|
268 |
-
});
|
269 |
-
|
270 |
-
if (request && request.body) {
|
271 |
-
const res = await request.json();
|
272 |
-
|
273 |
-
if (!request.ok) {
|
274 |
-
if (res.openLogin) {
|
275 |
-
setisAiWorking(false);
|
276 |
-
return { error: "login_required" };
|
277 |
-
} else if (res.openSelectProvider) {
|
278 |
-
setisAiWorking(false);
|
279 |
-
return { error: "provider_required", message: res.message };
|
280 |
-
} else if (res.openProModal) {
|
281 |
-
setisAiWorking(false);
|
282 |
-
return { error: "pro_required" };
|
283 |
-
} else {
|
284 |
-
toast.error(res.message);
|
285 |
-
setisAiWorking(false);
|
286 |
-
return { error: "api_error", message: res.message };
|
287 |
-
}
|
288 |
-
}
|
289 |
-
|
290 |
-
toast.success("AI responded successfully");
|
291 |
-
setisAiWorking(false);
|
292 |
-
|
293 |
-
setPages(res.pages);
|
294 |
-
onSuccess(res.pages, prompt, res.updatedLines);
|
295 |
-
|
296 |
-
if (audio.current) audio.current.play();
|
297 |
-
|
298 |
-
return { success: true, html: res.html, updatedLines: res.updatedLines };
|
299 |
-
}
|
300 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
301 |
-
} catch (error: any) {
|
302 |
-
setisAiWorking(false);
|
303 |
-
toast.error(error.message);
|
304 |
-
if (error.openLogin) {
|
305 |
-
return { error: "login_required" };
|
306 |
-
}
|
307 |
-
return { error: "network_error", message: error.message };
|
308 |
-
}
|
309 |
-
};
|
310 |
-
|
311 |
-
// Stop the current AI generation
|
312 |
-
const stopController = () => {
|
313 |
-
if (controller) {
|
314 |
-
controller.abort();
|
315 |
-
setController(null);
|
316 |
-
setisAiWorking(false);
|
317 |
-
}
|
318 |
-
};
|
319 |
-
|
320 |
-
const formatPages = (content: string) => {
|
321 |
-
const pages: Page[] = [];
|
322 |
-
if (!content.match(/<<<<<<< START_TITLE (.*?) >>>>>>> END_TITLE/)) {
|
323 |
-
return pages;
|
324 |
-
}
|
325 |
-
|
326 |
-
const cleanedContent = content.replace(
|
327 |
-
/[\s\S]*?<<<<<<< START_TITLE (.*?) >>>>>>> END_TITLE/,
|
328 |
-
"<<<<<<< START_TITLE $1 >>>>>>> END_TITLE"
|
329 |
-
);
|
330 |
-
const htmlChunks = cleanedContent.split(
|
331 |
-
/<<<<<<< START_TITLE (.*?) >>>>>>> END_TITLE/
|
332 |
-
);
|
333 |
-
const processedChunks = new Set<number>();
|
334 |
-
|
335 |
-
htmlChunks.forEach((chunk, index) => {
|
336 |
-
if (processedChunks.has(index) || !chunk?.trim()) {
|
337 |
-
return;
|
338 |
-
}
|
339 |
-
const htmlContent = extractHtmlContent(htmlChunks[index + 1]);
|
340 |
-
|
341 |
-
if (htmlContent) {
|
342 |
-
const page: Page = {
|
343 |
-
path: chunk.trim(),
|
344 |
-
html: htmlContent,
|
345 |
-
};
|
346 |
-
pages.push(page);
|
347 |
-
|
348 |
-
if (htmlContent.length > 200) {
|
349 |
-
onScrollToBottom();
|
350 |
-
}
|
351 |
-
|
352 |
-
processedChunks.add(index);
|
353 |
-
processedChunks.add(index + 1);
|
354 |
-
}
|
355 |
-
});
|
356 |
-
if (pages.length > 0) {
|
357 |
-
setPages(pages);
|
358 |
-
const lastPagePath = pages[pages.length - 1]?.path;
|
359 |
-
setCurrentPage(lastPagePath || "index.html");
|
360 |
-
}
|
361 |
-
|
362 |
-
return pages;
|
363 |
-
};
|
364 |
-
|
365 |
-
const formatPage = (content: string, currentPagePath: string) => {
|
366 |
-
if (!content.match(/<<<<<<< START_TITLE (.*?) >>>>>>> END_TITLE/)) {
|
367 |
-
return null;
|
368 |
-
}
|
369 |
-
|
370 |
-
const cleanedContent = content.replace(
|
371 |
-
/[\s\S]*?<<<<<<< START_TITLE (.*?) >>>>>>> END_TITLE/,
|
372 |
-
"<<<<<<< START_TITLE $1 >>>>>>> END_TITLE"
|
373 |
-
);
|
374 |
-
|
375 |
-
const htmlChunks = cleanedContent.split(
|
376 |
-
/<<<<<<< START_TITLE (.*?) >>>>>>> END_TITLE/
|
377 |
-
)?.filter(Boolean);
|
378 |
-
|
379 |
-
const pagePath = htmlChunks[0]?.trim() || "";
|
380 |
-
const htmlContent = extractHtmlContent(htmlChunks[1]);
|
381 |
-
|
382 |
-
if (!pagePath || !htmlContent) {
|
383 |
-
return null;
|
384 |
-
}
|
385 |
-
|
386 |
-
const page: Page = {
|
387 |
-
path: pagePath,
|
388 |
-
html: htmlContent,
|
389 |
-
};
|
390 |
-
|
391 |
-
setPages(prevPages => {
|
392 |
-
const existingPageIndex = prevPages.findIndex(p => p.path === currentPagePath || p.path === pagePath);
|
393 |
-
|
394 |
-
if (existingPageIndex !== -1) {
|
395 |
-
const updatedPages = [...prevPages];
|
396 |
-
updatedPages[existingPageIndex] = page;
|
397 |
-
return updatedPages;
|
398 |
-
} else {
|
399 |
-
return [...prevPages, page];
|
400 |
-
}
|
401 |
-
});
|
402 |
-
|
403 |
-
setCurrentPage(pagePath);
|
404 |
-
|
405 |
-
if (htmlContent.length > 200) {
|
406 |
-
onScrollToBottom();
|
407 |
-
}
|
408 |
-
|
409 |
-
return page;
|
410 |
-
};
|
411 |
-
|
412 |
-
// Helper function to extract and clean HTML content
|
413 |
-
const extractHtmlContent = (chunk: string): string => {
|
414 |
-
if (!chunk) return "";
|
415 |
-
|
416 |
-
// Extract HTML content
|
417 |
-
const htmlMatch = chunk.trim().match(/<!DOCTYPE html>[\s\S]*/);
|
418 |
-
if (!htmlMatch) return "";
|
419 |
-
|
420 |
-
let htmlContent = htmlMatch[0];
|
421 |
-
|
422 |
-
// Ensure proper HTML structure
|
423 |
-
htmlContent = ensureCompleteHtml(htmlContent);
|
424 |
-
|
425 |
-
// Remove markdown code blocks if present
|
426 |
-
htmlContent = htmlContent.replace(/```/g, "");
|
427 |
-
|
428 |
-
return htmlContent;
|
429 |
-
};
|
430 |
-
|
431 |
-
// Helper function to ensure HTML has complete structure
|
432 |
-
const ensureCompleteHtml = (html: string): string => {
|
433 |
-
let completeHtml = html;
|
434 |
-
|
435 |
-
// Add missing head closing tag
|
436 |
-
if (completeHtml.includes("<head>") && !completeHtml.includes("</head>")) {
|
437 |
-
completeHtml += "\n</head>";
|
438 |
-
}
|
439 |
-
|
440 |
-
// Add missing body closing tag
|
441 |
-
if (completeHtml.includes("<body") && !completeHtml.includes("</body>")) {
|
442 |
-
completeHtml += "\n</body>";
|
443 |
-
}
|
444 |
-
|
445 |
-
// Add missing html closing tag
|
446 |
-
if (!completeHtml.includes("</html>")) {
|
447 |
-
completeHtml += "\n</html>";
|
448 |
-
}
|
449 |
-
|
450 |
-
return completeHtml;
|
451 |
-
};
|
452 |
-
|
453 |
-
return {
|
454 |
-
callAiNewProject,
|
455 |
-
callAiFollowUp,
|
456 |
-
callAiNewPage,
|
457 |
-
stopController,
|
458 |
-
controller,
|
459 |
-
audio,
|
460 |
-
};
|
461 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hooks/useEditor.ts
CHANGED
@@ -1,18 +1,12 @@
|
|
1 |
-
import {
|
2 |
-
import { HtmlHistory, Page } from "@/types";
|
3 |
import { useState } from "react";
|
4 |
|
5 |
-
export const useEditor = (
|
6 |
/**
|
7 |
* State to manage the HTML content of the editor.
|
8 |
* This will be the main content that users edit.
|
9 |
*/
|
10 |
-
const [
|
11 |
-
{
|
12 |
-
path: "index.html",
|
13 |
-
html: initialHtmlStorage ?? defaultHTML,
|
14 |
-
},
|
15 |
-
]);
|
16 |
/**
|
17 |
* State to manage the history of HTML edits.
|
18 |
* This will store previous versions of the HTML content along with metadata. (not saved to DB)
|
@@ -23,17 +17,14 @@ export const useEditor = (initialPages?: Page[], initialPrompts?: string[], init
|
|
23 |
* State to manage the prompts used for generating HTML content.
|
24 |
* This can be used to track what prompts were used in the editor.
|
25 |
*/
|
26 |
-
const [prompts, setPrompts] = useState<string[]>(
|
27 |
-
initialPrompts ?? []
|
28 |
-
);
|
29 |
-
|
30 |
|
31 |
return {
|
|
|
|
|
32 |
htmlHistory,
|
33 |
setHtmlHistory,
|
34 |
prompts,
|
35 |
-
pages,
|
36 |
-
setPages,
|
37 |
setPrompts,
|
38 |
};
|
39 |
};
|
|
|
1 |
+
import { HtmlHistory } from "@/types";
|
|
|
2 |
import { useState } from "react";
|
3 |
|
4 |
+
export const useEditor = (defaultHtml: string) => {
|
5 |
/**
|
6 |
* State to manage the HTML content of the editor.
|
7 |
* This will be the main content that users edit.
|
8 |
*/
|
9 |
+
const [html, setHtml] = useState(defaultHtml);
|
|
|
|
|
|
|
|
|
|
|
10 |
/**
|
11 |
* State to manage the history of HTML edits.
|
12 |
* This will store previous versions of the HTML content along with metadata. (not saved to DB)
|
|
|
17 |
* State to manage the prompts used for generating HTML content.
|
18 |
* This can be used to track what prompts were used in the editor.
|
19 |
*/
|
20 |
+
const [prompts, setPrompts] = useState<string[]>([]);
|
|
|
|
|
|
|
21 |
|
22 |
return {
|
23 |
+
html,
|
24 |
+
setHtml,
|
25 |
htmlHistory,
|
26 |
setHtmlHistory,
|
27 |
prompts,
|
|
|
|
|
28 |
setPrompts,
|
29 |
};
|
30 |
};
|
lib/prompts.ts
CHANGED
@@ -2,81 +2,24 @@ export const SEARCH_START = "<<<<<<< SEARCH";
|
|
2 |
export const DIVIDER = "=======";
|
3 |
export const REPLACE_END = ">>>>>>> REPLACE";
|
4 |
export const MAX_REQUESTS_PER_IP = 2;
|
5 |
-
export const
|
6 |
-
export const
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
You
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
If you want to use animations you can use: Animejs.com (Make sure to add <script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script> and <script>const { animate } = anime;</script> in the head.), AOS.com (Make sure to add <link href="https://unpkg.com/[email protected]/dist/aos.css" rel="stylesheet"> and <script src="https://unpkg.com/[email protected]/dist/aos.js"></script> and <script>AOS.init();</script>).
|
21 |
-
You can create multiple pages website at once (following the format rules below) or a Single Page Application. If the user doesn't ask for a specific version, you have to determine the best version for the user, depending on the request. (Try to avoid the Single Page Application if the user asks for multiple pages.)
|
22 |
-
No need to explain what you did. Just return the expected result. AVOID Chinese characters in the code if not asked by the user.
|
23 |
-
Return the results in a \`\`\`html\`\`\` markdown. Format the results like:
|
24 |
-
1. Start with ${TITLE_PAGE_START}.
|
25 |
-
2. Add the name of the page without special character, such as spaces or punctuation, using the .html format only, right after the start tag.
|
26 |
-
3. Close the start tag with the ${TITLE_PAGE_END}.
|
27 |
-
4. Start the HTML response with the triple backticks, like \`\`\`html.
|
28 |
-
5. Insert the following html there.
|
29 |
-
6. Close with the triple backticks, like \`\`\`.
|
30 |
-
7. Retry if another pages.
|
31 |
-
Example Code:
|
32 |
-
${TITLE_PAGE_START}index.html${TITLE_PAGE_END}
|
33 |
-
\`\`\`html
|
34 |
-
<!DOCTYPE html>
|
35 |
-
<html lang="en">
|
36 |
-
<head>
|
37 |
-
<meta charset="UTF-8">
|
38 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
39 |
-
<title>Index</title>
|
40 |
-
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
41 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
42 |
-
<link href="https://unpkg.com/[email protected]/dist/aos.css" rel="stylesheet">
|
43 |
-
<script src="https://unpkg.com/[email protected]/dist/aos.js"></script>
|
44 |
-
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
45 |
-
<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>
|
46 |
-
<script src="https://unpkg.com/feather-icons"></script>
|
47 |
-
</head>
|
48 |
-
<body>
|
49 |
-
<h1>Hello World</h1>
|
50 |
-
<script>AOS.init();</script>
|
51 |
-
<script>const { animate } = anime;</script>
|
52 |
-
<script>feather.replace();</script>
|
53 |
-
</body>
|
54 |
-
</html>
|
55 |
-
\`\`\`
|
56 |
-
IMPORTANT: The first file should be always named index.html.`
|
57 |
-
|
58 |
-
export const FOLLOW_UP_SYSTEM_PROMPT = `You are an expert UI/UX and Front-End Developer modifying an existing HTML files.
|
59 |
-
The user wants to apply changes and probably add new features/pages to the website, based on their request.
|
60 |
-
You MUST output ONLY the changes required using the following UPDATE_PAGE_START and SEARCH/REPLACE format. Do NOT output the entire file.
|
61 |
-
If it's a new page, you MUST applied the following NEW_PAGE_START and UPDATE_PAGE_END format.
|
62 |
-
Do NOT explain the changes or what you did, just return the expected results.
|
63 |
-
Update Format Rules:
|
64 |
-
1. Start with ${UPDATE_PAGE_START}
|
65 |
-
2. Provide the name of the page you are modifying.
|
66 |
-
3. Close the start tag with the ${UPDATE_PAGE_END}.
|
67 |
-
4. Start with ${SEARCH_START}
|
68 |
-
5. Provide the exact lines from the current code that need to be replaced.
|
69 |
-
6. Use ${DIVIDER} to separate the search block from the replacement.
|
70 |
-
7. Provide the new lines that should replace the original lines.
|
71 |
-
8. End with ${REPLACE_END}
|
72 |
-
9. You can use multiple SEARCH/REPLACE blocks if changes are needed in different parts of the file.
|
73 |
-
10. To insert code, use an empty SEARCH block (only ${SEARCH_START} and ${DIVIDER} on their lines) if inserting at the very beginning, otherwise provide the line *before* the insertion point in the SEARCH block and include that line plus the new lines in the REPLACE block.
|
74 |
-
11. To delete code, provide the lines to delete in the SEARCH block and leave the REPLACE block empty (only ${DIVIDER} and ${REPLACE_END} on their lines).
|
75 |
-
12. IMPORTANT: The SEARCH block must *exactly* match the current code, including indentation and whitespace.
|
76 |
Example Modifying Code:
|
77 |
\`\`\`
|
78 |
Some explanation...
|
79 |
-
${UPDATE_PAGE_START}index.html${UPDATE_PAGE_END}
|
80 |
${SEARCH_START}
|
81 |
<h1>Old Title</h1>
|
82 |
${DIVIDER}
|
@@ -92,44 +35,8 @@ ${REPLACE_END}
|
|
92 |
Example Deleting Code:
|
93 |
\`\`\`
|
94 |
Removing the paragraph...
|
95 |
-
${TITLE_PAGE_START}index.html${TITLE_PAGE_END}
|
96 |
${SEARCH_START}
|
97 |
<p>This paragraph will be deleted.</p>
|
98 |
${DIVIDER}
|
99 |
${REPLACE_END}
|
100 |
-
|
101 |
-
The user can also ask to add a new page, in this case you should return the new page in the following format:
|
102 |
-
1. Start with ${NEW_PAGE_START}.
|
103 |
-
2. Add the name of the page without special character, such as spaces or punctuation, using the .html format only, right after the start tag.
|
104 |
-
3. Close the start tag with the ${NEW_PAGE_END}.
|
105 |
-
4. Start the HTML response with the triple backticks, like \`\`\`html.
|
106 |
-
5. Insert the following html there.
|
107 |
-
6. Close with the triple backticks, like \`\`\`.
|
108 |
-
7. Retry if another pages.
|
109 |
-
Example Code:
|
110 |
-
${NEW_PAGE_START}index.html${NEW_PAGE_END}
|
111 |
-
\`\`\`html
|
112 |
-
<!DOCTYPE html>
|
113 |
-
<html lang="en">
|
114 |
-
<head>
|
115 |
-
<meta charset="UTF-8">
|
116 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
117 |
-
<title>Index</title>
|
118 |
-
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
119 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
120 |
-
<link href="https://unpkg.com/[email protected]/dist/aos.css" rel="stylesheet">
|
121 |
-
<script src="https://unpkg.com/[email protected]/dist/aos.js"></script>
|
122 |
-
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
123 |
-
<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>
|
124 |
-
<script src="https://unpkg.com/feather-icons"></script>
|
125 |
-
</head>
|
126 |
-
<body>
|
127 |
-
<h1>Hello World</h1>
|
128 |
-
<script>AOS.init();</script>
|
129 |
-
<script>const { animate } = anime;</script>
|
130 |
-
<script>feather.replace();</script>
|
131 |
-
</body>
|
132 |
-
</html>
|
133 |
-
\`\`\`
|
134 |
-
IMPORTANT: While creating a new page, UPDATE ALL THE OTHERS (using the UPDATE_PAGE_START and SEARCH/REPLACE format) pages to add or replace the link to the new page, otherwise the user will not be able to navigate to the new page. (Dont use onclick to navigate, only href)
|
135 |
-
No need to explain what you did. Just return the expected result.`
|
|
|
2 |
export const DIVIDER = "=======";
|
3 |
export const REPLACE_END = ">>>>>>> REPLACE";
|
4 |
export const MAX_REQUESTS_PER_IP = 2;
|
5 |
+
export const INITIAL_SYSTEM_PROMPT = `ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. MAKE IT RESPONSIVE USING TAILWINDCSS. Use as much as you can TailwindCSS for the CSS, if you can't do something with TailwindCSS, then use custom CSS (make sure to import <script src="https://cdn.tailwindcss.com"></script> in the head). Also, try to ellaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE. AVOID CHINESE CHARACTERS IN THE CODE IF NOT ASKED BY THE USER.`;
|
6 |
+
export const FOLLOW_UP_SYSTEM_PROMPT = `You are an expert web developer modifying an existing HTML file.
|
7 |
+
The user wants to apply changes based on their request.
|
8 |
+
You MUST output ONLY the changes required using the following SEARCH/REPLACE block format. Do NOT output the entire file.
|
9 |
+
Explain the changes briefly *before* the blocks if necessary, but the code changes THEMSELVES MUST be within the blocks.
|
10 |
+
Format Rules:
|
11 |
+
1. Start with ${SEARCH_START}
|
12 |
+
2. Provide the exact lines from the current code that need to be replaced.
|
13 |
+
3. Use ${DIVIDER} to separate the search block from the replacement.
|
14 |
+
4. Provide the new lines that should replace the original lines.
|
15 |
+
5. End with ${REPLACE_END}
|
16 |
+
6. You can use multiple SEARCH/REPLACE blocks if changes are needed in different parts of the file.
|
17 |
+
7. To insert code, use an empty SEARCH block (only ${SEARCH_START} and ${DIVIDER} on their lines) if inserting at the very beginning, otherwise provide the line *before* the insertion point in the SEARCH block and include that line plus the new lines in the REPLACE block.
|
18 |
+
8. To delete code, provide the lines to delete in the SEARCH block and leave the REPLACE block empty (only ${DIVIDER} and ${REPLACE_END} on their lines).
|
19 |
+
9. IMPORTANT: The SEARCH block must *exactly* match the current code, including indentation and whitespace.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
Example Modifying Code:
|
21 |
\`\`\`
|
22 |
Some explanation...
|
|
|
23 |
${SEARCH_START}
|
24 |
<h1>Old Title</h1>
|
25 |
${DIVIDER}
|
|
|
35 |
Example Deleting Code:
|
36 |
\`\`\`
|
37 |
Removing the paragraph...
|
|
|
38 |
${SEARCH_START}
|
39 |
<p>This paragraph will be deleted.</p>
|
40 |
${DIVIDER}
|
41 |
${REPLACE_END}
|
42 |
+
\`\`\``;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lib/providers.ts
CHANGED
@@ -70,11 +70,4 @@ export const MODELS = [
|
|
70 |
providers: ["together", "novita", "groq"],
|
71 |
autoProvider: "groq",
|
72 |
},
|
73 |
-
{
|
74 |
-
value: "deepseek-ai/DeepSeek-V3.1",
|
75 |
-
label: "DeepSeek V3.1",
|
76 |
-
providers: ["fireworks-ai", "novita"],
|
77 |
-
isNew: true,
|
78 |
-
autoProvider: "fireworks-ai",
|
79 |
-
},
|
80 |
];
|
|
|
70 |
providers: ["together", "novita", "groq"],
|
71 |
autoProvider: "groq",
|
72 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
];
|
next.config.ts
CHANGED
@@ -24,9 +24,6 @@ const nextConfig: NextConfig = {
|
|
24 |
|
25 |
return config;
|
26 |
},
|
27 |
-
images: {
|
28 |
-
remotePatterns: [new URL('https://huggingface.co/**')],
|
29 |
-
},
|
30 |
};
|
31 |
|
32 |
export default nextConfig;
|
|
|
24 |
|
25 |
return config;
|
26 |
},
|
|
|
|
|
|
|
27 |
};
|
28 |
|
29 |
export default nextConfig;
|
package-lock.json
CHANGED
@@ -8,7 +8,6 @@
|
|
8 |
"name": "deepsite-v2",
|
9 |
"version": "0.1.0",
|
10 |
"dependencies": {
|
11 |
-
"@codesandbox/sandpack-react": "^2.20.0",
|
12 |
"@huggingface/hub": "^2.2.0",
|
13 |
"@huggingface/inference": "^4.0.3",
|
14 |
"@monaco-editor/react": "^4.7.0-rc.0",
|
@@ -98,182 +97,6 @@
|
|
98 |
"node": ">=6.9.0"
|
99 |
}
|
100 |
},
|
101 |
-
"node_modules/@codemirror/autocomplete": {
|
102 |
-
"version": "6.18.6",
|
103 |
-
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
104 |
-
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
105 |
-
"license": "MIT",
|
106 |
-
"dependencies": {
|
107 |
-
"@codemirror/language": "^6.0.0",
|
108 |
-
"@codemirror/state": "^6.0.0",
|
109 |
-
"@codemirror/view": "^6.17.0",
|
110 |
-
"@lezer/common": "^1.0.0"
|
111 |
-
}
|
112 |
-
},
|
113 |
-
"node_modules/@codemirror/commands": {
|
114 |
-
"version": "6.8.1",
|
115 |
-
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
|
116 |
-
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
117 |
-
"license": "MIT",
|
118 |
-
"dependencies": {
|
119 |
-
"@codemirror/language": "^6.0.0",
|
120 |
-
"@codemirror/state": "^6.4.0",
|
121 |
-
"@codemirror/view": "^6.27.0",
|
122 |
-
"@lezer/common": "^1.1.0"
|
123 |
-
}
|
124 |
-
},
|
125 |
-
"node_modules/@codemirror/lang-css": {
|
126 |
-
"version": "6.3.1",
|
127 |
-
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
128 |
-
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
129 |
-
"license": "MIT",
|
130 |
-
"dependencies": {
|
131 |
-
"@codemirror/autocomplete": "^6.0.0",
|
132 |
-
"@codemirror/language": "^6.0.0",
|
133 |
-
"@codemirror/state": "^6.0.0",
|
134 |
-
"@lezer/common": "^1.0.2",
|
135 |
-
"@lezer/css": "^1.1.7"
|
136 |
-
}
|
137 |
-
},
|
138 |
-
"node_modules/@codemirror/lang-html": {
|
139 |
-
"version": "6.4.9",
|
140 |
-
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
141 |
-
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
142 |
-
"license": "MIT",
|
143 |
-
"dependencies": {
|
144 |
-
"@codemirror/autocomplete": "^6.0.0",
|
145 |
-
"@codemirror/lang-css": "^6.0.0",
|
146 |
-
"@codemirror/lang-javascript": "^6.0.0",
|
147 |
-
"@codemirror/language": "^6.4.0",
|
148 |
-
"@codemirror/state": "^6.0.0",
|
149 |
-
"@codemirror/view": "^6.17.0",
|
150 |
-
"@lezer/common": "^1.0.0",
|
151 |
-
"@lezer/css": "^1.1.0",
|
152 |
-
"@lezer/html": "^1.3.0"
|
153 |
-
}
|
154 |
-
},
|
155 |
-
"node_modules/@codemirror/lang-javascript": {
|
156 |
-
"version": "6.2.4",
|
157 |
-
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
158 |
-
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
159 |
-
"license": "MIT",
|
160 |
-
"dependencies": {
|
161 |
-
"@codemirror/autocomplete": "^6.0.0",
|
162 |
-
"@codemirror/language": "^6.6.0",
|
163 |
-
"@codemirror/lint": "^6.0.0",
|
164 |
-
"@codemirror/state": "^6.0.0",
|
165 |
-
"@codemirror/view": "^6.17.0",
|
166 |
-
"@lezer/common": "^1.0.0",
|
167 |
-
"@lezer/javascript": "^1.0.0"
|
168 |
-
}
|
169 |
-
},
|
170 |
-
"node_modules/@codemirror/language": {
|
171 |
-
"version": "6.11.3",
|
172 |
-
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
173 |
-
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
174 |
-
"license": "MIT",
|
175 |
-
"dependencies": {
|
176 |
-
"@codemirror/state": "^6.0.0",
|
177 |
-
"@codemirror/view": "^6.23.0",
|
178 |
-
"@lezer/common": "^1.1.0",
|
179 |
-
"@lezer/highlight": "^1.0.0",
|
180 |
-
"@lezer/lr": "^1.0.0",
|
181 |
-
"style-mod": "^4.0.0"
|
182 |
-
}
|
183 |
-
},
|
184 |
-
"node_modules/@codemirror/lint": {
|
185 |
-
"version": "6.8.5",
|
186 |
-
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
187 |
-
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
188 |
-
"license": "MIT",
|
189 |
-
"dependencies": {
|
190 |
-
"@codemirror/state": "^6.0.0",
|
191 |
-
"@codemirror/view": "^6.35.0",
|
192 |
-
"crelt": "^1.0.5"
|
193 |
-
}
|
194 |
-
},
|
195 |
-
"node_modules/@codemirror/state": {
|
196 |
-
"version": "6.5.2",
|
197 |
-
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
198 |
-
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
199 |
-
"license": "MIT",
|
200 |
-
"dependencies": {
|
201 |
-
"@marijn/find-cluster-break": "^1.0.0"
|
202 |
-
}
|
203 |
-
},
|
204 |
-
"node_modules/@codemirror/view": {
|
205 |
-
"version": "6.38.1",
|
206 |
-
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz",
|
207 |
-
"integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==",
|
208 |
-
"license": "MIT",
|
209 |
-
"dependencies": {
|
210 |
-
"@codemirror/state": "^6.5.0",
|
211 |
-
"crelt": "^1.0.6",
|
212 |
-
"style-mod": "^4.1.0",
|
213 |
-
"w3c-keyname": "^2.2.4"
|
214 |
-
}
|
215 |
-
},
|
216 |
-
"node_modules/@codesandbox/nodebox": {
|
217 |
-
"version": "0.1.8",
|
218 |
-
"resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz",
|
219 |
-
"integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==",
|
220 |
-
"license": "SEE LICENSE IN ./LICENSE",
|
221 |
-
"dependencies": {
|
222 |
-
"outvariant": "^1.4.0",
|
223 |
-
"strict-event-emitter": "^0.4.3"
|
224 |
-
}
|
225 |
-
},
|
226 |
-
"node_modules/@codesandbox/sandpack-client": {
|
227 |
-
"version": "2.19.8",
|
228 |
-
"resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz",
|
229 |
-
"integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==",
|
230 |
-
"license": "Apache-2.0",
|
231 |
-
"dependencies": {
|
232 |
-
"@codesandbox/nodebox": "0.1.8",
|
233 |
-
"buffer": "^6.0.3",
|
234 |
-
"dequal": "^2.0.2",
|
235 |
-
"mime-db": "^1.52.0",
|
236 |
-
"outvariant": "1.4.0",
|
237 |
-
"static-browser-server": "1.0.3"
|
238 |
-
}
|
239 |
-
},
|
240 |
-
"node_modules/@codesandbox/sandpack-react": {
|
241 |
-
"version": "2.20.0",
|
242 |
-
"resolved": "https://registry.npmjs.org/@codesandbox/sandpack-react/-/sandpack-react-2.20.0.tgz",
|
243 |
-
"integrity": "sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==",
|
244 |
-
"license": "Apache-2.0",
|
245 |
-
"dependencies": {
|
246 |
-
"@codemirror/autocomplete": "^6.4.0",
|
247 |
-
"@codemirror/commands": "^6.1.3",
|
248 |
-
"@codemirror/lang-css": "^6.0.1",
|
249 |
-
"@codemirror/lang-html": "^6.4.0",
|
250 |
-
"@codemirror/lang-javascript": "^6.1.2",
|
251 |
-
"@codemirror/language": "^6.3.2",
|
252 |
-
"@codemirror/state": "^6.2.0",
|
253 |
-
"@codemirror/view": "^6.7.1",
|
254 |
-
"@codesandbox/sandpack-client": "^2.19.8",
|
255 |
-
"@lezer/highlight": "^1.1.3",
|
256 |
-
"@react-hook/intersection-observer": "^3.1.1",
|
257 |
-
"@stitches/core": "^1.2.6",
|
258 |
-
"anser": "^2.1.1",
|
259 |
-
"clean-set": "^1.1.2",
|
260 |
-
"dequal": "^2.0.2",
|
261 |
-
"escape-carriage": "^1.3.1",
|
262 |
-
"lz-string": "^1.4.4",
|
263 |
-
"react-devtools-inline": "4.4.0",
|
264 |
-
"react-is": "^17.0.2"
|
265 |
-
},
|
266 |
-
"peerDependencies": {
|
267 |
-
"react": "^16.8.0 || ^17 || ^18 || ^19",
|
268 |
-
"react-dom": "^16.8.0 || ^17 || ^18 || ^19"
|
269 |
-
}
|
270 |
-
},
|
271 |
-
"node_modules/@codesandbox/sandpack-react/node_modules/react-is": {
|
272 |
-
"version": "17.0.2",
|
273 |
-
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
274 |
-
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
275 |
-
"license": "MIT"
|
276 |
-
},
|
277 |
"node_modules/@emnapi/core": {
|
278 |
"version": "1.4.3",
|
279 |
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
|
@@ -1053,69 +876,6 @@
|
|
1053 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
1054 |
}
|
1055 |
},
|
1056 |
-
"node_modules/@lezer/common": {
|
1057 |
-
"version": "1.2.3",
|
1058 |
-
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
1059 |
-
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
|
1060 |
-
"license": "MIT"
|
1061 |
-
},
|
1062 |
-
"node_modules/@lezer/css": {
|
1063 |
-
"version": "1.3.0",
|
1064 |
-
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
|
1065 |
-
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
|
1066 |
-
"license": "MIT",
|
1067 |
-
"dependencies": {
|
1068 |
-
"@lezer/common": "^1.2.0",
|
1069 |
-
"@lezer/highlight": "^1.0.0",
|
1070 |
-
"@lezer/lr": "^1.3.0"
|
1071 |
-
}
|
1072 |
-
},
|
1073 |
-
"node_modules/@lezer/highlight": {
|
1074 |
-
"version": "1.2.1",
|
1075 |
-
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
1076 |
-
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
1077 |
-
"license": "MIT",
|
1078 |
-
"dependencies": {
|
1079 |
-
"@lezer/common": "^1.0.0"
|
1080 |
-
}
|
1081 |
-
},
|
1082 |
-
"node_modules/@lezer/html": {
|
1083 |
-
"version": "1.3.10",
|
1084 |
-
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
|
1085 |
-
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
1086 |
-
"license": "MIT",
|
1087 |
-
"dependencies": {
|
1088 |
-
"@lezer/common": "^1.2.0",
|
1089 |
-
"@lezer/highlight": "^1.0.0",
|
1090 |
-
"@lezer/lr": "^1.0.0"
|
1091 |
-
}
|
1092 |
-
},
|
1093 |
-
"node_modules/@lezer/javascript": {
|
1094 |
-
"version": "1.5.1",
|
1095 |
-
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
|
1096 |
-
"integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
|
1097 |
-
"license": "MIT",
|
1098 |
-
"dependencies": {
|
1099 |
-
"@lezer/common": "^1.2.0",
|
1100 |
-
"@lezer/highlight": "^1.1.3",
|
1101 |
-
"@lezer/lr": "^1.3.0"
|
1102 |
-
}
|
1103 |
-
},
|
1104 |
-
"node_modules/@lezer/lr": {
|
1105 |
-
"version": "1.4.2",
|
1106 |
-
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
1107 |
-
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
1108 |
-
"license": "MIT",
|
1109 |
-
"dependencies": {
|
1110 |
-
"@lezer/common": "^1.0.0"
|
1111 |
-
}
|
1112 |
-
},
|
1113 |
-
"node_modules/@marijn/find-cluster-break": {
|
1114 |
-
"version": "1.0.2",
|
1115 |
-
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
1116 |
-
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
1117 |
-
"license": "MIT"
|
1118 |
-
},
|
1119 |
"node_modules/@monaco-editor/loader": {
|
1120 |
"version": "1.5.0",
|
1121 |
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz",
|
@@ -1350,12 +1110,6 @@
|
|
1350 |
"node": ">=12.4.0"
|
1351 |
}
|
1352 |
},
|
1353 |
-
"node_modules/@open-draft/deferred-promise": {
|
1354 |
-
"version": "2.2.0",
|
1355 |
-
"resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
|
1356 |
-
"integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
|
1357 |
-
"license": "MIT"
|
1358 |
-
},
|
1359 |
"node_modules/@radix-ui/number": {
|
1360 |
"version": "1.1.1",
|
1361 |
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
|
@@ -2301,28 +2055,6 @@
|
|
2301 |
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
|
2302 |
"license": "MIT"
|
2303 |
},
|
2304 |
-
"node_modules/@react-hook/intersection-observer": {
|
2305 |
-
"version": "3.1.2",
|
2306 |
-
"resolved": "https://registry.npmjs.org/@react-hook/intersection-observer/-/intersection-observer-3.1.2.tgz",
|
2307 |
-
"integrity": "sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==",
|
2308 |
-
"license": "MIT",
|
2309 |
-
"dependencies": {
|
2310 |
-
"@react-hook/passive-layout-effect": "^1.2.0",
|
2311 |
-
"intersection-observer": "^0.10.0"
|
2312 |
-
},
|
2313 |
-
"peerDependencies": {
|
2314 |
-
"react": ">=16.8"
|
2315 |
-
}
|
2316 |
-
},
|
2317 |
-
"node_modules/@react-hook/passive-layout-effect": {
|
2318 |
-
"version": "1.2.1",
|
2319 |
-
"resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz",
|
2320 |
-
"integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==",
|
2321 |
-
"license": "MIT",
|
2322 |
-
"peerDependencies": {
|
2323 |
-
"react": ">=16.8"
|
2324 |
-
}
|
2325 |
-
},
|
2326 |
"node_modules/@rtsao/scc": {
|
2327 |
"version": "1.1.0",
|
2328 |
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
@@ -2337,12 +2069,6 @@
|
|
2337 |
"dev": true,
|
2338 |
"license": "MIT"
|
2339 |
},
|
2340 |
-
"node_modules/@stitches/core": {
|
2341 |
-
"version": "1.2.8",
|
2342 |
-
"resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz",
|
2343 |
-
"integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==",
|
2344 |
-
"license": "MIT"
|
2345 |
-
},
|
2346 |
"node_modules/@swc/counter": {
|
2347 |
"version": "0.1.3",
|
2348 |
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
@@ -3615,12 +3341,6 @@
|
|
3615 |
"ajv": "^6.9.1"
|
3616 |
}
|
3617 |
},
|
3618 |
-
"node_modules/anser": {
|
3619 |
-
"version": "2.3.2",
|
3620 |
-
"resolved": "https://registry.npmjs.org/anser/-/anser-2.3.2.tgz",
|
3621 |
-
"integrity": "sha512-PMqBCBvrOVDRqLGooQb+z+t1Q0PiPyurUQeZRR5uHBOVZcW8B04KMmnT12USnhpNX2wCPagWzLVppQMUG3u0Dw==",
|
3622 |
-
"license": "MIT"
|
3623 |
-
},
|
3624 |
"node_modules/ansi-styles": {
|
3625 |
"version": "4.3.0",
|
3626 |
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
@@ -3900,26 +3620,6 @@
|
|
3900 |
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
3901 |
"license": "MIT"
|
3902 |
},
|
3903 |
-
"node_modules/base64-js": {
|
3904 |
-
"version": "1.5.1",
|
3905 |
-
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
3906 |
-
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
3907 |
-
"funding": [
|
3908 |
-
{
|
3909 |
-
"type": "github",
|
3910 |
-
"url": "https://github.com/sponsors/feross"
|
3911 |
-
},
|
3912 |
-
{
|
3913 |
-
"type": "patreon",
|
3914 |
-
"url": "https://www.patreon.com/feross"
|
3915 |
-
},
|
3916 |
-
{
|
3917 |
-
"type": "consulting",
|
3918 |
-
"url": "https://feross.org/support"
|
3919 |
-
}
|
3920 |
-
],
|
3921 |
-
"license": "MIT"
|
3922 |
-
},
|
3923 |
"node_modules/big.js": {
|
3924 |
"version": "5.2.2",
|
3925 |
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
@@ -3995,30 +3695,6 @@
|
|
3995 |
"node": ">=16.20.1"
|
3996 |
}
|
3997 |
},
|
3998 |
-
"node_modules/buffer": {
|
3999 |
-
"version": "6.0.3",
|
4000 |
-
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
4001 |
-
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
4002 |
-
"funding": [
|
4003 |
-
{
|
4004 |
-
"type": "github",
|
4005 |
-
"url": "https://github.com/sponsors/feross"
|
4006 |
-
},
|
4007 |
-
{
|
4008 |
-
"type": "patreon",
|
4009 |
-
"url": "https://www.patreon.com/feross"
|
4010 |
-
},
|
4011 |
-
{
|
4012 |
-
"type": "consulting",
|
4013 |
-
"url": "https://feross.org/support"
|
4014 |
-
}
|
4015 |
-
],
|
4016 |
-
"license": "MIT",
|
4017 |
-
"dependencies": {
|
4018 |
-
"base64-js": "^1.3.1",
|
4019 |
-
"ieee754": "^1.2.1"
|
4020 |
-
}
|
4021 |
-
},
|
4022 |
"node_modules/buffer-from": {
|
4023 |
"version": "1.1.2",
|
4024 |
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
@@ -4171,12 +3847,6 @@
|
|
4171 |
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
4172 |
"license": "MIT"
|
4173 |
},
|
4174 |
-
"node_modules/clean-set": {
|
4175 |
-
"version": "1.1.2",
|
4176 |
-
"resolved": "https://registry.npmjs.org/clean-set/-/clean-set-1.1.2.tgz",
|
4177 |
-
"integrity": "sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==",
|
4178 |
-
"license": "MIT"
|
4179 |
-
},
|
4180 |
"node_modules/client-only": {
|
4181 |
"version": "0.0.1",
|
4182 |
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
@@ -4270,12 +3940,6 @@
|
|
4270 |
"toggle-selection": "^1.0.6"
|
4271 |
}
|
4272 |
},
|
4273 |
-
"node_modules/crelt": {
|
4274 |
-
"version": "1.0.6",
|
4275 |
-
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
4276 |
-
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
4277 |
-
"license": "MIT"
|
4278 |
-
},
|
4279 |
"node_modules/cross-spawn": {
|
4280 |
"version": "7.0.6",
|
4281 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
@@ -4318,19 +3982,6 @@
|
|
4318 |
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
4319 |
"license": "MIT"
|
4320 |
},
|
4321 |
-
"node_modules/d": {
|
4322 |
-
"version": "1.0.2",
|
4323 |
-
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
|
4324 |
-
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
|
4325 |
-
"license": "ISC",
|
4326 |
-
"dependencies": {
|
4327 |
-
"es5-ext": "^0.10.64",
|
4328 |
-
"type": "^2.7.2"
|
4329 |
-
},
|
4330 |
-
"engines": {
|
4331 |
-
"node": ">=0.12"
|
4332 |
-
}
|
4333 |
-
},
|
4334 |
"node_modules/damerau-levenshtein": {
|
4335 |
"version": "1.0.8",
|
4336 |
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
@@ -4470,15 +4121,6 @@
|
|
4470 |
"node": ">=0.4.0"
|
4471 |
}
|
4472 |
},
|
4473 |
-
"node_modules/dequal": {
|
4474 |
-
"version": "2.0.3",
|
4475 |
-
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
4476 |
-
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
4477 |
-
"license": "MIT",
|
4478 |
-
"engines": {
|
4479 |
-
"node": ">=6"
|
4480 |
-
}
|
4481 |
-
},
|
4482 |
"node_modules/detect-libc": {
|
4483 |
"version": "2.0.4",
|
4484 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
@@ -4508,18 +4150,6 @@
|
|
4508 |
"node": ">=0.10.0"
|
4509 |
}
|
4510 |
},
|
4511 |
-
"node_modules/dotenv": {
|
4512 |
-
"version": "16.6.1",
|
4513 |
-
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
4514 |
-
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
4515 |
-
"license": "BSD-2-Clause",
|
4516 |
-
"engines": {
|
4517 |
-
"node": ">=12"
|
4518 |
-
},
|
4519 |
-
"funding": {
|
4520 |
-
"url": "https://dotenvx.com"
|
4521 |
-
}
|
4522 |
-
},
|
4523 |
"node_modules/dunder-proto": {
|
4524 |
"version": "1.0.1",
|
4525 |
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
@@ -4763,46 +4393,6 @@
|
|
4763 |
"url": "https://github.com/sponsors/ljharb"
|
4764 |
}
|
4765 |
},
|
4766 |
-
"node_modules/es5-ext": {
|
4767 |
-
"version": "0.10.64",
|
4768 |
-
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
|
4769 |
-
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
|
4770 |
-
"hasInstallScript": true,
|
4771 |
-
"license": "ISC",
|
4772 |
-
"dependencies": {
|
4773 |
-
"es6-iterator": "^2.0.3",
|
4774 |
-
"es6-symbol": "^3.1.3",
|
4775 |
-
"esniff": "^2.0.1",
|
4776 |
-
"next-tick": "^1.1.0"
|
4777 |
-
},
|
4778 |
-
"engines": {
|
4779 |
-
"node": ">=0.10"
|
4780 |
-
}
|
4781 |
-
},
|
4782 |
-
"node_modules/es6-iterator": {
|
4783 |
-
"version": "2.0.3",
|
4784 |
-
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
4785 |
-
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
|
4786 |
-
"license": "MIT",
|
4787 |
-
"dependencies": {
|
4788 |
-
"d": "1",
|
4789 |
-
"es5-ext": "^0.10.35",
|
4790 |
-
"es6-symbol": "^3.1.1"
|
4791 |
-
}
|
4792 |
-
},
|
4793 |
-
"node_modules/es6-symbol": {
|
4794 |
-
"version": "3.1.4",
|
4795 |
-
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
|
4796 |
-
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
|
4797 |
-
"license": "ISC",
|
4798 |
-
"dependencies": {
|
4799 |
-
"d": "^1.0.2",
|
4800 |
-
"ext": "^1.7.0"
|
4801 |
-
},
|
4802 |
-
"engines": {
|
4803 |
-
"node": ">=0.12"
|
4804 |
-
}
|
4805 |
-
},
|
4806 |
"node_modules/escalade": {
|
4807 |
"version": "3.2.0",
|
4808 |
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
@@ -4814,12 +4404,6 @@
|
|
4814 |
"node": ">=6"
|
4815 |
}
|
4816 |
},
|
4817 |
-
"node_modules/escape-carriage": {
|
4818 |
-
"version": "1.3.1",
|
4819 |
-
"resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz",
|
4820 |
-
"integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==",
|
4821 |
-
"license": "MIT"
|
4822 |
-
},
|
4823 |
"node_modules/escape-string-regexp": {
|
4824 |
"version": "4.0.0",
|
4825 |
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
@@ -5191,21 +4775,6 @@
|
|
5191 |
"url": "https://opencollective.com/eslint"
|
5192 |
}
|
5193 |
},
|
5194 |
-
"node_modules/esniff": {
|
5195 |
-
"version": "2.0.1",
|
5196 |
-
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
|
5197 |
-
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
|
5198 |
-
"license": "ISC",
|
5199 |
-
"dependencies": {
|
5200 |
-
"d": "^1.0.1",
|
5201 |
-
"es5-ext": "^0.10.62",
|
5202 |
-
"event-emitter": "^0.3.5",
|
5203 |
-
"type": "^2.7.2"
|
5204 |
-
},
|
5205 |
-
"engines": {
|
5206 |
-
"node": ">=0.10"
|
5207 |
-
}
|
5208 |
-
},
|
5209 |
"node_modules/espree": {
|
5210 |
"version": "10.3.0",
|
5211 |
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
@@ -5265,16 +4834,6 @@
|
|
5265 |
"node": ">=0.10.0"
|
5266 |
}
|
5267 |
},
|
5268 |
-
"node_modules/event-emitter": {
|
5269 |
-
"version": "0.3.5",
|
5270 |
-
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
|
5271 |
-
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
|
5272 |
-
"license": "MIT",
|
5273 |
-
"dependencies": {
|
5274 |
-
"d": "1",
|
5275 |
-
"es5-ext": "~0.10.14"
|
5276 |
-
}
|
5277 |
-
},
|
5278 |
"node_modules/events": {
|
5279 |
"version": "3.3.0",
|
5280 |
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
@@ -5286,15 +4845,6 @@
|
|
5286 |
"node": ">=0.8.x"
|
5287 |
}
|
5288 |
},
|
5289 |
-
"node_modules/ext": {
|
5290 |
-
"version": "1.7.0",
|
5291 |
-
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
|
5292 |
-
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
|
5293 |
-
"license": "ISC",
|
5294 |
-
"dependencies": {
|
5295 |
-
"type": "^2.7.2"
|
5296 |
-
}
|
5297 |
-
},
|
5298 |
"node_modules/fast-deep-equal": {
|
5299 |
"version": "3.1.3",
|
5300 |
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
@@ -5801,26 +5351,6 @@
|
|
5801 |
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
5802 |
"license": "BSD-3-Clause"
|
5803 |
},
|
5804 |
-
"node_modules/ieee754": {
|
5805 |
-
"version": "1.2.1",
|
5806 |
-
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
5807 |
-
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
5808 |
-
"funding": [
|
5809 |
-
{
|
5810 |
-
"type": "github",
|
5811 |
-
"url": "https://github.com/sponsors/feross"
|
5812 |
-
},
|
5813 |
-
{
|
5814 |
-
"type": "patreon",
|
5815 |
-
"url": "https://www.patreon.com/feross"
|
5816 |
-
},
|
5817 |
-
{
|
5818 |
-
"type": "consulting",
|
5819 |
-
"url": "https://feross.org/support"
|
5820 |
-
}
|
5821 |
-
],
|
5822 |
-
"license": "BSD-3-Clause"
|
5823 |
-
},
|
5824 |
"node_modules/ignore": {
|
5825 |
"version": "5.3.2",
|
5826 |
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
@@ -5879,12 +5409,6 @@
|
|
5879 |
"node": ">= 0.4"
|
5880 |
}
|
5881 |
},
|
5882 |
-
"node_modules/intersection-observer": {
|
5883 |
-
"version": "0.10.0",
|
5884 |
-
"resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.10.0.tgz",
|
5885 |
-
"integrity": "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==",
|
5886 |
-
"license": "W3C-20150513"
|
5887 |
-
},
|
5888 |
"node_modules/is-array-buffer": {
|
5889 |
"version": "3.0.5",
|
5890 |
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
@@ -6816,15 +6340,6 @@
|
|
6816 |
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
6817 |
}
|
6818 |
},
|
6819 |
-
"node_modules/lz-string": {
|
6820 |
-
"version": "1.5.0",
|
6821 |
-
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
6822 |
-
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
6823 |
-
"license": "MIT",
|
6824 |
-
"bin": {
|
6825 |
-
"lz-string": "bin/bin.js"
|
6826 |
-
}
|
6827 |
-
},
|
6828 |
"node_modules/magic-string": {
|
6829 |
"version": "0.30.17",
|
6830 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
@@ -7211,12 +6726,6 @@
|
|
7211 |
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
7212 |
}
|
7213 |
},
|
7214 |
-
"node_modules/next-tick": {
|
7215 |
-
"version": "1.1.0",
|
7216 |
-
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
|
7217 |
-
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
|
7218 |
-
"license": "ISC"
|
7219 |
-
},
|
7220 |
"node_modules/next/node_modules/postcss": {
|
7221 |
"version": "8.4.31",
|
7222 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
@@ -7393,12 +6902,6 @@
|
|
7393 |
"node": ">= 0.8.0"
|
7394 |
}
|
7395 |
},
|
7396 |
-
"node_modules/outvariant": {
|
7397 |
-
"version": "1.4.0",
|
7398 |
-
"resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz",
|
7399 |
-
"integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==",
|
7400 |
-
"license": "MIT"
|
7401 |
-
},
|
7402 |
"node_modules/own-keys": {
|
7403 |
"version": "1.0.1",
|
7404 |
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
|
@@ -7617,15 +7120,6 @@
|
|
7617 |
"node": ">=0.10.0"
|
7618 |
}
|
7619 |
},
|
7620 |
-
"node_modules/react-devtools-inline": {
|
7621 |
-
"version": "4.4.0",
|
7622 |
-
"resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz",
|
7623 |
-
"integrity": "sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==",
|
7624 |
-
"license": "MIT",
|
7625 |
-
"dependencies": {
|
7626 |
-
"es6-symbol": "^3"
|
7627 |
-
}
|
7628 |
-
},
|
7629 |
"node_modules/react-dom": {
|
7630 |
"version": "19.1.0",
|
7631 |
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
@@ -8364,18 +7858,6 @@
|
|
8364 |
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
|
8365 |
"license": "MIT"
|
8366 |
},
|
8367 |
-
"node_modules/static-browser-server": {
|
8368 |
-
"version": "1.0.3",
|
8369 |
-
"resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz",
|
8370 |
-
"integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==",
|
8371 |
-
"license": "Apache-2.0",
|
8372 |
-
"dependencies": {
|
8373 |
-
"@open-draft/deferred-promise": "^2.1.0",
|
8374 |
-
"dotenv": "^16.0.3",
|
8375 |
-
"mime-db": "^1.52.0",
|
8376 |
-
"outvariant": "^1.3.0"
|
8377 |
-
}
|
8378 |
-
},
|
8379 |
"node_modules/stop-iteration-iterator": {
|
8380 |
"version": "1.1.0",
|
8381 |
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
@@ -8398,12 +7880,6 @@
|
|
8398 |
"node": ">=10.0.0"
|
8399 |
}
|
8400 |
},
|
8401 |
-
"node_modules/strict-event-emitter": {
|
8402 |
-
"version": "0.4.6",
|
8403 |
-
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz",
|
8404 |
-
"integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==",
|
8405 |
-
"license": "MIT"
|
8406 |
-
},
|
8407 |
"node_modules/string.prototype.includes": {
|
8408 |
"version": "2.0.1",
|
8409 |
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
|
@@ -8539,12 +8015,6 @@
|
|
8539 |
"url": "https://github.com/sponsors/sindresorhus"
|
8540 |
}
|
8541 |
},
|
8542 |
-
"node_modules/style-mod": {
|
8543 |
-
"version": "4.1.2",
|
8544 |
-
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
8545 |
-
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
8546 |
-
"license": "MIT"
|
8547 |
-
},
|
8548 |
"node_modules/styled-jsx": {
|
8549 |
"version": "5.1.6",
|
8550 |
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
@@ -8892,12 +8362,6 @@
|
|
8892 |
"url": "https://github.com/sponsors/Wombosvideo"
|
8893 |
}
|
8894 |
},
|
8895 |
-
"node_modules/type": {
|
8896 |
-
"version": "2.7.3",
|
8897 |
-
"resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
|
8898 |
-
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
|
8899 |
-
"license": "ISC"
|
8900 |
-
},
|
8901 |
"node_modules/type-check": {
|
8902 |
"version": "0.4.0",
|
8903 |
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
@@ -9181,12 +8645,6 @@
|
|
9181 |
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
9182 |
}
|
9183 |
},
|
9184 |
-
"node_modules/w3c-keyname": {
|
9185 |
-
"version": "2.2.8",
|
9186 |
-
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
9187 |
-
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
9188 |
-
"license": "MIT"
|
9189 |
-
},
|
9190 |
"node_modules/watchpack": {
|
9191 |
"version": "2.4.4",
|
9192 |
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
|
|
8 |
"name": "deepsite-v2",
|
9 |
"version": "0.1.0",
|
10 |
"dependencies": {
|
|
|
11 |
"@huggingface/hub": "^2.2.0",
|
12 |
"@huggingface/inference": "^4.0.3",
|
13 |
"@monaco-editor/react": "^4.7.0-rc.0",
|
|
|
97 |
"node": ">=6.9.0"
|
98 |
}
|
99 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
"node_modules/@emnapi/core": {
|
101 |
"version": "1.4.3",
|
102 |
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
|
|
|
876 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
877 |
}
|
878 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
879 |
"node_modules/@monaco-editor/loader": {
|
880 |
"version": "1.5.0",
|
881 |
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz",
|
|
|
1110 |
"node": ">=12.4.0"
|
1111 |
}
|
1112 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
1113 |
"node_modules/@radix-ui/number": {
|
1114 |
"version": "1.1.1",
|
1115 |
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
|
|
|
2055 |
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
|
2056 |
"license": "MIT"
|
2057 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2058 |
"node_modules/@rtsao/scc": {
|
2059 |
"version": "1.1.0",
|
2060 |
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
|
|
2069 |
"dev": true,
|
2070 |
"license": "MIT"
|
2071 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
2072 |
"node_modules/@swc/counter": {
|
2073 |
"version": "0.1.3",
|
2074 |
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
|
|
3341 |
"ajv": "^6.9.1"
|
3342 |
}
|
3343 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
3344 |
"node_modules/ansi-styles": {
|
3345 |
"version": "4.3.0",
|
3346 |
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
|
|
3620 |
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
3621 |
"license": "MIT"
|
3622 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3623 |
"node_modules/big.js": {
|
3624 |
"version": "5.2.2",
|
3625 |
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
|
|
3695 |
"node": ">=16.20.1"
|
3696 |
}
|
3697 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3698 |
"node_modules/buffer-from": {
|
3699 |
"version": "1.1.2",
|
3700 |
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
|
|
3847 |
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
3848 |
"license": "MIT"
|
3849 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
3850 |
"node_modules/client-only": {
|
3851 |
"version": "0.0.1",
|
3852 |
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
|
|
3940 |
"toggle-selection": "^1.0.6"
|
3941 |
}
|
3942 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
3943 |
"node_modules/cross-spawn": {
|
3944 |
"version": "7.0.6",
|
3945 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
|
|
3982 |
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
3983 |
"license": "MIT"
|
3984 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3985 |
"node_modules/damerau-levenshtein": {
|
3986 |
"version": "1.0.8",
|
3987 |
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
|
|
4121 |
"node": ">=0.4.0"
|
4122 |
}
|
4123 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4124 |
"node_modules/detect-libc": {
|
4125 |
"version": "2.0.4",
|
4126 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
|
|
4150 |
"node": ">=0.10.0"
|
4151 |
}
|
4152 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4153 |
"node_modules/dunder-proto": {
|
4154 |
"version": "1.0.1",
|
4155 |
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
|
4393 |
"url": "https://github.com/sponsors/ljharb"
|
4394 |
}
|
4395 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4396 |
"node_modules/escalade": {
|
4397 |
"version": "3.2.0",
|
4398 |
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
|
|
4404 |
"node": ">=6"
|
4405 |
}
|
4406 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
4407 |
"node_modules/escape-string-regexp": {
|
4408 |
"version": "4.0.0",
|
4409 |
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
|
|
4775 |
"url": "https://opencollective.com/eslint"
|
4776 |
}
|
4777 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4778 |
"node_modules/espree": {
|
4779 |
"version": "10.3.0",
|
4780 |
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
|
|
4834 |
"node": ">=0.10.0"
|
4835 |
}
|
4836 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4837 |
"node_modules/events": {
|
4838 |
"version": "3.3.0",
|
4839 |
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
|
|
4845 |
"node": ">=0.8.x"
|
4846 |
}
|
4847 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4848 |
"node_modules/fast-deep-equal": {
|
4849 |
"version": "3.1.3",
|
4850 |
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
|
5351 |
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
5352 |
"license": "BSD-3-Clause"
|
5353 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5354 |
"node_modules/ignore": {
|
5355 |
"version": "5.3.2",
|
5356 |
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
|
|
5409 |
"node": ">= 0.4"
|
5410 |
}
|
5411 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
5412 |
"node_modules/is-array-buffer": {
|
5413 |
"version": "3.0.5",
|
5414 |
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
|
|
6340 |
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
6341 |
}
|
6342 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6343 |
"node_modules/magic-string": {
|
6344 |
"version": "0.30.17",
|
6345 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
|
|
6726 |
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
6727 |
}
|
6728 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
6729 |
"node_modules/next/node_modules/postcss": {
|
6730 |
"version": "8.4.31",
|
6731 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
|
|
6902 |
"node": ">= 0.8.0"
|
6903 |
}
|
6904 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
6905 |
"node_modules/own-keys": {
|
6906 |
"version": "1.0.1",
|
6907 |
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
|
|
|
7120 |
"node": ">=0.10.0"
|
7121 |
}
|
7122 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7123 |
"node_modules/react-dom": {
|
7124 |
"version": "19.1.0",
|
7125 |
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
|
|
7858 |
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
|
7859 |
"license": "MIT"
|
7860 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7861 |
"node_modules/stop-iteration-iterator": {
|
7862 |
"version": "1.1.0",
|
7863 |
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
|
|
7880 |
"node": ">=10.0.0"
|
7881 |
}
|
7882 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
7883 |
"node_modules/string.prototype.includes": {
|
7884 |
"version": "2.0.1",
|
7885 |
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
|
|
|
8015 |
"url": "https://github.com/sponsors/sindresorhus"
|
8016 |
}
|
8017 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
8018 |
"node_modules/styled-jsx": {
|
8019 |
"version": "5.1.6",
|
8020 |
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
|
|
8362 |
"url": "https://github.com/sponsors/Wombosvideo"
|
8363 |
}
|
8364 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
8365 |
"node_modules/type-check": {
|
8366 |
"version": "0.4.0",
|
8367 |
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
|
|
8645 |
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
8646 |
}
|
8647 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
8648 |
"node_modules/watchpack": {
|
8649 |
"version": "2.4.4",
|
8650 |
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
package.json
CHANGED
@@ -9,7 +9,6 @@
|
|
9 |
"lint": "next lint"
|
10 |
},
|
11 |
"dependencies": {
|
12 |
-
"@codesandbox/sandpack-react": "^2.20.0",
|
13 |
"@huggingface/hub": "^2.2.0",
|
14 |
"@huggingface/inference": "^4.0.3",
|
15 |
"@monaco-editor/react": "^4.7.0-rc.0",
|
|
|
9 |
"lint": "next lint"
|
10 |
},
|
11 |
"dependencies": {
|
|
|
12 |
"@huggingface/hub": "^2.2.0",
|
13 |
"@huggingface/inference": "^4.0.3",
|
14 |
"@monaco-editor/react": "^4.7.0-rc.0",
|
types/index.ts
CHANGED
@@ -9,7 +9,7 @@ export interface User {
|
|
9 |
}
|
10 |
|
11 |
export interface HtmlHistory {
|
12 |
-
|
13 |
createdAt: Date;
|
14 |
prompt: string;
|
15 |
}
|
@@ -24,8 +24,3 @@ export interface Project {
|
|
24 |
_updatedAt?: Date;
|
25 |
_createdAt?: Date;
|
26 |
}
|
27 |
-
|
28 |
-
export interface Page {
|
29 |
-
path: string;
|
30 |
-
html: string;
|
31 |
-
}
|
|
|
9 |
}
|
10 |
|
11 |
export interface HtmlHistory {
|
12 |
+
html: string;
|
13 |
createdAt: Date;
|
14 |
prompt: string;
|
15 |
}
|
|
|
24 |
_updatedAt?: Date;
|
25 |
_createdAt?: Date;
|
26 |
}
|
|
|
|
|
|
|
|
|
|