Spaces:
Running
Running
sintred
#506
by
doyeet
- opened
- README.md +4 -3
- app/api/ask/route.ts +32 -27
- app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/promote/route.ts +22 -62
- app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/route.ts +0 -102
- app/api/me/projects/[namespace]/[repoId]/download/route.ts +0 -105
- app/api/me/projects/[namespace]/[repoId]/route.ts +1 -1
- app/layout.tsx +5 -20
- assets/globals.css +0 -9
- assets/minimax.svg +0 -1
- components/discord-promo-modal/index.tsx +0 -225
- components/domain-redirect/index.tsx +23 -0
- components/editor/ask-ai/context.tsx +0 -4
- components/editor/ask-ai/index.tsx +9 -40
- components/editor/ask-ai/prompt-builder/content-modal.tsx +1 -1
- components/editor/ask-ai/prompt-builder/tailwind-colors.tsx +1 -1
- components/editor/ask-ai/re-imagine.tsx +2 -8
- components/editor/ask-ai/selected-redesign-url.tsx +0 -37
- components/editor/ask-ai/settings.tsx +35 -81
- components/editor/file-browser/index.tsx +22 -10
- components/editor/header/index.tsx +18 -36
- components/editor/index.tsx +3 -7
- components/editor/preview/index.tsx +35 -340
- components/icons/discord.tsx +0 -27
- components/my-projects/project-card.tsx +1 -54
- components/public/navigation/index.tsx +2 -15
- hooks/useAi.ts +2 -76
- lib/best-provider.ts +8 -3
- lib/prompts.ts +126 -146
- lib/providers.ts +22 -39
- middleware.ts +1 -2
- package-lock.json +7 -113
- package.json +0 -1
README.md
CHANGED
|
@@ -7,7 +7,6 @@ sdk: docker
|
|
| 7 |
pinned: true
|
| 8 |
app_port: 3000
|
| 9 |
license: mit
|
| 10 |
-
failure_strategy: rollback
|
| 11 |
short_description: Generate any application by Vibe Coding
|
| 12 |
models:
|
| 13 |
- deepseek-ai/DeepSeek-V3-0324
|
|
@@ -19,10 +18,12 @@ models:
|
|
| 19 |
- moonshotai/Kimi-K2-Instruct
|
| 20 |
- moonshotai/Kimi-K2-Instruct-0905
|
| 21 |
- zai-org/GLM-4.6
|
| 22 |
-
- MiniMaxAI/MiniMax-M2
|
| 23 |
-
- moonshotai/Kimi-K2-Thinking
|
| 24 |
---
|
| 25 |
|
| 26 |
# DeepSite 🐳
|
| 27 |
|
| 28 |
DeepSite is a Vibe Coding Platform designed to make coding smarter and more efficient. Tailored for developers, data scientists, and AI engineers, it integrates generative AI into your coding projects to enhance creativity and productivity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
pinned: true
|
| 8 |
app_port: 3000
|
| 9 |
license: mit
|
|
|
|
| 10 |
short_description: Generate any application by Vibe Coding
|
| 11 |
models:
|
| 12 |
- deepseek-ai/DeepSeek-V3-0324
|
|
|
|
| 18 |
- moonshotai/Kimi-K2-Instruct
|
| 19 |
- moonshotai/Kimi-K2-Instruct-0905
|
| 20 |
- zai-org/GLM-4.6
|
|
|
|
|
|
|
| 21 |
---
|
| 22 |
|
| 23 |
# DeepSite 🐳
|
| 24 |
|
| 25 |
DeepSite is a Vibe Coding Platform designed to make coding smarter and more efficient. Tailored for developers, data scientists, and AI engineers, it integrates generative AI into your coding projects to enhance creativity and productivity.
|
| 26 |
+
|
| 27 |
+
## How to use it locally
|
| 28 |
+
|
| 29 |
+
Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
|
app/api/ask/route.ts
CHANGED
|
@@ -7,12 +7,11 @@ import { InferenceClient } from "@huggingface/inference";
|
|
| 7 |
import { MODELS } from "@/lib/providers";
|
| 8 |
import {
|
| 9 |
FOLLOW_UP_SYSTEM_PROMPT,
|
| 10 |
-
FOLLOW_UP_SYSTEM_PROMPT_LIGHT,
|
| 11 |
INITIAL_SYSTEM_PROMPT,
|
| 12 |
-
INITIAL_SYSTEM_PROMPT_LIGHT,
|
| 13 |
MAX_REQUESTS_PER_IP,
|
| 14 |
PROMPT_FOR_PROJECT_NAME,
|
| 15 |
} from "@/lib/prompts";
|
|
|
|
| 16 |
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 17 |
import { Page } from "@/types";
|
| 18 |
import { isAuthenticated } from "@/lib/auth";
|
|
@@ -22,8 +21,7 @@ const ipAddresses = new Map();
|
|
| 22 |
|
| 23 |
export async function POST(request: NextRequest) {
|
| 24 |
const authHeaders = await headers();
|
| 25 |
-
const
|
| 26 |
-
const userToken = tokenInHeaders ? tokenInHeaders.replace("Bearer ", "") : request.cookies.get(MY_TOKEN_KEY())?.value;
|
| 27 |
|
| 28 |
const body = await request.json();
|
| 29 |
const { prompt, provider, model, redesignMarkdown, enhancedSettings, pages } = body;
|
|
@@ -63,7 +61,7 @@ export async function POST(request: NextRequest) {
|
|
| 63 |
? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
|
| 64 |
: authHeaders.get("x-forwarded-for");
|
| 65 |
|
| 66 |
-
if (!token
|
| 67 |
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 68 |
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 69 |
return NextResponse.json(
|
|
@@ -80,6 +78,14 @@ export async function POST(request: NextRequest) {
|
|
| 80 |
billTo = "huggingface";
|
| 81 |
}
|
| 82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
try {
|
| 84 |
const encoder = new TextEncoder();
|
| 85 |
const stream = new TransformStream();
|
|
@@ -94,27 +100,26 @@ export async function POST(request: NextRequest) {
|
|
| 94 |
});
|
| 95 |
|
| 96 |
(async () => {
|
|
|
|
| 97 |
try {
|
| 98 |
const client = new InferenceClient(token);
|
| 99 |
|
| 100 |
-
const systemPrompt =
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
-
const userPrompt = prompt;
|
| 105 |
-
|
| 106 |
const chatCompletion = client.chatCompletionStream(
|
| 107 |
{
|
| 108 |
-
model: selectedModel.value
|
|
|
|
| 109 |
messages: [
|
| 110 |
{
|
| 111 |
role: "system",
|
| 112 |
content: systemPrompt,
|
| 113 |
},
|
| 114 |
-
...(redesignMarkdown ? [{
|
| 115 |
-
role: "assistant",
|
| 116 |
-
content: `User will ask you to redesign the site based on this markdown. Use the same images as the site, but you can improve the content and the design. Here is the markdown: ${redesignMarkdown}`
|
| 117 |
-
}] : []),
|
| 118 |
{
|
| 119 |
role: "user",
|
| 120 |
content: userPrompt + (enhancedSettings.isActive ? `1. I want to use the following primary color: ${enhancedSettings.primaryColor} (eg: bg-${enhancedSettings.primaryColor}-500).
|
|
@@ -122,9 +127,7 @@ export async function POST(request: NextRequest) {
|
|
| 122 |
3. I want to use the following theme: ${enhancedSettings.theme} mode.` : "")
|
| 123 |
},
|
| 124 |
],
|
| 125 |
-
...
|
| 126 |
-
...(selectedModel.temperature ? { temperature: selectedModel.temperature } : {}),
|
| 127 |
-
...(selectedModel.top_p ? { top_p: selectedModel.top_p } : {}),
|
| 128 |
},
|
| 129 |
billTo ? { billTo } : {}
|
| 130 |
);
|
|
@@ -243,7 +246,7 @@ export async function PUT(request: NextRequest) {
|
|
| 243 |
? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
|
| 244 |
: authHeaders.get("x-forwarded-for");
|
| 245 |
|
| 246 |
-
if (!token
|
| 247 |
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 248 |
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 249 |
return NextResponse.json(
|
|
@@ -260,6 +263,8 @@ export async function PUT(request: NextRequest) {
|
|
| 260 |
billTo = "huggingface";
|
| 261 |
}
|
| 262 |
|
|
|
|
|
|
|
| 263 |
try {
|
| 264 |
const encoder = new TextEncoder();
|
| 265 |
const stream = new TransformStream();
|
|
@@ -277,10 +282,7 @@ export async function PUT(request: NextRequest) {
|
|
| 277 |
try {
|
| 278 |
const client = new InferenceClient(token);
|
| 279 |
|
| 280 |
-
const
|
| 281 |
-
? FOLLOW_UP_SYSTEM_PROMPT_LIGHT
|
| 282 |
-
: FOLLOW_UP_SYSTEM_PROMPT;
|
| 283 |
-
const systemPrompt = basePrompt + (isNew ? PROMPT_FOR_PROJECT_NAME : "");
|
| 284 |
const userContext = "You are modifying the HTML file based on the user's request.";
|
| 285 |
|
| 286 |
const allPages = pages || [];
|
|
@@ -293,9 +295,14 @@ export async function PUT(request: NextRequest) {
|
|
| 293 |
: ""
|
| 294 |
}. Current pages (${allPages.length} total): ${pagesContext}. ${files?.length > 0 ? `Available images: ${files?.map((f: string) => f).join(', ')}.` : ""}`;
|
| 295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
const chatCompletion = client.chatCompletionStream(
|
| 297 |
{
|
| 298 |
-
model: selectedModel.value
|
|
|
|
| 299 |
messages: [
|
| 300 |
{
|
| 301 |
role: "system",
|
|
@@ -314,9 +321,7 @@ export async function PUT(request: NextRequest) {
|
|
| 314 |
content: prompt,
|
| 315 |
},
|
| 316 |
],
|
| 317 |
-
...
|
| 318 |
-
...(selectedModel.temperature ? { temperature: selectedModel.temperature } : {}),
|
| 319 |
-
...(selectedModel.top_p ? { top_p: selectedModel.top_p } : {}),
|
| 320 |
},
|
| 321 |
billTo ? { billTo } : {}
|
| 322 |
);
|
|
|
|
| 7 |
import { MODELS } from "@/lib/providers";
|
| 8 |
import {
|
| 9 |
FOLLOW_UP_SYSTEM_PROMPT,
|
|
|
|
| 10 |
INITIAL_SYSTEM_PROMPT,
|
|
|
|
| 11 |
MAX_REQUESTS_PER_IP,
|
| 12 |
PROMPT_FOR_PROJECT_NAME,
|
| 13 |
} from "@/lib/prompts";
|
| 14 |
+
import { calculateMaxTokens, estimateInputTokens, getProviderSpecificConfig } from "@/lib/max-tokens";
|
| 15 |
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 16 |
import { Page } from "@/types";
|
| 17 |
import { isAuthenticated } from "@/lib/auth";
|
|
|
|
| 21 |
|
| 22 |
export async function POST(request: NextRequest) {
|
| 23 |
const authHeaders = await headers();
|
| 24 |
+
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
|
|
|
| 25 |
|
| 26 |
const body = await request.json();
|
| 27 |
const { prompt, provider, model, redesignMarkdown, enhancedSettings, pages } = body;
|
|
|
|
| 61 |
? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
|
| 62 |
: authHeaders.get("x-forwarded-for");
|
| 63 |
|
| 64 |
+
if (!token) {
|
| 65 |
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 66 |
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 67 |
return NextResponse.json(
|
|
|
|
| 78 |
billTo = "huggingface";
|
| 79 |
}
|
| 80 |
|
| 81 |
+
const selectedProvider = await getBestProvider(selectedModel.value, provider)
|
| 82 |
+
|
| 83 |
+
let rewrittenPrompt = redesignMarkdown ? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown. Use the images in the markdown.` : prompt;
|
| 84 |
+
|
| 85 |
+
if (enhancedSettings.isActive) {
|
| 86 |
+
// rewrittenPrompt = await rewritePrompt(rewrittenPrompt, enhancedSettings, { token, billTo }, selectedModel.value, selectedProvider.provider);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
try {
|
| 90 |
const encoder = new TextEncoder();
|
| 91 |
const stream = new TransformStream();
|
|
|
|
| 100 |
});
|
| 101 |
|
| 102 |
(async () => {
|
| 103 |
+
// let completeResponse = "";
|
| 104 |
try {
|
| 105 |
const client = new InferenceClient(token);
|
| 106 |
|
| 107 |
+
const systemPrompt = INITIAL_SYSTEM_PROMPT;
|
| 108 |
+
|
| 109 |
+
const userPrompt = rewrittenPrompt;
|
| 110 |
+
const estimatedInputTokens = estimateInputTokens(systemPrompt, userPrompt);
|
| 111 |
+
const dynamicMaxTokens = calculateMaxTokens(selectedProvider, estimatedInputTokens, true);
|
| 112 |
+
const providerConfig = getProviderSpecificConfig(selectedProvider, dynamicMaxTokens);
|
| 113 |
|
|
|
|
|
|
|
| 114 |
const chatCompletion = client.chatCompletionStream(
|
| 115 |
{
|
| 116 |
+
model: selectedModel.value,
|
| 117 |
+
provider: selectedProvider.provider,
|
| 118 |
messages: [
|
| 119 |
{
|
| 120 |
role: "system",
|
| 121 |
content: systemPrompt,
|
| 122 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
{
|
| 124 |
role: "user",
|
| 125 |
content: userPrompt + (enhancedSettings.isActive ? `1. I want to use the following primary color: ${enhancedSettings.primaryColor} (eg: bg-${enhancedSettings.primaryColor}-500).
|
|
|
|
| 127 |
3. I want to use the following theme: ${enhancedSettings.theme} mode.` : "")
|
| 128 |
},
|
| 129 |
],
|
| 130 |
+
...providerConfig,
|
|
|
|
|
|
|
| 131 |
},
|
| 132 |
billTo ? { billTo } : {}
|
| 133 |
);
|
|
|
|
| 246 |
? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
|
| 247 |
: authHeaders.get("x-forwarded-for");
|
| 248 |
|
| 249 |
+
if (!token) {
|
| 250 |
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 251 |
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 252 |
return NextResponse.json(
|
|
|
|
| 263 |
billTo = "huggingface";
|
| 264 |
}
|
| 265 |
|
| 266 |
+
const selectedProvider = await getBestProvider(selectedModel.value, provider);
|
| 267 |
+
|
| 268 |
try {
|
| 269 |
const encoder = new TextEncoder();
|
| 270 |
const stream = new TransformStream();
|
|
|
|
| 282 |
try {
|
| 283 |
const client = new InferenceClient(token);
|
| 284 |
|
| 285 |
+
const systemPrompt = FOLLOW_UP_SYSTEM_PROMPT + (isNew ? PROMPT_FOR_PROJECT_NAME : "");
|
|
|
|
|
|
|
|
|
|
| 286 |
const userContext = "You are modifying the HTML file based on the user's request.";
|
| 287 |
|
| 288 |
const allPages = pages || [];
|
|
|
|
| 295 |
: ""
|
| 296 |
}. Current pages (${allPages.length} total): ${pagesContext}. ${files?.length > 0 ? `Available images: ${files?.map((f: string) => f).join(', ')}.` : ""}`;
|
| 297 |
|
| 298 |
+
const estimatedInputTokens = estimateInputTokens(systemPrompt, prompt, userContext + assistantContext);
|
| 299 |
+
const dynamicMaxTokens = calculateMaxTokens(selectedProvider, estimatedInputTokens, false);
|
| 300 |
+
const providerConfig = getProviderSpecificConfig(selectedProvider, dynamicMaxTokens);
|
| 301 |
+
|
| 302 |
const chatCompletion = client.chatCompletionStream(
|
| 303 |
{
|
| 304 |
+
model: selectedModel.value,
|
| 305 |
+
provider: selectedProvider.provider,
|
| 306 |
messages: [
|
| 307 |
{
|
| 308 |
role: "system",
|
|
|
|
| 321 |
content: prompt,
|
| 322 |
},
|
| 323 |
],
|
| 324 |
+
...providerConfig,
|
|
|
|
|
|
|
| 325 |
},
|
| 326 |
billTo ? { billTo } : {}
|
| 327 |
);
|
app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/promote/route.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
-
import { RepoDesignation, listFiles, spaceInfo, uploadFiles, deleteFiles
|
| 3 |
|
| 4 |
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
import { Page } from "@/types";
|
|
@@ -49,13 +49,13 @@ export async function POST(
|
|
| 49 |
);
|
| 50 |
}
|
| 51 |
|
|
|
|
| 52 |
const files: File[] = [];
|
| 53 |
const pages: Page[] = [];
|
| 54 |
-
const mediaFiles: string[] = [];
|
| 55 |
const allowedExtensions = ["html", "md", "css", "js", "json", "txt"];
|
| 56 |
-
const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif", "mp4", "webm", "ogg", "avi", "mov", "mp3", "wav", "ogg", "aac", "m4a"];
|
| 57 |
const commitFilePaths: Set<string> = new Set();
|
| 58 |
|
|
|
|
| 59 |
for await (const fileInfo of listFiles({
|
| 60 |
repo,
|
| 61 |
accessToken: user.token as string,
|
|
@@ -63,24 +63,26 @@ export async function POST(
|
|
| 63 |
})) {
|
| 64 |
const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase();
|
| 65 |
|
| 66 |
-
if (
|
| 67 |
commitFilePaths.add(fileInfo.path);
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
revision: commitId,
|
| 74 |
-
raw: true
|
| 75 |
-
});
|
| 76 |
-
const content = await blob?.text();
|
| 77 |
|
| 78 |
-
if (
|
|
|
|
| 79 |
let mimeType = "text/plain";
|
| 80 |
|
| 81 |
switch (fileExtension) {
|
| 82 |
case "html":
|
| 83 |
mimeType = "text/html";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
break;
|
| 85 |
case "css":
|
| 86 |
mimeType = "text/css";
|
|
@@ -91,62 +93,18 @@ export async function POST(
|
|
| 91 |
case "json":
|
| 92 |
mimeType = "application/json";
|
| 93 |
break;
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
pages.unshift({
|
| 98 |
-
path: fileInfo.path,
|
| 99 |
-
html: content,
|
| 100 |
-
});
|
| 101 |
-
} else {
|
| 102 |
-
pages.push({
|
| 103 |
-
path: fileInfo.path,
|
| 104 |
-
html: content,
|
| 105 |
-
});
|
| 106 |
}
|
| 107 |
|
| 108 |
const file = new File([content], fileInfo.path, { type: mimeType });
|
| 109 |
files.push(file);
|
| 110 |
}
|
| 111 |
}
|
| 112 |
-
else if (fileInfo.type === "directory" && (["videos", "images", "audio"].includes(fileInfo.path) || fileInfo.path === "components")) {
|
| 113 |
-
for await (const subFileInfo of listFiles({
|
| 114 |
-
repo,
|
| 115 |
-
accessToken: user.token as string,
|
| 116 |
-
revision: commitId,
|
| 117 |
-
path: fileInfo.path,
|
| 118 |
-
})) {
|
| 119 |
-
if (subFileInfo.path.includes("components")) {
|
| 120 |
-
commitFilePaths.add(subFileInfo.path);
|
| 121 |
-
const blob = await downloadFile({
|
| 122 |
-
repo,
|
| 123 |
-
accessToken: user.token as string,
|
| 124 |
-
path: subFileInfo.path,
|
| 125 |
-
revision: commitId,
|
| 126 |
-
raw: true
|
| 127 |
-
});
|
| 128 |
-
const content = await blob?.text();
|
| 129 |
-
|
| 130 |
-
if (content) {
|
| 131 |
-
pages.push({
|
| 132 |
-
path: subFileInfo.path,
|
| 133 |
-
html: content,
|
| 134 |
-
});
|
| 135 |
-
|
| 136 |
-
const file = new File([content], subFileInfo.path, { type: "text/html" });
|
| 137 |
-
files.push(file);
|
| 138 |
-
}
|
| 139 |
-
} else if (allowedFilesExtensions.includes(subFileInfo.path.split(".").pop() || "")) {
|
| 140 |
-
commitFilePaths.add(subFileInfo.path);
|
| 141 |
-
mediaFiles.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${subFileInfo.path}`);
|
| 142 |
-
}
|
| 143 |
-
}
|
| 144 |
-
}
|
| 145 |
-
else if (allowedExtensions.includes(fileExtension || "")) {
|
| 146 |
-
commitFilePaths.add(fileInfo.path);
|
| 147 |
-
}
|
| 148 |
}
|
| 149 |
|
|
|
|
| 150 |
const mainBranchFilePaths: Set<string> = new Set();
|
| 151 |
for await (const fileInfo of listFiles({
|
| 152 |
repo,
|
|
@@ -160,6 +118,7 @@ export async function POST(
|
|
| 160 |
}
|
| 161 |
}
|
| 162 |
|
|
|
|
| 163 |
const filesToDelete: string[] = [];
|
| 164 |
for (const mainFilePath of mainBranchFilePaths) {
|
| 165 |
if (!commitFilePaths.has(mainFilePath)) {
|
|
@@ -174,6 +133,7 @@ export async function POST(
|
|
| 174 |
);
|
| 175 |
}
|
| 176 |
|
|
|
|
| 177 |
if (filesToDelete.length > 0) {
|
| 178 |
await deleteFiles({
|
| 179 |
repo,
|
|
@@ -184,6 +144,7 @@ export async function POST(
|
|
| 184 |
});
|
| 185 |
}
|
| 186 |
|
|
|
|
| 187 |
if (files.length > 0) {
|
| 188 |
await uploadFiles({
|
| 189 |
repo,
|
|
@@ -200,7 +161,6 @@ export async function POST(
|
|
| 200 |
message: "Version promoted successfully",
|
| 201 |
promotedCommit: commitId,
|
| 202 |
pages: pages,
|
| 203 |
-
files: mediaFiles,
|
| 204 |
},
|
| 205 |
{ status: 200 }
|
| 206 |
);
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { RepoDesignation, listFiles, spaceInfo, uploadFiles, deleteFiles } from "@huggingface/hub";
|
| 3 |
|
| 4 |
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
import { Page } from "@/types";
|
|
|
|
| 49 |
);
|
| 50 |
}
|
| 51 |
|
| 52 |
+
// Fetch files from the specific commit
|
| 53 |
const files: File[] = [];
|
| 54 |
const pages: Page[] = [];
|
|
|
|
| 55 |
const allowedExtensions = ["html", "md", "css", "js", "json", "txt"];
|
|
|
|
| 56 |
const commitFilePaths: Set<string> = new Set();
|
| 57 |
|
| 58 |
+
// Get all files from the specific commit
|
| 59 |
for await (const fileInfo of listFiles({
|
| 60 |
repo,
|
| 61 |
accessToken: user.token as string,
|
|
|
|
| 63 |
})) {
|
| 64 |
const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase();
|
| 65 |
|
| 66 |
+
if (allowedExtensions.includes(fileExtension || "")) {
|
| 67 |
commitFilePaths.add(fileInfo.path);
|
| 68 |
|
| 69 |
+
// Fetch the file content from the specific commit
|
| 70 |
+
const response = await fetch(
|
| 71 |
+
`https://huggingface.co/spaces/${namespace}/${repoId}/raw/${commitId}/${fileInfo.path}`
|
| 72 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
+
if (response.ok) {
|
| 75 |
+
const content = await response.text();
|
| 76 |
let mimeType = "text/plain";
|
| 77 |
|
| 78 |
switch (fileExtension) {
|
| 79 |
case "html":
|
| 80 |
mimeType = "text/html";
|
| 81 |
+
// Add HTML files to pages array for client-side setPages
|
| 82 |
+
pages.push({
|
| 83 |
+
path: fileInfo.path,
|
| 84 |
+
html: content,
|
| 85 |
+
});
|
| 86 |
break;
|
| 87 |
case "css":
|
| 88 |
mimeType = "text/css";
|
|
|
|
| 93 |
case "json":
|
| 94 |
mimeType = "application/json";
|
| 95 |
break;
|
| 96 |
+
case "md":
|
| 97 |
+
mimeType = "text/markdown";
|
| 98 |
+
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
}
|
| 100 |
|
| 101 |
const file = new File([content], fileInfo.path, { type: mimeType });
|
| 102 |
files.push(file);
|
| 103 |
}
|
| 104 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
}
|
| 106 |
|
| 107 |
+
// Get files currently in main branch to identify files to delete
|
| 108 |
const mainBranchFilePaths: Set<string> = new Set();
|
| 109 |
for await (const fileInfo of listFiles({
|
| 110 |
repo,
|
|
|
|
| 118 |
}
|
| 119 |
}
|
| 120 |
|
| 121 |
+
// Identify files to delete (exist in main but not in commit)
|
| 122 |
const filesToDelete: string[] = [];
|
| 123 |
for (const mainFilePath of mainBranchFilePaths) {
|
| 124 |
if (!commitFilePaths.has(mainFilePath)) {
|
|
|
|
| 133 |
);
|
| 134 |
}
|
| 135 |
|
| 136 |
+
// Delete files that exist in main but not in the commit being promoted
|
| 137 |
if (filesToDelete.length > 0) {
|
| 138 |
await deleteFiles({
|
| 139 |
repo,
|
|
|
|
| 144 |
});
|
| 145 |
}
|
| 146 |
|
| 147 |
+
// Upload the files to the main branch with a promotion commit message
|
| 148 |
if (files.length > 0) {
|
| 149 |
await uploadFiles({
|
| 150 |
repo,
|
|
|
|
| 161 |
message: "Version promoted successfully",
|
| 162 |
promotedCommit: commitId,
|
| 163 |
pages: pages,
|
|
|
|
| 164 |
},
|
| 165 |
{ status: 200 }
|
| 166 |
);
|
app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/route.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
| 1 |
-
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
-
import { RepoDesignation, listFiles, spaceInfo, downloadFile } from "@huggingface/hub";
|
| 3 |
-
|
| 4 |
-
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
-
import { Page } from "@/types";
|
| 6 |
-
|
| 7 |
-
export async function GET(
|
| 8 |
-
req: NextRequest,
|
| 9 |
-
{ params }: {
|
| 10 |
-
params: Promise<{
|
| 11 |
-
namespace: string;
|
| 12 |
-
repoId: string;
|
| 13 |
-
commitId: string;
|
| 14 |
-
}>
|
| 15 |
-
}
|
| 16 |
-
) {
|
| 17 |
-
const user = await isAuthenticated();
|
| 18 |
-
|
| 19 |
-
if (user instanceof NextResponse || !user) {
|
| 20 |
-
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 21 |
-
}
|
| 22 |
-
|
| 23 |
-
const param = await params;
|
| 24 |
-
const { namespace, repoId, commitId } = param;
|
| 25 |
-
|
| 26 |
-
try {
|
| 27 |
-
const repo: RepoDesignation = {
|
| 28 |
-
type: "space",
|
| 29 |
-
name: `${namespace}/${repoId}`,
|
| 30 |
-
};
|
| 31 |
-
|
| 32 |
-
const space = await spaceInfo({
|
| 33 |
-
name: `${namespace}/${repoId}`,
|
| 34 |
-
accessToken: user.token as string,
|
| 35 |
-
additionalFields: ["author"],
|
| 36 |
-
});
|
| 37 |
-
|
| 38 |
-
if (!space || space.sdk !== "static") {
|
| 39 |
-
return NextResponse.json(
|
| 40 |
-
{ ok: false, error: "Space is not a static space." },
|
| 41 |
-
{ status: 404 }
|
| 42 |
-
);
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
if (space.author !== user.name) {
|
| 46 |
-
return NextResponse.json(
|
| 47 |
-
{ ok: false, error: "Space does not belong to the authenticated user." },
|
| 48 |
-
{ status: 403 }
|
| 49 |
-
);
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
const pages: Page[] = [];
|
| 53 |
-
|
| 54 |
-
for await (const fileInfo of listFiles({
|
| 55 |
-
repo,
|
| 56 |
-
accessToken: user.token as string,
|
| 57 |
-
revision: commitId,
|
| 58 |
-
})) {
|
| 59 |
-
const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase();
|
| 60 |
-
|
| 61 |
-
if (fileInfo.path.endsWith(".html") || fileInfo.path.endsWith(".css") || fileInfo.path.endsWith(".js") || fileInfo.path.endsWith(".json")) {
|
| 62 |
-
const blob = await downloadFile({
|
| 63 |
-
repo,
|
| 64 |
-
accessToken: user.token as string,
|
| 65 |
-
path: fileInfo.path,
|
| 66 |
-
revision: commitId,
|
| 67 |
-
raw: true
|
| 68 |
-
});
|
| 69 |
-
const content = await blob?.text();
|
| 70 |
-
|
| 71 |
-
if (content) {
|
| 72 |
-
if (fileInfo.path === "index.html") {
|
| 73 |
-
pages.unshift({
|
| 74 |
-
path: fileInfo.path,
|
| 75 |
-
html: content,
|
| 76 |
-
});
|
| 77 |
-
} else {
|
| 78 |
-
pages.push({
|
| 79 |
-
path: fileInfo.path,
|
| 80 |
-
html: content,
|
| 81 |
-
});
|
| 82 |
-
}
|
| 83 |
-
}
|
| 84 |
-
}
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
return NextResponse.json({
|
| 88 |
-
ok: true,
|
| 89 |
-
pages,
|
| 90 |
-
});
|
| 91 |
-
} catch (error: any) {
|
| 92 |
-
console.error("Error fetching commit pages:", error);
|
| 93 |
-
return NextResponse.json(
|
| 94 |
-
{
|
| 95 |
-
ok: false,
|
| 96 |
-
error: error.message || "Failed to fetch commit pages",
|
| 97 |
-
},
|
| 98 |
-
{ status: 500 }
|
| 99 |
-
);
|
| 100 |
-
}
|
| 101 |
-
}
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/me/projects/[namespace]/[repoId]/download/route.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
| 1 |
-
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
-
import { RepoDesignation, listFiles, spaceInfo, downloadFile } from "@huggingface/hub";
|
| 3 |
-
import JSZip from "jszip";
|
| 4 |
-
|
| 5 |
-
import { isAuthenticated } from "@/lib/auth";
|
| 6 |
-
|
| 7 |
-
export async function GET(
|
| 8 |
-
req: NextRequest,
|
| 9 |
-
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 10 |
-
) {
|
| 11 |
-
const user = await isAuthenticated();
|
| 12 |
-
|
| 13 |
-
if (user instanceof NextResponse || !user) {
|
| 14 |
-
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 15 |
-
}
|
| 16 |
-
|
| 17 |
-
const param = await params;
|
| 18 |
-
const { namespace, repoId } = param;
|
| 19 |
-
|
| 20 |
-
try {
|
| 21 |
-
const space = await spaceInfo({
|
| 22 |
-
name: `${namespace}/${repoId}`,
|
| 23 |
-
accessToken: user.token as string,
|
| 24 |
-
additionalFields: ["author"],
|
| 25 |
-
});
|
| 26 |
-
|
| 27 |
-
if (!space || space.sdk !== "static") {
|
| 28 |
-
return NextResponse.json(
|
| 29 |
-
{
|
| 30 |
-
ok: false,
|
| 31 |
-
error: "Space is not a static space",
|
| 32 |
-
},
|
| 33 |
-
{ status: 404 }
|
| 34 |
-
);
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
if (space.author !== user.name) {
|
| 38 |
-
return NextResponse.json(
|
| 39 |
-
{
|
| 40 |
-
ok: false,
|
| 41 |
-
error: "Space does not belong to the authenticated user",
|
| 42 |
-
},
|
| 43 |
-
{ status: 403 }
|
| 44 |
-
);
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
const repo: RepoDesignation = {
|
| 48 |
-
type: "space",
|
| 49 |
-
name: `${namespace}/${repoId}`,
|
| 50 |
-
};
|
| 51 |
-
|
| 52 |
-
const zip = new JSZip();
|
| 53 |
-
|
| 54 |
-
for await (const fileInfo of listFiles({
|
| 55 |
-
repo,
|
| 56 |
-
accessToken: user.token as string,
|
| 57 |
-
recursive: true,
|
| 58 |
-
})) {
|
| 59 |
-
if (fileInfo.type === "directory" || fileInfo.path.startsWith(".")) {
|
| 60 |
-
continue;
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
try {
|
| 64 |
-
const blob = await downloadFile({
|
| 65 |
-
repo,
|
| 66 |
-
accessToken: user.token as string,
|
| 67 |
-
path: fileInfo.path,
|
| 68 |
-
raw: true
|
| 69 |
-
});
|
| 70 |
-
|
| 71 |
-
if (blob) {
|
| 72 |
-
const arrayBuffer = await blob.arrayBuffer();
|
| 73 |
-
zip.file(fileInfo.path, arrayBuffer);
|
| 74 |
-
}
|
| 75 |
-
} catch (error) {
|
| 76 |
-
console.error(`Error downloading file ${fileInfo.path}:`, error);
|
| 77 |
-
}
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
const zipBlob = await zip.generateAsync({
|
| 81 |
-
type: "blob",
|
| 82 |
-
compression: "DEFLATE",
|
| 83 |
-
compressionOptions: {
|
| 84 |
-
level: 6
|
| 85 |
-
}
|
| 86 |
-
});
|
| 87 |
-
|
| 88 |
-
const projectName = `${namespace}-${repoId}`.replace(/[^a-zA-Z0-9-_]/g, '_');
|
| 89 |
-
const filename = `${projectName}.zip`;
|
| 90 |
-
|
| 91 |
-
return new NextResponse(zipBlob, {
|
| 92 |
-
headers: {
|
| 93 |
-
"Content-Type": "application/zip",
|
| 94 |
-
"Content-Disposition": `attachment; filename="${filename}"`,
|
| 95 |
-
"Content-Length": zipBlob.size.toString(),
|
| 96 |
-
},
|
| 97 |
-
});
|
| 98 |
-
} catch (error: any) {
|
| 99 |
-
return NextResponse.json(
|
| 100 |
-
{ ok: false, error: error.message || "Failed to create ZIP file" },
|
| 101 |
-
{ status: 500 }
|
| 102 |
-
);
|
| 103 |
-
}
|
| 104 |
-
}
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/me/projects/[namespace]/[repoId]/route.ts
CHANGED
|
@@ -108,7 +108,7 @@ export async function GET(
|
|
| 108 |
const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif", "mp4", "webm", "ogg", "avi", "mov", "mp3", "wav", "ogg", "aac", "m4a"];
|
| 109 |
|
| 110 |
for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) {
|
| 111 |
-
if (fileInfo.path.endsWith(".html") || fileInfo.path.endsWith(".css") || fileInfo.path.endsWith(".js")
|
| 112 |
const blob = await downloadFile({ repo, accessToken: user.token as string, path: fileInfo.path, raw: true });
|
| 113 |
const html = await blob?.text();
|
| 114 |
if (!html) {
|
|
|
|
| 108 |
const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif", "mp4", "webm", "ogg", "avi", "mov", "mp3", "wav", "ogg", "aac", "m4a"];
|
| 109 |
|
| 110 |
for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) {
|
| 111 |
+
if (fileInfo.path.endsWith(".html") || fileInfo.path.endsWith(".css") || fileInfo.path.endsWith(".js")) {
|
| 112 |
const blob = await downloadFile({ repo, accessToken: user.token as string, path: fileInfo.path, raw: true });
|
| 113 |
const html = await blob?.text();
|
| 114 |
if (!html) {
|
app/layout.tsx
CHANGED
|
@@ -1,18 +1,20 @@
|
|
| 1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import type { Metadata, Viewport } from "next";
|
| 3 |
import { Inter, PT_Sans } from "next/font/google";
|
|
|
|
| 4 |
import Script from "next/script";
|
| 5 |
-
import { headers } from "next/headers";
|
| 6 |
-
import { redirect } from "next/navigation";
|
| 7 |
|
| 8 |
import "@/assets/globals.css";
|
| 9 |
import { Toaster } from "@/components/ui/sonner";
|
|
|
|
|
|
|
| 10 |
import IframeDetector from "@/components/iframe-detector";
|
| 11 |
import AppContext from "@/components/contexts/app-context";
|
| 12 |
import TanstackContext from "@/components/contexts/tanstack-query-context";
|
| 13 |
import { LoginProvider } from "@/components/contexts/login-context";
|
| 14 |
import { ProProvider } from "@/components/contexts/pro-context";
|
| 15 |
import { generateSEO, generateStructuredData } from "@/lib/seo";
|
|
|
|
| 16 |
|
| 17 |
const inter = Inter({
|
| 18 |
variable: "--font-inter-sans",
|
|
@@ -76,24 +78,6 @@ export default async function RootLayout({
|
|
| 76 |
}: Readonly<{
|
| 77 |
children: React.ReactNode;
|
| 78 |
}>) {
|
| 79 |
-
// Domain redirect check
|
| 80 |
-
const headersList = await headers();
|
| 81 |
-
const forwardedHost = headersList.get("x-forwarded-host");
|
| 82 |
-
const host = headersList.get("host");
|
| 83 |
-
const hostname = (forwardedHost || host || "").split(":")[0];
|
| 84 |
-
|
| 85 |
-
const isLocalDev =
|
| 86 |
-
hostname === "localhost" ||
|
| 87 |
-
hostname === "127.0.0.1" ||
|
| 88 |
-
hostname.startsWith("192.168.");
|
| 89 |
-
const isHuggingFace =
|
| 90 |
-
hostname === "huggingface.co" || hostname.endsWith(".huggingface.co");
|
| 91 |
-
|
| 92 |
-
if (!isHuggingFace && !isLocalDev) {
|
| 93 |
-
const pathname = headersList.get("x-invoke-path") || "/deepsite";
|
| 94 |
-
redirect(`https://huggingface.co${pathname}`);
|
| 95 |
-
}
|
| 96 |
-
|
| 97 |
// const data = await getMe();
|
| 98 |
|
| 99 |
// Generate structured data
|
|
@@ -130,6 +114,7 @@ export default async function RootLayout({
|
|
| 130 |
data-domain="deepsite.hf.co"
|
| 131 |
src="https://plausible.io/js/script.js"
|
| 132 |
/>
|
|
|
|
| 133 |
<IframeDetector />
|
| 134 |
<Toaster richColors position="bottom-center" />
|
| 135 |
<TanstackContext>
|
|
|
|
| 1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import type { Metadata, Viewport } from "next";
|
| 3 |
import { Inter, PT_Sans } from "next/font/google";
|
| 4 |
+
import { cookies } from "next/headers";
|
| 5 |
import Script from "next/script";
|
|
|
|
|
|
|
| 6 |
|
| 7 |
import "@/assets/globals.css";
|
| 8 |
import { Toaster } from "@/components/ui/sonner";
|
| 9 |
+
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 10 |
+
import { apiServer } from "@/lib/api";
|
| 11 |
import IframeDetector from "@/components/iframe-detector";
|
| 12 |
import AppContext from "@/components/contexts/app-context";
|
| 13 |
import TanstackContext from "@/components/contexts/tanstack-query-context";
|
| 14 |
import { LoginProvider } from "@/components/contexts/login-context";
|
| 15 |
import { ProProvider } from "@/components/contexts/pro-context";
|
| 16 |
import { generateSEO, generateStructuredData } from "@/lib/seo";
|
| 17 |
+
import DomainRedirect from "@/components/domain-redirect";
|
| 18 |
|
| 19 |
const inter = Inter({
|
| 20 |
variable: "--font-inter-sans",
|
|
|
|
| 78 |
}: Readonly<{
|
| 79 |
children: React.ReactNode;
|
| 80 |
}>) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
// const data = await getMe();
|
| 82 |
|
| 83 |
// Generate structured data
|
|
|
|
| 114 |
data-domain="deepsite.hf.co"
|
| 115 |
src="https://plausible.io/js/script.js"
|
| 116 |
/>
|
| 117 |
+
<DomainRedirect />
|
| 118 |
<IframeDetector />
|
| 119 |
<Toaster richColors position="bottom-center" />
|
| 120 |
<TanstackContext>
|
assets/globals.css
CHANGED
|
@@ -369,12 +369,3 @@ body {
|
|
| 369 |
z-index: 1;
|
| 370 |
filter: blur(1px);
|
| 371 |
}
|
| 372 |
-
|
| 373 |
-
.transparent-scroll {
|
| 374 |
-
scrollbar-width: none; /* Firefox */
|
| 375 |
-
-ms-overflow-style: none; /* IE and Edge */
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
.transparent-scroll::-webkit-scrollbar {
|
| 379 |
-
display: none; /* Chrome, Safari, Opera */
|
| 380 |
-
}
|
|
|
|
| 369 |
z-index: 1;
|
| 370 |
filter: blur(1px);
|
| 371 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assets/minimax.svg
DELETED
components/discord-promo-modal/index.tsx
DELETED
|
@@ -1,225 +0,0 @@
|
|
| 1 |
-
"use client";
|
| 2 |
-
|
| 3 |
-
import { useEffect, useState } from "react";
|
| 4 |
-
import { useLocalStorage } from "react-use";
|
| 5 |
-
import Image from "next/image";
|
| 6 |
-
import { Button } from "@/components/ui/button";
|
| 7 |
-
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
| 8 |
-
import { DiscordIcon } from "@/components/icons/discord";
|
| 9 |
-
import Logo from "@/assets/logo.svg";
|
| 10 |
-
|
| 11 |
-
const DISCORD_PROMO_KEY = "discord-promo-dismissed";
|
| 12 |
-
const DISCORD_URL = "https://discord.gg/KpanwM3vXa";
|
| 13 |
-
|
| 14 |
-
const Sparkle = ({
|
| 15 |
-
size = "w-3 h-3",
|
| 16 |
-
delay = "0s",
|
| 17 |
-
top = "20%",
|
| 18 |
-
left = "20%",
|
| 19 |
-
}: {
|
| 20 |
-
size?: string;
|
| 21 |
-
delay?: string;
|
| 22 |
-
top?: string;
|
| 23 |
-
left?: string;
|
| 24 |
-
}) => (
|
| 25 |
-
<div
|
| 26 |
-
className={`absolute ${size}`}
|
| 27 |
-
style={{ top, left, animationDelay: delay }}
|
| 28 |
-
>
|
| 29 |
-
<svg
|
| 30 |
-
viewBox="0 0 24 24"
|
| 31 |
-
fill="none"
|
| 32 |
-
xmlns="http://www.w3.org/2000/svg"
|
| 33 |
-
className="w-full h-full animate-sparkle"
|
| 34 |
-
>
|
| 35 |
-
<path
|
| 36 |
-
d="M12 0L13.5 8.5L22 10L13.5 11.5L12 20L10.5 11.5L2 10L10.5 8.5L12 0Z"
|
| 37 |
-
fill="url(#sparkle-gradient)"
|
| 38 |
-
/>
|
| 39 |
-
<defs>
|
| 40 |
-
<linearGradient id="sparkle-gradient" x1="2" y1="10" x2="22" y2="10">
|
| 41 |
-
<stop offset="0%" stopColor="#818cf8" />
|
| 42 |
-
<stop offset="100%" stopColor="#a5b4fc" />
|
| 43 |
-
</linearGradient>
|
| 44 |
-
</defs>
|
| 45 |
-
</svg>
|
| 46 |
-
</div>
|
| 47 |
-
);
|
| 48 |
-
|
| 49 |
-
export const DiscordPromoModal = () => {
|
| 50 |
-
const [open, setOpen] = useState(false);
|
| 51 |
-
const [dismissed, setDismissed] = useLocalStorage<boolean>(
|
| 52 |
-
DISCORD_PROMO_KEY,
|
| 53 |
-
false
|
| 54 |
-
);
|
| 55 |
-
|
| 56 |
-
useEffect(() => {
|
| 57 |
-
const cookieDismissed = document.cookie
|
| 58 |
-
.split("; ")
|
| 59 |
-
.find((row) => row.startsWith(`${DISCORD_PROMO_KEY}=`))
|
| 60 |
-
?.split("=")[1];
|
| 61 |
-
|
| 62 |
-
if (dismissed || cookieDismissed === "true") {
|
| 63 |
-
return;
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
const timer = setTimeout(() => {
|
| 67 |
-
setOpen(true);
|
| 68 |
-
}, 60000);
|
| 69 |
-
|
| 70 |
-
return () => clearTimeout(timer);
|
| 71 |
-
}, [dismissed]);
|
| 72 |
-
|
| 73 |
-
const handleClose = () => {
|
| 74 |
-
setOpen(false);
|
| 75 |
-
setDismissed(true);
|
| 76 |
-
|
| 77 |
-
const expiryDate = new Date();
|
| 78 |
-
expiryDate.setDate(expiryDate.getDate() + 5);
|
| 79 |
-
document.cookie = `${DISCORD_PROMO_KEY}=true; expires=${expiryDate.toUTCString()}; path=/; SameSite=Lax`;
|
| 80 |
-
};
|
| 81 |
-
|
| 82 |
-
const handleJoinDiscord = () => {
|
| 83 |
-
window.open(DISCORD_URL, "_blank");
|
| 84 |
-
handleClose();
|
| 85 |
-
};
|
| 86 |
-
|
| 87 |
-
return (
|
| 88 |
-
<Dialog open={open} onOpenChange={handleClose}>
|
| 89 |
-
<DialogContent
|
| 90 |
-
className="sm:max-w-[480px] lg:!p-0 !rounded-3xl !bg-gradient-to-b !from-indigo-950/40 !via-neutral-900 !to-neutral-900 !border !border-neutral-800 overflow-hidden"
|
| 91 |
-
showCloseButton={true}
|
| 92 |
-
>
|
| 93 |
-
<DialogTitle className="hidden" />
|
| 94 |
-
|
| 95 |
-
<div className="relative">
|
| 96 |
-
<div className="absolute inset-0 bg-gradient-to-br from-indigo-500/10 via-indigo-500/5 to-transparent pointer-events-none" />
|
| 97 |
-
|
| 98 |
-
<div className="absolute inset-x-0 top-0 h-48 overflow-hidden pointer-events-none">
|
| 99 |
-
<Sparkle size="w-2 h-2" delay="0s" top="15%" left="15%" />
|
| 100 |
-
<Sparkle size="w-3 h-3" delay="0.5s" top="25%" left="75%" />
|
| 101 |
-
<Sparkle size="w-2 h-2" delay="1s" top="35%" left="20%" />
|
| 102 |
-
<Sparkle size="w-4 h-4" delay="1.5s" top="10%" left="80%" />
|
| 103 |
-
<Sparkle size="w-2 h-2" delay="2s" top="30%" left="85%" />
|
| 104 |
-
</div>
|
| 105 |
-
|
| 106 |
-
<div className="relative pt-12 pb-8">
|
| 107 |
-
<div className="relative z-10 flex justify-center">
|
| 108 |
-
<div className="relative">
|
| 109 |
-
<div className="absolute inset-0 bg-gradient-to-br from-indigo-400 via-indigo-500 to-indigo-600 rounded-full blur-md opacity-50" />
|
| 110 |
-
<div className="relative w-32 h-32 rounded-full bg-gradient-to-br from-neutral-900 via-neutral-800 to-neutral-900 p-1 shadow-2xl">
|
| 111 |
-
<div className="w-full h-full rounded-full bg-neutral-900 flex items-center justify-center overflow-hidden">
|
| 112 |
-
<div className="relative w-20 h-20 bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-full flex items-center justify-center">
|
| 113 |
-
<DiscordIcon className="w-12 h-12 text-white" />
|
| 114 |
-
</div>
|
| 115 |
-
</div>
|
| 116 |
-
</div>
|
| 117 |
-
|
| 118 |
-
<div className="absolute -bottom-2 -right-2 bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-full p-0.5 shadow-xl border-2 border-neutral-900">
|
| 119 |
-
<div className="w-10 h-10 bg-neutral-900 rounded-full flex items-center justify-center">
|
| 120 |
-
<Image
|
| 121 |
-
src={Logo}
|
| 122 |
-
alt="DeepSite"
|
| 123 |
-
width={20}
|
| 124 |
-
height={20}
|
| 125 |
-
className="w-5 h-5"
|
| 126 |
-
/>
|
| 127 |
-
</div>
|
| 128 |
-
</div>
|
| 129 |
-
</div>
|
| 130 |
-
</div>
|
| 131 |
-
</div>
|
| 132 |
-
|
| 133 |
-
<main className="px-8 pb-8 pt-4">
|
| 134 |
-
<div className="text-center mb-6">
|
| 135 |
-
<h2 className="text-2xl font-bold text-white mb-2">
|
| 136 |
-
Ready to level up your DeepSite experience?
|
| 137 |
-
</h2>
|
| 138 |
-
<p className="text-neutral-400 text-sm">
|
| 139 |
-
Get help, share your projects and ask for suggestions!
|
| 140 |
-
</p>
|
| 141 |
-
</div>
|
| 142 |
-
|
| 143 |
-
<div className="flex flex-col gap-3 mb-6">
|
| 144 |
-
{[
|
| 145 |
-
"Get exclusive preview to new features",
|
| 146 |
-
"Share your projects and get feedback",
|
| 147 |
-
"Priority support from the team",
|
| 148 |
-
"Enjoy real-time updates",
|
| 149 |
-
].map((benefit, index) => (
|
| 150 |
-
<div
|
| 151 |
-
key={index}
|
| 152 |
-
className="flex items-start gap-3 text-neutral-200"
|
| 153 |
-
style={{
|
| 154 |
-
animation: `fadeIn 0.4s ease-out ${index * 0.1}s both`,
|
| 155 |
-
}}
|
| 156 |
-
>
|
| 157 |
-
<div className="flex-shrink-0 w-5 h-5 rounded-full bg-gradient-to-br from-indigo-500 to-indigo-600 flex items-center justify-center mt-0.5">
|
| 158 |
-
<svg
|
| 159 |
-
className="w-3 h-3 text-white"
|
| 160 |
-
fill="none"
|
| 161 |
-
viewBox="0 0 24 24"
|
| 162 |
-
stroke="currentColor"
|
| 163 |
-
>
|
| 164 |
-
<path
|
| 165 |
-
strokeLinecap="round"
|
| 166 |
-
strokeLinejoin="round"
|
| 167 |
-
strokeWidth={3}
|
| 168 |
-
d="M5 13l4 4L19 7"
|
| 169 |
-
/>
|
| 170 |
-
</svg>
|
| 171 |
-
</div>
|
| 172 |
-
<span className="text-sm leading-6">{benefit}</span>
|
| 173 |
-
</div>
|
| 174 |
-
))}
|
| 175 |
-
</div>
|
| 176 |
-
|
| 177 |
-
{/* CTA Button */}
|
| 178 |
-
<div className="flex flex-col gap-3 w-full">
|
| 179 |
-
<Button
|
| 180 |
-
onClick={handleJoinDiscord}
|
| 181 |
-
className="w-full !h-12 !text-base font-semibold !bg-gradient-to-r !from-indigo-500 !to-indigo-600 hover:!from-indigo-600 hover:!to-indigo-700 !text-white !border-0 transform hover:scale-[1.02] transition-all duration-200 shadow-lg shadow-indigo-500/25"
|
| 182 |
-
>
|
| 183 |
-
<DiscordIcon className="w-5 h-5 mr-2" />
|
| 184 |
-
Join Discord Community
|
| 185 |
-
</Button>
|
| 186 |
-
|
| 187 |
-
<p className="text-center text-xs text-neutral-500">
|
| 188 |
-
Free to join. Connect instantly.
|
| 189 |
-
</p>
|
| 190 |
-
</div>
|
| 191 |
-
</main>
|
| 192 |
-
</div>
|
| 193 |
-
|
| 194 |
-
<style jsx>{`
|
| 195 |
-
@keyframes fadeIn {
|
| 196 |
-
from {
|
| 197 |
-
opacity: 0;
|
| 198 |
-
transform: translateY(5px);
|
| 199 |
-
}
|
| 200 |
-
to {
|
| 201 |
-
opacity: 1;
|
| 202 |
-
transform: translateY(0);
|
| 203 |
-
}
|
| 204 |
-
}
|
| 205 |
-
|
| 206 |
-
@keyframes sparkle {
|
| 207 |
-
0%,
|
| 208 |
-
100% {
|
| 209 |
-
opacity: 0;
|
| 210 |
-
transform: scale(0) rotate(0deg);
|
| 211 |
-
}
|
| 212 |
-
50% {
|
| 213 |
-
opacity: 1;
|
| 214 |
-
transform: scale(1) rotate(180deg);
|
| 215 |
-
}
|
| 216 |
-
}
|
| 217 |
-
|
| 218 |
-
:global(.animate-sparkle) {
|
| 219 |
-
animation: sparkle 2s ease-in-out infinite;
|
| 220 |
-
}
|
| 221 |
-
`}</style>
|
| 222 |
-
</DialogContent>
|
| 223 |
-
</Dialog>
|
| 224 |
-
);
|
| 225 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/domain-redirect/index.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useEffect } from "react";
|
| 4 |
+
|
| 5 |
+
export default function DomainRedirect() {
|
| 6 |
+
useEffect(() => {
|
| 7 |
+
if (typeof window === "undefined") return;
|
| 8 |
+
|
| 9 |
+
const host = window.location.host;
|
| 10 |
+
|
| 11 |
+
// Check if we're not on hf.co or huggingface.co
|
| 12 |
+
const isHfCo = host === "hf.co" || host.startsWith("hf.co:");
|
| 13 |
+
const isHuggingFaceCo =
|
| 14 |
+
host === "huggingface.co" || host.startsWith("huggingface.co:");
|
| 15 |
+
|
| 16 |
+
if (!isHfCo && !isHuggingFaceCo) {
|
| 17 |
+
// Redirect to the correct URL
|
| 18 |
+
window.location.replace("https://huggingface.co/deepsite");
|
| 19 |
+
}
|
| 20 |
+
}, []);
|
| 21 |
+
|
| 22 |
+
return null;
|
| 23 |
+
}
|
components/editor/ask-ai/context.tsx
CHANGED
|
@@ -23,8 +23,6 @@ export const Context = () => {
|
|
| 23 |
return <Braces className={size} />;
|
| 24 |
} else if (filePath.endsWith(".js")) {
|
| 25 |
return <FileCode className={size} />;
|
| 26 |
-
} else if (filePath.endsWith(".json")) {
|
| 27 |
-
return <Braces className={size} />;
|
| 28 |
} else {
|
| 29 |
return <FileText className={size} />;
|
| 30 |
}
|
|
@@ -54,8 +52,6 @@ export const Context = () => {
|
|
| 54 |
selectedFile && selectedFile.endsWith(".html"),
|
| 55 |
"!bg-amber-500/10 !border-amber-500/30 !text-amber-400":
|
| 56 |
selectedFile && selectedFile.endsWith(".js"),
|
| 57 |
-
"!bg-yellow-500/10 !border-yellow-500/30 !text-yellow-400":
|
| 58 |
-
selectedFile && selectedFile.endsWith(".json"),
|
| 59 |
})}
|
| 60 |
disabled={
|
| 61 |
globalAiLoading || globalEditorLoading || pages.length === 0
|
|
|
|
| 23 |
return <Braces className={size} />;
|
| 24 |
} else if (filePath.endsWith(".js")) {
|
| 25 |
return <FileCode className={size} />;
|
|
|
|
|
|
|
| 26 |
} else {
|
| 27 |
return <FileText className={size} />;
|
| 28 |
}
|
|
|
|
| 52 |
selectedFile && selectedFile.endsWith(".html"),
|
| 53 |
"!bg-amber-500/10 !border-amber-500/30 !text-amber-400":
|
| 54 |
selectedFile && selectedFile.endsWith(".js"),
|
|
|
|
|
|
|
| 55 |
})}
|
| 56 |
disabled={
|
| 57 |
globalAiLoading || globalEditorLoading || pages.length === 0
|
components/editor/ask-ai/index.tsx
CHANGED
|
@@ -22,7 +22,6 @@ import { Settings } from "./settings";
|
|
| 22 |
import { useProModal } from "@/components/contexts/pro-context";
|
| 23 |
import { MAX_FREE_PROJECTS } from "@/lib/utils";
|
| 24 |
import { PROMPTS_FOR_AI } from "@/lib/prompts";
|
| 25 |
-
import { SelectedRedesignUrl } from "./selected-redesign-url";
|
| 26 |
|
| 27 |
export const AskAi = ({
|
| 28 |
project,
|
|
@@ -39,7 +38,6 @@ export const AskAi = ({
|
|
| 39 |
const {
|
| 40 |
isAiWorking,
|
| 41 |
isThinking,
|
| 42 |
-
thinkingContent,
|
| 43 |
selectedFiles,
|
| 44 |
setSelectedFiles,
|
| 45 |
selectedElement,
|
|
@@ -53,9 +51,6 @@ export const AskAi = ({
|
|
| 53 |
const { openProModal } = useProModal();
|
| 54 |
const [openProvider, setOpenProvider] = useState(false);
|
| 55 |
const [providerError, setProviderError] = useState("");
|
| 56 |
-
const [redesignData, setRedesignData] = useState<
|
| 57 |
-
undefined | { markdown: string; url: string }
|
| 58 |
-
>(undefined);
|
| 59 |
const refThink = useRef<HTMLDivElement>(null);
|
| 60 |
|
| 61 |
const [enhancedSettings, setEnhancedSettings, removeEnhancedSettings] =
|
|
@@ -71,6 +66,7 @@ export const AskAi = ({
|
|
| 71 |
const [prompt, setPrompt] = useState(
|
| 72 |
promptStorage && promptStorage.trim() !== "" ? promptStorage : ""
|
| 73 |
);
|
|
|
|
| 74 |
const [openThink, setOpenThink] = useState(false);
|
| 75 |
const [randomPromptLoading, setRandomPromptLoading] = useState(false);
|
| 76 |
|
|
@@ -80,7 +76,7 @@ export const AskAi = ({
|
|
| 80 |
}
|
| 81 |
});
|
| 82 |
|
| 83 |
-
const callAi = async (redesignMarkdown?: string
|
| 84 |
removePromptStorage();
|
| 85 |
if (user && !user.isPro && projects.length >= MAX_FREE_PROJECTS)
|
| 86 |
return openProModal([]);
|
|
@@ -146,15 +142,7 @@ export const AskAi = ({
|
|
| 146 |
if (refThink.current) {
|
| 147 |
refThink.current.scrollTop = refThink.current.scrollHeight;
|
| 148 |
}
|
| 149 |
-
|
| 150 |
-
if (thinkingContent && isThinking && !openThink) {
|
| 151 |
-
setOpenThink(true);
|
| 152 |
-
}
|
| 153 |
-
// Auto-collapse when thinking is complete
|
| 154 |
-
if (thinkingContent && !isThinking && openThink) {
|
| 155 |
-
setOpenThink(false);
|
| 156 |
-
}
|
| 157 |
-
}, [thinkingContent, isThinking]);
|
| 158 |
|
| 159 |
const randomPrompt = () => {
|
| 160 |
setRandomPromptLoading(true);
|
|
@@ -169,7 +157,7 @@ export const AskAi = ({
|
|
| 169 |
return (
|
| 170 |
<div className="p-3 w-full">
|
| 171 |
<div className="relative bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent z-20 w-full group">
|
| 172 |
-
{
|
| 173 |
<div className="w-full border-b border-neutral-700 relative overflow-hidden">
|
| 174 |
<header
|
| 175 |
className="flex items-center justify-between px-5 py-2.5 group hover:bg-neutral-600/20 transition-colors duration-200 cursor-pointer"
|
|
@@ -201,7 +189,7 @@ export const AskAi = ({
|
|
| 201 |
)}
|
| 202 |
>
|
| 203 |
<p className="text-[13px] text-neutral-400 whitespace-pre-line px-5 pb-4 pt-3">
|
| 204 |
-
{
|
| 205 |
</p>
|
| 206 |
</main>
|
| 207 |
</div>
|
|
@@ -222,15 +210,6 @@ export const AskAi = ({
|
|
| 222 |
/>
|
| 223 |
</div>
|
| 224 |
)}
|
| 225 |
-
{redesignData && (
|
| 226 |
-
<div className="px-4 pt-3">
|
| 227 |
-
<SelectedRedesignUrl
|
| 228 |
-
url={redesignData.url}
|
| 229 |
-
isAiWorking={isAiWorking}
|
| 230 |
-
onDelete={() => setRedesignData(undefined)}
|
| 231 |
-
/>
|
| 232 |
-
</div>
|
| 233 |
-
)}
|
| 234 |
<div className="w-full relative flex items-center justify-between">
|
| 235 |
{(isAiWorking || isUploading || isThinking || isLoadingProject) && (
|
| 236 |
<div className="absolute bg-neutral-800 top-0 left-4 w-[calc(100%-30px)] h-full z-1 flex items-start pt-3.5 justify-between max-lg:text-sm">
|
|
@@ -242,7 +221,7 @@ export const AskAi = ({
|
|
| 242 |
? "Uploading images..."
|
| 243 |
: isAiWorking && !isSameHtml
|
| 244 |
? "DeepSite is working..."
|
| 245 |
-
: "DeepSite is
|
| 246 |
}
|
| 247 |
/>
|
| 248 |
{isAiWorking && (
|
|
@@ -272,8 +251,6 @@ export const AskAi = ({
|
|
| 272 |
placeholder={
|
| 273 |
selectedElement
|
| 274 |
? `Ask DeepSite about ${selectedElement.tagName.toLowerCase()}...`
|
| 275 |
-
: redesignData
|
| 276 |
-
? "Ask DeepSite anything about the redesign of your site..."
|
| 277 |
: isFollowUp && (!isSameHtml || pages?.length > 1)
|
| 278 |
? "Ask DeepSite for edits"
|
| 279 |
: "Ask DeepSite anything..."
|
|
@@ -318,13 +295,7 @@ export const AskAi = ({
|
|
| 318 |
onClose={setOpenProvider}
|
| 319 |
/>
|
| 320 |
{!isNew && <Uploader project={project} />}
|
| 321 |
-
{isNew && (
|
| 322 |
-
<ReImagine
|
| 323 |
-
onRedesign={(md, url) =>
|
| 324 |
-
setRedesignData({ markdown: md, url: url })
|
| 325 |
-
}
|
| 326 |
-
/>
|
| 327 |
-
)}
|
| 328 |
{!isNew && !isSameHtml && <Selector />}
|
| 329 |
</div>
|
| 330 |
<div className="flex items-center justify-end gap-2">
|
|
@@ -333,11 +304,9 @@ export const AskAi = ({
|
|
| 333 |
variant="outline"
|
| 334 |
className="!rounded-md"
|
| 335 |
disabled={
|
| 336 |
-
|
| 337 |
-
? false
|
| 338 |
-
: isAiWorking || isUploading || isThinking || !prompt.trim()
|
| 339 |
}
|
| 340 |
-
onClick={() => callAi(
|
| 341 |
>
|
| 342 |
<ArrowUp className="size-4" />
|
| 343 |
</Button>
|
|
|
|
| 22 |
import { useProModal } from "@/components/contexts/pro-context";
|
| 23 |
import { MAX_FREE_PROJECTS } from "@/lib/utils";
|
| 24 |
import { PROMPTS_FOR_AI } from "@/lib/prompts";
|
|
|
|
| 25 |
|
| 26 |
export const AskAi = ({
|
| 27 |
project,
|
|
|
|
| 38 |
const {
|
| 39 |
isAiWorking,
|
| 40 |
isThinking,
|
|
|
|
| 41 |
selectedFiles,
|
| 42 |
setSelectedFiles,
|
| 43 |
selectedElement,
|
|
|
|
| 51 |
const { openProModal } = useProModal();
|
| 52 |
const [openProvider, setOpenProvider] = useState(false);
|
| 53 |
const [providerError, setProviderError] = useState("");
|
|
|
|
|
|
|
|
|
|
| 54 |
const refThink = useRef<HTMLDivElement>(null);
|
| 55 |
|
| 56 |
const [enhancedSettings, setEnhancedSettings, removeEnhancedSettings] =
|
|
|
|
| 66 |
const [prompt, setPrompt] = useState(
|
| 67 |
promptStorage && promptStorage.trim() !== "" ? promptStorage : ""
|
| 68 |
);
|
| 69 |
+
const [think, setThink] = useState("");
|
| 70 |
const [openThink, setOpenThink] = useState(false);
|
| 71 |
const [randomPromptLoading, setRandomPromptLoading] = useState(false);
|
| 72 |
|
|
|
|
| 76 |
}
|
| 77 |
});
|
| 78 |
|
| 79 |
+
const callAi = async (redesignMarkdown?: string) => {
|
| 80 |
removePromptStorage();
|
| 81 |
if (user && !user.isPro && projects.length >= MAX_FREE_PROJECTS)
|
| 82 |
return openProModal([]);
|
|
|
|
| 142 |
if (refThink.current) {
|
| 143 |
refThink.current.scrollTop = refThink.current.scrollHeight;
|
| 144 |
}
|
| 145 |
+
}, [think]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
const randomPrompt = () => {
|
| 148 |
setRandomPromptLoading(true);
|
|
|
|
| 157 |
return (
|
| 158 |
<div className="p-3 w-full">
|
| 159 |
<div className="relative bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent z-20 w-full group">
|
| 160 |
+
{think && (
|
| 161 |
<div className="w-full border-b border-neutral-700 relative overflow-hidden">
|
| 162 |
<header
|
| 163 |
className="flex items-center justify-between px-5 py-2.5 group hover:bg-neutral-600/20 transition-colors duration-200 cursor-pointer"
|
|
|
|
| 189 |
)}
|
| 190 |
>
|
| 191 |
<p className="text-[13px] text-neutral-400 whitespace-pre-line px-5 pb-4 pt-3">
|
| 192 |
+
{think}
|
| 193 |
</p>
|
| 194 |
</main>
|
| 195 |
</div>
|
|
|
|
| 210 |
/>
|
| 211 |
</div>
|
| 212 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
<div className="w-full relative flex items-center justify-between">
|
| 214 |
{(isAiWorking || isUploading || isThinking || isLoadingProject) && (
|
| 215 |
<div className="absolute bg-neutral-800 top-0 left-4 w-[calc(100%-30px)] h-full z-1 flex items-start pt-3.5 justify-between max-lg:text-sm">
|
|
|
|
| 221 |
? "Uploading images..."
|
| 222 |
: isAiWorking && !isSameHtml
|
| 223 |
? "DeepSite is working..."
|
| 224 |
+
: "DeepSite is thinking..."
|
| 225 |
}
|
| 226 |
/>
|
| 227 |
{isAiWorking && (
|
|
|
|
| 251 |
placeholder={
|
| 252 |
selectedElement
|
| 253 |
? `Ask DeepSite about ${selectedElement.tagName.toLowerCase()}...`
|
|
|
|
|
|
|
| 254 |
: isFollowUp && (!isSameHtml || pages?.length > 1)
|
| 255 |
? "Ask DeepSite for edits"
|
| 256 |
: "Ask DeepSite anything..."
|
|
|
|
| 295 |
onClose={setOpenProvider}
|
| 296 |
/>
|
| 297 |
{!isNew && <Uploader project={project} />}
|
| 298 |
+
{isNew && <ReImagine onRedesign={(md) => callAi(md)} />}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
{!isNew && !isSameHtml && <Selector />}
|
| 300 |
</div>
|
| 301 |
<div className="flex items-center justify-end gap-2">
|
|
|
|
| 304 |
variant="outline"
|
| 305 |
className="!rounded-md"
|
| 306 |
disabled={
|
| 307 |
+
isAiWorking || isUploading || isThinking || !prompt.trim()
|
|
|
|
|
|
|
| 308 |
}
|
| 309 |
+
onClick={() => callAi()}
|
| 310 |
>
|
| 311 |
<ArrowUp className="size-4" />
|
| 312 |
</Button>
|
components/editor/ask-ai/prompt-builder/content-modal.tsx
CHANGED
|
@@ -16,7 +16,7 @@ export const ContentModal = ({
|
|
| 16 |
}) => {
|
| 17 |
const [collapsed, setCollapsed] = useState(["colors", "theme"]);
|
| 18 |
return (
|
| 19 |
-
<main className="overflow-x-hidden max-h-[50dvh] overflow-y-auto
|
| 20 |
<section className="w-full border-b border-neutral-800/80 px-6 py-3.5 sticky top-0 bg-neutral-900 z-10">
|
| 21 |
<div className="flex items-center justify-between gap-3">
|
| 22 |
<p className="text-base font-semibold text-neutral-200">
|
|
|
|
| 16 |
}) => {
|
| 17 |
const [collapsed, setCollapsed] = useState(["colors", "theme"]);
|
| 18 |
return (
|
| 19 |
+
<main className="overflow-x-hidden max-h-[50dvh] overflow-y-auto">
|
| 20 |
<section className="w-full border-b border-neutral-800/80 px-6 py-3.5 sticky top-0 bg-neutral-900 z-10">
|
| 21 |
<div className="flex items-center justify-between gap-3">
|
| 22 |
<p className="text-base font-semibold text-neutral-200">
|
components/editor/ask-ai/prompt-builder/tailwind-colors.tsx
CHANGED
|
@@ -26,7 +26,7 @@ export const TailwindColors = ({
|
|
| 26 |
return (
|
| 27 |
<div
|
| 28 |
ref={ref}
|
| 29 |
-
className="flex items-center justify-start gap-3 overflow-x-auto px-5
|
| 30 |
>
|
| 31 |
{TAILWIND_COLORS.map((color) => (
|
| 32 |
<div
|
|
|
|
| 26 |
return (
|
| 27 |
<div
|
| 28 |
ref={ref}
|
| 29 |
+
className="flex items-center justify-start gap-3 overflow-x-auto px-5 scrollbar-hide"
|
| 30 |
>
|
| 31 |
{TAILWIND_COLORS.map((color) => (
|
| 32 |
<div
|
components/editor/ask-ai/re-imagine.tsx
CHANGED
|
@@ -13,12 +13,11 @@ import Loading from "@/components/loading";
|
|
| 13 |
import { api } from "@/lib/api";
|
| 14 |
import { useAi } from "@/hooks/useAi";
|
| 15 |
import { useEditor } from "@/hooks/useEditor";
|
| 16 |
-
import classNames from "classnames";
|
| 17 |
|
| 18 |
export function ReImagine({
|
| 19 |
onRedesign,
|
| 20 |
}: {
|
| 21 |
-
onRedesign: (md: string
|
| 22 |
}) {
|
| 23 |
const [url, setUrl] = useState<string>("");
|
| 24 |
const [open, setOpen] = useState(false);
|
|
@@ -50,8 +49,8 @@ export function ReImagine({
|
|
| 50 |
});
|
| 51 |
if (response?.data?.ok) {
|
| 52 |
setOpen(false);
|
| 53 |
-
onRedesign(response.data.markdown, url.trim());
|
| 54 |
setUrl("");
|
|
|
|
| 55 |
toast.success("DeepSite is redesigning your site! Let him cook... 🔥");
|
| 56 |
} else {
|
| 57 |
toast.error(response?.data?.error || "Failed to redesign the site.");
|
|
@@ -118,11 +117,6 @@ export function ReImagine({
|
|
| 118 |
}
|
| 119 |
setUrl(inputUrl);
|
| 120 |
}}
|
| 121 |
-
onKeyDown={(e) => {
|
| 122 |
-
if (e.key === "Enter") {
|
| 123 |
-
handleClick();
|
| 124 |
-
}
|
| 125 |
-
}}
|
| 126 |
className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
|
| 127 |
/>
|
| 128 |
</div>
|
|
|
|
| 13 |
import { api } from "@/lib/api";
|
| 14 |
import { useAi } from "@/hooks/useAi";
|
| 15 |
import { useEditor } from "@/hooks/useEditor";
|
|
|
|
| 16 |
|
| 17 |
export function ReImagine({
|
| 18 |
onRedesign,
|
| 19 |
}: {
|
| 20 |
+
onRedesign: (md: string) => void;
|
| 21 |
}) {
|
| 22 |
const [url, setUrl] = useState<string>("");
|
| 23 |
const [open, setOpen] = useState(false);
|
|
|
|
| 49 |
});
|
| 50 |
if (response?.data?.ok) {
|
| 51 |
setOpen(false);
|
|
|
|
| 52 |
setUrl("");
|
| 53 |
+
onRedesign(response.data.markdown);
|
| 54 |
toast.success("DeepSite is redesigning your site! Let him cook... 🔥");
|
| 55 |
} else {
|
| 56 |
toast.error(response?.data?.error || "Failed to redesign the site.");
|
|
|
|
| 117 |
}
|
| 118 |
setUrl(inputUrl);
|
| 119 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
|
| 121 |
/>
|
| 122 |
</div>
|
components/editor/ask-ai/selected-redesign-url.tsx
DELETED
|
@@ -1,37 +0,0 @@
|
|
| 1 |
-
import classNames from "classnames";
|
| 2 |
-
import { Paintbrush, XCircle } from "lucide-react";
|
| 3 |
-
|
| 4 |
-
export const SelectedRedesignUrl = ({
|
| 5 |
-
url,
|
| 6 |
-
isAiWorking = false,
|
| 7 |
-
onDelete,
|
| 8 |
-
}: {
|
| 9 |
-
url: string;
|
| 10 |
-
isAiWorking: boolean;
|
| 11 |
-
onDelete?: () => void;
|
| 12 |
-
}) => {
|
| 13 |
-
return (
|
| 14 |
-
<div
|
| 15 |
-
className={classNames(
|
| 16 |
-
"border border-emerald-500/50 bg-emerald-500/10 rounded-xl p-1.5 pr-3 max-w-max hover:brightness-110 transition-all duration-200 ease-in-out",
|
| 17 |
-
{
|
| 18 |
-
"cursor-pointer": !isAiWorking,
|
| 19 |
-
"opacity-50 cursor-not-allowed": isAiWorking,
|
| 20 |
-
}
|
| 21 |
-
)}
|
| 22 |
-
onClick={() => {
|
| 23 |
-
if (!isAiWorking && onDelete) {
|
| 24 |
-
onDelete();
|
| 25 |
-
}
|
| 26 |
-
}}
|
| 27 |
-
>
|
| 28 |
-
<div className="flex items-center justify-start gap-2">
|
| 29 |
-
<div className="rounded-lg bg-emerald-500/20 size-6 flex items-center justify-center">
|
| 30 |
-
<Paintbrush className="text-emerald-300 size-3.5" />
|
| 31 |
-
</div>
|
| 32 |
-
<p className="text-sm font-semibold text-emerald-200">{url}</p>
|
| 33 |
-
<XCircle className="text-emerald-300 size-4 hover:text-emerald-200 transition-colors" />
|
| 34 |
-
</div>
|
| 35 |
-
</div>
|
| 36 |
-
);
|
| 37 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/editor/ask-ai/settings.tsx
CHANGED
|
@@ -20,14 +20,7 @@ import {
|
|
| 20 |
import { useMemo, useState, useEffect } from "react";
|
| 21 |
import { useUpdateEffect } from "react-use";
|
| 22 |
import Image from "next/image";
|
| 23 |
-
import {
|
| 24 |
-
BrainIcon,
|
| 25 |
-
CheckCheck,
|
| 26 |
-
ChevronDown,
|
| 27 |
-
Sparkles,
|
| 28 |
-
Zap,
|
| 29 |
-
DollarSign,
|
| 30 |
-
} from "lucide-react";
|
| 31 |
import { useAi } from "@/hooks/useAi";
|
| 32 |
import { getProviders } from "@/lib/get-providers";
|
| 33 |
import Loading from "@/components/loading";
|
|
@@ -69,10 +62,7 @@ export function Settings({
|
|
| 69 |
// }, [model]);
|
| 70 |
|
| 71 |
useUpdateEffect(() => {
|
| 72 |
-
if (
|
| 73 |
-
!["auto", "fastest", "cheapest"].includes(provider as string) &&
|
| 74 |
-
!providers.includes(provider as string)
|
| 75 |
-
) {
|
| 76 |
setProvider("auto");
|
| 77 |
}
|
| 78 |
}, [model, provider]);
|
|
@@ -215,83 +205,47 @@ export function Settings({
|
|
| 215 |
</div>
|
| 216 |
)} */}
|
| 217 |
<div className="flex flex-col gap-3">
|
| 218 |
-
<div>
|
| 219 |
-
<
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
(
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
<Sparkles
|
| 248 |
-
className={classNames("size-3.5 mb-0.5", {
|
| 249 |
-
"text-pink-400": provider !== "auto",
|
| 250 |
-
})}
|
| 251 |
-
/>
|
| 252 |
-
<span className="text-[10px] font-medium">Auto</span>
|
| 253 |
-
</button>
|
| 254 |
-
<button
|
| 255 |
-
className={classNames(
|
| 256 |
-
"flex flex-col items-center justify-center cursor-pointer py-1.5 rounded-full transition-all duration-200",
|
| 257 |
-
{
|
| 258 |
-
"bg-white text-neutral-800": provider === "fastest",
|
| 259 |
-
"text-neutral-400 hover:text-neutral-200":
|
| 260 |
-
provider !== "fastest",
|
| 261 |
-
}
|
| 262 |
-
)}
|
| 263 |
-
onClick={() => setProvider("fastest")}
|
| 264 |
-
>
|
| 265 |
-
<Zap
|
| 266 |
-
className={classNames("size-3.5 mb-0.5", {
|
| 267 |
-
"text-yellow-400": provider !== "fastest",
|
| 268 |
-
})}
|
| 269 |
-
/>
|
| 270 |
-
<span className="text-[10px] font-medium">Fastest</span>
|
| 271 |
-
</button>
|
| 272 |
-
<button
|
| 273 |
className={classNames(
|
| 274 |
-
"
|
| 275 |
{
|
| 276 |
-
"
|
| 277 |
-
"text-neutral-400 hover:text-neutral-200":
|
| 278 |
-
provider !== "cheapest",
|
| 279 |
}
|
| 280 |
)}
|
| 281 |
-
|
| 282 |
-
>
|
| 283 |
-
<DollarSign
|
| 284 |
-
className={classNames("size-3.5 mb-0.5", {
|
| 285 |
-
"text-green-400": provider !== "cheapest",
|
| 286 |
-
})}
|
| 287 |
-
/>
|
| 288 |
-
<span className="text-[10px] font-medium">Cheapest</span>
|
| 289 |
-
</button>
|
| 290 |
</div>
|
| 291 |
</div>
|
| 292 |
<label className="block">
|
| 293 |
<p className="text-neutral-300 text-sm mb-2">
|
| 294 |
-
|
| 295 |
</p>
|
| 296 |
<div className="grid grid-cols-2 gap-1.5 relative">
|
| 297 |
{loadingProviders ? (
|
|
|
|
| 20 |
import { useMemo, useState, useEffect } from "react";
|
| 21 |
import { useUpdateEffect } from "react-use";
|
| 22 |
import Image from "next/image";
|
| 23 |
+
import { Brain, BrainIcon, CheckCheck, ChevronDown } from "lucide-react";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
import { useAi } from "@/hooks/useAi";
|
| 25 |
import { getProviders } from "@/lib/get-providers";
|
| 26 |
import Loading from "@/components/loading";
|
|
|
|
| 62 |
// }, [model]);
|
| 63 |
|
| 64 |
useUpdateEffect(() => {
|
| 65 |
+
if (provider !== "auto" && !providers.includes(provider as string)) {
|
|
|
|
|
|
|
|
|
|
| 66 |
setProvider("auto");
|
| 67 |
}
|
| 68 |
}, [model, provider]);
|
|
|
|
| 205 |
</div>
|
| 206 |
)} */}
|
| 207 |
<div className="flex flex-col gap-3">
|
| 208 |
+
<div className="flex items-center justify-between">
|
| 209 |
+
<div>
|
| 210 |
+
<p className="text-neutral-300 text-sm mb-1.5">
|
| 211 |
+
Use auto-provider
|
| 212 |
+
</p>
|
| 213 |
+
<p className="text-xs text-neutral-400/70">
|
| 214 |
+
We'll automatically select the best provider for you
|
| 215 |
+
based on your prompt.
|
| 216 |
+
</p>
|
| 217 |
+
</div>
|
| 218 |
+
<div
|
| 219 |
+
className={classNames(
|
| 220 |
+
"bg-neutral-700 rounded-full min-w-10 w-10 h-6 flex items-center justify-between p-1 cursor-pointer transition-all duration-200",
|
| 221 |
+
{
|
| 222 |
+
"!bg-sky-500": provider === "auto",
|
| 223 |
+
}
|
| 224 |
+
)}
|
| 225 |
+
onClick={() => {
|
| 226 |
+
const foundModel = MODELS.find(
|
| 227 |
+
(m: { value: string }) => m.value === model
|
| 228 |
+
);
|
| 229 |
+
if (provider === "auto" && foundModel?.autoProvider) {
|
| 230 |
+
setProvider(foundModel.autoProvider);
|
| 231 |
+
} else {
|
| 232 |
+
setProvider("auto");
|
| 233 |
+
}
|
| 234 |
+
}}
|
| 235 |
+
>
|
| 236 |
+
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
className={classNames(
|
| 238 |
+
"w-4 h-4 rounded-full shadow-md transition-all duration-200 bg-neutral-200",
|
| 239 |
{
|
| 240 |
+
"translate-x-4": provider === "auto",
|
|
|
|
|
|
|
| 241 |
}
|
| 242 |
)}
|
| 243 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
</div>
|
| 245 |
</div>
|
| 246 |
<label className="block">
|
| 247 |
<p className="text-neutral-300 text-sm mb-2">
|
| 248 |
+
Inference Provider
|
| 249 |
</p>
|
| 250 |
<div className="grid grid-cols-2 gap-1.5 relative">
|
| 251 |
{loadingProviders ? (
|
components/editor/file-browser/index.tsx
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
| 7 |
Folder,
|
| 8 |
ChevronRight,
|
| 9 |
ChevronDown,
|
| 10 |
-
FileJson,
|
| 11 |
} from "lucide-react";
|
| 12 |
import classNames from "classnames";
|
| 13 |
|
|
@@ -198,7 +197,27 @@ export function FileBrowser() {
|
|
| 198 |
</svg>
|
| 199 |
);
|
| 200 |
case "json":
|
| 201 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
default:
|
| 203 |
return <FileCode2 className="size-4 shrink-0 text-neutral-400" />;
|
| 204 |
}
|
|
@@ -230,7 +249,7 @@ export function FileBrowser() {
|
|
| 230 |
case "json":
|
| 231 |
return {
|
| 232 |
name: "JSON",
|
| 233 |
-
color: "bg-
|
| 234 |
};
|
| 235 |
default:
|
| 236 |
return {
|
|
@@ -439,13 +458,6 @@ export function FileBrowser() {
|
|
| 439 |
JS: {pages.filter((p) => p.path.endsWith(".js")).length}
|
| 440 |
</span>
|
| 441 |
</div>
|
| 442 |
-
<div className="flex items-center gap-2 text-neutral-500">
|
| 443 |
-
<div className="size-2 rounded-full bg-yellow-600" />
|
| 444 |
-
<span>
|
| 445 |
-
JSON:{" "}
|
| 446 |
-
{pages.filter((p) => p.path.endsWith(".json")).length}
|
| 447 |
-
</span>
|
| 448 |
-
</div>
|
| 449 |
</div>
|
| 450 |
</div>
|
| 451 |
</SheetContent>
|
|
|
|
| 7 |
Folder,
|
| 8 |
ChevronRight,
|
| 9 |
ChevronDown,
|
|
|
|
| 10 |
} from "lucide-react";
|
| 11 |
import classNames from "classnames";
|
| 12 |
|
|
|
|
| 197 |
</svg>
|
| 198 |
);
|
| 199 |
case "json":
|
| 200 |
+
return (
|
| 201 |
+
<svg className="size-4 shrink-0" viewBox="0 0 32 32" fill="none">
|
| 202 |
+
<rect width="32" height="32" rx="2" fill="#F7DF1E" />
|
| 203 |
+
<path
|
| 204 |
+
d="M16 2L4 8v16l12 6 12-6V8L16 2zm8.8 20.4l-8.8 4.4-8.8-4.4V9.6l8.8-4.4 8.8 4.4v12.8z"
|
| 205 |
+
fill="#000"
|
| 206 |
+
opacity="0.15"
|
| 207 |
+
/>
|
| 208 |
+
<text
|
| 209 |
+
x="50%"
|
| 210 |
+
y="50%"
|
| 211 |
+
dominantBaseline="middle"
|
| 212 |
+
textAnchor="middle"
|
| 213 |
+
fill="#000"
|
| 214 |
+
fontSize="14"
|
| 215 |
+
fontWeight="600"
|
| 216 |
+
>
|
| 217 |
+
{}
|
| 218 |
+
</text>
|
| 219 |
+
</svg>
|
| 220 |
+
);
|
| 221 |
default:
|
| 222 |
return <FileCode2 className="size-4 shrink-0 text-neutral-400" />;
|
| 223 |
}
|
|
|
|
| 249 |
case "json":
|
| 250 |
return {
|
| 251 |
name: "JSON",
|
| 252 |
+
color: "bg-yellow-500/20 border-yellow-500/30 text-yellow-400",
|
| 253 |
};
|
| 254 |
default:
|
| 255 |
return {
|
|
|
|
| 458 |
JS: {pages.filter((p) => p.path.endsWith(".js")).length}
|
| 459 |
</span>
|
| 460 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
</div>
|
| 462 |
</div>
|
| 463 |
</SheetContent>
|
components/editor/header/index.tsx
CHANGED
|
@@ -23,9 +23,8 @@ import {
|
|
| 23 |
TooltipContent,
|
| 24 |
TooltipTrigger,
|
| 25 |
} from "@/components/ui/tooltip";
|
| 26 |
-
import { DiscordIcon } from "@/components/icons/discord";
|
| 27 |
|
| 28 |
-
export function Header(
|
| 29 |
const { project } = useEditor();
|
| 30 |
const { user, openLoginWindow } = useUser();
|
| 31 |
return (
|
|
@@ -60,26 +59,24 @@ export function Header({ isNew }: { isNew: boolean }) {
|
|
| 60 |
<div className="lg:w-full px-2 lg:px-3 py-2 flex items-center justify-end lg:justify-between lg:border-l lg:border-neutral-800">
|
| 61 |
<div className="font-mono text-muted-foreground flex items-center gap-2">
|
| 62 |
<SwitchDevice />
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
</Button>
|
| 80 |
-
)}
|
| 81 |
<Link
|
| 82 |
-
href="https://
|
| 83 |
target="_blank"
|
| 84 |
className="max-lg:hidden"
|
| 85 |
>
|
|
@@ -88,20 +85,6 @@ export function Header({ isNew }: { isNew: boolean }) {
|
|
| 88 |
Help
|
| 89 |
</Button>
|
| 90 |
</Link>
|
| 91 |
-
<Link
|
| 92 |
-
href="https://discord.gg/KpanwM3vXa"
|
| 93 |
-
target="_blank"
|
| 94 |
-
className="max-lg:hidden"
|
| 95 |
-
>
|
| 96 |
-
<Button
|
| 97 |
-
size="xs"
|
| 98 |
-
variant="bordered"
|
| 99 |
-
className="!border-indigo-500 !text-white !bg-indigo-500"
|
| 100 |
-
>
|
| 101 |
-
<DiscordIcon className="size-3 mr-0.5" />
|
| 102 |
-
Discord Community
|
| 103 |
-
</Button>
|
| 104 |
-
</Link>
|
| 105 |
</div>
|
| 106 |
<div className="flex items-center gap-2">
|
| 107 |
{project?.space_id && (
|
|
@@ -127,7 +110,6 @@ export function Header({ isNew }: { isNew: boolean }) {
|
|
| 127 |
</Button>
|
| 128 |
</Link>
|
| 129 |
)}
|
| 130 |
-
|
| 131 |
{project?.private && (
|
| 132 |
<Tooltip>
|
| 133 |
<TooltipTrigger>
|
|
|
|
| 23 |
TooltipContent,
|
| 24 |
TooltipTrigger,
|
| 25 |
} from "@/components/ui/tooltip";
|
|
|
|
| 26 |
|
| 27 |
+
export function Header() {
|
| 28 |
const { project } = useEditor();
|
| 29 |
const { user, openLoginWindow } = useUser();
|
| 30 |
return (
|
|
|
|
| 59 |
<div className="lg:w-full px-2 lg:px-3 py-2 flex items-center justify-end lg:justify-between lg:border-l lg:border-neutral-800">
|
| 60 |
<div className="font-mono text-muted-foreground flex items-center gap-2">
|
| 61 |
<SwitchDevice />
|
| 62 |
+
<Button
|
| 63 |
+
size="xs"
|
| 64 |
+
variant="bordered"
|
| 65 |
+
className="max-lg:hidden"
|
| 66 |
+
onClick={() => {
|
| 67 |
+
const iframe = document.getElementById(
|
| 68 |
+
"preview-iframe"
|
| 69 |
+
) as HTMLIFrameElement;
|
| 70 |
+
if (iframe) {
|
| 71 |
+
iframe.src = iframe.src;
|
| 72 |
+
}
|
| 73 |
+
}}
|
| 74 |
+
>
|
| 75 |
+
<RefreshCcw className="size-3 mr-0.5" />
|
| 76 |
+
Refresh Preview
|
| 77 |
+
</Button>
|
|
|
|
|
|
|
| 78 |
<Link
|
| 79 |
+
href="https://huggingface.co/spaces/enzostvs/deepsite/discussions/427"
|
| 80 |
target="_blank"
|
| 81 |
className="max-lg:hidden"
|
| 82 |
>
|
|
|
|
| 85 |
Help
|
| 86 |
</Button>
|
| 87 |
</Link>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
</div>
|
| 89 |
<div className="flex items-center gap-2">
|
| 90 |
{project?.space_id && (
|
|
|
|
| 110 |
</Button>
|
| 111 |
</Link>
|
| 112 |
)}
|
|
|
|
| 113 |
{project?.private && (
|
| 114 |
<Tooltip>
|
| 115 |
<TooltipTrigger>
|
components/editor/index.tsx
CHANGED
|
@@ -15,7 +15,6 @@ import { FileBrowser } from "./file-browser";
|
|
| 15 |
import { AskAi } from "./ask-ai";
|
| 16 |
import { Preview } from "./preview";
|
| 17 |
import { SaveChangesPopup } from "./save-changes-popup";
|
| 18 |
-
import { DiscordPromoModal } from "@/components/discord-promo-modal";
|
| 19 |
import Loading from "../loading";
|
| 20 |
import { Page } from "@/types";
|
| 21 |
|
|
@@ -69,7 +68,6 @@ export const AppEditor = ({
|
|
| 69 |
const path = currentPageData.path;
|
| 70 |
if (path.endsWith(".css")) return "css";
|
| 71 |
if (path.endsWith(".js")) return "javascript";
|
| 72 |
-
if (path.endsWith(".json")) return "json";
|
| 73 |
return "html";
|
| 74 |
}, [currentPageData.path]);
|
| 75 |
|
|
@@ -78,13 +76,12 @@ export const AppEditor = ({
|
|
| 78 |
if (editorLanguage === "css") return "CSS copied to clipboard!";
|
| 79 |
if (editorLanguage === "javascript")
|
| 80 |
return "JavaScript copied to clipboard!";
|
| 81 |
-
if (editorLanguage === "json") return "JSON copied to clipboard!";
|
| 82 |
return "HTML copied to clipboard!";
|
| 83 |
}, [editorLanguage]);
|
| 84 |
|
| 85 |
return (
|
| 86 |
<section className="h-screen w-full bg-neutral-950 flex flex-col">
|
| 87 |
-
<Header
|
| 88 |
<main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full relative">
|
| 89 |
<div
|
| 90 |
ref={editor}
|
|
@@ -156,9 +153,10 @@ export const AppEditor = ({
|
|
| 156 |
}}
|
| 157 |
/>
|
| 158 |
</div>
|
| 159 |
-
<Preview isNew={isNew}
|
| 160 |
</main>
|
| 161 |
|
|
|
|
| 162 |
<SaveChangesPopup
|
| 163 |
isOpen={showSavePopup}
|
| 164 |
onClose={() => setShowSavePopup(false)}
|
|
@@ -167,8 +165,6 @@ export const AppEditor = ({
|
|
| 167 |
pages={pages}
|
| 168 |
project={project}
|
| 169 |
/>
|
| 170 |
-
|
| 171 |
-
<DiscordPromoModal />
|
| 172 |
</section>
|
| 173 |
);
|
| 174 |
};
|
|
|
|
| 15 |
import { AskAi } from "./ask-ai";
|
| 16 |
import { Preview } from "./preview";
|
| 17 |
import { SaveChangesPopup } from "./save-changes-popup";
|
|
|
|
| 18 |
import Loading from "../loading";
|
| 19 |
import { Page } from "@/types";
|
| 20 |
|
|
|
|
| 68 |
const path = currentPageData.path;
|
| 69 |
if (path.endsWith(".css")) return "css";
|
| 70 |
if (path.endsWith(".js")) return "javascript";
|
|
|
|
| 71 |
return "html";
|
| 72 |
}, [currentPageData.path]);
|
| 73 |
|
|
|
|
| 76 |
if (editorLanguage === "css") return "CSS copied to clipboard!";
|
| 77 |
if (editorLanguage === "javascript")
|
| 78 |
return "JavaScript copied to clipboard!";
|
|
|
|
| 79 |
return "HTML copied to clipboard!";
|
| 80 |
}, [editorLanguage]);
|
| 81 |
|
| 82 |
return (
|
| 83 |
<section className="h-screen w-full bg-neutral-950 flex flex-col">
|
| 84 |
+
<Header />
|
| 85 |
<main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full relative">
|
| 86 |
<div
|
| 87 |
ref={editor}
|
|
|
|
| 153 |
}}
|
| 154 |
/>
|
| 155 |
</div>
|
| 156 |
+
<Preview isNew={isNew} />
|
| 157 |
</main>
|
| 158 |
|
| 159 |
+
{/* Save Changes Popup */}
|
| 160 |
<SaveChangesPopup
|
| 161 |
isOpen={showSavePopup}
|
| 162 |
onClose={() => setShowSavePopup(false)}
|
|
|
|
| 165 |
pages={pages}
|
| 166 |
project={project}
|
| 167 |
/>
|
|
|
|
|
|
|
| 168 |
</section>
|
| 169 |
);
|
| 170 |
};
|
components/editor/preview/index.tsx
CHANGED
|
@@ -15,18 +15,8 @@ import { defaultHTML } from "@/lib/consts";
|
|
| 15 |
import { HistoryNotification } from "../history-notification";
|
| 16 |
import { api } from "@/lib/api";
|
| 17 |
import { toast } from "sonner";
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
export const Preview = ({
|
| 22 |
-
isNew,
|
| 23 |
-
namespace,
|
| 24 |
-
repoId,
|
| 25 |
-
}: {
|
| 26 |
-
isNew: boolean;
|
| 27 |
-
namespace?: string;
|
| 28 |
-
repoId?: string;
|
| 29 |
-
}) => {
|
| 30 |
const {
|
| 31 |
project,
|
| 32 |
device,
|
|
@@ -40,8 +30,6 @@ export const Preview = ({
|
|
| 40 |
setCurrentPage,
|
| 41 |
previewPage,
|
| 42 |
setPreviewPage,
|
| 43 |
-
setLastSavedPages,
|
| 44 |
-
hasUnsavedChanges,
|
| 45 |
} = useEditor();
|
| 46 |
const {
|
| 47 |
isEditableModeEnabled,
|
|
@@ -60,10 +48,6 @@ export const Preview = ({
|
|
| 60 |
const [stableHtml, setStableHtml] = useState<string>("");
|
| 61 |
const [throttledHtml, setThrottledHtml] = useState<string>("");
|
| 62 |
const lastUpdateTimeRef = useRef<number>(0);
|
| 63 |
-
const [iframeKey, setIframeKey] = useState(0);
|
| 64 |
-
const [commitPages, setCommitPages] = useState<Page[]>([]);
|
| 65 |
-
const [isLoadingCommitPages, setIsLoadingCommitPages] = useState(false);
|
| 66 |
-
const prevCommitRef = useRef<string | null>(null);
|
| 67 |
|
| 68 |
useEffect(() => {
|
| 69 |
if (!previewPage && pages.length > 0) {
|
|
@@ -75,189 +59,28 @@ export const Preview = ({
|
|
| 75 |
}
|
| 76 |
}, [pages, previewPage]);
|
| 77 |
|
| 78 |
-
const pagesToUse = currentCommit ? commitPages : pages;
|
| 79 |
-
|
| 80 |
const previewPageData = useMemo(() => {
|
| 81 |
-
const found =
|
| 82 |
const normalizedPagePath = p.path.replace(/^\.?\//, "");
|
| 83 |
const normalizedPreviewPage = previewPage.replace(/^\.?\//, "");
|
| 84 |
return normalizedPagePath === normalizedPreviewPage;
|
| 85 |
});
|
| 86 |
-
return found ||
|
| 87 |
-
}, [
|
| 88 |
-
|
| 89 |
-
// Fetch commit pages when currentCommit changes
|
| 90 |
-
useEffect(() => {
|
| 91 |
-
if (currentCommit && namespace && repoId) {
|
| 92 |
-
setIsLoadingCommitPages(true);
|
| 93 |
-
api
|
| 94 |
-
.get(`/me/projects/${namespace}/${repoId}/commits/${currentCommit}`)
|
| 95 |
-
.then((res) => {
|
| 96 |
-
if (res.data.ok) {
|
| 97 |
-
setCommitPages(res.data.pages);
|
| 98 |
-
// Set preview page to index.html if available
|
| 99 |
-
const indexPage = res.data.pages.find(
|
| 100 |
-
(p: Page) =>
|
| 101 |
-
p.path === "index.html" || p.path === "index" || p.path === "/"
|
| 102 |
-
);
|
| 103 |
-
if (indexPage) {
|
| 104 |
-
setPreviewPage(indexPage.path);
|
| 105 |
-
}
|
| 106 |
-
// Refresh iframe to show commit version
|
| 107 |
-
setIframeKey((prev) => prev + 1);
|
| 108 |
-
}
|
| 109 |
-
})
|
| 110 |
-
.catch((err) => {
|
| 111 |
-
toast.error(
|
| 112 |
-
err.response?.data?.error || "Failed to fetch commit pages"
|
| 113 |
-
);
|
| 114 |
-
})
|
| 115 |
-
.finally(() => {
|
| 116 |
-
setIsLoadingCommitPages(false);
|
| 117 |
-
});
|
| 118 |
-
} else if (!currentCommit && prevCommitRef.current !== null) {
|
| 119 |
-
// Only clear commitPages when transitioning from a commit to no commit
|
| 120 |
-
setCommitPages([]);
|
| 121 |
-
}
|
| 122 |
-
prevCommitRef.current = currentCommit;
|
| 123 |
-
}, [currentCommit, namespace, repoId]);
|
| 124 |
-
|
| 125 |
-
// Create navigation interception script
|
| 126 |
-
const createNavigationScript = useCallback((availablePages: Page[]) => {
|
| 127 |
-
const pagePaths = availablePages.map((p) => p.path.replace(/^\.?\//, ""));
|
| 128 |
-
return `
|
| 129 |
-
(function() {
|
| 130 |
-
const availablePages = ${JSON.stringify(pagePaths)};
|
| 131 |
-
|
| 132 |
-
function normalizePath(path) {
|
| 133 |
-
let normalized = path.replace(/^\.?\//, "");
|
| 134 |
-
if (normalized === "" || normalized === "/") {
|
| 135 |
-
normalized = "index.html";
|
| 136 |
-
}
|
| 137 |
-
const hashIndex = normalized.indexOf("#");
|
| 138 |
-
if (hashIndex !== -1) {
|
| 139 |
-
normalized = normalized.substring(0, hashIndex);
|
| 140 |
-
}
|
| 141 |
-
if (!normalized.includes(".")) {
|
| 142 |
-
normalized = normalized + ".html";
|
| 143 |
-
}
|
| 144 |
-
return normalized;
|
| 145 |
-
}
|
| 146 |
-
|
| 147 |
-
function handleNavigation(url) {
|
| 148 |
-
if (!url) return;
|
| 149 |
-
|
| 150 |
-
// Handle hash-only navigation
|
| 151 |
-
if (url.startsWith("#")) {
|
| 152 |
-
const targetElement = document.querySelector(url);
|
| 153 |
-
if (targetElement) {
|
| 154 |
-
targetElement.scrollIntoView({ behavior: "smooth" });
|
| 155 |
-
}
|
| 156 |
-
// Search in shadow DOM
|
| 157 |
-
const searchInShadows = (root) => {
|
| 158 |
-
const elements = root.querySelectorAll("*");
|
| 159 |
-
for (const el of elements) {
|
| 160 |
-
if (el.shadowRoot) {
|
| 161 |
-
const found = el.shadowRoot.querySelector(url);
|
| 162 |
-
if (found) {
|
| 163 |
-
found.scrollIntoView({ behavior: "smooth" });
|
| 164 |
-
return;
|
| 165 |
-
}
|
| 166 |
-
searchInShadows(el.shadowRoot);
|
| 167 |
-
}
|
| 168 |
-
}
|
| 169 |
-
};
|
| 170 |
-
searchInShadows(document);
|
| 171 |
-
return;
|
| 172 |
-
}
|
| 173 |
-
|
| 174 |
-
// Handle external URLs
|
| 175 |
-
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
|
| 176 |
-
window.open(url, "_blank");
|
| 177 |
-
return;
|
| 178 |
-
}
|
| 179 |
-
|
| 180 |
-
const normalizedPath = normalizePath(url);
|
| 181 |
-
if (availablePages.includes(normalizedPath)) {
|
| 182 |
-
// Dispatch custom event to notify parent
|
| 183 |
-
window.parent.postMessage({ type: 'navigate', path: normalizedPath }, '*');
|
| 184 |
-
} else {
|
| 185 |
-
console.warn('Page not found:', normalizedPath);
|
| 186 |
-
}
|
| 187 |
-
}
|
| 188 |
-
|
| 189 |
-
// Intercept window.location methods
|
| 190 |
-
const originalAssign = window.location.assign;
|
| 191 |
-
const originalReplace = window.location.replace;
|
| 192 |
-
|
| 193 |
-
window.location.assign = function(url) {
|
| 194 |
-
handleNavigation(url);
|
| 195 |
-
};
|
| 196 |
-
|
| 197 |
-
window.location.replace = function(url) {
|
| 198 |
-
handleNavigation(url);
|
| 199 |
-
};
|
| 200 |
-
|
| 201 |
-
// Intercept window.location.href setter
|
| 202 |
-
try {
|
| 203 |
-
let currentHref = window.location.href;
|
| 204 |
-
Object.defineProperty(window.location, 'href', {
|
| 205 |
-
get: function() {
|
| 206 |
-
return currentHref;
|
| 207 |
-
},
|
| 208 |
-
set: function(url) {
|
| 209 |
-
handleNavigation(url);
|
| 210 |
-
},
|
| 211 |
-
configurable: true
|
| 212 |
-
});
|
| 213 |
-
} catch (e) {
|
| 214 |
-
// Fallback: use proxy if defineProperty fails
|
| 215 |
-
console.warn('Could not intercept location.href:', e);
|
| 216 |
-
}
|
| 217 |
-
|
| 218 |
-
// Intercept link clicks
|
| 219 |
-
document.addEventListener('click', function(e) {
|
| 220 |
-
const anchor = e.target.closest('a');
|
| 221 |
-
if (anchor && anchor.href) {
|
| 222 |
-
const href = anchor.getAttribute('href');
|
| 223 |
-
if (href && !href.startsWith('http://') && !href.startsWith('https://') && !href.startsWith('//') && !href.startsWith('mailto:') && !href.startsWith('tel:')) {
|
| 224 |
-
e.preventDefault();
|
| 225 |
-
handleNavigation(href);
|
| 226 |
-
}
|
| 227 |
-
}
|
| 228 |
-
}, true);
|
| 229 |
-
|
| 230 |
-
// Intercept form submissions
|
| 231 |
-
document.addEventListener('submit', function(e) {
|
| 232 |
-
const form = e.target;
|
| 233 |
-
if (form.action && !form.action.startsWith('http://') && !form.action.startsWith('https://') && !form.action.startsWith('//')) {
|
| 234 |
-
e.preventDefault();
|
| 235 |
-
handleNavigation(form.action);
|
| 236 |
-
}
|
| 237 |
-
}, true);
|
| 238 |
-
})();
|
| 239 |
-
`;
|
| 240 |
-
}, []);
|
| 241 |
|
| 242 |
const injectAssetsIntoHtml = useCallback(
|
| 243 |
-
(html: string
|
| 244 |
if (!html) return html;
|
| 245 |
|
| 246 |
-
const cssFiles =
|
| 247 |
(p) => p.path.endsWith(".css") && p.path !== previewPageData?.path
|
| 248 |
);
|
| 249 |
-
const jsFiles =
|
| 250 |
(p) => p.path.endsWith(".js") && p.path !== previewPageData?.path
|
| 251 |
);
|
| 252 |
-
const jsonFiles = pagesToUse.filter(
|
| 253 |
-
(p) => p.path.endsWith(".json") && p.path !== previewPageData?.path
|
| 254 |
-
);
|
| 255 |
|
| 256 |
let modifiedHtml = html;
|
| 257 |
|
| 258 |
-
// Inject navigation script for srcDoc
|
| 259 |
-
const navigationScript = createNavigationScript(pagesToUse);
|
| 260 |
-
|
| 261 |
// Inject all CSS files
|
| 262 |
if (cssFiles.length > 0) {
|
| 263 |
const allCssContent = cssFiles
|
|
@@ -324,69 +147,9 @@ export const Preview = ({
|
|
| 324 |
});
|
| 325 |
}
|
| 326 |
|
| 327 |
-
// Inject all JSON files as script tags with type="application/json"
|
| 328 |
-
if (jsonFiles.length > 0) {
|
| 329 |
-
const allJsonContent = jsonFiles
|
| 330 |
-
.map(
|
| 331 |
-
(file) =>
|
| 332 |
-
`<script type="application/json" data-injected-from="${
|
| 333 |
-
file.path
|
| 334 |
-
}" id="${file.path.replace(/[^a-zA-Z0-9]/g, "-")}">\n${
|
| 335 |
-
file.html
|
| 336 |
-
}\n</script>`
|
| 337 |
-
)
|
| 338 |
-
.join("\n");
|
| 339 |
-
|
| 340 |
-
if (modifiedHtml.includes("</body>")) {
|
| 341 |
-
modifiedHtml = modifiedHtml.replace(
|
| 342 |
-
"</body>",
|
| 343 |
-
`${allJsonContent}\n</body>`
|
| 344 |
-
);
|
| 345 |
-
} else if (modifiedHtml.includes("<body>")) {
|
| 346 |
-
modifiedHtml = modifiedHtml + allJsonContent;
|
| 347 |
-
} else {
|
| 348 |
-
modifiedHtml = modifiedHtml + "\n" + allJsonContent;
|
| 349 |
-
}
|
| 350 |
-
|
| 351 |
-
jsonFiles.forEach((file) => {
|
| 352 |
-
const escapedPath = file.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
| 353 |
-
modifiedHtml = modifiedHtml.replace(
|
| 354 |
-
new RegExp(
|
| 355 |
-
`<script\\s+[^>]*src=["'][\\.\/]*${escapedPath}["'][^>]*><\\/script>`,
|
| 356 |
-
"gi"
|
| 357 |
-
),
|
| 358 |
-
""
|
| 359 |
-
);
|
| 360 |
-
});
|
| 361 |
-
}
|
| 362 |
-
|
| 363 |
-
// Inject navigation script early in the document
|
| 364 |
-
if (navigationScript) {
|
| 365 |
-
// Try to inject right after <head> or <body> opening tag
|
| 366 |
-
if (modifiedHtml.includes("<head>")) {
|
| 367 |
-
modifiedHtml = modifiedHtml.replace(
|
| 368 |
-
"<head>",
|
| 369 |
-
`<head>\n<script>${navigationScript}</script>`
|
| 370 |
-
);
|
| 371 |
-
} else if (modifiedHtml.includes("<body>")) {
|
| 372 |
-
modifiedHtml = modifiedHtml.replace(
|
| 373 |
-
"<body>",
|
| 374 |
-
`<body>\n<script>${navigationScript}</script>`
|
| 375 |
-
);
|
| 376 |
-
} else if (modifiedHtml.includes("</body>")) {
|
| 377 |
-
modifiedHtml = modifiedHtml.replace(
|
| 378 |
-
"</body>",
|
| 379 |
-
`<script>${navigationScript}</script>\n</body>`
|
| 380 |
-
);
|
| 381 |
-
} else {
|
| 382 |
-
modifiedHtml =
|
| 383 |
-
`<script>${navigationScript}</script>\n` + modifiedHtml;
|
| 384 |
-
}
|
| 385 |
-
}
|
| 386 |
-
|
| 387 |
return modifiedHtml;
|
| 388 |
},
|
| 389 |
-
[pages, previewPageData?.path
|
| 390 |
);
|
| 391 |
|
| 392 |
useEffect(() => {
|
|
@@ -395,33 +158,24 @@ export const Preview = ({
|
|
| 395 |
const timeSinceLastUpdate = now - lastUpdateTimeRef.current;
|
| 396 |
|
| 397 |
if (lastUpdateTimeRef.current === 0 || timeSinceLastUpdate >= 3000) {
|
| 398 |
-
const processedHtml = injectAssetsIntoHtml(
|
| 399 |
-
previewPageData.html,
|
| 400 |
-
pagesToUse
|
| 401 |
-
);
|
| 402 |
setThrottledHtml(processedHtml);
|
| 403 |
lastUpdateTimeRef.current = now;
|
| 404 |
} else {
|
| 405 |
const timeUntilNextUpdate = 3000 - timeSinceLastUpdate;
|
| 406 |
const timer = setTimeout(() => {
|
| 407 |
-
const processedHtml = injectAssetsIntoHtml(
|
| 408 |
-
previewPageData.html,
|
| 409 |
-
pagesToUse
|
| 410 |
-
);
|
| 411 |
setThrottledHtml(processedHtml);
|
| 412 |
lastUpdateTimeRef.current = Date.now();
|
| 413 |
}, timeUntilNextUpdate);
|
| 414 |
return () => clearTimeout(timer);
|
| 415 |
}
|
| 416 |
}
|
| 417 |
-
}, [isNew, previewPageData?.html, injectAssetsIntoHtml
|
| 418 |
|
| 419 |
useEffect(() => {
|
| 420 |
if (!isAiWorking && !globalAiLoading && previewPageData?.html) {
|
| 421 |
-
const processedHtml = injectAssetsIntoHtml(
|
| 422 |
-
previewPageData.html,
|
| 423 |
-
pagesToUse
|
| 424 |
-
);
|
| 425 |
setStableHtml(processedHtml);
|
| 426 |
}
|
| 427 |
}, [
|
|
@@ -430,7 +184,6 @@ export const Preview = ({
|
|
| 430 |
previewPageData?.html,
|
| 431 |
injectAssetsIntoHtml,
|
| 432 |
previewPage,
|
| 433 |
-
pagesToUse,
|
| 434 |
]);
|
| 435 |
|
| 436 |
useEffect(() => {
|
|
@@ -440,10 +193,7 @@ export const Preview = ({
|
|
| 440 |
!isAiWorking &&
|
| 441 |
!globalAiLoading
|
| 442 |
) {
|
| 443 |
-
const processedHtml = injectAssetsIntoHtml(
|
| 444 |
-
previewPageData.html,
|
| 445 |
-
pagesToUse
|
| 446 |
-
);
|
| 447 |
setStableHtml(processedHtml);
|
| 448 |
}
|
| 449 |
}, [
|
|
@@ -452,7 +202,6 @@ export const Preview = ({
|
|
| 452 |
isAiWorking,
|
| 453 |
globalAiLoading,
|
| 454 |
injectAssetsIntoHtml,
|
| 455 |
-
pagesToUse,
|
| 456 |
]);
|
| 457 |
|
| 458 |
const setupIframeListeners = () => {
|
|
@@ -473,17 +222,6 @@ export const Preview = ({
|
|
| 473 |
}
|
| 474 |
};
|
| 475 |
|
| 476 |
-
// Listen for navigation messages from iframe
|
| 477 |
-
useEffect(() => {
|
| 478 |
-
const handleMessage = (event: MessageEvent) => {
|
| 479 |
-
if (event.data?.type === "navigate" && event.data?.path) {
|
| 480 |
-
setPreviewPage(event.data.path);
|
| 481 |
-
}
|
| 482 |
-
};
|
| 483 |
-
window.addEventListener("message", handleMessage);
|
| 484 |
-
return () => window.removeEventListener("message", handleMessage);
|
| 485 |
-
}, [setPreviewPage]);
|
| 486 |
-
|
| 487 |
useEffect(() => {
|
| 488 |
const cleanupListeners = () => {
|
| 489 |
if (iframeRef?.current?.contentDocument) {
|
|
@@ -512,10 +250,6 @@ export const Preview = ({
|
|
| 512 |
};
|
| 513 |
}, [isEditableModeEnabled, stableHtml, throttledHtml, previewPage]);
|
| 514 |
|
| 515 |
-
const refreshIframe = () => {
|
| 516 |
-
setIframeKey((prev) => prev + 1);
|
| 517 |
-
};
|
| 518 |
-
|
| 519 |
const promoteVersion = async () => {
|
| 520 |
setIsPromotingVersion(true);
|
| 521 |
await api
|
|
@@ -527,7 +261,6 @@ export const Preview = ({
|
|
| 527 |
setCurrentCommit(null);
|
| 528 |
setPages(res.data.pages);
|
| 529 |
setCurrentPage(res.data.pages[0].path);
|
| 530 |
-
setLastSavedPages(res.data.pages);
|
| 531 |
setPreviewPage(res.data.pages[0].path);
|
| 532 |
toast.success("Version promoted successfully");
|
| 533 |
}
|
|
@@ -698,7 +431,7 @@ export const Preview = ({
|
|
| 698 |
normalizedHref = normalizedHref + ".html";
|
| 699 |
}
|
| 700 |
|
| 701 |
-
const isPageExist =
|
| 702 |
const pagePath = page.path.replace(/^\.?\//, "");
|
| 703 |
return pagePath === normalizedHref;
|
| 704 |
});
|
|
@@ -756,36 +489,7 @@ export const Preview = ({
|
|
| 756 |
</div>
|
| 757 |
) : (
|
| 758 |
<>
|
| 759 |
-
{isLoadingCommitPages && (
|
| 760 |
-
<div className="top-0 left-0 right-0 z-20 bg-blue-500/90 backdrop-blur-sm border-b border-blue-600 px-4 py-2 flex items-center justify-center gap-3 text-sm w-full">
|
| 761 |
-
<div className="flex items-center gap-2">
|
| 762 |
-
<AiLoading
|
| 763 |
-
text="Loading commit version..."
|
| 764 |
-
className="flex-row"
|
| 765 |
-
/>
|
| 766 |
-
</div>
|
| 767 |
-
</div>
|
| 768 |
-
)}
|
| 769 |
-
{!isNew && !currentCommit && (
|
| 770 |
-
<div className="top-0 left-0 right-0 z-20 bg-neutral-900/95 backdrop-blur-sm border-b border-neutral-800 px-4 py-2 max-h-[40px] flex items-center justify-between gap-3 text-xs w-full">
|
| 771 |
-
<div className="flex items-center gap-2 flex-1">
|
| 772 |
-
<TriangleAlert className="size-4 text-neutral-500 flex-shrink-0" />
|
| 773 |
-
<span className="text-neutral-400 font-medium">
|
| 774 |
-
Preview version of the project. Try refreshing the preview if
|
| 775 |
-
you experience any issues.
|
| 776 |
-
</span>
|
| 777 |
-
</div>
|
| 778 |
-
<button
|
| 779 |
-
onClick={refreshIframe}
|
| 780 |
-
className="cursor-pointer text-xs px-3 py-1 bg-neutral-800 hover:bg-neutral-700 text-neutral-300 rounded-md font-medium transition-colors whitespace-nowrap flex items-center gap-1.5"
|
| 781 |
-
>
|
| 782 |
-
<RefreshCcw className="size-3 text-neutral-300 flex-shrink-0" />
|
| 783 |
-
Refresh
|
| 784 |
-
</button>
|
| 785 |
-
</div>
|
| 786 |
-
)}
|
| 787 |
<iframe
|
| 788 |
-
key={iframeKey}
|
| 789 |
id="preview-iframe"
|
| 790 |
ref={iframeRef}
|
| 791 |
className={classNames(
|
|
@@ -796,45 +500,36 @@ export const Preview = ({
|
|
| 796 |
}
|
| 797 |
)}
|
| 798 |
src={
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
!hasUnsavedChanges &&
|
| 802 |
-
project?.space_id &&
|
| 803 |
-
!project?.private
|
| 804 |
-
? `https://${project.space_id.replaceAll(
|
| 805 |
"/",
|
| 806 |
"-"
|
| 807 |
-
)}.static.hf.space`
|
| 808 |
: undefined
|
| 809 |
}
|
| 810 |
srcDoc={
|
| 811 |
-
currentCommit
|
| 812 |
-
? commitPages.length > 0 && previewPageData?.html
|
| 813 |
-
? injectAssetsIntoHtml(previewPageData.html, commitPages)
|
| 814 |
-
: defaultHTML
|
| 815 |
-
: isNew || hasUnsavedChanges || project?.private
|
| 816 |
? isNew
|
| 817 |
? throttledHtml || defaultHTML
|
| 818 |
: stableHtml
|
| 819 |
: undefined
|
| 820 |
}
|
| 821 |
-
onLoad={
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
}}
|
| 838 |
sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-modals allow-forms"
|
| 839 |
allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; clipboard-read; clipboard-write; display-capture; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; serial; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking"
|
| 840 |
/>
|
|
|
|
| 15 |
import { HistoryNotification } from "../history-notification";
|
| 16 |
import { api } from "@/lib/api";
|
| 17 |
import { toast } from "sonner";
|
| 18 |
+
|
| 19 |
+
export const Preview = ({ isNew }: { isNew: boolean }) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
const {
|
| 21 |
project,
|
| 22 |
device,
|
|
|
|
| 30 |
setCurrentPage,
|
| 31 |
previewPage,
|
| 32 |
setPreviewPage,
|
|
|
|
|
|
|
| 33 |
} = useEditor();
|
| 34 |
const {
|
| 35 |
isEditableModeEnabled,
|
|
|
|
| 48 |
const [stableHtml, setStableHtml] = useState<string>("");
|
| 49 |
const [throttledHtml, setThrottledHtml] = useState<string>("");
|
| 50 |
const lastUpdateTimeRef = useRef<number>(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
useEffect(() => {
|
| 53 |
if (!previewPage && pages.length > 0) {
|
|
|
|
| 59 |
}
|
| 60 |
}, [pages, previewPage]);
|
| 61 |
|
|
|
|
|
|
|
| 62 |
const previewPageData = useMemo(() => {
|
| 63 |
+
const found = pages.find((p) => {
|
| 64 |
const normalizedPagePath = p.path.replace(/^\.?\//, "");
|
| 65 |
const normalizedPreviewPage = previewPage.replace(/^\.?\//, "");
|
| 66 |
return normalizedPagePath === normalizedPreviewPage;
|
| 67 |
});
|
| 68 |
+
return found || currentPageData;
|
| 69 |
+
}, [pages, previewPage, currentPageData]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
const injectAssetsIntoHtml = useCallback(
|
| 72 |
+
(html: string): string => {
|
| 73 |
if (!html) return html;
|
| 74 |
|
| 75 |
+
const cssFiles = pages.filter(
|
| 76 |
(p) => p.path.endsWith(".css") && p.path !== previewPageData?.path
|
| 77 |
);
|
| 78 |
+
const jsFiles = pages.filter(
|
| 79 |
(p) => p.path.endsWith(".js") && p.path !== previewPageData?.path
|
| 80 |
);
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
let modifiedHtml = html;
|
| 83 |
|
|
|
|
|
|
|
|
|
|
| 84 |
// Inject all CSS files
|
| 85 |
if (cssFiles.length > 0) {
|
| 86 |
const allCssContent = cssFiles
|
|
|
|
| 147 |
});
|
| 148 |
}
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
return modifiedHtml;
|
| 151 |
},
|
| 152 |
+
[pages, previewPageData?.path]
|
| 153 |
);
|
| 154 |
|
| 155 |
useEffect(() => {
|
|
|
|
| 158 |
const timeSinceLastUpdate = now - lastUpdateTimeRef.current;
|
| 159 |
|
| 160 |
if (lastUpdateTimeRef.current === 0 || timeSinceLastUpdate >= 3000) {
|
| 161 |
+
const processedHtml = injectAssetsIntoHtml(previewPageData.html);
|
|
|
|
|
|
|
|
|
|
| 162 |
setThrottledHtml(processedHtml);
|
| 163 |
lastUpdateTimeRef.current = now;
|
| 164 |
} else {
|
| 165 |
const timeUntilNextUpdate = 3000 - timeSinceLastUpdate;
|
| 166 |
const timer = setTimeout(() => {
|
| 167 |
+
const processedHtml = injectAssetsIntoHtml(previewPageData.html);
|
|
|
|
|
|
|
|
|
|
| 168 |
setThrottledHtml(processedHtml);
|
| 169 |
lastUpdateTimeRef.current = Date.now();
|
| 170 |
}, timeUntilNextUpdate);
|
| 171 |
return () => clearTimeout(timer);
|
| 172 |
}
|
| 173 |
}
|
| 174 |
+
}, [isNew, previewPageData?.html, injectAssetsIntoHtml]);
|
| 175 |
|
| 176 |
useEffect(() => {
|
| 177 |
if (!isAiWorking && !globalAiLoading && previewPageData?.html) {
|
| 178 |
+
const processedHtml = injectAssetsIntoHtml(previewPageData.html);
|
|
|
|
|
|
|
|
|
|
| 179 |
setStableHtml(processedHtml);
|
| 180 |
}
|
| 181 |
}, [
|
|
|
|
| 184 |
previewPageData?.html,
|
| 185 |
injectAssetsIntoHtml,
|
| 186 |
previewPage,
|
|
|
|
| 187 |
]);
|
| 188 |
|
| 189 |
useEffect(() => {
|
|
|
|
| 193 |
!isAiWorking &&
|
| 194 |
!globalAiLoading
|
| 195 |
) {
|
| 196 |
+
const processedHtml = injectAssetsIntoHtml(previewPageData.html);
|
|
|
|
|
|
|
|
|
|
| 197 |
setStableHtml(processedHtml);
|
| 198 |
}
|
| 199 |
}, [
|
|
|
|
| 202 |
isAiWorking,
|
| 203 |
globalAiLoading,
|
| 204 |
injectAssetsIntoHtml,
|
|
|
|
| 205 |
]);
|
| 206 |
|
| 207 |
const setupIframeListeners = () => {
|
|
|
|
| 222 |
}
|
| 223 |
};
|
| 224 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
useEffect(() => {
|
| 226 |
const cleanupListeners = () => {
|
| 227 |
if (iframeRef?.current?.contentDocument) {
|
|
|
|
| 250 |
};
|
| 251 |
}, [isEditableModeEnabled, stableHtml, throttledHtml, previewPage]);
|
| 252 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
const promoteVersion = async () => {
|
| 254 |
setIsPromotingVersion(true);
|
| 255 |
await api
|
|
|
|
| 261 |
setCurrentCommit(null);
|
| 262 |
setPages(res.data.pages);
|
| 263 |
setCurrentPage(res.data.pages[0].path);
|
|
|
|
| 264 |
setPreviewPage(res.data.pages[0].path);
|
| 265 |
toast.success("Version promoted successfully");
|
| 266 |
}
|
|
|
|
| 431 |
normalizedHref = normalizedHref + ".html";
|
| 432 |
}
|
| 433 |
|
| 434 |
+
const isPageExist = pages.some((page) => {
|
| 435 |
const pagePath = page.path.replace(/^\.?\//, "");
|
| 436 |
return pagePath === normalizedHref;
|
| 437 |
});
|
|
|
|
| 489 |
</div>
|
| 490 |
) : (
|
| 491 |
<>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
<iframe
|
|
|
|
| 493 |
id="preview-iframe"
|
| 494 |
ref={iframeRef}
|
| 495 |
className={classNames(
|
|
|
|
| 500 |
}
|
| 501 |
)}
|
| 502 |
src={
|
| 503 |
+
currentCommit
|
| 504 |
+
? `https://${project?.space_id?.replaceAll(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
"/",
|
| 506 |
"-"
|
| 507 |
+
)}--rev-${currentCommit.slice(0, 7)}.static.hf.space`
|
| 508 |
: undefined
|
| 509 |
}
|
| 510 |
srcDoc={
|
| 511 |
+
!currentCommit
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
? isNew
|
| 513 |
? throttledHtml || defaultHTML
|
| 514 |
: stableHtml
|
| 515 |
: undefined
|
| 516 |
}
|
| 517 |
+
onLoad={
|
| 518 |
+
!currentCommit
|
| 519 |
+
? () => {
|
| 520 |
+
if (iframeRef?.current?.contentWindow?.document?.body) {
|
| 521 |
+
iframeRef.current.contentWindow.document.body.scrollIntoView(
|
| 522 |
+
{
|
| 523 |
+
block: isAiWorking ? "end" : "start",
|
| 524 |
+
inline: "nearest",
|
| 525 |
+
behavior: isAiWorking ? "instant" : "smooth",
|
| 526 |
+
}
|
| 527 |
+
);
|
| 528 |
+
}
|
| 529 |
+
setupIframeListeners();
|
| 530 |
+
}
|
| 531 |
+
: undefined
|
| 532 |
+
}
|
|
|
|
| 533 |
sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-modals allow-forms"
|
| 534 |
allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; clipboard-read; clipboard-write; display-capture; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; serial; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking"
|
| 535 |
/>
|
components/icons/discord.tsx
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
export const DiscordIcon = ({ className }: { className?: string }) => {
|
| 2 |
-
return (
|
| 3 |
-
<svg
|
| 4 |
-
width="1em"
|
| 5 |
-
height="1em"
|
| 6 |
-
viewBox="0 0 127 96"
|
| 7 |
-
fill="none"
|
| 8 |
-
aria-hidden="true"
|
| 9 |
-
focusable="false"
|
| 10 |
-
preserveAspectRatio="xMidYMid meet"
|
| 11 |
-
xmlns="http://www.w3.org/2000/svg"
|
| 12 |
-
className={className}
|
| 13 |
-
>
|
| 14 |
-
<g clipPath="url(#clip0_1084_3121)">
|
| 15 |
-
<path
|
| 16 |
-
d="M81.15 0C79.9124 2.1973 78.8011 4.4704 77.7909 6.794C68.1934 5.3544 58.4191 5.3544 48.7964 6.794C47.8114 4.4704 46.6748 2.1973 45.4373 0C36.4207 1.5407 27.6314 4.2431 19.2968 8.0568C2.77901 32.5304 -1.69139 56.3725 0.531208 79.8863C10.2044 87.0339 21.0395 92.4893 32.5817 95.9747C35.1831 92.4893 37.4815 88.7766 39.4515 84.9124C35.7135 83.5233 32.1018 81.7806 28.6417 79.7601C29.5509 79.1034 30.4349 78.4215 31.2936 77.7648C51.5746 87.3118 75.0632 87.3118 95.3694 77.7648C96.2281 78.472 97.1121 79.1539 98.0213 79.7601C94.5612 81.8058 90.9495 83.5233 87.1863 84.9377C89.1563 88.8019 91.4546 92.5146 94.0561 96C105.598 92.5146 116.433 87.0844 126.107 79.9369C128.733 52.6598 121.611 29.0197 107.29 8.0821C98.9811 4.2684 90.1918 1.5659 81.1752 0.0505L81.15 0ZM42.2802 65.4144C36.0419 65.4144 30.8643 59.7569 30.8643 52.7609C30.8643 45.7649 35.8398 40.0821 42.255 40.0821C48.6702 40.0821 53.7719 45.7901 53.6709 52.7609C53.5699 59.7317 48.6449 65.4144 42.2802 65.4144ZM84.3576 65.4144C78.0939 65.4144 72.9669 59.7569 72.9669 52.7609C72.9669 45.7649 77.9424 40.0821 84.3576 40.0821C90.7728 40.0821 95.8493 45.7901 95.7482 52.7609C95.6472 59.7317 90.7222 65.4144 84.3576 65.4144Z"
|
| 17 |
-
fill="currentColor"
|
| 18 |
-
/>
|
| 19 |
-
</g>
|
| 20 |
-
<defs>
|
| 21 |
-
<clipPath id="clip0_1084_3121">
|
| 22 |
-
<rect width="126.644" height="96" fill="currentColor" />
|
| 23 |
-
</clipPath>
|
| 24 |
-
</defs>
|
| 25 |
-
</svg>
|
| 26 |
-
);
|
| 27 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/my-projects/project-card.tsx
CHANGED
|
@@ -1,12 +1,6 @@
|
|
| 1 |
import Link from "next/link";
|
| 2 |
import { formatDistance } from "date-fns";
|
| 3 |
-
import {
|
| 4 |
-
Download,
|
| 5 |
-
EllipsisVertical,
|
| 6 |
-
Lock,
|
| 7 |
-
Settings,
|
| 8 |
-
Trash,
|
| 9 |
-
} from "lucide-react";
|
| 10 |
|
| 11 |
import { Button } from "@/components/ui/button";
|
| 12 |
import {
|
|
@@ -17,8 +11,6 @@ import {
|
|
| 17 |
DropdownMenuTrigger,
|
| 18 |
} from "@/components/ui/dropdown-menu";
|
| 19 |
import { ProjectType } from "@/types";
|
| 20 |
-
import { toast } from "sonner";
|
| 21 |
-
import { useUser } from "@/hooks/useUser";
|
| 22 |
|
| 23 |
// from-red-500 to-red-500
|
| 24 |
// from-yellow-500 to-yellow-500
|
|
@@ -36,7 +28,6 @@ export function ProjectCard({
|
|
| 36 |
project: ProjectType;
|
| 37 |
onDelete: () => void;
|
| 38 |
}) {
|
| 39 |
-
const { token } = useUser();
|
| 40 |
const handleDelete = () => {
|
| 41 |
if (
|
| 42 |
confirm(
|
|
@@ -46,46 +37,6 @@ export function ProjectCard({
|
|
| 46 |
onDelete();
|
| 47 |
}
|
| 48 |
};
|
| 49 |
-
|
| 50 |
-
const handleDownload = async () => {
|
| 51 |
-
try {
|
| 52 |
-
toast.info("Preparing download...");
|
| 53 |
-
const response = await fetch(
|
| 54 |
-
`/deepsite/api/me/projects/${project.name}/download`,
|
| 55 |
-
{
|
| 56 |
-
credentials: "include",
|
| 57 |
-
headers: {
|
| 58 |
-
Accept: "application/zip",
|
| 59 |
-
Authorization: `Bearer ${token}`,
|
| 60 |
-
},
|
| 61 |
-
}
|
| 62 |
-
);
|
| 63 |
-
|
| 64 |
-
if (!response.ok) {
|
| 65 |
-
const error = await response
|
| 66 |
-
.json()
|
| 67 |
-
.catch(() => ({ error: "Download failed" }));
|
| 68 |
-
toast.error(error.error || "Failed to download project");
|
| 69 |
-
return;
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
const blob = await response.blob();
|
| 73 |
-
|
| 74 |
-
const url = window.URL.createObjectURL(blob);
|
| 75 |
-
const link = document.createElement("a");
|
| 76 |
-
link.href = url;
|
| 77 |
-
link.download = `${project.name.replace(/\//g, "-")}.zip`;
|
| 78 |
-
document.body.appendChild(link);
|
| 79 |
-
link.click();
|
| 80 |
-
document.body.removeChild(link);
|
| 81 |
-
window.URL.revokeObjectURL(url);
|
| 82 |
-
|
| 83 |
-
toast.success("Download started!");
|
| 84 |
-
} catch (error) {
|
| 85 |
-
console.error("Download error:", error);
|
| 86 |
-
toast.error("Failed to download project");
|
| 87 |
-
}
|
| 88 |
-
};
|
| 89 |
// from-gray-600 to-gray-600
|
| 90 |
// from-blue-600 to-blue-600
|
| 91 |
// from-green-600 to-green-600
|
|
@@ -169,10 +120,6 @@ export function ProjectCard({
|
|
| 169 |
Project Settings
|
| 170 |
</DropdownMenuItem>
|
| 171 |
</a>
|
| 172 |
-
<DropdownMenuItem onClick={handleDownload}>
|
| 173 |
-
<Download className="size-4 text-neutral-100" />
|
| 174 |
-
Download as ZIP
|
| 175 |
-
</DropdownMenuItem>
|
| 176 |
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
|
| 177 |
<Trash className="size-4 text-red-500" />
|
| 178 |
Delete Project
|
|
|
|
| 1 |
import Link from "next/link";
|
| 2 |
import { formatDistance } from "date-fns";
|
| 3 |
+
import { EllipsisVertical, Lock, Settings, Trash } from "lucide-react";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
import { Button } from "@/components/ui/button";
|
| 6 |
import {
|
|
|
|
| 11 |
DropdownMenuTrigger,
|
| 12 |
} from "@/components/ui/dropdown-menu";
|
| 13 |
import { ProjectType } from "@/types";
|
|
|
|
|
|
|
| 14 |
|
| 15 |
// from-red-500 to-red-500
|
| 16 |
// from-yellow-500 to-yellow-500
|
|
|
|
| 28 |
project: ProjectType;
|
| 29 |
onDelete: () => void;
|
| 30 |
}) {
|
|
|
|
| 31 |
const handleDelete = () => {
|
| 32 |
if (
|
| 33 |
confirm(
|
|
|
|
| 37 |
onDelete();
|
| 38 |
}
|
| 39 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
// from-gray-600 to-gray-600
|
| 41 |
// from-blue-600 to-blue-600
|
| 42 |
// from-green-600 to-green-600
|
|
|
|
| 120 |
Project Settings
|
| 121 |
</DropdownMenuItem>
|
| 122 |
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
|
| 124 |
<Trash className="size-4 text-red-500" />
|
| 125 |
Delete Project
|
components/public/navigation/index.tsx
CHANGED
|
@@ -12,7 +12,6 @@ import Logo from "@/assets/logo.svg";
|
|
| 12 |
import { useUser } from "@/hooks/useUser";
|
| 13 |
import { UserMenu } from "@/components/user-menu";
|
| 14 |
import { ProTag } from "@/components/pro-modal";
|
| 15 |
-
import { DiscordIcon } from "@/components/icons/discord";
|
| 16 |
|
| 17 |
const navigationLinks = [
|
| 18 |
{
|
|
@@ -97,9 +96,7 @@ export default function Navigation() {
|
|
| 97 |
width={64}
|
| 98 |
height={64}
|
| 99 |
/>
|
| 100 |
-
<p className="font-sans text-white text-xl font-bold
|
| 101 |
-
DeepSite
|
| 102 |
-
</p>
|
| 103 |
{user?.isPro && <ProTag className="ml-1" />}
|
| 104 |
</Link>
|
| 105 |
<ul className="items-center justify-center gap-6 !hidden">
|
|
@@ -144,17 +141,7 @@ export default function Navigation() {
|
|
| 144 |
<div className="size-1 bg-white rounded-full" />
|
| 145 |
</div>
|
| 146 |
</ul>
|
| 147 |
-
<div className="flex items-center justify-end gap-
|
| 148 |
-
<Link href="https://discord.gg/KpanwM3vXa" target="_blank">
|
| 149 |
-
<Button
|
| 150 |
-
variant="bordered"
|
| 151 |
-
className="!border-indigo-500 !text-white !bg-indigo-500 transition-all duration-300"
|
| 152 |
-
>
|
| 153 |
-
<DiscordIcon className="size-4 mr-0.5" />
|
| 154 |
-
<span className="max-lg:hidden">Discord Community</span>
|
| 155 |
-
<span className="lg:hidden">Discord</span>
|
| 156 |
-
</Button>
|
| 157 |
-
</Link>
|
| 158 |
{loading ? (
|
| 159 |
<Button
|
| 160 |
variant="ghostDarker"
|
|
|
|
| 12 |
import { useUser } from "@/hooks/useUser";
|
| 13 |
import { UserMenu } from "@/components/user-menu";
|
| 14 |
import { ProTag } from "@/components/pro-modal";
|
|
|
|
| 15 |
|
| 16 |
const navigationLinks = [
|
| 17 |
{
|
|
|
|
| 96 |
width={64}
|
| 97 |
height={64}
|
| 98 |
/>
|
| 99 |
+
<p className="font-sans text-white text-xl font-bold">DeepSite</p>
|
|
|
|
|
|
|
| 100 |
{user?.isPro && <ProTag className="ml-1" />}
|
| 101 |
</Link>
|
| 102 |
<ul className="items-center justify-center gap-6 !hidden">
|
|
|
|
| 141 |
<div className="size-1 bg-white rounded-full" />
|
| 142 |
</div>
|
| 143 |
</ul>
|
| 144 |
+
<div className="flex items-center justify-end gap-2">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
{loading ? (
|
| 146 |
<Button
|
| 147 |
variant="ghostDarker"
|
hooks/useAi.ts
CHANGED
|
@@ -19,7 +19,7 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 19 |
const [storageProvider, setStorageProvider] = useLocalStorage("provider", "auto");
|
| 20 |
const [storageModel, setStorageModel] = useLocalStorage("model", MODELS[0].value);
|
| 21 |
const router = useRouter();
|
| 22 |
-
const { token } = useUser();
|
| 23 |
const streamingPagesRef = useRef<Set<string>>(new Set());
|
| 24 |
|
| 25 |
const { data: isAiWorking = false } = useQuery({
|
|
@@ -44,18 +44,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 44 |
client.setQueryData(["ai.isThinking"], newIsThinking);
|
| 45 |
};
|
| 46 |
|
| 47 |
-
const { data: thinkingContent } = useQuery<string>({
|
| 48 |
-
queryKey: ["ai.thinkingContent"],
|
| 49 |
-
queryFn: async () => "",
|
| 50 |
-
refetchOnWindowFocus: false,
|
| 51 |
-
refetchOnReconnect: false,
|
| 52 |
-
refetchOnMount: false,
|
| 53 |
-
initialData: ""
|
| 54 |
-
});
|
| 55 |
-
const setThinkingContent = (newThinkingContent: string) => {
|
| 56 |
-
client.setQueryData(["ai.thinkingContent"], newThinkingContent);
|
| 57 |
-
};
|
| 58 |
-
|
| 59 |
const { data: selectedElement } = useQuery<HTMLElement | null>({
|
| 60 |
queryKey: ["ai.selectedElement"],
|
| 61 |
queryFn: async () => null,
|
|
@@ -179,7 +167,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 179 |
if (!redesignMarkdown && !prompt.trim()) return;
|
| 180 |
|
| 181 |
setIsAiWorking(true);
|
| 182 |
-
setThinkingContent(""); // Reset thinking content
|
| 183 |
streamingPagesRef.current.clear(); // Reset tracking for new generation
|
| 184 |
|
| 185 |
const abortController = new AbortController();
|
|
@@ -212,14 +199,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 212 |
const { done, value } = await reader.read();
|
| 213 |
|
| 214 |
if (done) {
|
| 215 |
-
// Final processing - extract and remove thinking content
|
| 216 |
-
const thinkMatch = contentResponse.match(/<think>([\s\S]*?)<\/think>/);
|
| 217 |
-
if (thinkMatch) {
|
| 218 |
-
setThinkingContent(thinkMatch[1].trim());
|
| 219 |
-
setIsThinking(false);
|
| 220 |
-
contentResponse = contentResponse.replace(/<think>[\s\S]*?<\/think>/, '').trim();
|
| 221 |
-
}
|
| 222 |
-
|
| 223 |
const trimmedResponse = contentResponse.trim();
|
| 224 |
if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
|
| 225 |
try {
|
|
@@ -244,7 +223,7 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 244 |
const newPages = formatPages(contentResponse, false);
|
| 245 |
let projectName = contentResponse.match(/<<<<<<< PROJECT_NAME_START\s*([\s\S]*?)\s*>>>>>>> PROJECT_NAME_END/)?.[1]?.trim();
|
| 246 |
if (!projectName) {
|
| 247 |
-
projectName = prompt.substring(0,
|
| 248 |
}
|
| 249 |
setPages(newPages);
|
| 250 |
setLastSavedPages([...newPages]);
|
|
@@ -259,26 +238,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 259 |
const chunk = decoder.decode(value, { stream: true });
|
| 260 |
contentResponse += chunk;
|
| 261 |
|
| 262 |
-
// Extract thinking content while streaming
|
| 263 |
-
if (contentResponse.includes('</think>')) {
|
| 264 |
-
// Thinking is complete, extract final content and stop thinking
|
| 265 |
-
const thinkMatch = contentResponse.match(/<think>([\s\S]*?)<\/think>/);
|
| 266 |
-
if (thinkMatch) {
|
| 267 |
-
setThinkingContent(thinkMatch[1].trim());
|
| 268 |
-
setIsThinking(false);
|
| 269 |
-
}
|
| 270 |
-
} else if (contentResponse.includes('<think>')) {
|
| 271 |
-
// Still thinking, update content
|
| 272 |
-
const thinkMatch = contentResponse.match(/<think>([\s\S]*)$/);
|
| 273 |
-
if (thinkMatch) {
|
| 274 |
-
const thinkingText = thinkMatch[1].trim();
|
| 275 |
-
if (thinkingText) {
|
| 276 |
-
setIsThinking(true);
|
| 277 |
-
setThinkingContent(thinkingText);
|
| 278 |
-
}
|
| 279 |
-
}
|
| 280 |
-
}
|
| 281 |
-
|
| 282 |
const trimmedResponse = contentResponse.trim();
|
| 283 |
if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
|
| 284 |
try {
|
|
@@ -311,7 +270,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 311 |
} catch (error: any) {
|
| 312 |
setIsAiWorking(false);
|
| 313 |
setIsThinking(false);
|
| 314 |
-
setThinkingContent("");
|
| 315 |
setController(null);
|
| 316 |
|
| 317 |
if (!abortController.signal.aborted) {
|
|
@@ -331,7 +289,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 331 |
|
| 332 |
|
| 333 |
setIsAiWorking(true);
|
| 334 |
-
setThinkingContent(""); // Reset thinking content
|
| 335 |
|
| 336 |
const abortController = new AbortController();
|
| 337 |
setController(abortController);
|
|
@@ -373,14 +330,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 373 |
const { done, value } = await reader.read();
|
| 374 |
|
| 375 |
if (done) {
|
| 376 |
-
// Extract and remove thinking content
|
| 377 |
-
const thinkMatch = contentResponse.match(/<think>([\s\S]*?)<\/think>/);
|
| 378 |
-
if (thinkMatch) {
|
| 379 |
-
setThinkingContent(thinkMatch[1].trim());
|
| 380 |
-
setIsThinking(false);
|
| 381 |
-
contentResponse = contentResponse.replace(/<think>[\s\S]*?<\/think>/, '').trim();
|
| 382 |
-
}
|
| 383 |
-
|
| 384 |
const metadataMatch = contentResponse.match(/___METADATA_START___([\s\S]*?)___METADATA_END___/);
|
| 385 |
if (metadataMatch) {
|
| 386 |
try {
|
|
@@ -490,26 +439,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 490 |
const chunk = decoder.decode(value, { stream: true });
|
| 491 |
contentResponse += chunk;
|
| 492 |
|
| 493 |
-
// Extract thinking content while streaming
|
| 494 |
-
if (contentResponse.includes('</think>')) {
|
| 495 |
-
// Thinking is complete, extract final content and stop thinking
|
| 496 |
-
const thinkMatch = contentResponse.match(/<think>([\s\S]*?)<\/think>/);
|
| 497 |
-
if (thinkMatch) {
|
| 498 |
-
setThinkingContent(thinkMatch[1].trim());
|
| 499 |
-
setIsThinking(false);
|
| 500 |
-
}
|
| 501 |
-
} else if (contentResponse.includes('<think>')) {
|
| 502 |
-
// Still thinking, update content
|
| 503 |
-
const thinkMatch = contentResponse.match(/<think>([\s\S]*)$/);
|
| 504 |
-
if (thinkMatch) {
|
| 505 |
-
const thinkingText = thinkMatch[1].trim();
|
| 506 |
-
if (thinkingText) {
|
| 507 |
-
setIsThinking(true);
|
| 508 |
-
setThinkingContent(thinkingText);
|
| 509 |
-
}
|
| 510 |
-
}
|
| 511 |
-
}
|
| 512 |
-
|
| 513 |
// Check for error responses during streaming
|
| 514 |
const trimmedResponse = contentResponse.trim();
|
| 515 |
if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
|
|
@@ -542,7 +471,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 542 |
} catch (error: any) {
|
| 543 |
setIsAiWorking(false);
|
| 544 |
setIsThinking(false);
|
| 545 |
-
setThinkingContent("");
|
| 546 |
setController(null);
|
| 547 |
|
| 548 |
if (!abortController.signal.aborted) {
|
|
@@ -690,8 +618,6 @@ export const useAi = (onScrollToBottom?: () => void) => {
|
|
| 690 |
return {
|
| 691 |
isThinking,
|
| 692 |
setIsThinking,
|
| 693 |
-
thinkingContent,
|
| 694 |
-
setThinkingContent,
|
| 695 |
callAiNewProject,
|
| 696 |
callAiFollowUp,
|
| 697 |
isAiWorking,
|
|
|
|
| 19 |
const [storageProvider, setStorageProvider] = useLocalStorage("provider", "auto");
|
| 20 |
const [storageModel, setStorageModel] = useLocalStorage("model", MODELS[0].value);
|
| 21 |
const router = useRouter();
|
| 22 |
+
const { projects, setProjects, token } = useUser();
|
| 23 |
const streamingPagesRef = useRef<Set<string>>(new Set());
|
| 24 |
|
| 25 |
const { data: isAiWorking = false } = useQuery({
|
|
|
|
| 44 |
client.setQueryData(["ai.isThinking"], newIsThinking);
|
| 45 |
};
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
const { data: selectedElement } = useQuery<HTMLElement | null>({
|
| 48 |
queryKey: ["ai.selectedElement"],
|
| 49 |
queryFn: async () => null,
|
|
|
|
| 167 |
if (!redesignMarkdown && !prompt.trim()) return;
|
| 168 |
|
| 169 |
setIsAiWorking(true);
|
|
|
|
| 170 |
streamingPagesRef.current.clear(); // Reset tracking for new generation
|
| 171 |
|
| 172 |
const abortController = new AbortController();
|
|
|
|
| 199 |
const { done, value } = await reader.read();
|
| 200 |
|
| 201 |
if (done) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
const trimmedResponse = contentResponse.trim();
|
| 203 |
if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
|
| 204 |
try {
|
|
|
|
| 223 |
const newPages = formatPages(contentResponse, false);
|
| 224 |
let projectName = contentResponse.match(/<<<<<<< PROJECT_NAME_START\s*([\s\S]*?)\s*>>>>>>> PROJECT_NAME_END/)?.[1]?.trim();
|
| 225 |
if (!projectName) {
|
| 226 |
+
projectName = prompt.substring(0, 40).replace(/[^a-zA-Z0-9]/g, "-").slice(0, 40) + "-" + Math.random().toString(36).substring(2, 15);
|
| 227 |
}
|
| 228 |
setPages(newPages);
|
| 229 |
setLastSavedPages([...newPages]);
|
|
|
|
| 238 |
const chunk = decoder.decode(value, { stream: true });
|
| 239 |
contentResponse += chunk;
|
| 240 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
const trimmedResponse = contentResponse.trim();
|
| 242 |
if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
|
| 243 |
try {
|
|
|
|
| 270 |
} catch (error: any) {
|
| 271 |
setIsAiWorking(false);
|
| 272 |
setIsThinking(false);
|
|
|
|
| 273 |
setController(null);
|
| 274 |
|
| 275 |
if (!abortController.signal.aborted) {
|
|
|
|
| 289 |
|
| 290 |
|
| 291 |
setIsAiWorking(true);
|
|
|
|
| 292 |
|
| 293 |
const abortController = new AbortController();
|
| 294 |
setController(abortController);
|
|
|
|
| 330 |
const { done, value } = await reader.read();
|
| 331 |
|
| 332 |
if (done) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
const metadataMatch = contentResponse.match(/___METADATA_START___([\s\S]*?)___METADATA_END___/);
|
| 334 |
if (metadataMatch) {
|
| 335 |
try {
|
|
|
|
| 439 |
const chunk = decoder.decode(value, { stream: true });
|
| 440 |
contentResponse += chunk;
|
| 441 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
// Check for error responses during streaming
|
| 443 |
const trimmedResponse = contentResponse.trim();
|
| 444 |
if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
|
|
|
|
| 471 |
} catch (error: any) {
|
| 472 |
setIsAiWorking(false);
|
| 473 |
setIsThinking(false);
|
|
|
|
| 474 |
setController(null);
|
| 475 |
|
| 476 |
if (!abortController.signal.aborted) {
|
|
|
|
| 618 |
return {
|
| 619 |
isThinking,
|
| 620 |
setIsThinking,
|
|
|
|
|
|
|
| 621 |
callAiNewProject,
|
| 622 |
callAiFollowUp,
|
| 623 |
isAiWorking,
|
lib/best-provider.ts
CHANGED
|
@@ -3,13 +3,18 @@ export const getBestProvider = async (model: string, provider?: string) => {
|
|
| 3 |
const { data } = await response.json()
|
| 4 |
let bestProvider = null;
|
| 5 |
if (provider === "auto") {
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
} else {
|
| 8 |
const providerData = data.providers.find((p: any) => p.provider === provider)
|
| 9 |
if (providerData?.status === "live") {
|
| 10 |
-
bestProvider = providerData
|
| 11 |
} else {
|
| 12 |
-
bestProvider = "
|
| 13 |
}
|
| 14 |
}
|
| 15 |
|
|
|
|
| 3 |
const { data } = await response.json()
|
| 4 |
let bestProvider = null;
|
| 5 |
if (provider === "auto") {
|
| 6 |
+
const sortedProviders = data.providers.sort((a: any, b: any) => {
|
| 7 |
+
if (a.status === "live" && b.status !== "live") return -1
|
| 8 |
+
if (a.status !== "live" && b.status === "live") return 1
|
| 9 |
+
return a?.pricing?.output - b?.pricing?.output + a?.pricing?.input - b?.pricing?.input
|
| 10 |
+
})
|
| 11 |
+
bestProvider = sortedProviders[0]
|
| 12 |
} else {
|
| 13 |
const providerData = data.providers.find((p: any) => p.provider === provider)
|
| 14 |
if (providerData?.status === "live") {
|
| 15 |
+
bestProvider = providerData
|
| 16 |
} else {
|
| 17 |
+
bestProvider = data.providers?.find((p: any) => p.status === "live")
|
| 18 |
}
|
| 19 |
}
|
| 20 |
|
lib/prompts.ts
CHANGED
|
@@ -15,168 +15,79 @@ export const PROMPT_FOR_IMAGE_GENERATION = `If you want to use image placeholder
|
|
| 15 |
Examples: http://static.photos/red/320x240/133 (red-themed with seed 133), http://static.photos/640x360 (random category and image), http://static.photos/nature/1200x630/42 (nature-themed with seed 42).`
|
| 16 |
export const PROMPT_FOR_PROJECT_NAME = `REQUIRED: Generate a name for the project, based on the user's request. Try to be creative and unique. Add a emoji at the end of the name. It should be short, like 6 words. Be fancy, creative and funny. DON'T FORGET IT, IT'S IMPORTANT!`
|
| 17 |
|
| 18 |
-
export const INITIAL_SYSTEM_PROMPT_LIGHT = `You are an expert UI/UX and Front-End Developer.
|
| 19 |
-
No need to explain what you did. Just return the expected result. Use always TailwindCSS, don't forget to import it.
|
| 20 |
-
Return the results following this format:
|
| 21 |
-
1. Start with ${PROJECT_NAME_START}.
|
| 22 |
-
2. Add the name of the project, right after the start tag.
|
| 23 |
-
3. Close the start tag with the ${PROJECT_NAME_END}.
|
| 24 |
-
4. The name of the project should be short and concise.
|
| 25 |
-
5. Generate files in this ORDER: index.html FIRST, then style.css, then script.js, then web components if needed.
|
| 26 |
-
6. For each file, start with ${NEW_FILE_START}.
|
| 27 |
-
7. Add the file name right after the start tag.
|
| 28 |
-
8. Close the start tag with the ${NEW_FILE_END}.
|
| 29 |
-
9. Start the file content with the triple backticks and appropriate language marker
|
| 30 |
-
10. Insert the file content there.
|
| 31 |
-
11. Close with the triple backticks, like \`\`\`.
|
| 32 |
-
12. Repeat for each file.
|
| 33 |
-
Example Code:
|
| 34 |
-
${PROJECT_NAME_START} Project Name ${PROJECT_NAME_END}
|
| 35 |
-
${NEW_FILE_START}index.html${NEW_FILE_END}
|
| 36 |
-
\`\`\`html
|
| 37 |
-
<!DOCTYPE html>
|
| 38 |
-
<html lang="en">
|
| 39 |
-
<head>
|
| 40 |
-
<meta charset="UTF-8">
|
| 41 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 42 |
-
<title>Index</title>
|
| 43 |
-
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
| 44 |
-
<link rel="stylesheet" href="style.css">
|
| 45 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 46 |
-
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 47 |
-
<script src="https://unpkg.com/feather-icons"></script>
|
| 48 |
-
</head>
|
| 49 |
-
<body>
|
| 50 |
-
<h1>Hello World</h1>
|
| 51 |
-
<custom-example></custom-example>
|
| 52 |
-
<script src="components/example.js"></script>
|
| 53 |
-
<script src="script.js"></script>
|
| 54 |
-
<script>feather.replace();</script>
|
| 55 |
-
</body>
|
| 56 |
-
</html>
|
| 57 |
-
\`\`\`
|
| 58 |
-
CRITICAL: The first file MUST always be index.html.`
|
| 59 |
-
|
| 60 |
-
export const FOLLOW_UP_SYSTEM_PROMPT_LIGHT = `You are an expert UI/UX and Front-End Developer modifying existing files (HTML, CSS, JavaScript).
|
| 61 |
-
You MUST output ONLY the changes required using the following UPDATE_FILE_START and SEARCH/REPLACE format. Do NOT output the entire file.
|
| 62 |
-
Do NOT explain the changes or what you did, just return the expected results.
|
| 63 |
-
Update Format Rules:
|
| 64 |
-
1. Start with ${PROJECT_NAME_START}.
|
| 65 |
-
2. Add the name of the project, right after the start tag.
|
| 66 |
-
3. Close the start tag with the ${PROJECT_NAME_END}.
|
| 67 |
-
4. Start with ${UPDATE_FILE_START}
|
| 68 |
-
5. Provide the name of the file you are modifying (index.html, style.css, script.js, etc.).
|
| 69 |
-
6. Close the start tag with the ${UPDATE_FILE_END}.
|
| 70 |
-
7. Start with ${SEARCH_START}
|
| 71 |
-
8. Provide the exact lines from the current code that need to be replaced.
|
| 72 |
-
9. Use ${DIVIDER} to separate the search block from the replacement.
|
| 73 |
-
10. Provide the new lines that should replace the original lines.
|
| 74 |
-
11. End with ${REPLACE_END}
|
| 75 |
-
12. You can use multiple SEARCH/REPLACE blocks if changes are needed in different parts of the file.
|
| 76 |
-
13. 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.
|
| 77 |
-
14. 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).
|
| 78 |
-
15. IMPORTANT: The SEARCH block must *exactly* match the current code, including indentation and whitespace.
|
| 79 |
-
Example Modifying Code:
|
| 80 |
-
\`\`\`
|
| 81 |
-
${PROJECT_NAME_START} Project Name ${PROJECT_NAME_END}
|
| 82 |
-
${UPDATE_FILE_START}index.html${UPDATE_FILE_END}
|
| 83 |
-
${SEARCH_START}
|
| 84 |
-
<h1>Old Title</h1>
|
| 85 |
-
${DIVIDER}
|
| 86 |
-
<h1>New Title</h1>
|
| 87 |
-
${REPLACE_END}
|
| 88 |
-
${SEARCH_START}
|
| 89 |
-
</body>
|
| 90 |
-
${DIVIDER}
|
| 91 |
-
<script src="script.js"></script>
|
| 92 |
-
</body>
|
| 93 |
-
${REPLACE_END}
|
| 94 |
-
\`\`\`
|
| 95 |
-
Example Updating CSS:
|
| 96 |
-
\`\`\`
|
| 97 |
-
${UPDATE_FILE_START}style.css${UPDATE_FILE_END}
|
| 98 |
-
${SEARCH_START}
|
| 99 |
-
body {
|
| 100 |
-
background: white;
|
| 101 |
-
}
|
| 102 |
-
${DIVIDER}
|
| 103 |
-
body {
|
| 104 |
-
background: linear-gradient(to right, #667eea, #764ba2);
|
| 105 |
-
}
|
| 106 |
-
${REPLACE_END}
|
| 107 |
-
\`\`\`
|
| 108 |
-
Example Deleting Code:
|
| 109 |
-
\`\`\`
|
| 110 |
-
${UPDATE_FILE_START}index.html${UPDATE_FILE_END}
|
| 111 |
-
${SEARCH_START}
|
| 112 |
-
<p>This paragraph will be deleted.</p>
|
| 113 |
-
${DIVIDER}
|
| 114 |
-
${REPLACE_END}
|
| 115 |
-
\`\`\`
|
| 116 |
-
For creating new files, use the following format:
|
| 117 |
-
1. Start with ${NEW_FILE_START}.
|
| 118 |
-
2. Add the name of the file (e.g., about.html, style.css, script.js, components/navbar.js), right after the start tag.
|
| 119 |
-
3. Close the start tag with the ${NEW_FILE_END}.
|
| 120 |
-
4. Start the file content with the triple backticks and appropriate language marker (\`\`\`html, \`\`\`css, or \`\`\`javascript).
|
| 121 |
-
5. Insert the file content there.
|
| 122 |
-
6. Close with the triple backticks, like \`\`\`.
|
| 123 |
-
7. Repeat for additional files.
|
| 124 |
-
Example Creating New HTML Page:
|
| 125 |
-
${NEW_FILE_START}about.html${NEW_FILE_END}
|
| 126 |
-
\`\`\`html
|
| 127 |
-
<!DOCTYPE html>
|
| 128 |
-
<html lang="en">
|
| 129 |
-
<head>
|
| 130 |
-
<meta charset="UTF-8">
|
| 131 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 132 |
-
<title>About</title>
|
| 133 |
-
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
| 134 |
-
<link rel="stylesheet" href="style.css">
|
| 135 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 136 |
-
</head>
|
| 137 |
-
<body>
|
| 138 |
-
<h1>About Page</h1>
|
| 139 |
-
<script src="script.js"></script>
|
| 140 |
-
</body>
|
| 141 |
-
</html>
|
| 142 |
-
\`\`\`
|
| 143 |
-
No need to explain what you did. Just return the expected result.`
|
| 144 |
-
|
| 145 |
export const INITIAL_SYSTEM_PROMPT = `You are an expert UI/UX and Front-End Developer.
|
| 146 |
You create website in a way a designer would, using ONLY HTML, CSS and Javascript.
|
| 147 |
Try to create the best UI possible. Important: Make the website responsive by using TailwindCSS. Use it as much as you can, if you can't use it, use custom css (make sure to import tailwind with <script src="https://cdn.tailwindcss.com"></script> in the head).
|
| 148 |
Also try to elaborate as much as you can, to create something unique, with a great design.
|
| 149 |
If you want to use ICONS import Feather Icons (Make sure to add <script src="https://unpkg.com/feather-icons"></script> and <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> in the head., and <script>feather.replace();</script> in the body. Ex : <i data-feather="user"></i>).
|
|
|
|
| 150 |
Don't hesitate to use real public API for the datas, you can find good ones here https://github.com/public-apis/public-apis depending on what the user asks for.
|
| 151 |
You can create multiple pages website at once (following the format rules below) or a Single Page Application. But make sure to create multiple pages if the user asks for different pages.
|
| 152 |
IMPORTANT: To avoid duplicate code across pages, you MUST create separate style.css and script.js files for shared CSS and JavaScript code. Each HTML file should link to these files using <link rel="stylesheet" href="style.css"> and <script src="script.js"></script>.
|
| 153 |
WEB COMPONENTS: For reusable UI elements like navbars, footers, sidebars, headers, etc., create Native Web Components as separate files in components/ folder:
|
| 154 |
-
- Create each component as a separate .js file in components/ folder (e.g., components/
|
| 155 |
- Each component file defines a class extending HTMLElement and registers it with customElements.define()
|
| 156 |
- Use Shadow DOM for style encapsulation
|
| 157 |
- Components render using template literals with inline styles
|
| 158 |
-
- Include component files in HTML before using them: <script src="components/
|
| 159 |
-
- Use them in HTML pages with custom element tags (e.g., <custom-
|
| 160 |
- If you want to use ICON you can use Feather Icons, as it's already included in the main pages.
|
| 161 |
IMPORTANT: NEVER USE ONCLICK FUNCTION TO MAKE A REDIRECT TO NEW PAGE. MAKE SURE TO ALWAYS USE <a href=""/>, OTHERWISE IT WONT WORK WITH SHADOW ROOT AND WEB COMPONENTS.
|
| 162 |
-
Example components/
|
| 163 |
-
class
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
connectedCallback() {
|
| 165 |
this.attachShadow({ mode: 'open' });
|
| 166 |
this.shadowRoot.innerHTML = \`
|
| 167 |
<style>
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
</style>
|
| 170 |
-
<
|
| 171 |
-
<
|
| 172 |
-
</
|
| 173 |
\`;
|
| 174 |
}
|
| 175 |
}
|
| 176 |
-
customElements.define('custom-
|
|
|
|
| 177 |
Then in HTML, include the component scripts and use the tags:
|
| 178 |
-
<script src="components/
|
| 179 |
-
<
|
|
|
|
|
|
|
| 180 |
${PROMPT_FOR_IMAGE_GENERATION}
|
| 181 |
${PROMPT_FOR_PROJECT_NAME}
|
| 182 |
No need to explain what you did. Just return the expected result. AVOID Chinese characters in the code if not asked by the user.
|
|
@@ -185,14 +96,15 @@ Return the results following this format:
|
|
| 185 |
2. Add the name of the project, right after the start tag.
|
| 186 |
3. Close the start tag with the ${PROJECT_NAME_END}.
|
| 187 |
4. The name of the project should be short and concise.
|
| 188 |
-
5. Generate files in this ORDER: index.html FIRST, then style.css, then script.js, then web components
|
| 189 |
6. For each file, start with ${NEW_FILE_START}.
|
| 190 |
-
7. Add the file name right after the start tag.
|
| 191 |
8. Close the start tag with the ${NEW_FILE_END}.
|
| 192 |
-
9. Start the file content with the triple backticks and appropriate language marker
|
| 193 |
10. Insert the file content there.
|
| 194 |
11. Close with the triple backticks, like \`\`\`.
|
| 195 |
12. Repeat for each file.
|
|
|
|
| 196 |
Example Code:
|
| 197 |
${PROJECT_NAME_START} Project Name ${PROJECT_NAME_END}
|
| 198 |
${NEW_FILE_START}index.html${NEW_FILE_END}
|
|
@@ -210,15 +122,83 @@ ${NEW_FILE_START}index.html${NEW_FILE_END}
|
|
| 210 |
<script src="https://unpkg.com/feather-icons"></script>
|
| 211 |
</head>
|
| 212 |
<body>
|
| 213 |
-
<
|
| 214 |
-
<
|
| 215 |
-
<
|
|
|
|
|
|
|
| 216 |
<script src="script.js"></script>
|
| 217 |
<script>feather.replace();</script>
|
| 218 |
</body>
|
| 219 |
</html>
|
| 220 |
\`\`\`
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
|
| 223 |
export const FOLLOW_UP_SYSTEM_PROMPT = `You are an expert UI/UX and Front-End Developer modifying existing files (HTML, CSS, JavaScript).
|
| 224 |
The user wants to apply changes and probably add new features/pages/styles/scripts to the website, based on their request.
|
|
|
|
| 15 |
Examples: http://static.photos/red/320x240/133 (red-themed with seed 133), http://static.photos/640x360 (random category and image), http://static.photos/nature/1200x630/42 (nature-themed with seed 42).`
|
| 16 |
export const PROMPT_FOR_PROJECT_NAME = `REQUIRED: Generate a name for the project, based on the user's request. Try to be creative and unique. Add a emoji at the end of the name. It should be short, like 6 words. Be fancy, creative and funny. DON'T FORGET IT, IT'S IMPORTANT!`
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
export const INITIAL_SYSTEM_PROMPT = `You are an expert UI/UX and Front-End Developer.
|
| 19 |
You create website in a way a designer would, using ONLY HTML, CSS and Javascript.
|
| 20 |
Try to create the best UI possible. Important: Make the website responsive by using TailwindCSS. Use it as much as you can, if you can't use it, use custom css (make sure to import tailwind with <script src="https://cdn.tailwindcss.com"></script> in the head).
|
| 21 |
Also try to elaborate as much as you can, to create something unique, with a great design.
|
| 22 |
If you want to use ICONS import Feather Icons (Make sure to add <script src="https://unpkg.com/feather-icons"></script> and <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> in the head., and <script>feather.replace();</script> in the body. Ex : <i data-feather="user"></i>).
|
| 23 |
+
For interactive animations you can use: Vanta.js (Make sure to add <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script> and <script>VANTA.GLOBE({...</script> in the body.).
|
| 24 |
Don't hesitate to use real public API for the datas, you can find good ones here https://github.com/public-apis/public-apis depending on what the user asks for.
|
| 25 |
You can create multiple pages website at once (following the format rules below) or a Single Page Application. But make sure to create multiple pages if the user asks for different pages.
|
| 26 |
IMPORTANT: To avoid duplicate code across pages, you MUST create separate style.css and script.js files for shared CSS and JavaScript code. Each HTML file should link to these files using <link rel="stylesheet" href="style.css"> and <script src="script.js"></script>.
|
| 27 |
WEB COMPONENTS: For reusable UI elements like navbars, footers, sidebars, headers, etc., create Native Web Components as separate files in components/ folder:
|
| 28 |
+
- Create each component as a separate .js file in components/ folder (e.g., components/navbar.js, components/footer.js)
|
| 29 |
- Each component file defines a class extending HTMLElement and registers it with customElements.define()
|
| 30 |
- Use Shadow DOM for style encapsulation
|
| 31 |
- Components render using template literals with inline styles
|
| 32 |
+
- Include component files in HTML before using them: <script src="components/navbar.js"></script>
|
| 33 |
+
- Use them in HTML pages with custom element tags (e.g., <custom-navbar></custom-navbar>)
|
| 34 |
- If you want to use ICON you can use Feather Icons, as it's already included in the main pages.
|
| 35 |
IMPORTANT: NEVER USE ONCLICK FUNCTION TO MAKE A REDIRECT TO NEW PAGE. MAKE SURE TO ALWAYS USE <a href=""/>, OTHERWISE IT WONT WORK WITH SHADOW ROOT AND WEB COMPONENTS.
|
| 36 |
+
Example components/navbar.js:
|
| 37 |
+
class CustomNavbar extends HTMLElement {
|
| 38 |
+
connectedCallback() {
|
| 39 |
+
this.attachShadow({ mode: 'open' });
|
| 40 |
+
this.shadowRoot.innerHTML = \`
|
| 41 |
+
<style>
|
| 42 |
+
nav {
|
| 43 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 44 |
+
padding: 1rem;
|
| 45 |
+
display: flex;
|
| 46 |
+
justify-content: space-between;
|
| 47 |
+
align-items: center;
|
| 48 |
+
}
|
| 49 |
+
.logo { color: white; font-weight: bold; }
|
| 50 |
+
ul { display: flex; gap: 1rem; list-style: none; margin: 0; padding: 0; }
|
| 51 |
+
a { color: white; text-decoration: none; }
|
| 52 |
+
</style>
|
| 53 |
+
<nav>
|
| 54 |
+
<div class="logo">My Website</div>
|
| 55 |
+
<ul>
|
| 56 |
+
<li><a href="/">Home</a></li>
|
| 57 |
+
<li><a href="/about.html">About</a></li>
|
| 58 |
+
</ul>
|
| 59 |
+
</nav>
|
| 60 |
+
\`;
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
customElements.define('custom-navbar', CustomNavbar);
|
| 64 |
+
|
| 65 |
+
Example components/footer.js:
|
| 66 |
+
class CustomFooter extends HTMLElement {
|
| 67 |
connectedCallback() {
|
| 68 |
this.attachShadow({ mode: 'open' });
|
| 69 |
this.shadowRoot.innerHTML = \`
|
| 70 |
<style>
|
| 71 |
+
footer {
|
| 72 |
+
background: #1a202c;
|
| 73 |
+
color: white;
|
| 74 |
+
padding: 2rem;
|
| 75 |
+
text-align: center;
|
| 76 |
+
}
|
| 77 |
</style>
|
| 78 |
+
<footer>
|
| 79 |
+
<p>© 2024 My Website. All rights reserved.</p>
|
| 80 |
+
</footer>
|
| 81 |
\`;
|
| 82 |
}
|
| 83 |
}
|
| 84 |
+
customElements.define('custom-footer', CustomFooter);
|
| 85 |
+
|
| 86 |
Then in HTML, include the component scripts and use the tags:
|
| 87 |
+
<script src="components/navbar.js"></script>
|
| 88 |
+
<script src="components/footer.js"></script>
|
| 89 |
+
<custom-navbar></custom-navbar>
|
| 90 |
+
<custom-footer></custom-footer>
|
| 91 |
${PROMPT_FOR_IMAGE_GENERATION}
|
| 92 |
${PROMPT_FOR_PROJECT_NAME}
|
| 93 |
No need to explain what you did. Just return the expected result. AVOID Chinese characters in the code if not asked by the user.
|
|
|
|
| 96 |
2. Add the name of the project, right after the start tag.
|
| 97 |
3. Close the start tag with the ${PROJECT_NAME_END}.
|
| 98 |
4. The name of the project should be short and concise.
|
| 99 |
+
5. Generate files in this ORDER: index.html FIRST, then style.css, then script.js, then web components (components/navbar.js, components/footer.js, etc.), then other HTML pages.
|
| 100 |
6. For each file, start with ${NEW_FILE_START}.
|
| 101 |
+
7. Add the file name (index.html, style.css, script.js, components/navbar.js, about.html, etc.) right after the start tag.
|
| 102 |
8. Close the start tag with the ${NEW_FILE_END}.
|
| 103 |
+
9. Start the file content with the triple backticks and appropriate language marker (\`\`\`html, \`\`\`css, or \`\`\`javascript).
|
| 104 |
10. Insert the file content there.
|
| 105 |
11. Close with the triple backticks, like \`\`\`.
|
| 106 |
12. Repeat for each file.
|
| 107 |
+
13. Web components should be in separate .js files in components/ folder and included via <script> tags before use.
|
| 108 |
Example Code:
|
| 109 |
${PROJECT_NAME_START} Project Name ${PROJECT_NAME_END}
|
| 110 |
${NEW_FILE_START}index.html${NEW_FILE_END}
|
|
|
|
| 122 |
<script src="https://unpkg.com/feather-icons"></script>
|
| 123 |
</head>
|
| 124 |
<body>
|
| 125 |
+
<custom-navbar></custom-navbar>
|
| 126 |
+
<h1>Hello World</h1>
|
| 127 |
+
<custom-footer></custom-footer>
|
| 128 |
+
<script src="components/navbar.js"></script>
|
| 129 |
+
<script src="components/footer.js"></script>
|
| 130 |
<script src="script.js"></script>
|
| 131 |
<script>feather.replace();</script>
|
| 132 |
</body>
|
| 133 |
</html>
|
| 134 |
\`\`\`
|
| 135 |
+
${NEW_FILE_START}style.css${NEW_FILE_END}
|
| 136 |
+
\`\`\`css
|
| 137 |
+
/* Shared styles across all pages */
|
| 138 |
+
body {
|
| 139 |
+
font-family: 'Inter', sans-serif;
|
| 140 |
+
}
|
| 141 |
+
\`\`\`
|
| 142 |
+
${NEW_FILE_START}script.js${NEW_FILE_END}
|
| 143 |
+
\`\`\`javascript
|
| 144 |
+
// Shared JavaScript across all pages
|
| 145 |
+
console.log('App loaded');
|
| 146 |
+
\`\`\`
|
| 147 |
+
${NEW_FILE_START}components/navbar.js${NEW_FILE_END}
|
| 148 |
+
\`\`\`javascript
|
| 149 |
+
class CustomNavbar extends HTMLElement {
|
| 150 |
+
connectedCallback() {
|
| 151 |
+
this.attachShadow({ mode: 'open' });
|
| 152 |
+
this.shadowRoot.innerHTML = \`
|
| 153 |
+
<style>
|
| 154 |
+
nav {
|
| 155 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 156 |
+
padding: 1rem;
|
| 157 |
+
display: flex;
|
| 158 |
+
justify-content: space-between;
|
| 159 |
+
align-items: center;
|
| 160 |
+
}
|
| 161 |
+
.logo { color: white; font-weight: bold; font-size: 1.25rem; }
|
| 162 |
+
ul { display: flex; gap: 1rem; list-style: none; margin: 0; padding: 0; }
|
| 163 |
+
a { color: white; text-decoration: none; transition: opacity 0.2s; }
|
| 164 |
+
a:hover { opacity: 0.8; }
|
| 165 |
+
</style>
|
| 166 |
+
<nav>
|
| 167 |
+
<div class="logo">My Website</div>
|
| 168 |
+
<ul>
|
| 169 |
+
<li><a href="/">Home</a></li>
|
| 170 |
+
<li><a href="/about.html">About</a></li>
|
| 171 |
+
</ul>
|
| 172 |
+
</nav>
|
| 173 |
+
\`;
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
customElements.define('custom-navbar', CustomNavbar);
|
| 177 |
+
\`\`\`
|
| 178 |
+
${NEW_FILE_START}components/footer.js${NEW_FILE_END}
|
| 179 |
+
\`\`\`javascript
|
| 180 |
+
class CustomFooter extends HTMLElement {
|
| 181 |
+
connectedCallback() {
|
| 182 |
+
this.attachShadow({ mode: 'open' });
|
| 183 |
+
this.shadowRoot.innerHTML = \`
|
| 184 |
+
<style>
|
| 185 |
+
footer {
|
| 186 |
+
background: #1a202c;
|
| 187 |
+
color: white;
|
| 188 |
+
padding: 2rem;
|
| 189 |
+
text-align: center;
|
| 190 |
+
margin-top: auto;
|
| 191 |
+
}
|
| 192 |
+
</style>
|
| 193 |
+
<footer>
|
| 194 |
+
<p>© 2024 My Website. All rights reserved.</p>
|
| 195 |
+
</footer>
|
| 196 |
+
\`;
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
customElements.define('custom-footer', CustomFooter);
|
| 200 |
+
\`\`\`
|
| 201 |
+
CRITICAL: The first file MUST always be index.html. Then generate style.css and script.js. If you create web components, place them in components/ folder as separate .js files. All HTML files MUST include <link rel="stylesheet" href="style.css"> and component scripts before using them (e.g., <script src="components/navbar.js"></script>), then <script src="script.js"></script>.`
|
| 202 |
|
| 203 |
export const FOLLOW_UP_SYSTEM_PROMPT = `You are an expert UI/UX and Front-End Developer modifying existing files (HTML, CSS, JavaScript).
|
| 204 |
The user wants to apply changes and probably add new features/pages/styles/scripts to the website, based on their request.
|
lib/providers.ts
CHANGED
|
@@ -2,7 +2,6 @@ import DeepSeekLogo from "@/assets/deepseek.svg";
|
|
| 2 |
import QwenLogo from "@/assets/qwen.svg";
|
| 3 |
import KimiLogo from "@/assets/kimi.svg";
|
| 4 |
import ZaiLogo from "@/assets/zai.svg";
|
| 5 |
-
import MiniMaxLogo from "@/assets/minimax.svg";
|
| 6 |
|
| 7 |
export const PROVIDERS = {
|
| 8 |
"fireworks-ai": {
|
|
@@ -48,28 +47,29 @@ export const MODELS = [
|
|
| 48 |
logo: DeepSeekLogo,
|
| 49 |
companyName: "DeepSeek",
|
| 50 |
},
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
{
|
| 68 |
value: "deepseek-ai/DeepSeek-V3.2-Exp",
|
| 69 |
label: "DeepSeek V3.2 Exp",
|
| 70 |
providers: ["novita"],
|
| 71 |
autoProvider: "novita",
|
| 72 |
logo: DeepSeekLogo,
|
|
|
|
| 73 |
companyName: "DeepSeek",
|
| 74 |
},
|
| 75 |
{
|
|
@@ -88,35 +88,18 @@ export const MODELS = [
|
|
| 88 |
logo: KimiLogo,
|
| 89 |
companyName: "Kimi",
|
| 90 |
},
|
| 91 |
-
// {
|
| 92 |
-
// value: "moonshotai/Kimi-K2-Instruct-0905",
|
| 93 |
-
// label: "Kimi K2 Instruct 0905",
|
| 94 |
-
// providers: ["together", "groq", "novita"],
|
| 95 |
-
// autoProvider: "groq",
|
| 96 |
-
// logo: KimiLogo,
|
| 97 |
-
// companyName: "Kimi",
|
| 98 |
-
// },
|
| 99 |
{
|
| 100 |
-
value: "moonshotai/Kimi-K2-
|
| 101 |
-
label: "Kimi K2
|
|
|
|
|
|
|
| 102 |
logo: KimiLogo,
|
| 103 |
companyName: "Kimi",
|
| 104 |
-
isNew: true,
|
| 105 |
-
temperature: 1.0,
|
| 106 |
},
|
| 107 |
{
|
| 108 |
value: "zai-org/GLM-4.6",
|
| 109 |
label: "GLM-4.6",
|
| 110 |
logo: ZaiLogo,
|
| 111 |
companyName: "Z.ai",
|
| 112 |
-
}
|
| 113 |
-
{
|
| 114 |
-
value: "MiniMaxAI/MiniMax-M2",
|
| 115 |
-
label: "MiniMax M2",
|
| 116 |
-
logo: MiniMaxLogo,
|
| 117 |
-
companyName: "MiniMax",
|
| 118 |
-
top_k: 40,
|
| 119 |
-
temperature: 1.0,
|
| 120 |
-
top_p: 0.95,
|
| 121 |
-
},
|
| 122 |
];
|
|
|
|
| 2 |
import QwenLogo from "@/assets/qwen.svg";
|
| 3 |
import KimiLogo from "@/assets/kimi.svg";
|
| 4 |
import ZaiLogo from "@/assets/zai.svg";
|
|
|
|
| 5 |
|
| 6 |
export const PROVIDERS = {
|
| 7 |
"fireworks-ai": {
|
|
|
|
| 47 |
logo: DeepSeekLogo,
|
| 48 |
companyName: "DeepSeek",
|
| 49 |
},
|
| 50 |
+
{
|
| 51 |
+
value: "deepseek-ai/DeepSeek-V3.1",
|
| 52 |
+
label: "DeepSeek V3.1",
|
| 53 |
+
providers: ["fireworks-ai", "novita"],
|
| 54 |
+
autoProvider: "fireworks-ai",
|
| 55 |
+
logo: DeepSeekLogo,
|
| 56 |
+
companyName: "DeepSeek",
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
value: "deepseek-ai/DeepSeek-V3.1-Terminus",
|
| 60 |
+
label: "DeepSeek V3.1 Terminus",
|
| 61 |
+
providers: ["novita"],
|
| 62 |
+
autoProvider: "novita",
|
| 63 |
+
logo: DeepSeekLogo,
|
| 64 |
+
companyName: "DeepSeek",
|
| 65 |
+
},
|
| 66 |
{
|
| 67 |
value: "deepseek-ai/DeepSeek-V3.2-Exp",
|
| 68 |
label: "DeepSeek V3.2 Exp",
|
| 69 |
providers: ["novita"],
|
| 70 |
autoProvider: "novita",
|
| 71 |
logo: DeepSeekLogo,
|
| 72 |
+
isNew: true,
|
| 73 |
companyName: "DeepSeek",
|
| 74 |
},
|
| 75 |
{
|
|
|
|
| 88 |
logo: KimiLogo,
|
| 89 |
companyName: "Kimi",
|
| 90 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
{
|
| 92 |
+
value: "moonshotai/Kimi-K2-Instruct-0905",
|
| 93 |
+
label: "Kimi K2 Instruct 0905",
|
| 94 |
+
providers: ["together", "groq", "novita"],
|
| 95 |
+
autoProvider: "groq",
|
| 96 |
logo: KimiLogo,
|
| 97 |
companyName: "Kimi",
|
|
|
|
|
|
|
| 98 |
},
|
| 99 |
{
|
| 100 |
value: "zai-org/GLM-4.6",
|
| 101 |
label: "GLM-4.6",
|
| 102 |
logo: ZaiLogo,
|
| 103 |
companyName: "Z.ai",
|
| 104 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
];
|
middleware.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
import { NextResponse } from "next/server";
|
| 2 |
import type { NextRequest } from "next/server";
|
| 3 |
|
| 4 |
-
export function middleware(request: NextRequest) {
|
| 5 |
const headers = new Headers(request.headers);
|
| 6 |
headers.set("x-current-host", request.nextUrl.host);
|
| 7 |
-
headers.set("x-invoke-path", request.nextUrl.pathname + request.nextUrl.search);
|
| 8 |
|
| 9 |
const response = NextResponse.next({ headers });
|
| 10 |
|
|
|
|
| 1 |
import { NextResponse } from "next/server";
|
| 2 |
import type { NextRequest } from "next/server";
|
| 3 |
|
| 4 |
+
export function middleware(request: NextRequest) {
|
| 5 |
const headers = new Headers(request.headers);
|
| 6 |
headers.set("x-current-host", request.nextUrl.host);
|
|
|
|
| 7 |
|
| 8 |
const response = NextResponse.next({ headers });
|
| 9 |
|
package-lock.json
CHANGED
|
@@ -33,7 +33,6 @@
|
|
| 33 |
"clsx": "^2.1.1",
|
| 34 |
"date-fns": "^4.1.0",
|
| 35 |
"framer-motion": "^12.23.22",
|
| 36 |
-
"jszip": "^3.10.1",
|
| 37 |
"log4js": "^6.9.1",
|
| 38 |
"log4js-json-layout": "^2.2.3",
|
| 39 |
"lucide-react": "^0.542.0",
|
|
@@ -320,13 +319,13 @@
|
|
| 320 |
}
|
| 321 |
},
|
| 322 |
"node_modules/@huggingface/inference": {
|
| 323 |
-
"version": "4.
|
| 324 |
-
"resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-4.
|
| 325 |
-
"integrity": "sha512-
|
| 326 |
"license": "MIT",
|
| 327 |
"dependencies": {
|
| 328 |
"@huggingface/jinja": "^0.5.1",
|
| 329 |
-
"@huggingface/tasks": "^0.19.
|
| 330 |
},
|
| 331 |
"engines": {
|
| 332 |
"node": ">=18"
|
|
@@ -342,9 +341,9 @@
|
|
| 342 |
}
|
| 343 |
},
|
| 344 |
"node_modules/@huggingface/tasks": {
|
| 345 |
-
"version": "0.19.
|
| 346 |
-
"resolved": "https://registry.npmjs.org/@huggingface/tasks/-/tasks-0.19.
|
| 347 |
-
"integrity": "sha512-
|
| 348 |
"license": "MIT"
|
| 349 |
},
|
| 350 |
"node_modules/@humanfs/core": {
|
|
@@ -3240,12 +3239,6 @@
|
|
| 3240 |
"toggle-selection": "^1.0.6"
|
| 3241 |
}
|
| 3242 |
},
|
| 3243 |
-
"node_modules/core-util-is": {
|
| 3244 |
-
"version": "1.0.3",
|
| 3245 |
-
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
| 3246 |
-
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
| 3247 |
-
"license": "MIT"
|
| 3248 |
-
},
|
| 3249 |
"node_modules/cross-spawn": {
|
| 3250 |
"version": "7.0.6",
|
| 3251 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
|
@@ -4092,12 +4085,6 @@
|
|
| 4092 |
"node": ">= 4"
|
| 4093 |
}
|
| 4094 |
},
|
| 4095 |
-
"node_modules/immediate": {
|
| 4096 |
-
"version": "3.0.6",
|
| 4097 |
-
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
| 4098 |
-
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
| 4099 |
-
"license": "MIT"
|
| 4100 |
-
},
|
| 4101 |
"node_modules/import-fresh": {
|
| 4102 |
"version": "3.3.1",
|
| 4103 |
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
|
@@ -4125,12 +4112,6 @@
|
|
| 4125 |
"node": ">=0.8.19"
|
| 4126 |
}
|
| 4127 |
},
|
| 4128 |
-
"node_modules/inherits": {
|
| 4129 |
-
"version": "2.0.4",
|
| 4130 |
-
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
| 4131 |
-
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
| 4132 |
-
"license": "ISC"
|
| 4133 |
-
},
|
| 4134 |
"node_modules/inline-style-prefixer": {
|
| 4135 |
"version": "7.0.1",
|
| 4136 |
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz",
|
|
@@ -4177,12 +4158,6 @@
|
|
| 4177 |
"node": ">=0.12.0"
|
| 4178 |
}
|
| 4179 |
},
|
| 4180 |
-
"node_modules/isarray": {
|
| 4181 |
-
"version": "1.0.0",
|
| 4182 |
-
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
| 4183 |
-
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
| 4184 |
-
"license": "MIT"
|
| 4185 |
-
},
|
| 4186 |
"node_modules/isexe": {
|
| 4187 |
"version": "2.0.0",
|
| 4188 |
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
|
@@ -4298,18 +4273,6 @@
|
|
| 4298 |
"graceful-fs": "^4.1.6"
|
| 4299 |
}
|
| 4300 |
},
|
| 4301 |
-
"node_modules/jszip": {
|
| 4302 |
-
"version": "3.10.1",
|
| 4303 |
-
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
| 4304 |
-
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
| 4305 |
-
"license": "(MIT OR GPL-3.0-or-later)",
|
| 4306 |
-
"dependencies": {
|
| 4307 |
-
"lie": "~3.3.0",
|
| 4308 |
-
"pako": "~1.0.2",
|
| 4309 |
-
"readable-stream": "~2.3.6",
|
| 4310 |
-
"setimmediate": "^1.0.5"
|
| 4311 |
-
}
|
| 4312 |
-
},
|
| 4313 |
"node_modules/kareem": {
|
| 4314 |
"version": "2.6.3",
|
| 4315 |
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
|
|
@@ -4343,15 +4306,6 @@
|
|
| 4343 |
"node": ">= 0.8.0"
|
| 4344 |
}
|
| 4345 |
},
|
| 4346 |
-
"node_modules/lie": {
|
| 4347 |
-
"version": "3.3.0",
|
| 4348 |
-
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
| 4349 |
-
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
| 4350 |
-
"license": "MIT",
|
| 4351 |
-
"dependencies": {
|
| 4352 |
-
"immediate": "~3.0.5"
|
| 4353 |
-
}
|
| 4354 |
-
},
|
| 4355 |
"node_modules/lightningcss": {
|
| 4356 |
"version": "1.30.1",
|
| 4357 |
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
|
@@ -5132,12 +5086,6 @@
|
|
| 5132 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 5133 |
}
|
| 5134 |
},
|
| 5135 |
-
"node_modules/pako": {
|
| 5136 |
-
"version": "1.0.11",
|
| 5137 |
-
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
| 5138 |
-
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
| 5139 |
-
"license": "(MIT AND Zlib)"
|
| 5140 |
-
},
|
| 5141 |
"node_modules/parent-module": {
|
| 5142 |
"version": "1.0.1",
|
| 5143 |
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
|
@@ -5228,12 +5176,6 @@
|
|
| 5228 |
"node": ">= 0.8.0"
|
| 5229 |
}
|
| 5230 |
},
|
| 5231 |
-
"node_modules/process-nextick-args": {
|
| 5232 |
-
"version": "2.0.1",
|
| 5233 |
-
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
| 5234 |
-
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
| 5235 |
-
"license": "MIT"
|
| 5236 |
-
},
|
| 5237 |
"node_modules/proxy-from-env": {
|
| 5238 |
"version": "1.1.0",
|
| 5239 |
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
@@ -5404,27 +5346,6 @@
|
|
| 5404 |
"react-dom": "*"
|
| 5405 |
}
|
| 5406 |
},
|
| 5407 |
-
"node_modules/readable-stream": {
|
| 5408 |
-
"version": "2.3.8",
|
| 5409 |
-
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
| 5410 |
-
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
| 5411 |
-
"license": "MIT",
|
| 5412 |
-
"dependencies": {
|
| 5413 |
-
"core-util-is": "~1.0.0",
|
| 5414 |
-
"inherits": "~2.0.3",
|
| 5415 |
-
"isarray": "~1.0.0",
|
| 5416 |
-
"process-nextick-args": "~2.0.0",
|
| 5417 |
-
"safe-buffer": "~5.1.1",
|
| 5418 |
-
"string_decoder": "~1.1.1",
|
| 5419 |
-
"util-deprecate": "~1.0.1"
|
| 5420 |
-
}
|
| 5421 |
-
},
|
| 5422 |
-
"node_modules/readable-stream/node_modules/safe-buffer": {
|
| 5423 |
-
"version": "5.1.2",
|
| 5424 |
-
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
| 5425 |
-
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
| 5426 |
-
"license": "MIT"
|
| 5427 |
-
},
|
| 5428 |
"node_modules/require-from-string": {
|
| 5429 |
"version": "2.0.2",
|
| 5430 |
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
|
@@ -5587,12 +5508,6 @@
|
|
| 5587 |
"node": ">=6.9"
|
| 5588 |
}
|
| 5589 |
},
|
| 5590 |
-
"node_modules/setimmediate": {
|
| 5591 |
-
"version": "1.0.5",
|
| 5592 |
-
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
| 5593 |
-
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
| 5594 |
-
"license": "MIT"
|
| 5595 |
-
},
|
| 5596 |
"node_modules/sharp": {
|
| 5597 |
"version": "0.34.3",
|
| 5598 |
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
|
|
@@ -5788,21 +5703,6 @@
|
|
| 5788 |
"node": ">=8.0"
|
| 5789 |
}
|
| 5790 |
},
|
| 5791 |
-
"node_modules/string_decoder": {
|
| 5792 |
-
"version": "1.1.1",
|
| 5793 |
-
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
| 5794 |
-
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
| 5795 |
-
"license": "MIT",
|
| 5796 |
-
"dependencies": {
|
| 5797 |
-
"safe-buffer": "~5.1.0"
|
| 5798 |
-
}
|
| 5799 |
-
},
|
| 5800 |
-
"node_modules/string_decoder/node_modules/safe-buffer": {
|
| 5801 |
-
"version": "5.1.2",
|
| 5802 |
-
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
| 5803 |
-
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
| 5804 |
-
"license": "MIT"
|
| 5805 |
-
},
|
| 5806 |
"node_modules/strip-json-comments": {
|
| 5807 |
"version": "3.1.1",
|
| 5808 |
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
|
@@ -6257,12 +6157,6 @@
|
|
| 6257 |
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 6258 |
}
|
| 6259 |
},
|
| 6260 |
-
"node_modules/util-deprecate": {
|
| 6261 |
-
"version": "1.0.2",
|
| 6262 |
-
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
| 6263 |
-
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
| 6264 |
-
"license": "MIT"
|
| 6265 |
-
},
|
| 6266 |
"node_modules/watchpack": {
|
| 6267 |
"version": "2.4.4",
|
| 6268 |
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
|
|
|
| 33 |
"clsx": "^2.1.1",
|
| 34 |
"date-fns": "^4.1.0",
|
| 35 |
"framer-motion": "^12.23.22",
|
|
|
|
| 36 |
"log4js": "^6.9.1",
|
| 37 |
"log4js-json-layout": "^2.2.3",
|
| 38 |
"lucide-react": "^0.542.0",
|
|
|
|
| 319 |
}
|
| 320 |
},
|
| 321 |
"node_modules/@huggingface/inference": {
|
| 322 |
+
"version": "4.7.1",
|
| 323 |
+
"resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-4.7.1.tgz",
|
| 324 |
+
"integrity": "sha512-gXrMocGDsE6kUZPEj82c3O+/OKnIfbHvg9rYjGA6svbWrYVmHCIAdCrrgCwNl2v5GELfPJrrfIv0bvzCTfa64A==",
|
| 325 |
"license": "MIT",
|
| 326 |
"dependencies": {
|
| 327 |
"@huggingface/jinja": "^0.5.1",
|
| 328 |
+
"@huggingface/tasks": "^0.19.35"
|
| 329 |
},
|
| 330 |
"engines": {
|
| 331 |
"node": ">=18"
|
|
|
|
| 341 |
}
|
| 342 |
},
|
| 343 |
"node_modules/@huggingface/tasks": {
|
| 344 |
+
"version": "0.19.43",
|
| 345 |
+
"resolved": "https://registry.npmjs.org/@huggingface/tasks/-/tasks-0.19.43.tgz",
|
| 346 |
+
"integrity": "sha512-ANO23K3ugclBl6VLwdt+7MxBkRkKEE17USUSqprHb29UB5ISigH+0AJcEuDA064uzn0hqYrG/nOcv1yARRt8bw==",
|
| 347 |
"license": "MIT"
|
| 348 |
},
|
| 349 |
"node_modules/@humanfs/core": {
|
|
|
|
| 3239 |
"toggle-selection": "^1.0.6"
|
| 3240 |
}
|
| 3241 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3242 |
"node_modules/cross-spawn": {
|
| 3243 |
"version": "7.0.6",
|
| 3244 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
|
|
|
| 4085 |
"node": ">= 4"
|
| 4086 |
}
|
| 4087 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4088 |
"node_modules/import-fresh": {
|
| 4089 |
"version": "3.3.1",
|
| 4090 |
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
|
|
|
| 4112 |
"node": ">=0.8.19"
|
| 4113 |
}
|
| 4114 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4115 |
"node_modules/inline-style-prefixer": {
|
| 4116 |
"version": "7.0.1",
|
| 4117 |
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz",
|
|
|
|
| 4158 |
"node": ">=0.12.0"
|
| 4159 |
}
|
| 4160 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4161 |
"node_modules/isexe": {
|
| 4162 |
"version": "2.0.0",
|
| 4163 |
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
|
|
|
| 4273 |
"graceful-fs": "^4.1.6"
|
| 4274 |
}
|
| 4275 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4276 |
"node_modules/kareem": {
|
| 4277 |
"version": "2.6.3",
|
| 4278 |
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
|
|
|
|
| 4306 |
"node": ">= 0.8.0"
|
| 4307 |
}
|
| 4308 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4309 |
"node_modules/lightningcss": {
|
| 4310 |
"version": "1.30.1",
|
| 4311 |
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
|
|
|
| 5086 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 5087 |
}
|
| 5088 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5089 |
"node_modules/parent-module": {
|
| 5090 |
"version": "1.0.1",
|
| 5091 |
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
|
|
|
| 5176 |
"node": ">= 0.8.0"
|
| 5177 |
}
|
| 5178 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5179 |
"node_modules/proxy-from-env": {
|
| 5180 |
"version": "1.1.0",
|
| 5181 |
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
|
|
| 5346 |
"react-dom": "*"
|
| 5347 |
}
|
| 5348 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5349 |
"node_modules/require-from-string": {
|
| 5350 |
"version": "2.0.2",
|
| 5351 |
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
|
|
|
| 5508 |
"node": ">=6.9"
|
| 5509 |
}
|
| 5510 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5511 |
"node_modules/sharp": {
|
| 5512 |
"version": "0.34.3",
|
| 5513 |
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
|
|
|
|
| 5703 |
"node": ">=8.0"
|
| 5704 |
}
|
| 5705 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5706 |
"node_modules/strip-json-comments": {
|
| 5707 |
"version": "3.1.1",
|
| 5708 |
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
|
|
|
| 6157 |
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 6158 |
}
|
| 6159 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6160 |
"node_modules/watchpack": {
|
| 6161 |
"version": "2.4.4",
|
| 6162 |
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
package.json
CHANGED
|
@@ -33,7 +33,6 @@
|
|
| 33 |
"clsx": "^2.1.1",
|
| 34 |
"date-fns": "^4.1.0",
|
| 35 |
"framer-motion": "^12.23.22",
|
| 36 |
-
"jszip": "^3.10.1",
|
| 37 |
"log4js": "^6.9.1",
|
| 38 |
"log4js-json-layout": "^2.2.3",
|
| 39 |
"lucide-react": "^0.542.0",
|
|
|
|
| 33 |
"clsx": "^2.1.1",
|
| 34 |
"date-fns": "^4.1.0",
|
| 35 |
"framer-motion": "^12.23.22",
|
|
|
|
| 36 |
"log4js": "^6.9.1",
|
| 37 |
"log4js-json-layout": "^2.2.3",
|
| 38 |
"lucide-react": "^0.542.0",
|