This view is limited to 50 files because it contains too many changes.  See the raw diff here.
Files changed (50) hide show
  1. .env.example +5 -0
  2. .gitignore +23 -38
  3. Dockerfile +5 -2
  4. README.md +4 -20
  5. app/(public)/layout.tsx +0 -15
  6. app/(public)/page.tsx +0 -5
  7. app/[namespace]/[repoId]/page.tsx +0 -28
  8. app/actions/auth.ts +0 -18
  9. app/actions/projects.ts +0 -47
  10. app/api/ask/route.ts +0 -717
  11. app/api/auth/login-url/route.ts +0 -23
  12. app/api/auth/logout/route.ts +0 -25
  13. app/api/auth/route.ts +0 -106
  14. app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/promote/route.ts +0 -190
  15. app/api/me/projects/[namespace]/[repoId]/images/route.ts +0 -113
  16. app/api/me/projects/[namespace]/[repoId]/route.ts +0 -187
  17. app/api/me/projects/[namespace]/[repoId]/save/route.ts +0 -64
  18. app/api/me/projects/route.ts +0 -107
  19. app/api/me/route.ts +0 -46
  20. app/api/re-design/route.ts +0 -39
  21. app/auth/callback/page.tsx +0 -97
  22. app/auth/page.tsx +0 -28
  23. app/favicon.ico +0 -0
  24. app/layout.tsx +0 -128
  25. app/new/page.tsx +0 -14
  26. app/sitemap.ts +0 -28
  27. assets/deepseek.svg +0 -1
  28. assets/globals.css +0 -371
  29. assets/kimi.svg +0 -1
  30. assets/logo.svg +0 -316
  31. assets/qwen.svg +0 -1
  32. assets/zai.svg +0 -13
  33. components.json +0 -21
  34. components/animated-blobs/index.tsx +0 -34
  35. components/animated-text/index.tsx +0 -123
  36. components/contexts/app-context.tsx +0 -53
  37. components/contexts/login-context.tsx +0 -62
  38. components/contexts/pro-context.tsx +0 -48
  39. components/contexts/tanstack-query-context.tsx +0 -31
  40. components/contexts/user-context.tsx +0 -8
  41. components/editor/ask-ai/fake-ask.tsx +0 -97
  42. components/editor/ask-ai/index.tsx +0 -322
  43. components/editor/ask-ai/loading.tsx +0 -68
  44. components/editor/ask-ai/prompt-builder/content-modal.tsx +0 -196
  45. components/editor/ask-ai/prompt-builder/index.tsx +0 -68
  46. components/editor/ask-ai/prompt-builder/tailwind-colors.tsx +0 -58
  47. components/editor/ask-ai/prompt-builder/themes.tsx +0 -48
  48. components/editor/ask-ai/re-imagine.tsx +0 -152
  49. components/editor/ask-ai/selected-files.tsx +0 -47
  50. components/editor/ask-ai/selected-html-element.tsx +0 -57
.env.example ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ OAUTH_CLIENT_ID=
2
+ OAUTH_CLIENT_SECRET=
3
+ APP_PORT=5173
4
+ REDIRECT_URI=http://localhost:5173/auth/login
5
+ DEFAULT_HF_TOKEN=
.gitignore CHANGED
@@ -1,41 +1,26 @@
1
- # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
-
3
- # dependencies
4
- /node_modules
5
- /.pnp
6
- .pnp.*
7
- .yarn/*
8
- !.yarn/patches
9
- !.yarn/plugins
10
- !.yarn/releases
11
- !.yarn/versions
12
-
13
- # testing
14
- /coverage
15
-
16
- # next.js
17
- /.next/
18
- /out/
19
-
20
- # production
21
- /build
22
-
23
- # misc
24
- .DS_Store
25
- *.pem
26
-
27
- # debug
28
  npm-debug.log*
29
  yarn-debug.log*
30
  yarn-error.log*
31
- .pnpm-debug.log*
32
-
33
- # env files (can opt-in for committing if needed)
34
- .env*
35
-
36
- # vercel
37
- .vercel
38
-
39
- # typescript
40
- *.tsbuildinfo
41
- next-env.d.ts
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  npm-debug.log*
5
  yarn-debug.log*
6
  yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
25
+ .env
26
+ .aider*
Dockerfile CHANGED
@@ -1,6 +1,9 @@
1
- FROM node:20-alpine
 
 
2
  USER root
3
 
 
4
  USER 1000
5
  WORKDIR /usr/src/app
6
  # Copy package.json and package-lock.json to the container
@@ -13,7 +16,7 @@ RUN npm install
13
  RUN npm run build
14
 
15
  # Expose the application port (assuming your app runs on port 3000)
16
- EXPOSE 3000
17
 
18
  # Start the application
19
  CMD ["npm", "start"]
 
1
+ # Dockerfile
2
+ # Use an official Node.js runtime as the base image
3
+ FROM node:22.1.0
4
  USER root
5
 
6
+ RUN apt-get update
7
  USER 1000
8
  WORKDIR /usr/src/app
9
  # Copy package.json and package-lock.json to the container
 
16
  RUN npm run build
17
 
18
  # Expose the application port (assuming your app runs on port 3000)
19
+ EXPOSE 5173
20
 
21
  # Start the application
22
  CMD ["npm", "start"]
README.md CHANGED
@@ -1,29 +1,13 @@
1
  ---
2
- title: DeepSite v3
3
  emoji: 🐳
4
  colorFrom: blue
5
  colorTo: blue
6
  sdk: docker
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
13
- - deepseek-ai/DeepSeek-R1-0528
14
- - deepseek-ai/DeepSeek-V3.1
15
- - deepseek-ai/DeepSeek-V3.1-Terminus
16
- - deepseek-ai/DeepSeek-V3.2-Exp
17
- - Qwen/Qwen3-Coder-480B-A35B-Instruct
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)
 
1
  ---
2
+ title: DeepSite
3
  emoji: 🐳
4
  colorFrom: blue
5
  colorTo: blue
6
  sdk: docker
7
  pinned: true
8
+ app_port: 5173
9
  license: mit
10
+ short_description: Generate any application with DeepSeek
 
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
app/(public)/layout.tsx DELETED
@@ -1,15 +0,0 @@
1
- import Navigation from "@/components/public/navigation";
2
-
3
- export default async function PublicLayout({
4
- children,
5
- }: Readonly<{
6
- children: React.ReactNode;
7
- }>) {
8
- return (
9
- <div className="h-screen bg-neutral-950 z-1 relative overflow-auto scroll-smooth">
10
- <div className="background__noisy" />
11
- <Navigation />
12
- {children}
13
- </div>
14
- );
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/(public)/page.tsx DELETED
@@ -1,5 +0,0 @@
1
- import { MyProjects } from "@/components/my-projects";
2
-
3
- export default async function HomePage() {
4
- return <MyProjects />;
5
- }
 
 
 
 
 
 
app/[namespace]/[repoId]/page.tsx DELETED
@@ -1,28 +0,0 @@
1
- import { AppEditor } from "@/components/editor";
2
- import { generateSEO } from "@/lib/seo";
3
- import { Metadata } from "next";
4
-
5
- export async function generateMetadata({
6
- params,
7
- }: {
8
- params: Promise<{ namespace: string; repoId: string }>;
9
- }): Promise<Metadata> {
10
- const { namespace, repoId } = await params;
11
-
12
- return generateSEO({
13
- title: `${namespace}/${repoId} - DeepSite Editor`,
14
- description: `Edit and build ${namespace}/${repoId} with AI-powered tools on DeepSite. Create stunning websites with no code required.`,
15
- path: `/${namespace}/${repoId}`,
16
- // Prevent indexing of individual project editor pages if they contain sensitive content
17
- noIndex: false, // Set to true if you want to keep project pages private
18
- });
19
- }
20
-
21
- export default async function ProjectNamespacePage({
22
- params,
23
- }: {
24
- params: Promise<{ namespace: string; repoId: string }>;
25
- }) {
26
- const { namespace, repoId } = await params;
27
- return <AppEditor namespace={namespace} repoId={repoId} />;
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/actions/auth.ts DELETED
@@ -1,18 +0,0 @@
1
- "use server";
2
-
3
- import { headers } from "next/headers";
4
-
5
- export async function getAuth() {
6
- const authList = await headers();
7
- const host = authList.get("host") ?? "localhost:3000";
8
- const url = host.includes("/spaces/enzostvs")
9
- ? "enzostvs-deepsite.hf.space"
10
- : host;
11
- const redirect_uri =
12
- `${host.includes("localhost") ? "http://" : "https://"}` +
13
- url +
14
- "/auth/callback";
15
-
16
- const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
17
- return loginRedirectUrl;
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/actions/projects.ts DELETED
@@ -1,47 +0,0 @@
1
- "use server";
2
-
3
- import { isAuthenticated } from "@/lib/auth";
4
- import { NextResponse } from "next/server";
5
- import { listSpaces } from "@huggingface/hub";
6
- import { ProjectType } from "@/types";
7
-
8
- export async function getProjects(): Promise<{
9
- ok: boolean;
10
- projects: ProjectType[];
11
- isEmpty?: boolean;
12
- }> {
13
- const user = await isAuthenticated();
14
-
15
- if (user instanceof NextResponse || !user) {
16
- return {
17
- ok: false,
18
- projects: [],
19
- };
20
- }
21
-
22
- const projects = [];
23
- for await (const space of listSpaces({
24
- accessToken: user.token as string,
25
- additionalFields: ["author", "cardData"],
26
- search: {
27
- owner: user.name,
28
- }
29
- })) {
30
- if (
31
- !space.private &&
32
- space.sdk === "static" &&
33
- Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
34
- (
35
- ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite-v3")) ||
36
- ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite"))
37
- )
38
- ) {
39
- projects.push(space);
40
- }
41
- }
42
-
43
- return {
44
- ok: true,
45
- projects,
46
- };
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/ask/route.ts DELETED
@@ -1,717 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { NextRequest } from "next/server";
3
- import { NextResponse } from "next/server";
4
- import { headers } from "next/headers";
5
- import { InferenceClient } from "@huggingface/inference";
6
-
7
- import { MODELS } from "@/lib/providers";
8
- import {
9
- DIVIDER,
10
- FOLLOW_UP_SYSTEM_PROMPT,
11
- INITIAL_SYSTEM_PROMPT,
12
- MAX_REQUESTS_PER_IP,
13
- NEW_PAGE_END,
14
- NEW_PAGE_START,
15
- REPLACE_END,
16
- SEARCH_START,
17
- UPDATE_PAGE_START,
18
- UPDATE_PAGE_END,
19
- PROMPT_FOR_PROJECT_NAME,
20
- } from "@/lib/prompts";
21
- import { calculateMaxTokens, estimateInputTokens, getProviderSpecificConfig } from "@/lib/max-tokens";
22
- import MY_TOKEN_KEY from "@/lib/get-cookie-name";
23
- import { Page } from "@/types";
24
- import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub";
25
- import { isAuthenticated } from "@/lib/auth";
26
- import { getBestProvider } from "@/lib/best-provider";
27
- // import { rewritePrompt } from "@/lib/rewrite-prompt";
28
- import { COLORS } from "@/lib/utils";
29
- import { templates } from "@/lib/templates";
30
-
31
- const ipAddresses = new Map();
32
-
33
- export async function POST(request: NextRequest) {
34
- const authHeaders = await headers();
35
- const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
36
-
37
- const body = await request.json();
38
- const { prompt, provider, model, redesignMarkdown, enhancedSettings, pages } = body;
39
-
40
- if (!model || (!prompt && !redesignMarkdown)) {
41
- return NextResponse.json(
42
- { ok: false, error: "Missing required fields" },
43
- { status: 400 }
44
- );
45
- }
46
-
47
- const selectedModel = MODELS.find(
48
- (m) => m.value === model || m.label === model
49
- );
50
-
51
- if (!selectedModel) {
52
- return NextResponse.json(
53
- { ok: false, error: "Invalid model selected" },
54
- { status: 400 }
55
- );
56
- }
57
-
58
- let token: string | null = null;
59
- if (userToken) token = userToken;
60
- let billTo: string | null = null;
61
-
62
- /**
63
- * Handle local usage token, this bypass the need for a user token
64
- * and allows local testing without authentication.
65
- * This is useful for development and testing purposes.
66
- */
67
- if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
68
- token = process.env.HF_TOKEN;
69
- }
70
-
71
- const ip = authHeaders.get("x-forwarded-for")?.includes(",")
72
- ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
73
- : authHeaders.get("x-forwarded-for");
74
-
75
- if (!token) {
76
- ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
77
- if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
78
- return NextResponse.json(
79
- {
80
- ok: false,
81
- openLogin: true,
82
- message: "Log In to continue using the service",
83
- },
84
- { status: 429 }
85
- );
86
- }
87
-
88
- token = process.env.DEFAULT_HF_TOKEN as string;
89
- billTo = "huggingface";
90
- }
91
-
92
- const selectedProvider = await getBestProvider(selectedModel.value, provider)
93
-
94
- 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;
95
-
96
- if (enhancedSettings.isActive) {
97
- // rewrittenPrompt = await rewritePrompt(rewrittenPrompt, enhancedSettings, { token, billTo }, selectedModel.value, selectedProvider.provider);
98
- }
99
-
100
- try {
101
- const encoder = new TextEncoder();
102
- const stream = new TransformStream();
103
- const writer = stream.writable.getWriter();
104
-
105
- const response = new NextResponse(stream.readable, {
106
- headers: {
107
- "Content-Type": "text/plain; charset=utf-8",
108
- "Cache-Control": "no-cache",
109
- Connection: "keep-alive",
110
- },
111
- });
112
-
113
- (async () => {
114
- // let completeResponse = "";
115
- try {
116
- const client = new InferenceClient(token);
117
-
118
- const systemPrompt = INITIAL_SYSTEM_PROMPT;
119
-
120
- const userPrompt = rewrittenPrompt;
121
- const estimatedInputTokens = estimateInputTokens(systemPrompt, userPrompt);
122
- const dynamicMaxTokens = calculateMaxTokens(selectedProvider, estimatedInputTokens, true);
123
- const providerConfig = getProviderSpecificConfig(selectedProvider, dynamicMaxTokens);
124
-
125
- const chatCompletion = client.chatCompletionStream(
126
- {
127
- model: selectedModel.value,
128
- provider: selectedProvider.provider,
129
- messages: [
130
- {
131
- role: "system",
132
- content: systemPrompt,
133
- },
134
- {
135
- role: "user",
136
- content: userPrompt + (enhancedSettings.isActive ? `1. I want to use the following primary color: ${enhancedSettings.primaryColor} (eg: bg-${enhancedSettings.primaryColor}-500).
137
- 2. I want to use the following secondary color: ${enhancedSettings.secondaryColor} (eg: bg-${enhancedSettings.secondaryColor}-500).
138
- 3. I want to use the following theme: ${enhancedSettings.theme} mode.` : "")
139
- },
140
- ],
141
- ...providerConfig,
142
- },
143
- billTo ? { billTo } : {}
144
- );
145
-
146
- while (true) {
147
- const { done, value } = await chatCompletion.next()
148
- if (done) {
149
- break;
150
- }
151
-
152
- const chunk = value.choices[0]?.delta?.content;
153
- if (chunk) {
154
- await writer.write(encoder.encode(chunk));
155
- }
156
- }
157
-
158
- // Explicitly close the writer after successful completion
159
- await writer.close();
160
- } catch (error: any) {
161
- if (error.message?.includes("exceeded your monthly included credits")) {
162
- await writer.write(
163
- encoder.encode(
164
- JSON.stringify({
165
- ok: false,
166
- openProModal: true,
167
- message: error.message,
168
- })
169
- )
170
- );
171
- } else if (error?.message?.includes("inference provider information")) {
172
- await writer.write(
173
- encoder.encode(
174
- JSON.stringify({
175
- ok: false,
176
- openSelectProvider: true,
177
- message: error.message,
178
- })
179
- )
180
- );
181
- }
182
- else {
183
- await writer.write(
184
- encoder.encode(
185
- JSON.stringify({
186
- ok: false,
187
- message:
188
- error.message ||
189
- "An error occurred while processing your request.",
190
- })
191
- )
192
- );
193
- }
194
- } finally {
195
- // Ensure the writer is always closed, even if already closed
196
- try {
197
- await writer?.close();
198
- } catch {
199
- // Ignore errors when closing the writer as it might already be closed
200
- }
201
- }
202
- })();
203
-
204
- return response;
205
- } catch (error: any) {
206
- return NextResponse.json(
207
- {
208
- ok: false,
209
- openSelectProvider: true,
210
- message:
211
- error?.message || "An error occurred while processing your request.",
212
- },
213
- { status: 500 }
214
- );
215
- }
216
- }
217
-
218
- export async function PUT(request: NextRequest) {
219
- const user = await isAuthenticated();
220
- if (user instanceof NextResponse || !user) {
221
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
222
- }
223
-
224
- const authHeaders = await headers();
225
-
226
- const body = await request.json();
227
- const { prompt, previousPrompts, provider, selectedElementHtml, model, pages, files, repoId: repoIdFromBody, isNew, enhancedSettings } =
228
- body;
229
-
230
- let repoId = repoIdFromBody;
231
-
232
- if (!prompt || pages.length === 0) {
233
- return NextResponse.json(
234
- { ok: false, error: "Missing required fields" },
235
- { status: 400 }
236
- );
237
- }
238
-
239
- const selectedModel = MODELS.find(
240
- (m) => m.value === model || m.label === model
241
- );
242
- if (!selectedModel) {
243
- return NextResponse.json(
244
- { ok: false, error: "Invalid model selected" },
245
- { status: 400 }
246
- );
247
- }
248
-
249
- let token = user.token as string;
250
- let billTo: string | null = null;
251
-
252
- /**
253
- * Handle local usage token, this bypass the need for a user token
254
- * and allows local testing without authentication.
255
- * This is useful for development and testing purposes.
256
- */
257
- if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
258
- token = process.env.HF_TOKEN;
259
- }
260
-
261
- const ip = authHeaders.get("x-forwarded-for")?.includes(",")
262
- ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
263
- : authHeaders.get("x-forwarded-for");
264
-
265
- if (!token) {
266
- ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
267
- if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
268
- return NextResponse.json(
269
- {
270
- ok: false,
271
- openLogin: true,
272
- message: "Log In to continue using the service",
273
- },
274
- { status: 429 }
275
- );
276
- }
277
-
278
- token = process.env.DEFAULT_HF_TOKEN as string;
279
- billTo = "huggingface";
280
- }
281
-
282
- const client = new InferenceClient(token);
283
-
284
- const escapeRegExp = (string: string) => {
285
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
286
- };
287
-
288
- const normalizeHtml = (html: string): string => {
289
- return html
290
- // Normalize whitespace within tags
291
- .replace(/\s+/g, ' ')
292
- // Remove spaces before closing >
293
- .replace(/\s+>/g, '>')
294
- // Remove spaces before />
295
- .replace(/\s+\/>/g, '/>')
296
- // Normalize spaces around = in attributes
297
- .replace(/\s*=\s*/g, '=')
298
- // Normalize quotes (convert single to double)
299
- .replace(/='([^']*)'/g, '="$1"')
300
- // Remove trailing spaces in opening/closing tags
301
- .replace(/<([^>]*?)\s+>/g, '<$1>')
302
- // Normalize self-closing tags
303
- .replace(/\/\s*>/g, '/>')
304
- .trim();
305
- };
306
-
307
- const createFlexibleHtmlRegex = (searchBlock: string) => {
308
- // Normalize both the search block for comparison
309
- const normalizedSearch = normalizeHtml(searchBlock);
310
-
311
- // Escape regex special characters
312
- let searchRegex = escapeRegExp(normalizedSearch)
313
- // Make whitespace flexible (but only between elements, not within tags)
314
- .replace(/>\s*</g, '>\\s*<')
315
- // Make line breaks and spaces around content flexible
316
- .replace(/>\s*([^<]+)\s*</g, '>\\s*$1\\s*<');
317
-
318
- return new RegExp(searchRegex, 'g');
319
- };
320
-
321
- const selectedProvider = await getBestProvider(selectedModel.value, provider)
322
-
323
- try {
324
- const systemPrompt = FOLLOW_UP_SYSTEM_PROMPT + (isNew ? PROMPT_FOR_PROJECT_NAME : "");
325
- const userContext = "You are modifying the HTML file based on the user's request.";
326
-
327
- // Send all pages without filtering
328
- const allPages = pages || [];
329
- const pagesContext = allPages
330
- .map((p: Page) => `- ${p.path}\n${p.html}`)
331
- .join("\n\n");
332
-
333
- const assistantContext = `${
334
- selectedElementHtml
335
- ? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\` Could be in multiple pages, if so, update all the pages.`
336
- : ""
337
- }. Current pages (${allPages.length} total): ${pagesContext}. ${files?.length > 0 ? `Available images: ${files?.map((f: string) => f).join(', ')}.` : ""}`;
338
-
339
- const estimatedInputTokens = estimateInputTokens(systemPrompt, prompt, userContext + assistantContext);
340
- const dynamicMaxTokens = calculateMaxTokens(selectedProvider, estimatedInputTokens, false);
341
- const providerConfig = getProviderSpecificConfig(selectedProvider, dynamicMaxTokens);
342
-
343
- const chatCompletion = client.chatCompletionStream(
344
- {
345
- model: selectedModel.value,
346
- provider: selectedProvider.provider,
347
- messages: [
348
- {
349
- role: "system",
350
- content: systemPrompt,
351
- },
352
- {
353
- role: "user",
354
- content: userContext,
355
- },
356
- {
357
- role: "assistant",
358
- content: assistantContext,
359
- },
360
- {
361
- role: "user",
362
- content: prompt,
363
- },
364
- ],
365
- ...providerConfig,
366
- },
367
- billTo ? { billTo } : {}
368
- );
369
-
370
- let chunk = "";
371
- while (true) {
372
- const { done, value } = await chatCompletion.next();
373
- if (done) {
374
- break;
375
- }
376
-
377
- const deltaContent = value.choices[0]?.delta?.content;
378
- if (deltaContent) {
379
- chunk += deltaContent;
380
- }
381
- }
382
- if (!chunk) {
383
- return NextResponse.json(
384
- { ok: false, message: "No content returned from the model" },
385
- { status: 400 }
386
- );
387
- }
388
-
389
- if (chunk) {
390
- const updatedLines: number[][] = [];
391
- let newHtml = "";
392
- const updatedPages = [...(pages || [])];
393
-
394
- const updatePageRegex = new RegExp(`${UPDATE_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^\\s]+)\\s*${UPDATE_PAGE_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([\\s\\S]*?)(?=${UPDATE_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|${NEW_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|$)`, 'g');
395
- let updatePageMatch;
396
-
397
- while ((updatePageMatch = updatePageRegex.exec(chunk)) !== null) {
398
- const [, pagePath, pageContent] = updatePageMatch;
399
-
400
- const pageIndex = updatedPages.findIndex(p => p.path === pagePath);
401
- if (pageIndex !== -1) {
402
- let pageHtml = updatedPages[pageIndex].html;
403
-
404
- let processedContent = pageContent;
405
- const htmlMatch = pageContent.match(/```html\s*([\s\S]*?)\s*```/);
406
- if (htmlMatch) {
407
- processedContent = htmlMatch[1];
408
- }
409
- let position = 0;
410
- let moreBlocks = true;
411
-
412
- while (moreBlocks) {
413
- const searchStartIndex = processedContent.indexOf(SEARCH_START, position);
414
- if (searchStartIndex === -1) {
415
- moreBlocks = false;
416
- continue;
417
- }
418
-
419
- const dividerIndex = processedContent.indexOf(DIVIDER, searchStartIndex);
420
- if (dividerIndex === -1) {
421
- moreBlocks = false;
422
- continue;
423
- }
424
-
425
- const replaceEndIndex = processedContent.indexOf(REPLACE_END, dividerIndex);
426
- if (replaceEndIndex === -1) {
427
- moreBlocks = false;
428
- continue;
429
- }
430
-
431
- const searchBlock = processedContent.substring(
432
- searchStartIndex + SEARCH_START.length,
433
- dividerIndex
434
- );
435
- const replaceBlock = processedContent.substring(
436
- dividerIndex + DIVIDER.length,
437
- replaceEndIndex
438
- );
439
-
440
- if (searchBlock.trim() === "") {
441
- pageHtml = `${replaceBlock}\n${pageHtml}`;
442
- updatedLines.push([1, replaceBlock.split("\n").length]);
443
- } else {
444
- const regex = createFlexibleHtmlRegex(searchBlock);
445
-
446
- // Normalize the pageHtml for matching
447
- const normalizedPageHtml = normalizeHtml(pageHtml);
448
- const match = regex.exec(normalizedPageHtml);
449
-
450
- if (match) {
451
- // Find the original match in the non-normalized HTML
452
- const normalizedSearch = normalizeHtml(searchBlock);
453
- const originalMatchIndex = pageHtml.indexOf(searchBlock);
454
-
455
- if (originalMatchIndex !== -1) {
456
- const beforeText = pageHtml.substring(0, originalMatchIndex);
457
- const startLineNumber = beforeText.split("\n").length;
458
- const replaceLines = replaceBlock.split("\n").length;
459
- const endLineNumber = startLineNumber + replaceLines - 1;
460
-
461
- updatedLines.push([startLineNumber, endLineNumber]);
462
- pageHtml = pageHtml.replace(searchBlock, replaceBlock);
463
- } else {
464
- // Fallback: try to find similar pattern in the original HTML
465
- const flexibleRegex = new RegExp(
466
- escapeRegExp(searchBlock)
467
- .replace(/\s+/g, '\\s+')
468
- .replace(/\s*=\s*/g, '\\s*=\\s*')
469
- .replace(/'\s*([^']*)\s*'/g, "'\\s*$1\\s*'")
470
- .replace(/"\s*([^"]*)\s*"/g, '"\\s*$1\\s*"')
471
- .replace(/\s*>/g, '\\s*>')
472
- .replace(/\s*\/>/g, '\\s*/>'),
473
- 'g'
474
- );
475
-
476
- const flexibleMatch = flexibleRegex.exec(pageHtml);
477
- if (flexibleMatch) {
478
- const matchedText = flexibleMatch[0];
479
- const beforeText = pageHtml.substring(0, flexibleMatch.index);
480
- const startLineNumber = beforeText.split("\n").length;
481
- const replaceLines = replaceBlock.split("\n").length;
482
- const endLineNumber = startLineNumber + replaceLines - 1;
483
-
484
- updatedLines.push([startLineNumber, endLineNumber]);
485
- pageHtml = pageHtml.replace(matchedText, replaceBlock);
486
- }
487
- }
488
- }
489
- }
490
-
491
- position = replaceEndIndex + REPLACE_END.length;
492
- }
493
-
494
- updatedPages[pageIndex].html = pageHtml;
495
-
496
- if (pagePath === '/' || pagePath === '/index' || pagePath === 'index') {
497
- newHtml = pageHtml;
498
- }
499
- }
500
- }
501
-
502
- const newPageRegex = new RegExp(`${NEW_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^\\s]+)\\s*${NEW_PAGE_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([\\s\\S]*?)(?=${UPDATE_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|${NEW_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|$)`, 'g');
503
- let newPageMatch;
504
-
505
- while ((newPageMatch = newPageRegex.exec(chunk)) !== null) {
506
- const [, pagePath, pageContent] = newPageMatch;
507
-
508
- let pageHtml = pageContent;
509
- const htmlMatch = pageContent.match(/```html\s*([\s\S]*?)\s*```/);
510
- if (htmlMatch) {
511
- pageHtml = htmlMatch[1];
512
- }
513
-
514
- const existingPageIndex = updatedPages.findIndex(p => p.path === pagePath);
515
-
516
- if (existingPageIndex !== -1) {
517
- updatedPages[existingPageIndex] = {
518
- path: pagePath,
519
- html: pageHtml.trim()
520
- };
521
- } else {
522
- updatedPages.push({
523
- path: pagePath,
524
- html: pageHtml.trim()
525
- });
526
- }
527
- }
528
-
529
- if (updatedPages.length === pages?.length && !chunk.includes(UPDATE_PAGE_START)) {
530
- let position = 0;
531
- let moreBlocks = true;
532
-
533
- while (moreBlocks) {
534
- const searchStartIndex = chunk.indexOf(SEARCH_START, position);
535
- if (searchStartIndex === -1) {
536
- moreBlocks = false;
537
- continue;
538
- }
539
-
540
- const dividerIndex = chunk.indexOf(DIVIDER, searchStartIndex);
541
- if (dividerIndex === -1) {
542
- moreBlocks = false;
543
- continue;
544
- }
545
-
546
- const replaceEndIndex = chunk.indexOf(REPLACE_END, dividerIndex);
547
- if (replaceEndIndex === -1) {
548
- moreBlocks = false;
549
- continue;
550
- }
551
-
552
- const searchBlock = chunk.substring(
553
- searchStartIndex + SEARCH_START.length,
554
- dividerIndex
555
- );
556
- const replaceBlock = chunk.substring(
557
- dividerIndex + DIVIDER.length,
558
- replaceEndIndex
559
- );
560
-
561
- if (searchBlock.trim() === "") {
562
- newHtml = `${replaceBlock}\n${newHtml}`;
563
- updatedLines.push([1, replaceBlock.split("\n").length]);
564
- } else {
565
- const regex = createFlexibleHtmlRegex(searchBlock);
566
-
567
- // Get the main page HTML (first page or index page)
568
- const mainPage = updatedPages.find(p => p.path === '/' || p.path === '/index' || p.path === 'index') || updatedPages[0];
569
- if (!mainPage) continue;
570
-
571
- newHtml = mainPage.html;
572
-
573
- // Normalize the newHtml for matching
574
- const normalizedNewHtml = normalizeHtml(newHtml);
575
- const match = regex.exec(normalizedNewHtml);
576
-
577
- if (match) {
578
- // Find the original match in the non-normalized HTML
579
- const originalMatchIndex = newHtml.indexOf(searchBlock);
580
-
581
- if (originalMatchIndex !== -1) {
582
- const beforeText = newHtml.substring(0, originalMatchIndex);
583
- const startLineNumber = beforeText.split("\n").length;
584
- const replaceLines = replaceBlock.split("\n").length;
585
- const endLineNumber = startLineNumber + replaceLines - 1;
586
-
587
- updatedLines.push([startLineNumber, endLineNumber]);
588
- newHtml = newHtml.replace(searchBlock, replaceBlock);
589
- } else {
590
- // Fallback: try to find similar pattern in the original HTML
591
- const flexibleRegex = new RegExp(
592
- escapeRegExp(searchBlock)
593
- .replace(/\s+/g, '\\s+')
594
- .replace(/\s*=\s*/g, '\\s*=\\s*')
595
- .replace(/'\s*([^']*)\s*'/g, "'\\s*$1\\s*'")
596
- .replace(/"\s*([^"]*)\s*"/g, '"\\s*$1\\s*"')
597
- .replace(/\s*>/g, '\\s*>')
598
- .replace(/\s*\/>/g, '\\s*/>'),
599
- 'g'
600
- );
601
-
602
- const flexibleMatch = flexibleRegex.exec(newHtml);
603
- if (flexibleMatch) {
604
- const matchedText = flexibleMatch[0];
605
- const beforeText = newHtml.substring(0, flexibleMatch.index);
606
- const startLineNumber = beforeText.split("\n").length;
607
- const replaceLines = replaceBlock.split("\n").length;
608
- const endLineNumber = startLineNumber + replaceLines - 1;
609
-
610
- updatedLines.push([startLineNumber, endLineNumber]);
611
- newHtml = newHtml.replace(matchedText, replaceBlock);
612
- }
613
- }
614
- }
615
- }
616
-
617
- position = replaceEndIndex + REPLACE_END.length;
618
- }
619
-
620
- // Update the main HTML if it's the index page
621
- const mainPageIndex = updatedPages.findIndex(p => p.path === '/' || p.path === '/index' || p.path === 'index');
622
- if (mainPageIndex !== -1) {
623
- updatedPages[mainPageIndex].html = newHtml;
624
- }
625
- }
626
-
627
- const files: File[] = [];
628
- updatedPages.forEach((page: Page) => {
629
- const file = new File([page.html], page.path, { type: "text/html" });
630
- files.push(file);
631
- });
632
-
633
- if (isNew) {
634
- const projectName = chunk.match(/<<<<<<< PROJECT_NAME_START ([\s\S]*?) >>>>>>> PROJECT_NAME_END/)?.[1]?.trim();
635
- const formattedTitle = projectName?.toLowerCase()
636
- .replace(/[^a-z0-9]+/g, "-")
637
- .split("-")
638
- .filter(Boolean)
639
- .join("-")
640
- .slice(0, 96);
641
- const repo: RepoDesignation = {
642
- type: "space",
643
- name: `${user.name}/${formattedTitle}`,
644
- };
645
- const { repoUrl} = await createRepo({
646
- repo,
647
- accessToken: user.token as string,
648
- });
649
- repoId = repoUrl.split("/").slice(-2).join("/");
650
- const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
651
- const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
652
- const README = `---
653
- title: ${projectName}
654
- colorFrom: ${colorFrom}
655
- colorTo: ${colorTo}
656
- emoji: 🐳
657
- sdk: static
658
- pinned: false
659
- tags:
660
- - deepsite-v3
661
- ---
662
-
663
- # Welcome to your new DeepSite project!
664
- This project was created with [DeepSite](https://deepsite.hf.co).
665
- `;
666
- files.push(new File([README], "README.md", { type: "text/markdown" }));
667
- }
668
-
669
- const response = await uploadFiles({
670
- repo: {
671
- type: "space",
672
- name: repoId,
673
- },
674
- files,
675
- commitTitle: prompt,
676
- accessToken: user.token as string,
677
- });
678
-
679
- return NextResponse.json({
680
- ok: true,
681
- updatedLines,
682
- pages: updatedPages,
683
- repoId,
684
- commit: {
685
- ...response.commit,
686
- title: prompt,
687
- }
688
- });
689
- } else {
690
- return NextResponse.json(
691
- { ok: false, message: "No content returned from the model" },
692
- { status: 400 }
693
- );
694
- }
695
- } catch (error: any) {
696
- if (error.message?.includes("exceeded your monthly included credits")) {
697
- return NextResponse.json(
698
- {
699
- ok: false,
700
- openProModal: true,
701
- message: error.message,
702
- },
703
- { status: 402 }
704
- );
705
- }
706
- return NextResponse.json(
707
- {
708
- ok: false,
709
- openSelectProvider: true,
710
- message:
711
- error.message || "An error occurred while processing your request.",
712
- },
713
- { status: 500 }
714
- );
715
- }
716
- }
717
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/auth/login-url/route.ts DELETED
@@ -1,23 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
-
3
- export async function GET(req: NextRequest) {
4
- const host = req.headers.get("host") ?? "localhost:3000";
5
-
6
- let url: string;
7
- if (host.includes("localhost")) {
8
- url = host;
9
- } else if (host.includes("hf.space") || host.includes("/spaces/enzostvs")) {
10
- url = "enzostvs-deepsite.hf.space";
11
- } else {
12
- url = "deepsite.hf.co";
13
- }
14
-
15
- const redirect_uri =
16
- `${host.includes("localhost") ? "http://" : "https://"}` +
17
- url +
18
- "/auth/callback";
19
-
20
- const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
21
-
22
- return NextResponse.json({ loginUrl: loginRedirectUrl });
23
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/auth/logout/route.ts DELETED
@@ -1,25 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import MY_TOKEN_KEY from "@/lib/get-cookie-name";
3
-
4
- export async function POST() {
5
- const cookieName = MY_TOKEN_KEY();
6
- const isProduction = process.env.NODE_ENV === "production";
7
-
8
- const response = NextResponse.json(
9
- { message: "Logged out successfully" },
10
- { status: 200 }
11
- );
12
-
13
- // Clear the HTTP-only cookie
14
- const cookieOptions = [
15
- `${cookieName}=`,
16
- "Max-Age=0",
17
- "Path=/",
18
- "HttpOnly",
19
- ...(isProduction ? ["Secure", "SameSite=None"] : ["SameSite=Lax"])
20
- ].join("; ");
21
-
22
- response.headers.set("Set-Cookie", cookieOptions);
23
-
24
- return response;
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/auth/route.ts DELETED
@@ -1,106 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import MY_TOKEN_KEY from "@/lib/get-cookie-name";
3
-
4
- export async function POST(req: NextRequest) {
5
- const body = await req.json();
6
- const { code } = body;
7
-
8
- if (!code) {
9
- return NextResponse.json(
10
- { error: "Code is required" },
11
- {
12
- status: 400,
13
- headers: {
14
- "Content-Type": "application/json",
15
- },
16
- }
17
- );
18
- }
19
-
20
- const Authorization = `Basic ${Buffer.from(
21
- `${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
22
- ).toString("base64")}`;
23
-
24
- const host =
25
- req.headers.get("host") ?? req.headers.get("origin") ?? "localhost:3000";
26
-
27
- const url = host.includes("/spaces/enzostvs")
28
- ? "enzostvs-deepsite.hf.space"
29
- : host;
30
- const redirect_uri =
31
- `${host.includes("localhost") ? "http://" : "https://"}` +
32
- url +
33
- "/auth/callback";
34
- const request_auth = await fetch("https://huggingface.co/oauth/token", {
35
- method: "POST",
36
- headers: {
37
- "Content-Type": "application/x-www-form-urlencoded",
38
- Authorization,
39
- },
40
- body: new URLSearchParams({
41
- grant_type: "authorization_code",
42
- code,
43
- redirect_uri,
44
- }),
45
- });
46
-
47
- const response = await request_auth.json();
48
- if (!response.access_token) {
49
- return NextResponse.json(
50
- { error: "Failed to retrieve access token" },
51
- {
52
- status: 400,
53
- headers: {
54
- "Content-Type": "application/json",
55
- },
56
- }
57
- );
58
- }
59
-
60
- const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
61
- headers: {
62
- Authorization: `Bearer ${response.access_token}`,
63
- },
64
- });
65
-
66
- if (!userResponse.ok) {
67
- return NextResponse.json(
68
- { user: null, errCode: userResponse.status },
69
- { status: userResponse.status }
70
- );
71
- }
72
- const user = await userResponse.json();
73
-
74
- const cookieName = MY_TOKEN_KEY();
75
- const isProduction = process.env.NODE_ENV === "production";
76
-
77
- // Create response with user data
78
- const nextResponse = NextResponse.json(
79
- {
80
- access_token: response.access_token,
81
- expires_in: response.expires_in,
82
- user,
83
- // Include fallback flag for iframe contexts
84
- useLocalStorageFallback: true,
85
- },
86
- {
87
- status: 200,
88
- headers: {
89
- "Content-Type": "application/json",
90
- },
91
- }
92
- );
93
-
94
- // Set HTTP-only cookie with proper attributes for iframe support
95
- const cookieOptions = [
96
- `${cookieName}=${response.access_token}`,
97
- `Max-Age=${response.expires_in || 3600}`, // Default 1 hour if not provided
98
- "Path=/",
99
- "HttpOnly",
100
- ...(isProduction ? ["Secure", "SameSite=None"] : ["SameSite=Lax"])
101
- ].join("; ");
102
-
103
- nextResponse.headers.set("Set-Cookie", cookieOptions);
104
-
105
- return nextResponse;
106
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/promote/route.ts DELETED
@@ -1,190 +0,0 @@
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";
6
-
7
- export async function POST(
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
- // 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,
62
- revision: commitId,
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";
89
- break;
90
- case "js":
91
- mimeType = "application/javascript";
92
- break;
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,
111
- accessToken: user.token as string,
112
- revision: "main",
113
- })) {
114
- const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase();
115
-
116
- if (allowedExtensions.includes(fileExtension || "")) {
117
- mainBranchFilePaths.add(fileInfo.path);
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)) {
125
- filesToDelete.push(mainFilePath);
126
- }
127
- }
128
-
129
- if (files.length === 0 && filesToDelete.length === 0) {
130
- return NextResponse.json(
131
- { ok: false, error: "No files found in the specified commit and no files to delete" },
132
- { status: 404 }
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,
140
- paths: filesToDelete,
141
- accessToken: user.token as string,
142
- commitTitle: `Removed files from promoting ${commitId.slice(0, 7)}`,
143
- commitDescription: `Removed files that don't exist in commit ${commitId}:\n${filesToDelete.map(path => `- ${path}`).join('\n')}`,
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,
151
- files,
152
- accessToken: user.token as string,
153
- commitTitle: `Promote version ${commitId.slice(0, 7)} to main`,
154
- commitDescription: `Promoted commit ${commitId} to main branch`,
155
- });
156
- }
157
-
158
- return NextResponse.json(
159
- {
160
- ok: true,
161
- message: "Version promoted successfully",
162
- promotedCommit: commitId,
163
- pages: pages,
164
- },
165
- { status: 200 }
166
- );
167
-
168
- } catch (error: any) {
169
-
170
- // Handle specific HuggingFace API errors
171
- if (error.statusCode === 404) {
172
- return NextResponse.json(
173
- { ok: false, error: "Commit not found" },
174
- { status: 404 }
175
- );
176
- }
177
-
178
- if (error.statusCode === 403) {
179
- return NextResponse.json(
180
- { ok: false, error: "Access denied to repository" },
181
- { status: 403 }
182
- );
183
- }
184
-
185
- return NextResponse.json(
186
- { ok: false, error: error.message || "Failed to promote version" },
187
- { status: 500 }
188
- );
189
- }
190
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/images/route.ts DELETED
@@ -1,113 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, spaceInfo, uploadFiles } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import Project from "@/models/Project";
6
- import dbConnect from "@/lib/mongodb";
7
-
8
- export async function POST(
9
- req: NextRequest,
10
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
11
- ) {
12
- try {
13
- const user = await isAuthenticated();
14
-
15
- if (user instanceof NextResponse || !user) {
16
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const param = await params;
20
- const { namespace, repoId } = param;
21
-
22
- const space = await spaceInfo({
23
- name: `${namespace}/${repoId}`,
24
- accessToken: user.token as string,
25
- additionalFields: ["author"],
26
- });
27
-
28
- if (!space || space.sdk !== "static") {
29
- return NextResponse.json(
30
- { ok: false, error: "Space is not a static space." },
31
- { status: 404 }
32
- );
33
- }
34
-
35
- if (space.author !== user.name) {
36
- return NextResponse.json(
37
- { ok: false, error: "Space does not belong to the authenticated user." },
38
- { status: 403 }
39
- );
40
- }
41
-
42
- // Parse the FormData to get the images
43
- const formData = await req.formData();
44
- const imageFiles = formData.getAll("images") as File[];
45
-
46
- if (!imageFiles || imageFiles.length === 0) {
47
- return NextResponse.json(
48
- {
49
- ok: false,
50
- error: "At least one image file is required under the 'images' key",
51
- },
52
- { status: 400 }
53
- );
54
- }
55
-
56
- const files: File[] = [];
57
- for (const file of imageFiles) {
58
- if (!(file instanceof File)) {
59
- return NextResponse.json(
60
- {
61
- ok: false,
62
- error: "Invalid file format - all items under 'images' key must be files",
63
- },
64
- { status: 400 }
65
- );
66
- }
67
-
68
- if (!file.type.startsWith('image/')) {
69
- return NextResponse.json(
70
- {
71
- ok: false,
72
- error: `File ${file.name} is not an image`,
73
- },
74
- { status: 400 }
75
- );
76
- }
77
-
78
- // Create File object with images/ folder prefix
79
- const fileName = `images/${file.name}`;
80
- const processedFile = new File([file], fileName, { type: file.type });
81
- files.push(processedFile);
82
- }
83
-
84
- // Upload files to HuggingFace space
85
- const repo: RepoDesignation = {
86
- type: "space",
87
- name: `${namespace}/${repoId}`,
88
- };
89
-
90
- await uploadFiles({
91
- repo,
92
- files,
93
- accessToken: user.token as string,
94
- commitTitle: `Upload ${files.length} image(s)`,
95
- });
96
-
97
- return NextResponse.json({
98
- ok: true,
99
- message: `Successfully uploaded ${files.length} image(s) to ${namespace}/${repoId}/images/`,
100
- uploadedFiles: files.map((file) => `https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${file.name}`),
101
- }, { status: 200 });
102
-
103
- } catch (error) {
104
- console.error('Error uploading images:', error);
105
- return NextResponse.json(
106
- {
107
- ok: false,
108
- error: "Failed to upload images",
109
- },
110
- { status: 500 }
111
- );
112
- }
113
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/route.ts DELETED
@@ -1,187 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, spaceInfo, listFiles, deleteRepo, listCommits, downloadFile } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import { Commit, Page } from "@/types";
6
-
7
- export async function DELETE(
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
- { ok: false, error: "Space is not a static space." },
30
- { status: 404 }
31
- );
32
- }
33
-
34
- if (space.author !== user.name) {
35
- return NextResponse.json(
36
- { ok: false, error: "Space does not belong to the authenticated user." },
37
- { status: 403 }
38
- );
39
- }
40
-
41
- const repo: RepoDesignation = {
42
- type: "space",
43
- name: `${namespace}/${repoId}`,
44
- };
45
-
46
- await deleteRepo({
47
- repo,
48
- accessToken: user.token as string,
49
- });
50
-
51
-
52
- return NextResponse.json({ ok: true }, { status: 200 });
53
- } catch (error: any) {
54
- return NextResponse.json(
55
- { ok: false, error: error.message },
56
- { status: 500 }
57
- );
58
- }
59
- }
60
-
61
- export async function GET(
62
- req: NextRequest,
63
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
64
- ) {
65
- const user = await isAuthenticated();
66
-
67
- if (user instanceof NextResponse || !user) {
68
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
69
- }
70
-
71
- const param = await params;
72
- const { namespace, repoId } = param;
73
-
74
- try {
75
- const space = await spaceInfo({
76
- name: namespace + "/" + repoId,
77
- accessToken: user.token as string,
78
- additionalFields: ["author"],
79
- });
80
-
81
- if (!space || space.sdk !== "static") {
82
- return NextResponse.json(
83
- {
84
- ok: false,
85
- error: "Space is not a static space",
86
- },
87
- { status: 404 }
88
- );
89
- }
90
- if (space.author !== user.name) {
91
- return NextResponse.json(
92
- {
93
- ok: false,
94
- error: "Space does not belong to the authenticated user",
95
- },
96
- { status: 403 }
97
- );
98
- }
99
-
100
- const repo: RepoDesignation = {
101
- type: "space",
102
- name: `${namespace}/${repoId}`,
103
- };
104
-
105
- const htmlFiles: Page[] = [];
106
- const files: string[] = [];
107
-
108
- const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif"];
109
-
110
- for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) {
111
- if (fileInfo.path.endsWith(".html")) {
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) {
115
- continue;
116
- }
117
- if (fileInfo.path === "index.html") {
118
- htmlFiles.unshift({
119
- path: fileInfo.path,
120
- html,
121
- });
122
- } else {
123
- htmlFiles.push({
124
- path: fileInfo.path,
125
- html,
126
- });
127
- }
128
- }
129
- if (fileInfo.type === "directory" && fileInfo.path === "images") {
130
- for await (const imageInfo of listFiles({repo, accessToken: user.token as string, path: fileInfo.path})) {
131
- if (allowedFilesExtensions.includes(imageInfo.path.split(".").pop() || "")) {
132
- files.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${imageInfo.path}`);
133
- }
134
- }
135
- }
136
- }
137
- const commits: Commit[] = [];
138
- for await (const commit of listCommits({ repo, accessToken: user.token as string })) {
139
- if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Removed files from promoting")) {
140
- continue;
141
- }
142
- commits.push({
143
- title: commit.title,
144
- oid: commit.oid,
145
- date: commit.date,
146
- });
147
- }
148
-
149
- if (htmlFiles.length === 0) {
150
- return NextResponse.json(
151
- {
152
- ok: false,
153
- error: "No HTML files found",
154
- },
155
- { status: 404 }
156
- );
157
- }
158
- return NextResponse.json(
159
- {
160
- project: {
161
- id: space.id,
162
- space_id: space.name,
163
- private: space.private,
164
- _updatedAt: space.updatedAt,
165
- },
166
- pages: htmlFiles,
167
- files,
168
- commits,
169
- ok: true,
170
- },
171
- { status: 200 }
172
- );
173
-
174
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
- } catch (error: any) {
176
- if (error.statusCode === 404) {
177
- return NextResponse.json(
178
- { error: "Space not found", ok: false },
179
- { status: 404 }
180
- );
181
- }
182
- return NextResponse.json(
183
- { error: error.message, ok: false },
184
- { status: 500 }
185
- );
186
- }
187
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/save/route.ts DELETED
@@ -1,64 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { uploadFiles } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import { Page } from "@/types";
6
-
7
- export async function PUT(
8
- req: NextRequest,
9
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
10
- ) {
11
- const user = await isAuthenticated();
12
- if (user instanceof NextResponse || !user) {
13
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
14
- }
15
-
16
- const param = await params;
17
- const { namespace, repoId } = param;
18
- const { pages, commitTitle = "Manual changes saved" } = await req.json();
19
-
20
- if (!pages || !Array.isArray(pages) || pages.length === 0) {
21
- return NextResponse.json(
22
- { ok: false, error: "Pages are required" },
23
- { status: 400 }
24
- );
25
- }
26
-
27
- try {
28
- // Prepare files for upload
29
- const files: File[] = [];
30
- pages.forEach((page: Page) => {
31
- const file = new File([page.html], page.path, { type: "text/html" });
32
- files.push(file);
33
- });
34
-
35
- // Upload files to HuggingFace Hub
36
- const response = await uploadFiles({
37
- repo: {
38
- type: "space",
39
- name: `${namespace}/${repoId}`,
40
- },
41
- files,
42
- commitTitle,
43
- accessToken: user.token as string,
44
- });
45
-
46
- return NextResponse.json({
47
- ok: true,
48
- pages,
49
- commit: {
50
- ...response.commit,
51
- title: commitTitle,
52
- }
53
- });
54
- } catch (error: any) {
55
- console.error("Error saving manual changes:", error);
56
- return NextResponse.json(
57
- {
58
- ok: false,
59
- error: error.message || "Failed to save changes",
60
- },
61
- { status: 500 }
62
- );
63
- }
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/route.ts DELETED
@@ -1,107 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, createRepo, listCommits, spaceInfo, uploadFiles } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import { Commit, Page } from "@/types";
6
- import { COLORS } from "@/lib/utils";
7
-
8
- export async function POST(
9
- req: NextRequest,
10
- ) {
11
- const user = await isAuthenticated();
12
- if (user instanceof NextResponse || !user) {
13
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
14
- }
15
-
16
- const { title: titleFromRequest, pages, prompt } = await req.json();
17
-
18
- const title = titleFromRequest ?? "DeepSite Project";
19
-
20
- const formattedTitle = title
21
- .toLowerCase()
22
- .replace(/[^a-z0-9]+/g, "-")
23
- .split("-")
24
- .filter(Boolean)
25
- .join("-")
26
- .slice(0, 96);
27
-
28
- const repo: RepoDesignation = {
29
- type: "space",
30
- name: `${user.name}/${formattedTitle}`,
31
- };
32
- const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
33
- const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
34
- const README = `---
35
- title: ${title}
36
- colorFrom: ${colorFrom}
37
- colorTo: ${colorTo}
38
- emoji: 🐳
39
- sdk: static
40
- pinned: false
41
- tags:
42
- - deepsite-v3
43
- ---
44
-
45
- # Welcome to your new DeepSite project!
46
- This project was created with [DeepSite](https://deepsite.hf.co).
47
- `;
48
-
49
- const files: File[] = [];
50
- const readmeFile = new File([README], "README.md", { type: "text/markdown" });
51
- files.push(readmeFile);
52
- pages.forEach((page: Page) => {
53
- const file = new File([page.html], page.path, { type: "text/html" });
54
- files.push(file);
55
- });
56
-
57
- try {
58
- const { repoUrl} = await createRepo({
59
- repo,
60
- accessToken: user.token as string,
61
- });
62
- const commitTitle = !prompt || prompt.trim() === "" ? "Redesign my website" : prompt;
63
- await uploadFiles({
64
- repo,
65
- files,
66
- accessToken: user.token as string,
67
- commitTitle
68
- });
69
-
70
- const path = repoUrl.split("/").slice(-2).join("/");
71
-
72
- const commits: Commit[] = [];
73
- for await (const commit of listCommits({ repo, accessToken: user.token as string })) {
74
- if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Promote version")) {
75
- continue;
76
- }
77
- commits.push({
78
- title: commit.title,
79
- oid: commit.oid,
80
- date: commit.date,
81
- });
82
- }
83
-
84
- const space = await spaceInfo({
85
- name: repo.name,
86
- accessToken: user.token as string,
87
- });
88
-
89
- let newProject = {
90
- files,
91
- pages,
92
- commits,
93
- project: {
94
- id: space.id,
95
- space_id: space.name,
96
- _updatedAt: space.updatedAt,
97
- }
98
- }
99
-
100
- return NextResponse.json({ space: newProject, path, ok: true }, { status: 201 });
101
- } catch (err: any) {
102
- return NextResponse.json(
103
- { error: err.message, ok: false },
104
- { status: 500 }
105
- );
106
- }
107
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/route.ts DELETED
@@ -1,46 +0,0 @@
1
- import { listSpaces } from "@huggingface/hub";
2
- import { headers } from "next/headers";
3
- import { NextResponse } from "next/server";
4
-
5
- export async function GET() {
6
- const authHeaders = await headers();
7
- const token = authHeaders.get("Authorization");
8
- if (!token) {
9
- return NextResponse.json({ user: null, errCode: 401 }, { status: 401 });
10
- }
11
-
12
- const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
13
- headers: {
14
- Authorization: `${token}`,
15
- },
16
- });
17
-
18
- if (!userResponse.ok) {
19
- return NextResponse.json(
20
- { user: null, errCode: userResponse.status },
21
- { status: userResponse.status }
22
- );
23
- }
24
- const user = await userResponse.json();
25
- const projects = [];
26
- for await (const space of listSpaces({
27
- accessToken: token.replace("Bearer ", "") as string,
28
- additionalFields: ["author", "cardData"],
29
- search: {
30
- owner: user.name,
31
- }
32
- })) {
33
- if (
34
- space.sdk === "static" &&
35
- Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
36
- (
37
- ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite-v3")) ||
38
- ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite"))
39
- )
40
- ) {
41
- projects.push(space);
42
- }
43
- }
44
-
45
- return NextResponse.json({ user, projects, errCode: null }, { status: 200 });
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/re-design/route.ts DELETED
@@ -1,39 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
-
3
- export async function PUT(request: NextRequest) {
4
- const body = await request.json();
5
- const { url } = body;
6
-
7
- if (!url) {
8
- return NextResponse.json({ error: "URL is required" }, { status: 400 });
9
- }
10
-
11
- try {
12
- const response = await fetch(
13
- `https://r.jina.ai/${encodeURIComponent(url)}`,
14
- {
15
- method: "POST",
16
- }
17
- );
18
- if (!response.ok) {
19
- return NextResponse.json(
20
- { error: "Failed to fetch redesign" },
21
- { status: 500 }
22
- );
23
- }
24
- const markdown = await response.text();
25
- return NextResponse.json(
26
- {
27
- ok: true,
28
- markdown,
29
- },
30
- { status: 200 }
31
- );
32
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
- } catch (error: any) {
34
- return NextResponse.json(
35
- { error: error.message || "An error occurred" },
36
- { status: 500 }
37
- );
38
- }
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/auth/callback/page.tsx DELETED
@@ -1,97 +0,0 @@
1
- "use client";
2
- import Link from "next/link";
3
- import { useUser } from "@/hooks/useUser";
4
- import { use, useState } from "react";
5
- import { useMount, useTimeoutFn } from "react-use";
6
-
7
- import { Button } from "@/components/ui/button";
8
- import { AnimatedBlobs } from "@/components/animated-blobs";
9
- import { useBroadcastChannel } from "@/lib/useBroadcastChannel";
10
- export default function AuthCallback({
11
- searchParams,
12
- }: {
13
- searchParams: Promise<{ code: string }>;
14
- }) {
15
- const [showButton, setShowButton] = useState(false);
16
- const [isPopupAuth, setIsPopupAuth] = useState(false);
17
- const { code } = use(searchParams);
18
- const { loginFromCode } = useUser();
19
- const { postMessage } = useBroadcastChannel("auth", () => {});
20
-
21
- useMount(async () => {
22
- if (code) {
23
- const isPopup = window.opener || window.parent !== window;
24
- setIsPopupAuth(isPopup);
25
-
26
- if (isPopup) {
27
- postMessage({
28
- type: "user-oauth",
29
- code: code,
30
- });
31
-
32
- setTimeout(() => {
33
- if (window.opener) {
34
- window.close();
35
- }
36
- }, 1000);
37
- } else {
38
- await loginFromCode(code);
39
- }
40
- }
41
- });
42
-
43
- useTimeoutFn(() => setShowButton(true), 7000);
44
-
45
- return (
46
- <div className="h-screen flex flex-col justify-center items-center bg-neutral-950 z-1 relative">
47
- <div className="background__noisy" />
48
- <div className="relative max-w-4xl py-10 flex items-center justify-center w-full">
49
- <div className="max-w-lg mx-auto !rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden ring-[8px] ring-white/20">
50
- <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
51
- <div className="flex items-center justify-center -space-x-4 mb-3">
52
- <div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
53
- 🚀
54
- </div>
55
- <div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
56
- 👋
57
- </div>
58
- <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
59
- 🙌
60
- </div>
61
- </div>
62
- <p className="text-xl font-semibold text-neutral-950">
63
- {isPopupAuth
64
- ? "Authentication Complete!"
65
- : "Login In Progress..."}
66
- </p>
67
- <p className="text-sm text-neutral-500 mt-1.5">
68
- {isPopupAuth
69
- ? "You can now close this tab and return to the previous page."
70
- : "Wait a moment while we log you in with your code."}
71
- </p>
72
- </header>
73
- <main className="space-y-4 p-6">
74
- <div>
75
- <p className="text-sm text-neutral-700 mb-4 max-w-xs">
76
- If you are not redirected automatically in the next 5 seconds,
77
- please click the button below
78
- </p>
79
- {showButton ? (
80
- <Link href="/">
81
- <Button variant="black" className="relative">
82
- Go to Home
83
- </Button>
84
- </Link>
85
- ) : (
86
- <p className="text-xs text-neutral-500">
87
- Please wait, we are logging you in...
88
- </p>
89
- )}
90
- </div>
91
- </main>
92
- </div>
93
- <AnimatedBlobs />
94
- </div>
95
- </div>
96
- );
97
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/auth/page.tsx DELETED
@@ -1,28 +0,0 @@
1
- import { redirect } from "next/navigation";
2
- import { Metadata } from "next";
3
-
4
- import { getAuth } from "@/app/actions/auth";
5
-
6
- export const revalidate = 1;
7
-
8
- export const metadata: Metadata = {
9
- robots: "noindex, nofollow",
10
- };
11
-
12
- export default async function Auth() {
13
- const loginRedirectUrl = await getAuth();
14
- if (loginRedirectUrl) {
15
- redirect(loginRedirectUrl);
16
- }
17
-
18
- return (
19
- <div className="p-4">
20
- <div className="border bg-red-500/10 border-red-500/20 text-red-500 px-5 py-3 rounded-lg">
21
- <h1 className="text-xl font-bold">Error</h1>
22
- <p className="text-sm">
23
- An error occurred while trying to log in. Please try again later.
24
- </p>
25
- </div>
26
- </div>
27
- );
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/favicon.ico DELETED
Binary file (25.9 kB)
 
app/layout.tsx DELETED
@@ -1,128 +0,0 @@
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
-
18
- const inter = Inter({
19
- variable: "--font-inter-sans",
20
- subsets: ["latin"],
21
- });
22
-
23
- const ptSans = PT_Sans({
24
- variable: "--font-ptSans-mono",
25
- subsets: ["latin"],
26
- weight: ["400", "700"],
27
- });
28
-
29
- export const metadata: Metadata = {
30
- ...generateSEO({
31
- title: "DeepSite | Build with AI ✨",
32
- description:
33
- "DeepSite is a web development tool that helps you build websites with AI, no code required. Let's deploy your website with DeepSite and enjoy the magic of AI.",
34
- path: "/",
35
- }),
36
- appleWebApp: {
37
- capable: true,
38
- title: "DeepSite",
39
- statusBarStyle: "black-translucent",
40
- },
41
- icons: {
42
- icon: "/logo.svg",
43
- shortcut: "/logo.svg",
44
- apple: "/logo.svg",
45
- },
46
- verification: {
47
- google: process.env.GOOGLE_SITE_VERIFICATION,
48
- },
49
- };
50
-
51
- export const viewport: Viewport = {
52
- initialScale: 1,
53
- maximumScale: 1,
54
- themeColor: "#000000",
55
- };
56
-
57
- async function getMe() {
58
- const cookieStore = await cookies();
59
- const token = cookieStore.get(MY_TOKEN_KEY())?.value;
60
- if (!token) return { user: null, projects: [], errCode: null };
61
- try {
62
- const res = await apiServer.get("/me", {
63
- headers: {
64
- Authorization: `Bearer ${token}`,
65
- },
66
- });
67
- return { user: res.data.user, projects: res.data.projects, errCode: null };
68
- } catch (err: any) {
69
- return { user: null, projects: [], errCode: err.status };
70
- }
71
- }
72
-
73
- export default async function RootLayout({
74
- children,
75
- }: Readonly<{
76
- children: React.ReactNode;
77
- }>) {
78
- const data = await getMe();
79
-
80
- // Generate structured data
81
- const structuredData = generateStructuredData("WebApplication", {
82
- name: "DeepSite",
83
- description: "Build websites with AI, no code required",
84
- url: "https://deepsite.hf.co",
85
- });
86
-
87
- const organizationData = generateStructuredData("Organization", {
88
- name: "DeepSite",
89
- url: "https://deepsite.hf.co",
90
- });
91
-
92
- return (
93
- <html lang="en">
94
- <head>
95
- <script
96
- type="application/ld+json"
97
- dangerouslySetInnerHTML={{
98
- __html: JSON.stringify(structuredData),
99
- }}
100
- />
101
- <script
102
- type="application/ld+json"
103
- dangerouslySetInnerHTML={{
104
- __html: JSON.stringify(organizationData),
105
- }}
106
- />
107
- </head>
108
- <Script
109
- defer
110
- data-domain="deepsite.hf.co"
111
- src="https://plausible.io/js/script.js"
112
- ></Script>
113
- <body
114
- className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
115
- >
116
- <IframeDetector />
117
- <Toaster richColors position="bottom-center" />
118
- <TanstackContext>
119
- <AppContext me={data}>
120
- <LoginProvider>
121
- <ProProvider>{children}</ProProvider>
122
- </LoginProvider>
123
- </AppContext>
124
- </TanstackContext>
125
- </body>
126
- </html>
127
- );
128
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/new/page.tsx DELETED
@@ -1,14 +0,0 @@
1
- import { AppEditor } from "@/components/editor";
2
- import { Metadata } from "next";
3
- import { generateSEO } from "@/lib/seo";
4
-
5
- export const metadata: Metadata = generateSEO({
6
- title: "Create New Project - DeepSite",
7
- description:
8
- "Start building your next website with AI. Create a new project on DeepSite and experience the power of AI-driven web development.",
9
- path: "/new",
10
- });
11
-
12
- export default function NewProjectPage() {
13
- return <AppEditor isNew />;
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/sitemap.ts DELETED
@@ -1,28 +0,0 @@
1
- import { MetadataRoute } from 'next';
2
-
3
- export default function sitemap(): MetadataRoute.Sitemap {
4
- const baseUrl = 'https://deepsite.hf.co';
5
-
6
- return [
7
- {
8
- url: baseUrl,
9
- lastModified: new Date(),
10
- changeFrequency: 'daily',
11
- priority: 1,
12
- },
13
- {
14
- url: `${baseUrl}/new`,
15
- lastModified: new Date(),
16
- changeFrequency: 'weekly',
17
- priority: 0.8,
18
- },
19
- {
20
- url: `${baseUrl}/auth`,
21
- lastModified: new Date(),
22
- changeFrequency: 'monthly',
23
- priority: 0.5,
24
- },
25
- // Note: Dynamic project routes will be handled by Next.js automatically
26
- // but you can add specific high-priority project pages here if needed
27
- ];
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/deepseek.svg DELETED
assets/globals.css DELETED
@@ -1,371 +0,0 @@
1
- @import "tailwindcss";
2
- @import "tw-animate-css";
3
-
4
- @custom-variant dark (&:is(.dark *));
5
-
6
- @theme inline {
7
- --color-background: var(--background);
8
- --color-foreground: var(--foreground);
9
- --font-sans: var(--font-inter-sans);
10
- --font-mono: var(--font-ptSans-mono);
11
- --color-sidebar-ring: var(--sidebar-ring);
12
- --color-sidebar-border: var(--sidebar-border);
13
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
14
- --color-sidebar-accent: var(--sidebar-accent);
15
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
16
- --color-sidebar-primary: var(--sidebar-primary);
17
- --color-sidebar-foreground: var(--sidebar-foreground);
18
- --color-sidebar: var(--sidebar);
19
- --color-chart-5: var(--chart-5);
20
- --color-chart-4: var(--chart-4);
21
- --color-chart-3: var(--chart-3);
22
- --color-chart-2: var(--chart-2);
23
- --color-chart-1: var(--chart-1);
24
- --color-ring: var(--ring);
25
- --color-input: var(--input);
26
- --color-border: var(--border);
27
- --color-destructive: var(--destructive);
28
- --color-accent-foreground: var(--accent-foreground);
29
- --color-accent: var(--accent);
30
- --color-muted-foreground: var(--muted-foreground);
31
- --color-muted: var(--muted);
32
- --color-secondary-foreground: var(--secondary-foreground);
33
- --color-secondary: var(--secondary);
34
- --color-primary-foreground: var(--primary-foreground);
35
- --color-primary: var(--primary);
36
- --color-popover-foreground: var(--popover-foreground);
37
- --color-popover: var(--popover);
38
- --color-card-foreground: var(--card-foreground);
39
- --color-card: var(--card);
40
- --radius-sm: calc(var(--radius) - 4px);
41
- --radius-md: calc(var(--radius) - 2px);
42
- --radius-lg: var(--radius);
43
- --radius-xl: calc(var(--radius) + 4px);
44
- }
45
-
46
- :root {
47
- --radius: 0.625rem;
48
- --background: oklch(1 0 0);
49
- --foreground: oklch(0.145 0 0);
50
- --card: oklch(1 0 0);
51
- --card-foreground: oklch(0.145 0 0);
52
- --popover: oklch(1 0 0);
53
- --popover-foreground: oklch(0.145 0 0);
54
- --primary: oklch(0.205 0 0);
55
- --primary-foreground: oklch(0.985 0 0);
56
- --secondary: oklch(0.97 0 0);
57
- --secondary-foreground: oklch(0.205 0 0);
58
- --muted: oklch(0.97 0 0);
59
- --muted-foreground: oklch(0.556 0 0);
60
- --accent: oklch(0.97 0 0);
61
- --accent-foreground: oklch(0.205 0 0);
62
- --destructive: oklch(0.577 0.245 27.325);
63
- --border: oklch(0.922 0 0);
64
- --input: oklch(0.922 0 0);
65
- --ring: oklch(0.708 0 0);
66
- --chart-1: oklch(0.646 0.222 41.116);
67
- --chart-2: oklch(0.6 0.118 184.704);
68
- --chart-3: oklch(0.398 0.07 227.392);
69
- --chart-4: oklch(0.828 0.189 84.429);
70
- --chart-5: oklch(0.769 0.188 70.08);
71
- --sidebar: oklch(0.985 0 0);
72
- --sidebar-foreground: oklch(0.145 0 0);
73
- --sidebar-primary: oklch(0.205 0 0);
74
- --sidebar-primary-foreground: oklch(0.985 0 0);
75
- --sidebar-accent: oklch(0.97 0 0);
76
- --sidebar-accent-foreground: oklch(0.205 0 0);
77
- --sidebar-border: oklch(0.922 0 0);
78
- --sidebar-ring: oklch(0.708 0 0);
79
- }
80
-
81
- .dark {
82
- --background: oklch(0.145 0 0);
83
- --foreground: oklch(0.985 0 0);
84
- --card: oklch(0.205 0 0);
85
- --card-foreground: oklch(0.985 0 0);
86
- --popover: oklch(0.205 0 0);
87
- --popover-foreground: oklch(0.985 0 0);
88
- --primary: oklch(0.922 0 0);
89
- --primary-foreground: oklch(0.205 0 0);
90
- --secondary: oklch(0.269 0 0);
91
- --secondary-foreground: oklch(0.985 0 0);
92
- --muted: oklch(0.269 0 0);
93
- --muted-foreground: oklch(0.708 0 0);
94
- --accent: oklch(0.269 0 0);
95
- --accent-foreground: oklch(0.985 0 0);
96
- --destructive: oklch(0.704 0.191 22.216);
97
- --border: oklch(1 0 0 / 10%);
98
- --input: oklch(1 0 0 / 15%);
99
- --ring: oklch(0.556 0 0);
100
- --chart-1: oklch(0.488 0.243 264.376);
101
- --chart-2: oklch(0.696 0.17 162.48);
102
- --chart-3: oklch(0.769 0.188 70.08);
103
- --chart-4: oklch(0.627 0.265 303.9);
104
- --chart-5: oklch(0.645 0.246 16.439);
105
- --sidebar: oklch(0.205 0 0);
106
- --sidebar-foreground: oklch(0.985 0 0);
107
- --sidebar-primary: oklch(0.488 0.243 264.376);
108
- --sidebar-primary-foreground: oklch(0.985 0 0);
109
- --sidebar-accent: oklch(0.269 0 0);
110
- --sidebar-accent-foreground: oklch(0.985 0 0);
111
- --sidebar-border: oklch(1 0 0 / 10%);
112
- --sidebar-ring: oklch(0.556 0 0);
113
- }
114
-
115
- body {
116
- @apply scroll-smooth
117
- }
118
-
119
- @layer base {
120
- * {
121
- @apply border-border outline-ring/50;
122
- }
123
- body {
124
- @apply bg-background text-foreground;
125
- }
126
- html {
127
- @apply scroll-smooth;
128
- }
129
- }
130
-
131
- .background__noisy {
132
- @apply bg-blend-normal pointer-events-none opacity-90;
133
- background-size: 25ww auto;
134
- background-image: url("/background_noisy.webp");
135
- @apply fixed w-screen h-screen -z-1 top-0 left-0;
136
- }
137
-
138
- .monaco-editor .margin {
139
- @apply !bg-neutral-900;
140
- }
141
- .monaco-editor .monaco-editor-background {
142
- @apply !bg-neutral-900;
143
- }
144
- .monaco-editor .line-numbers {
145
- @apply !text-neutral-500;
146
- }
147
-
148
- .matched-line {
149
- @apply bg-sky-500/30;
150
- }
151
-
152
- /* Fast liquid deformation animations */
153
- @keyframes liquidBlob1 {
154
- 0%, 100% {
155
- border-radius: 40% 60% 50% 50%;
156
- transform: scaleX(1) scaleY(1) rotate(0deg);
157
- }
158
- 12.5% {
159
- border-radius: 20% 80% 70% 30%;
160
- transform: scaleX(1.6) scaleY(0.4) rotate(25deg);
161
- }
162
- 25% {
163
- border-radius: 80% 20% 30% 70%;
164
- transform: scaleX(0.5) scaleY(2.1) rotate(-15deg);
165
- }
166
- 37.5% {
167
- border-radius: 30% 70% 80% 20%;
168
- transform: scaleX(1.8) scaleY(0.6) rotate(40deg);
169
- }
170
- 50% {
171
- border-radius: 70% 30% 20% 80%;
172
- transform: scaleX(0.4) scaleY(1.9) rotate(-30deg);
173
- }
174
- 62.5% {
175
- border-radius: 25% 75% 60% 40%;
176
- transform: scaleX(1.5) scaleY(0.7) rotate(55deg);
177
- }
178
- 75% {
179
- border-radius: 75% 25% 40% 60%;
180
- transform: scaleX(0.6) scaleY(1.7) rotate(-10deg);
181
- }
182
- 87.5% {
183
- border-radius: 50% 50% 75% 25%;
184
- transform: scaleX(1.3) scaleY(0.8) rotate(35deg);
185
- }
186
- }
187
-
188
- @keyframes liquidBlob2 {
189
- 0%, 100% {
190
- border-radius: 60% 40% 50% 50%;
191
- transform: scaleX(1) scaleY(1) rotate(12deg);
192
- }
193
- 16% {
194
- border-radius: 15% 85% 60% 40%;
195
- transform: scaleX(0.3) scaleY(2.3) rotate(50deg);
196
- }
197
- 32% {
198
- border-radius: 85% 15% 25% 75%;
199
- transform: scaleX(2.0) scaleY(0.5) rotate(-20deg);
200
- }
201
- 48% {
202
- border-radius: 30% 70% 85% 15%;
203
- transform: scaleX(0.4) scaleY(1.8) rotate(70deg);
204
- }
205
- 64% {
206
- border-radius: 70% 30% 15% 85%;
207
- transform: scaleX(1.9) scaleY(0.6) rotate(-35deg);
208
- }
209
- 80% {
210
- border-radius: 40% 60% 70% 30%;
211
- transform: scaleX(0.7) scaleY(1.6) rotate(45deg);
212
- }
213
- }
214
-
215
- @keyframes liquidBlob3 {
216
- 0%, 100% {
217
- border-radius: 50% 50% 40% 60%;
218
- transform: scaleX(1) scaleY(1) rotate(0deg);
219
- }
220
- 20% {
221
- border-radius: 10% 90% 75% 25%;
222
- transform: scaleX(2.2) scaleY(0.3) rotate(-45deg);
223
- }
224
- 40% {
225
- border-radius: 90% 10% 20% 80%;
226
- transform: scaleX(0.4) scaleY(2.5) rotate(60deg);
227
- }
228
- 60% {
229
- border-radius: 25% 75% 90% 10%;
230
- transform: scaleX(1.7) scaleY(0.5) rotate(-25deg);
231
- }
232
- 80% {
233
- border-radius: 75% 25% 10% 90%;
234
- transform: scaleX(0.6) scaleY(2.0) rotate(80deg);
235
- }
236
- }
237
-
238
- @keyframes liquidBlob4 {
239
- 0%, 100% {
240
- border-radius: 45% 55% 50% 50%;
241
- transform: scaleX(1) scaleY(1) rotate(-15deg);
242
- }
243
- 14% {
244
- border-radius: 90% 10% 65% 35%;
245
- transform: scaleX(0.2) scaleY(2.8) rotate(35deg);
246
- }
247
- 28% {
248
- border-radius: 10% 90% 20% 80%;
249
- transform: scaleX(2.4) scaleY(0.4) rotate(-50deg);
250
- }
251
- 42% {
252
- border-radius: 35% 65% 90% 10%;
253
- transform: scaleX(0.3) scaleY(2.1) rotate(70deg);
254
- }
255
- 56% {
256
- border-radius: 80% 20% 10% 90%;
257
- transform: scaleX(2.0) scaleY(0.5) rotate(-40deg);
258
- }
259
- 70% {
260
- border-radius: 20% 80% 55% 45%;
261
- transform: scaleX(0.5) scaleY(1.9) rotate(55deg);
262
- }
263
- 84% {
264
- border-radius: 65% 35% 80% 20%;
265
- transform: scaleX(1.6) scaleY(0.6) rotate(-25deg);
266
- }
267
- }
268
-
269
- /* Fast flowing movement animations */
270
- @keyframes liquidFlow1 {
271
- 0%, 100% { transform: translate(0, 0); }
272
- 16% { transform: translate(60px, -40px); }
273
- 32% { transform: translate(-45px, -70px); }
274
- 48% { transform: translate(80px, 25px); }
275
- 64% { transform: translate(-30px, 60px); }
276
- 80% { transform: translate(50px, -20px); }
277
- }
278
-
279
- @keyframes liquidFlow2 {
280
- 0%, 100% { transform: translate(0, 0); }
281
- 20% { transform: translate(-70px, 50px); }
282
- 40% { transform: translate(90px, -30px); }
283
- 60% { transform: translate(-40px, -55px); }
284
- 80% { transform: translate(65px, 35px); }
285
- }
286
-
287
- @keyframes liquidFlow3 {
288
- 0%, 100% { transform: translate(0, 0); }
289
- 12% { transform: translate(-50px, -60px); }
290
- 24% { transform: translate(40px, -20px); }
291
- 36% { transform: translate(-30px, 70px); }
292
- 48% { transform: translate(70px, 20px); }
293
- 60% { transform: translate(-60px, -35px); }
294
- 72% { transform: translate(35px, 55px); }
295
- 84% { transform: translate(-25px, -45px); }
296
- }
297
-
298
- @keyframes liquidFlow4 {
299
- 0%, 100% { transform: translate(0, 0); }
300
- 14% { transform: translate(50px, 60px); }
301
- 28% { transform: translate(-80px, -40px); }
302
- 42% { transform: translate(30px, -90px); }
303
- 56% { transform: translate(-55px, 45px); }
304
- 70% { transform: translate(75px, -25px); }
305
- 84% { transform: translate(-35px, 65px); }
306
- }
307
-
308
- /* Light sweep animation for buttons */
309
- @keyframes lightSweep {
310
- 0% {
311
- transform: translateX(-150%);
312
- opacity: 0;
313
- }
314
- 8% {
315
- opacity: 0.3;
316
- }
317
- 25% {
318
- opacity: 0.8;
319
- }
320
- 42% {
321
- opacity: 0.3;
322
- }
323
- 50% {
324
- transform: translateX(150%);
325
- opacity: 0;
326
- }
327
- 58% {
328
- opacity: 0.3;
329
- }
330
- 75% {
331
- opacity: 0.8;
332
- }
333
- 92% {
334
- opacity: 0.3;
335
- }
336
- 100% {
337
- transform: translateX(-150%);
338
- opacity: 0;
339
- }
340
- }
341
-
342
- .light-sweep {
343
- position: relative;
344
- overflow: hidden;
345
- }
346
-
347
- .light-sweep::before {
348
- content: '';
349
- position: absolute;
350
- top: 0;
351
- left: 0;
352
- right: 0;
353
- bottom: 0;
354
- width: 300%;
355
- background: linear-gradient(
356
- 90deg,
357
- transparent 0%,
358
- transparent 20%,
359
- rgba(56, 189, 248, 0.1) 35%,
360
- rgba(56, 189, 248, 0.2) 45%,
361
- rgba(255, 255, 255, 0.2) 50%,
362
- rgba(168, 85, 247, 0.2) 55%,
363
- rgba(168, 85, 247, 0.1) 65%,
364
- transparent 80%,
365
- transparent 100%
366
- );
367
- animation: lightSweep 7s cubic-bezier(0.4, 0, 0.2, 1) infinite;
368
- pointer-events: none;
369
- z-index: 1;
370
- filter: blur(1px);
371
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/kimi.svg DELETED
assets/logo.svg DELETED
assets/qwen.svg DELETED
assets/zai.svg DELETED
components.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "$schema": "https://ui.shadcn.com/schema.json",
3
- "style": "new-york",
4
- "rsc": true,
5
- "tsx": true,
6
- "tailwind": {
7
- "config": "",
8
- "css": "assets/globals.css",
9
- "baseColor": "neutral",
10
- "cssVariables": true,
11
- "prefix": ""
12
- },
13
- "aliases": {
14
- "components": "@/components",
15
- "utils": "@/lib/utils",
16
- "ui": "@/components/ui",
17
- "lib": "@/lib",
18
- "hooks": "@/hooks"
19
- },
20
- "iconLibrary": "lucide"
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/animated-blobs/index.tsx DELETED
@@ -1,34 +0,0 @@
1
- export function AnimatedBlobs() {
2
- return (
3
- <div className="absolute inset-0 pointer-events-none -z-[1]">
4
- <div
5
- className="w-full h-full bg-gradient-to-r from-purple-500 to-pink-500 opacity-10 blur-3xl"
6
- style={{
7
- animation:
8
- "liquidBlob1 4s ease-in-out infinite, liquidFlow1 6s ease-in-out infinite",
9
- }}
10
- />
11
- <div
12
- className="w-2/3 h-3/4 bg-gradient-to-r from-blue-500 to-teal-500 opacity-24 blur-3xl absolute -top-20 right-10"
13
- style={{
14
- animation:
15
- "liquidBlob2 5s ease-in-out infinite, liquidFlow2 7s ease-in-out infinite",
16
- }}
17
- />
18
- <div
19
- className="w-1/2 h-1/2 bg-gradient-to-r from-amber-500 to-rose-500 opacity-20 blur-3xl absolute bottom-0 left-10"
20
- style={{
21
- animation:
22
- "liquidBlob3 3.5s ease-in-out infinite, liquidFlow3 8s ease-in-out infinite",
23
- }}
24
- />
25
- <div
26
- className="w-48 h-48 bg-gradient-to-r from-cyan-500 to-indigo-500 opacity-20 blur-3xl absolute top-1/3 right-1/3"
27
- style={{
28
- animation:
29
- "liquidBlob4 4.5s ease-in-out infinite, liquidFlow4 6.5s ease-in-out infinite",
30
- }}
31
- />
32
- </div>
33
- );
34
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/animated-text/index.tsx DELETED
@@ -1,123 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useEffect } from "react";
4
-
5
- interface AnimatedTextProps {
6
- className?: string;
7
- }
8
-
9
- export function AnimatedText({ className = "" }: AnimatedTextProps) {
10
- const [displayText, setDisplayText] = useState("");
11
- const [currentSuggestionIndex, setCurrentSuggestionIndex] = useState(0);
12
- const [isTyping, setIsTyping] = useState(true);
13
- const [showCursor, setShowCursor] = useState(true);
14
- const [lastTypedIndex, setLastTypedIndex] = useState(-1);
15
- const [animationComplete, setAnimationComplete] = useState(false);
16
-
17
- // Randomize suggestions on each component mount
18
- const [suggestions] = useState(() => {
19
- const baseSuggestions = [
20
- "create a stunning portfolio!",
21
- "build a tic tac toe game!",
22
- "design a website for my restaurant!",
23
- "make a sleek landing page!",
24
- "build an e-commerce store!",
25
- "create a personal blog!",
26
- "develop a modern dashboard!",
27
- "design a company website!",
28
- "build a todo app!",
29
- "create an online gallery!",
30
- "make a contact form!",
31
- "build a weather app!",
32
- ];
33
-
34
- // Fisher-Yates shuffle algorithm
35
- const shuffled = [...baseSuggestions];
36
- for (let i = shuffled.length - 1; i > 0; i--) {
37
- const j = Math.floor(Math.random() * (i + 1));
38
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
39
- }
40
-
41
- return shuffled;
42
- });
43
-
44
- useEffect(() => {
45
- if (animationComplete) return;
46
-
47
- let timeout: NodeJS.Timeout;
48
-
49
- const typeText = () => {
50
- const currentSuggestion = suggestions[currentSuggestionIndex];
51
-
52
- if (isTyping) {
53
- if (displayText.length < currentSuggestion.length) {
54
- setDisplayText(currentSuggestion.slice(0, displayText.length + 1));
55
- setLastTypedIndex(displayText.length);
56
- timeout = setTimeout(typeText, 80);
57
- } else {
58
- // Finished typing, wait then start erasing
59
- setLastTypedIndex(-1);
60
- timeout = setTimeout(() => {
61
- setIsTyping(false);
62
- }, 2000);
63
- }
64
- }
65
- };
66
-
67
- timeout = setTimeout(typeText, 100);
68
- return () => clearTimeout(timeout);
69
- }, [
70
- displayText,
71
- currentSuggestionIndex,
72
- isTyping,
73
- suggestions,
74
- animationComplete,
75
- ]);
76
-
77
- // Cursor blinking effect
78
- useEffect(() => {
79
- if (animationComplete) {
80
- setShowCursor(false);
81
- return;
82
- }
83
-
84
- const cursorInterval = setInterval(() => {
85
- setShowCursor((prev) => !prev);
86
- }, 600);
87
-
88
- return () => clearInterval(cursorInterval);
89
- }, [animationComplete]);
90
-
91
- useEffect(() => {
92
- if (lastTypedIndex >= 0) {
93
- const timeout = setTimeout(() => {
94
- setLastTypedIndex(-1);
95
- }, 400);
96
-
97
- return () => clearTimeout(timeout);
98
- }
99
- }, [lastTypedIndex]);
100
-
101
- return (
102
- <p className={`font-mono ${className}`}>
103
- Hey DeepSite,&nbsp;
104
- {displayText.split("").map((char, index) => (
105
- <span
106
- key={`${currentSuggestionIndex}-${index}`}
107
- className={`transition-colors duration-300 ${
108
- index === lastTypedIndex ? "text-neutral-100" : ""
109
- }`}
110
- >
111
- {char}
112
- </span>
113
- ))}
114
- <span
115
- className={`${
116
- showCursor ? "opacity-100" : "opacity-0"
117
- } transition-opacity`}
118
- >
119
- |
120
- </span>
121
- </p>
122
- );
123
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/contexts/app-context.tsx DELETED
@@ -1,53 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- "use client";
3
- import { useMount } from "react-use";
4
- import { toast } from "sonner";
5
- import { usePathname, useRouter } from "next/navigation";
6
-
7
- import { useUser } from "@/hooks/useUser";
8
- import { ProjectType, User } from "@/types";
9
- import { useBroadcastChannel } from "@/lib/useBroadcastChannel";
10
-
11
- export default function AppContext({
12
- children,
13
- me: initialData,
14
- }: {
15
- children: React.ReactNode;
16
- me?: {
17
- user: User | null;
18
- projects: ProjectType[];
19
- errCode: number | null;
20
- };
21
- }) {
22
- const { loginFromCode, user, logout, loading, errCode } =
23
- useUser(initialData);
24
- const pathname = usePathname();
25
- const router = useRouter();
26
-
27
- useMount(() => {
28
- if (!initialData?.user && !user) {
29
- if ([401, 403].includes(errCode as number)) {
30
- logout();
31
- } else if (pathname.includes("/spaces")) {
32
- if (errCode) {
33
- toast.error("An error occured while trying to log in");
34
- }
35
- // If we did not manage to log in (probs because api is down), we simply redirect to the home page
36
- router.push("/");
37
- }
38
- }
39
- });
40
-
41
- const events: any = {};
42
-
43
- useBroadcastChannel("auth", (message) => {
44
- if (pathname.includes("/auth/callback")) return;
45
-
46
- if (!message.code) return;
47
- if (message.type === "user-oauth" && message?.code && !events.code) {
48
- loginFromCode(message.code);
49
- }
50
- });
51
-
52
- return children;
53
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/contexts/login-context.tsx DELETED
@@ -1,62 +0,0 @@
1
- "use client";
2
-
3
- import React, { createContext, useContext, useState, ReactNode } from "react";
4
- import { LoginModal } from "@/components/login-modal";
5
- import { Page } from "@/types";
6
-
7
- interface LoginContextType {
8
- isOpen: boolean;
9
- openLoginModal: (options?: LoginModalOptions) => void;
10
- closeLoginModal: () => void;
11
- }
12
-
13
- interface LoginModalOptions {
14
- pages?: Page[];
15
- title?: string;
16
- prompt?: string;
17
- description?: string;
18
- }
19
-
20
- const LoginContext = createContext<LoginContextType | undefined>(undefined);
21
-
22
- export function LoginProvider({ children }: { children: ReactNode }) {
23
- const [isOpen, setIsOpen] = useState(false);
24
- const [modalOptions, setModalOptions] = useState<LoginModalOptions>({});
25
-
26
- const openLoginModal = (options: LoginModalOptions = {}) => {
27
- setModalOptions(options);
28
- setIsOpen(true);
29
- };
30
-
31
- const closeLoginModal = () => {
32
- setIsOpen(false);
33
- setModalOptions({});
34
- };
35
-
36
- const value = {
37
- isOpen,
38
- openLoginModal,
39
- closeLoginModal,
40
- };
41
-
42
- return (
43
- <LoginContext.Provider value={value}>
44
- {children}
45
- <LoginModal
46
- open={isOpen}
47
- onClose={setIsOpen}
48
- title={modalOptions.title}
49
- prompt={modalOptions.prompt}
50
- description={modalOptions.description}
51
- />
52
- </LoginContext.Provider>
53
- );
54
- }
55
-
56
- export function useLoginModal() {
57
- const context = useContext(LoginContext);
58
- if (context === undefined) {
59
- throw new Error("useLoginModal must be used within a LoginProvider");
60
- }
61
- return context;
62
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/contexts/pro-context.tsx DELETED
@@ -1,48 +0,0 @@
1
- "use client";
2
-
3
- import React, { createContext, useContext, useState, ReactNode } from "react";
4
- import { ProModal } from "@/components/pro-modal";
5
- import { Page } from "@/types";
6
- import { useEditor } from "@/hooks/useEditor";
7
-
8
- interface ProContextType {
9
- isOpen: boolean;
10
- openProModal: (pages: Page[]) => void;
11
- closeProModal: () => void;
12
- }
13
-
14
- const ProContext = createContext<ProContextType | undefined>(undefined);
15
-
16
- export function ProProvider({ children }: { children: ReactNode }) {
17
- const [isOpen, setIsOpen] = useState(false);
18
- const { pages } = useEditor();
19
-
20
- const openProModal = () => {
21
- setIsOpen(true);
22
- };
23
-
24
- const closeProModal = () => {
25
- setIsOpen(false);
26
- };
27
-
28
- const value = {
29
- isOpen,
30
- openProModal,
31
- closeProModal,
32
- };
33
-
34
- return (
35
- <ProContext.Provider value={value}>
36
- {children}
37
- <ProModal open={isOpen} onClose={setIsOpen} pages={pages} />
38
- </ProContext.Provider>
39
- );
40
- }
41
-
42
- export function useProModal() {
43
- const context = useContext(ProContext);
44
- if (context === undefined) {
45
- throw new Error("useProModal must be used within a ProProvider");
46
- }
47
- return context;
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/contexts/tanstack-query-context.tsx DELETED
@@ -1,31 +0,0 @@
1
- "use client";
2
-
3
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
- import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
5
- import { useState } from "react";
6
-
7
- export default function TanstackContext({
8
- children,
9
- }: {
10
- children: React.ReactNode;
11
- }) {
12
- // Create QueryClient instance only once using useState with a function
13
- const [queryClient] = useState(
14
- () =>
15
- new QueryClient({
16
- defaultOptions: {
17
- queries: {
18
- staleTime: 60 * 1000, // 1 minute
19
- refetchOnWindowFocus: false,
20
- },
21
- },
22
- })
23
- );
24
-
25
- return (
26
- <QueryClientProvider client={queryClient}>
27
- {children}
28
- <ReactQueryDevtools initialIsOpen={false} />
29
- </QueryClientProvider>
30
- );
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/contexts/user-context.tsx DELETED
@@ -1,8 +0,0 @@
1
- "use client";
2
-
3
- import { createContext } from "react";
4
- import { User } from "@/types";
5
-
6
- export const UserContext = createContext({
7
- user: undefined as User | undefined,
8
- });
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/fake-ask.tsx DELETED
@@ -1,97 +0,0 @@
1
- import { useState } from "react";
2
- import { useLocalStorage } from "react-use";
3
- import { ArrowUp, Dice6 } from "lucide-react";
4
- import { useRouter } from "next/navigation";
5
-
6
- import { Button } from "@/components/ui/button";
7
- import { PromptBuilder } from "./prompt-builder";
8
- import { EnhancedSettings } from "@/types";
9
- import { Settings } from "./settings";
10
- import classNames from "classnames";
11
- import { PROMPTS_FOR_AI } from "@/lib/prompts";
12
-
13
- export const FakeAskAi = () => {
14
- const router = useRouter();
15
- const [prompt, setPrompt] = useState("");
16
- const [openProvider, setOpenProvider] = useState(false);
17
- const [enhancedSettings, setEnhancedSettings, removeEnhancedSettings] =
18
- useLocalStorage<EnhancedSettings>("deepsite-enhancedSettings", {
19
- isActive: true,
20
- primaryColor: undefined,
21
- secondaryColor: undefined,
22
- theme: undefined,
23
- });
24
- const [, setPromptStorage] = useLocalStorage("prompt", "");
25
- const [randomPromptLoading, setRandomPromptLoading] = useState(false);
26
-
27
- const callAi = async () => {
28
- setPromptStorage(prompt);
29
- router.push("/new");
30
- };
31
-
32
- const randomPrompt = () => {
33
- setRandomPromptLoading(true);
34
- setTimeout(() => {
35
- setPrompt(
36
- PROMPTS_FOR_AI[Math.floor(Math.random() * PROMPTS_FOR_AI.length)]
37
- );
38
- setRandomPromptLoading(false);
39
- }, 400);
40
- };
41
-
42
- return (
43
- <div className="p-3 w-full max-w-xl mx-auto">
44
- <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">
45
- <div className="w-full relative flex items-start justify-between pr-4 pt-4">
46
- <textarea
47
- className="w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 px-4 pb-4 resize-none"
48
- placeholder="Ask DeepSite anything..."
49
- value={prompt}
50
- onChange={(e) => setPrompt(e.target.value)}
51
- onKeyDown={(e) => {
52
- if (e.key === "Enter" && !e.shiftKey) {
53
- callAi();
54
- }
55
- }}
56
- />
57
- <Button
58
- size="iconXs"
59
- variant="outline"
60
- className="!rounded-md"
61
- onClick={() => randomPrompt()}
62
- >
63
- <Dice6
64
- className={classNames("size-4", {
65
- "animate-spin animation-duration-500": randomPromptLoading,
66
- })}
67
- />
68
- </Button>
69
- </div>
70
- <div className="flex items-center justify-between gap-2 px-4 pb-3 mt-2">
71
- <div className="flex-1 flex items-center justify-start gap-1.5 flex-wrap">
72
- <PromptBuilder
73
- enhancedSettings={enhancedSettings!}
74
- setEnhancedSettings={setEnhancedSettings}
75
- />
76
- <Settings
77
- open={openProvider}
78
- isFollowUp={false}
79
- error=""
80
- onClose={setOpenProvider}
81
- />
82
- </div>
83
- <div className="flex items-center justify-end gap-2">
84
- <Button
85
- size="iconXs"
86
- variant="outline"
87
- className="!rounded-md"
88
- onClick={() => callAi()}
89
- >
90
- <ArrowUp className="size-4" />
91
- </Button>
92
- </div>
93
- </div>
94
- </div>
95
- </div>
96
- );
97
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/index.tsx DELETED
@@ -1,322 +0,0 @@
1
- import { useRef, useState } from "react";
2
- import classNames from "classnames";
3
- import { ArrowUp, ChevronDown, CircleStop, Dice6 } from "lucide-react";
4
- import { useLocalStorage, useUpdateEffect, useMount } from "react-use";
5
- import { toast } from "sonner";
6
-
7
- import { useAi } from "@/hooks/useAi";
8
- import { useEditor } from "@/hooks/useEditor";
9
- import { EnhancedSettings, Project } from "@/types";
10
- import { SelectedFiles } from "@/components/editor/ask-ai/selected-files";
11
- import { SelectedHtmlElement } from "@/components/editor/ask-ai/selected-html-element";
12
- import { AiLoading } from "@/components/editor/ask-ai/loading";
13
- import { Button } from "@/components/ui/button";
14
- import { Uploader } from "@/components/editor/ask-ai/uploader";
15
- import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
16
- import { Selector } from "@/components/editor/ask-ai/selector";
17
- import { PromptBuilder } from "@/components/editor/ask-ai/prompt-builder";
18
- import { useUser } from "@/hooks/useUser";
19
- import { useLoginModal } from "@/components/contexts/login-context";
20
- import { Settings } from "./settings";
21
- import { useProModal } from "@/components/contexts/pro-context";
22
- import { MAX_FREE_PROJECTS } from "@/lib/utils";
23
- import { PROMPTS_FOR_AI } from "@/lib/prompts";
24
-
25
- export const AskAi = ({
26
- project,
27
- isNew,
28
- onScrollToBottom,
29
- }: {
30
- project?: Project;
31
- files?: string[];
32
- isNew?: boolean;
33
- onScrollToBottom?: () => void;
34
- }) => {
35
- const { user, projects } = useUser();
36
- const { isSameHtml, isUploading, pages, isLoadingProject } = useEditor();
37
- const {
38
- isAiWorking,
39
- isThinking,
40
- selectedFiles,
41
- setSelectedFiles,
42
- selectedElement,
43
- setSelectedElement,
44
- setIsThinking,
45
- callAiNewProject,
46
- callAiFollowUp,
47
- setModel,
48
- selectedModel,
49
- audio: hookAudio,
50
- cancelRequest,
51
- } = useAi(onScrollToBottom);
52
- const { openLoginModal } = useLoginModal();
53
- const { openProModal } = useProModal();
54
- const [openProvider, setOpenProvider] = useState(false);
55
- const [providerError, setProviderError] = useState("");
56
- const refThink = useRef<HTMLDivElement>(null);
57
-
58
- const [enhancedSettings, setEnhancedSettings, removeEnhancedSettings] =
59
- useLocalStorage<EnhancedSettings>("deepsite-enhancedSettings", {
60
- isActive: false,
61
- primaryColor: undefined,
62
- secondaryColor: undefined,
63
- theme: undefined,
64
- });
65
- const [promptStorage, , removePromptStorage] = useLocalStorage("prompt", "");
66
-
67
- const [isFollowUp, setIsFollowUp] = useState(true);
68
- const [prompt, setPrompt] = useState(
69
- promptStorage && promptStorage.trim() !== "" ? promptStorage : ""
70
- );
71
- const [think, setThink] = useState("");
72
- const [openThink, setOpenThink] = useState(false);
73
- const [randomPromptLoading, setRandomPromptLoading] = useState(false);
74
-
75
- useMount(() => {
76
- if (promptStorage && promptStorage.trim() !== "") {
77
- callAi();
78
- }
79
- });
80
-
81
- const callAi = async (redesignMarkdown?: string) => {
82
- removePromptStorage();
83
- if (user && !user.isPro && projects.length >= MAX_FREE_PROJECTS)
84
- return openProModal([]);
85
- if (isAiWorking) return;
86
- if (!redesignMarkdown && !prompt.trim()) return;
87
-
88
- if (isFollowUp && !redesignMarkdown && !isSameHtml) {
89
- if (!user) return openLoginModal({ prompt });
90
- const result = await callAiFollowUp(prompt, enhancedSettings, isNew);
91
-
92
- if (result?.error) {
93
- handleError(result.error, result.message);
94
- return;
95
- }
96
-
97
- if (result?.success) {
98
- setPrompt("");
99
- }
100
- } else {
101
- const result = await callAiNewProject(
102
- prompt,
103
- enhancedSettings,
104
- redesignMarkdown,
105
- !!user
106
- );
107
-
108
- if (result?.error) {
109
- handleError(result.error, result.message);
110
- return;
111
- }
112
-
113
- if (result?.success) {
114
- setPrompt("");
115
- // if (selectedModel?.isThinker) {
116
- // setModel(MODELS[0].value);
117
- // }
118
- }
119
- }
120
- };
121
-
122
- const handleError = (error: string, message?: string) => {
123
- switch (error) {
124
- case "login_required":
125
- openLoginModal();
126
- break;
127
- case "provider_required":
128
- setOpenProvider(true);
129
- setProviderError(message || "");
130
- break;
131
- case "pro_required":
132
- openProModal([]);
133
- break;
134
- case "api_error":
135
- toast.error(message || "An error occurred");
136
- break;
137
- case "network_error":
138
- toast.error(message || "Network error occurred");
139
- break;
140
- default:
141
- toast.error("An unexpected error occurred");
142
- }
143
- };
144
-
145
- useUpdateEffect(() => {
146
- if (refThink.current) {
147
- refThink.current.scrollTop = refThink.current.scrollHeight;
148
- }
149
- }, [think]);
150
-
151
- const randomPrompt = () => {
152
- setRandomPromptLoading(true);
153
- setTimeout(() => {
154
- setPrompt(
155
- PROMPTS_FOR_AI[Math.floor(Math.random() * PROMPTS_FOR_AI.length)]
156
- );
157
- setRandomPromptLoading(false);
158
- }, 400);
159
- };
160
-
161
- return (
162
- <div className="p-3 w-full">
163
- <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">
164
- {think && (
165
- <div className="w-full border-b border-neutral-700 relative overflow-hidden">
166
- <header
167
- className="flex items-center justify-between px-5 py-2.5 group hover:bg-neutral-600/20 transition-colors duration-200 cursor-pointer"
168
- onClick={() => {
169
- setOpenThink(!openThink);
170
- }}
171
- >
172
- <p className="text-sm font-medium text-neutral-300 group-hover:text-neutral-200 transition-colors duration-200">
173
- {isThinking ? "DeepSite is thinking..." : "DeepSite's plan"}
174
- </p>
175
- <ChevronDown
176
- className={classNames(
177
- "size-4 text-neutral-400 group-hover:text-neutral-300 transition-all duration-200",
178
- {
179
- "rotate-180": openThink,
180
- }
181
- )}
182
- />
183
- </header>
184
- <main
185
- ref={refThink}
186
- className={classNames(
187
- "overflow-y-auto transition-all duration-200 ease-in-out",
188
- {
189
- "max-h-[0px]": !openThink,
190
- "min-h-[250px] max-h-[250px] border-t border-neutral-700":
191
- openThink,
192
- }
193
- )}
194
- >
195
- <p className="text-[13px] text-neutral-400 whitespace-pre-line px-5 pb-4 pt-3">
196
- {think}
197
- </p>
198
- </main>
199
- </div>
200
- )}
201
- <SelectedFiles
202
- files={selectedFiles}
203
- isAiWorking={isAiWorking}
204
- onDelete={(file) =>
205
- setSelectedFiles(selectedFiles.filter((f) => f !== file))
206
- }
207
- />
208
- {selectedElement && (
209
- <div className="px-4 pt-3">
210
- <SelectedHtmlElement
211
- element={selectedElement}
212
- isAiWorking={isAiWorking}
213
- onDelete={() => setSelectedElement(null)}
214
- />
215
- </div>
216
- )}
217
- <div className="w-full relative flex items-center justify-between">
218
- {(isAiWorking || isUploading || isThinking || isLoadingProject) && (
219
- <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">
220
- <AiLoading
221
- text={
222
- isLoadingProject
223
- ? "Fetching your project..."
224
- : isUploading
225
- ? "Uploading images..."
226
- : isAiWorking && !isSameHtml
227
- ? "DeepSite is working..."
228
- : "DeepSite is thinking..."
229
- }
230
- />
231
- {isAiWorking && (
232
- <Button
233
- size="iconXs"
234
- variant="outline"
235
- className="!rounded-md mr-0.5"
236
- onClick={cancelRequest}
237
- >
238
- <CircleStop className="size-4" />
239
- </Button>
240
- )}
241
- </div>
242
- )}
243
- <textarea
244
- disabled={
245
- isAiWorking || isUploading || isThinking || isLoadingProject
246
- }
247
- className={classNames(
248
- "w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4 resize-none",
249
- {
250
- "!pt-2.5":
251
- selectedElement &&
252
- !(isAiWorking || isUploading || isThinking),
253
- }
254
- )}
255
- placeholder={
256
- selectedElement
257
- ? `Ask DeepSite about ${selectedElement.tagName.toLowerCase()}...`
258
- : isFollowUp && (!isSameHtml || pages?.length > 1)
259
- ? "Ask DeepSite for edits"
260
- : "Ask DeepSite anything..."
261
- }
262
- value={prompt}
263
- onChange={(e) => setPrompt(e.target.value)}
264
- onKeyDown={(e) => {
265
- if (e.key === "Enter" && !e.shiftKey) {
266
- callAi();
267
- }
268
- }}
269
- />
270
- {isNew && !isAiWorking && isSameHtml && (
271
- <Button
272
- size="iconXs"
273
- variant="outline"
274
- className="!rounded-md -translate-y-2 -translate-x-4"
275
- onClick={() => randomPrompt()}
276
- >
277
- <Dice6
278
- className={classNames("size-4", {
279
- "animate-spin animation-duration-500": randomPromptLoading,
280
- })}
281
- />
282
- </Button>
283
- )}
284
- </div>
285
- <div className="flex items-center justify-between gap-2 px-4 pb-3 mt-2">
286
- <div className="flex-1 flex items-center justify-start gap-1.5 flex-wrap">
287
- <PromptBuilder
288
- enhancedSettings={enhancedSettings!}
289
- setEnhancedSettings={setEnhancedSettings}
290
- />
291
- <Settings
292
- open={openProvider}
293
- error={providerError}
294
- isFollowUp={!isSameHtml && isFollowUp}
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">
302
- <Button
303
- size="iconXs"
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>
313
- </div>
314
- </div>
315
- </div>
316
- <audio ref={hookAudio} id="audio" className="hidden">
317
- <source src="/success.mp3" type="audio/mpeg" />
318
- Your browser does not support the audio element.
319
- </audio>
320
- </div>
321
- );
322
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/loading.tsx DELETED
@@ -1,68 +0,0 @@
1
- "use client";
2
- import Loading from "@/components/loading";
3
- import { useState, useEffect } from "react";
4
- import { useInterval } from "react-use";
5
-
6
- const TEXTS = [
7
- "Teaching pixels to dance with style...",
8
- "AI is having a creative breakthrough...",
9
- "Channeling digital vibes into pure code...",
10
- "Summoning the website spirits...",
11
- "Brewing some algorithmic magic...",
12
- "Composing a symphony of divs and spans...",
13
- "Riding the wave of computational creativity...",
14
- "Aligning the stars for perfect design...",
15
- "Training circus animals to write CSS...",
16
- "Launching ideas into the digital stratosphere...",
17
- ];
18
-
19
- export const AiLoading = ({
20
- text,
21
- className,
22
- }: {
23
- text?: string;
24
- className?: string;
25
- }) => {
26
- const [selectedText, setSelectedText] = useState(
27
- text ?? TEXTS[0] // Start with first text to avoid hydration issues
28
- );
29
-
30
- // Set random text on client-side only to avoid hydration mismatch
31
- useEffect(() => {
32
- if (!text) {
33
- setSelectedText(TEXTS[Math.floor(Math.random() * TEXTS.length)]);
34
- }
35
- }, [text]);
36
-
37
- useInterval(() => {
38
- if (!text) {
39
- if (selectedText === TEXTS[TEXTS.length - 1]) {
40
- setSelectedText(TEXTS[0]);
41
- } else {
42
- setSelectedText(TEXTS[TEXTS.indexOf(selectedText) + 1]);
43
- }
44
- }
45
- }, 12000);
46
- return (
47
- <div className={`flex items-center justify-start gap-2 ${className}`}>
48
- <Loading overlay={false} className="!size-5 opacity-50" />
49
- <p className="text-neutral-400 text-sm">
50
- <span className="inline-flex">
51
- {selectedText.split("").map((char, index) => (
52
- <span
53
- key={index}
54
- className="bg-gradient-to-r from-neutral-100 to-neutral-300 bg-clip-text text-transparent animate-pulse"
55
- style={{
56
- animationDelay: `${index * 0.1}s`,
57
- animationDuration: "1.3s",
58
- animationIterationCount: "infinite",
59
- }}
60
- >
61
- {char === " " ? "\u00A0" : char}
62
- </span>
63
- ))}
64
- </span>
65
- </p>
66
- </div>
67
- );
68
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/prompt-builder/content-modal.tsx DELETED
@@ -1,196 +0,0 @@
1
- import classNames from "classnames";
2
- import { ChevronRight, RefreshCcw } from "lucide-react";
3
- import { useState } from "react";
4
- import { TailwindColors } from "./tailwind-colors";
5
- import { Switch } from "@/components/ui/switch";
6
- import { Button } from "@/components/ui/button";
7
- import { Themes } from "./themes";
8
- import { EnhancedSettings } from "@/types";
9
-
10
- export const ContentModal = ({
11
- enhancedSettings,
12
- setEnhancedSettings,
13
- }: {
14
- enhancedSettings: EnhancedSettings;
15
- setEnhancedSettings: (settings: EnhancedSettings) => void;
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">
23
- Allow DeepSite to enhance your prompt
24
- </p>
25
- <Switch
26
- checked={enhancedSettings.isActive}
27
- onCheckedChange={() =>
28
- setEnhancedSettings({
29
- ...enhancedSettings,
30
- isActive: !enhancedSettings.isActive,
31
- })
32
- }
33
- />
34
- </div>
35
- <p className="text-sm text-neutral-500 mt-2">
36
- While using DeepSite enhanced prompt, you'll get better results. We'll
37
- add more details and features to your request.
38
- </p>
39
- <div className="text-sm text-sky-500 mt-3 bg-gradient-to-r from-sky-400/15 to-purple-400/15 rounded-md px-3 py-2 border border-white/10">
40
- <p className="text-transparent bg-gradient-to-r from-sky-400 to-purple-400 bg-clip-text">
41
- You can also use the custom properties below to set specific
42
- information.
43
- </p>
44
- </div>
45
- </section>
46
- <section className="py-3.5 border-b border-neutral-800/80">
47
- <div
48
- className={classNames(
49
- "flex items-center justify-start gap-3 px-4 cursor-pointer text-neutral-400 hover:text-neutral-200",
50
- {
51
- "!text-neutral-200": collapsed.includes("colors"),
52
- }
53
- )}
54
- onClick={() =>
55
- setCollapsed((prev) => {
56
- if (prev.includes("colors")) {
57
- return prev.filter((item) => item !== "colors");
58
- }
59
- return [...prev, "colors"];
60
- })
61
- }
62
- >
63
- <ChevronRight className="size-4" />
64
- <p className="text-base font-semibold">Colors</p>
65
- </div>
66
- {collapsed.includes("colors") && (
67
- <div className="mt-4 space-y-4">
68
- <article className="w-full">
69
- <div className="flex items-center justify-start gap-2 px-5">
70
- <p className="text-xs font-medium uppercase text-neutral-400">
71
- Primary Color
72
- </p>
73
- <Button
74
- variant="bordered"
75
- size="xss"
76
- className={`${
77
- enhancedSettings.primaryColor ? "" : "opacity-0"
78
- }`}
79
- onClick={() =>
80
- setEnhancedSettings({
81
- ...enhancedSettings,
82
- primaryColor: undefined,
83
- })
84
- }
85
- >
86
- <RefreshCcw className="size-2.5" />
87
- Reset
88
- </Button>
89
- </div>
90
- <div className="text-muted-foreground text-sm mt-4">
91
- <TailwindColors
92
- value={enhancedSettings.primaryColor}
93
- onChange={(value) =>
94
- setEnhancedSettings({
95
- ...enhancedSettings,
96
- primaryColor: value,
97
- })
98
- }
99
- />
100
- </div>
101
- </article>
102
- <article className="w-full">
103
- <div className="flex items-center justify-start gap-2 px-5">
104
- <p className="text-xs font-medium uppercase text-neutral-400">
105
- Secondary Color
106
- </p>
107
- <Button
108
- variant="bordered"
109
- size="xss"
110
- className={`${
111
- enhancedSettings.secondaryColor ? "" : "opacity-0"
112
- }`}
113
- onClick={() =>
114
- setEnhancedSettings({
115
- ...enhancedSettings,
116
- secondaryColor: undefined,
117
- })
118
- }
119
- >
120
- <RefreshCcw className="size-2.5" />
121
- Reset
122
- </Button>
123
- </div>
124
- <div className="text-muted-foreground text-sm mt-4">
125
- <TailwindColors
126
- value={enhancedSettings.secondaryColor}
127
- onChange={(value) =>
128
- setEnhancedSettings({
129
- ...enhancedSettings,
130
- secondaryColor: value,
131
- })
132
- }
133
- />
134
- </div>
135
- </article>
136
- </div>
137
- )}
138
- </section>
139
- <section className="py-3.5 border-b border-neutral-800/80">
140
- <div
141
- className={classNames(
142
- "flex items-center justify-start gap-3 px-4 cursor-pointer text-neutral-400 hover:text-neutral-200",
143
- {
144
- "!text-neutral-200": collapsed.includes("theme"),
145
- }
146
- )}
147
- onClick={() =>
148
- setCollapsed((prev) => {
149
- if (prev.includes("theme")) {
150
- return prev.filter((item) => item !== "theme");
151
- }
152
- return [...prev, "theme"];
153
- })
154
- }
155
- >
156
- <ChevronRight className="size-4" />
157
- <p className="text-base font-semibold">Theme</p>
158
- </div>
159
- {collapsed.includes("theme") && (
160
- <article className="w-full mt-4">
161
- <div className="flex items-center justify-start gap-2 px-5">
162
- <p className="text-xs font-medium uppercase text-neutral-400">
163
- Theme
164
- </p>
165
- <Button
166
- variant="bordered"
167
- size="xss"
168
- className={`${enhancedSettings.theme ? "" : "opacity-0"}`}
169
- onClick={() =>
170
- setEnhancedSettings({
171
- ...enhancedSettings,
172
- theme: undefined,
173
- })
174
- }
175
- >
176
- <RefreshCcw className="size-2.5" />
177
- Reset
178
- </Button>
179
- </div>
180
- <div className="text-muted-foreground text-sm mt-4">
181
- <Themes
182
- value={enhancedSettings.theme}
183
- onChange={(value) =>
184
- setEnhancedSettings({
185
- ...enhancedSettings,
186
- theme: value,
187
- })
188
- }
189
- />
190
- </div>
191
- </article>
192
- )}
193
- </section>
194
- </main>
195
- );
196
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/prompt-builder/index.tsx DELETED
@@ -1,68 +0,0 @@
1
- import { useState } from "react";
2
- import { WandSparkles } from "lucide-react";
3
-
4
- import { Button } from "@/components/ui/button";
5
- import { useEditor } from "@/hooks/useEditor";
6
- import { useAi } from "@/hooks/useAi";
7
- import {
8
- Dialog,
9
- DialogContent,
10
- DialogFooter,
11
- DialogTitle,
12
- } from "@/components/ui/dialog";
13
- import { ContentModal } from "./content-modal";
14
- import { EnhancedSettings } from "@/types";
15
-
16
- export const PromptBuilder = ({
17
- enhancedSettings,
18
- setEnhancedSettings,
19
- }: {
20
- enhancedSettings: EnhancedSettings;
21
- setEnhancedSettings: (settings: EnhancedSettings) => void;
22
- }) => {
23
- const { globalAiLoading } = useAi();
24
- const { globalEditorLoading } = useEditor();
25
-
26
- const [open, setOpen] = useState(false);
27
- return (
28
- <>
29
- <Button
30
- size="xs"
31
- variant="outline"
32
- className="!rounded-md !border-white/10 !bg-gradient-to-r from-sky-400/15 to-purple-400/15 light-sweep hover:brightness-110"
33
- disabled={globalAiLoading || globalEditorLoading}
34
- onClick={() => {
35
- setOpen(true);
36
- }}
37
- >
38
- <WandSparkles className="size-3.5 text-sky-500 relative z-10" />
39
- <span className="text-transparent bg-gradient-to-r from-sky-400 to-purple-400 bg-clip-text relative z-10">
40
- Enhance
41
- </span>
42
- </Button>
43
- <Dialog open={open} onOpenChange={() => setOpen(false)}>
44
- <DialogContent className="sm:max-w-xl !p-0 !rounded-3xl !bg-neutral-900 !border-neutral-800/80 !gap-0">
45
- <DialogTitle className="px-6 py-3.5 border-b border-neutral-800">
46
- <div className="flex items-center justify-start gap-2 text-neutral-200 text-base font-medium">
47
- <WandSparkles className="size-3.5" />
48
- <p>Enhance Prompt</p>
49
- </div>
50
- </DialogTitle>
51
- <ContentModal
52
- enhancedSettings={enhancedSettings}
53
- setEnhancedSettings={setEnhancedSettings}
54
- />
55
- <DialogFooter className="px-6 py-3.5 border-t border-neutral-800">
56
- <Button
57
- variant="bordered"
58
- size="default"
59
- onClick={() => setOpen(false)}
60
- >
61
- Close
62
- </Button>
63
- </DialogFooter>
64
- </DialogContent>
65
- </Dialog>
66
- </>
67
- );
68
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/prompt-builder/tailwind-colors.tsx DELETED
@@ -1,58 +0,0 @@
1
- import classNames from "classnames";
2
- import { useRef } from "react";
3
-
4
- import { TAILWIND_COLORS } from "@/lib/prompt-builder";
5
- import { useMount } from "react-use";
6
-
7
- export const TailwindColors = ({
8
- value,
9
- onChange,
10
- }: {
11
- value: string | undefined;
12
- onChange: (value: string) => void;
13
- }) => {
14
- const ref = useRef<HTMLDivElement>(null);
15
-
16
- useMount(() => {
17
- if (ref.current) {
18
- if (value) {
19
- const color = ref.current.querySelector(`[data-color="${value}"]`);
20
- if (color) {
21
- color.scrollIntoView({ inline: "center" });
22
- }
23
- }
24
- }
25
- });
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
33
- key={color}
34
- className={classNames(
35
- "flex flex-col items-center justify-center p-3 size-16 min-w-16 gap-2 rounded-lg border border-neutral-800 bg-neutral-800/30 hover:brightness-120 cursor-pointer",
36
- {
37
- "!border-neutral-700 !bg-neutral-800/80 hover:!brightness-100":
38
- value === color,
39
- }
40
- )}
41
- data-color={color}
42
- onClick={() => onChange(color)}
43
- >
44
- <div
45
- className={`w-4 h-4 min-w-4 min-h-4 rounded-xl ${
46
- ["white", "black"].includes(color)
47
- ? `bg-${color}`
48
- : `bg-${color}-500`
49
- }`}
50
- />
51
- <p className="text-xs capitalize text-neutral-200 truncate">
52
- {color}
53
- </p>
54
- </div>
55
- ))}
56
- </div>
57
- );
58
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/prompt-builder/themes.tsx DELETED
@@ -1,48 +0,0 @@
1
- import { Theme } from "@/types";
2
- import classNames from "classnames";
3
- import { Moon, Sun } from "lucide-react";
4
- import { useRef } from "react";
5
-
6
- export const Themes = ({
7
- value,
8
- onChange,
9
- }: {
10
- value: Theme;
11
- onChange: (value: Theme) => void;
12
- }) => {
13
- const ref = useRef<HTMLDivElement>(null);
14
-
15
- return (
16
- <div
17
- ref={ref}
18
- className="flex items-center justify-start gap-3 overflow-x-auto px-5 scrollbar-hide"
19
- >
20
- <div
21
- className={classNames(
22
- "flex flex-col items-center justify-center p-3 size-16 min-w-32 gap-2 rounded-lg border border-neutral-800 bg-neutral-800/30 hover:brightness-120 cursor-pointer",
23
- {
24
- "!border-neutral-700 !bg-neutral-800/80 hover:!brightness-100":
25
- value === "light",
26
- }
27
- )}
28
- onClick={() => onChange("light")}
29
- >
30
- <Sun className="size-4 text-amber-500" />
31
- <p className="text-xs capitalize text-neutral-200 truncate">Light</p>
32
- </div>
33
- <div
34
- className={classNames(
35
- "flex flex-col items-center justify-center p-3 size-16 min-w-32 gap-2 rounded-lg border border-neutral-800 bg-neutral-800/30 hover:brightness-120 cursor-pointer",
36
- {
37
- "!border-neutral-700 !bg-neutral-800/80 hover:!brightness-100":
38
- value === "dark",
39
- }
40
- )}
41
- onClick={() => onChange("dark")}
42
- >
43
- <Moon className="size-4 text-indigo-500" />
44
- <p className="text-xs capitalize text-neutral-200 truncate">Dark</p>
45
- </div>
46
- </div>
47
- );
48
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/re-imagine.tsx DELETED
@@ -1,152 +0,0 @@
1
- import { useState } from "react";
2
- import { Paintbrush } from "lucide-react";
3
- import { toast } from "sonner";
4
-
5
- import { Button } from "@/components/ui/button";
6
- import {
7
- Popover,
8
- PopoverContent,
9
- PopoverTrigger,
10
- } from "@/components/ui/popover";
11
- import { Input } from "@/components/ui/input";
12
- import Loading from "@/components/loading";
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);
24
- const [isLoading, setIsLoading] = useState(false);
25
- const { globalAiLoading } = useAi();
26
- const { globalEditorLoading } = useEditor();
27
-
28
- const checkIfUrlIsValid = (url: string) => {
29
- const urlPattern = new RegExp(
30
- /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/,
31
- "i"
32
- );
33
- return urlPattern.test(url);
34
- };
35
-
36
- const handleClick = async () => {
37
- if (isLoading) return; // Prevent multiple clicks while loading
38
- if (!url) {
39
- toast.error("Please enter a URL.");
40
- return;
41
- }
42
- if (!checkIfUrlIsValid(url)) {
43
- toast.error("Please enter a valid URL.");
44
- return;
45
- }
46
- setIsLoading(true);
47
- const response = await api.put("/re-design", {
48
- url: url.trim(),
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.");
57
- }
58
- setIsLoading(false);
59
- };
60
-
61
- return (
62
- <Popover open={open} onOpenChange={setOpen}>
63
- <form>
64
- <PopoverTrigger asChild>
65
- <Button
66
- size="xs"
67
- variant={open ? "default" : "outline"}
68
- className="!rounded-md"
69
- disabled={globalAiLoading || globalEditorLoading}
70
- >
71
- <Paintbrush className="size-3.5" />
72
- Redesign
73
- </Button>
74
- </PopoverTrigger>
75
- <PopoverContent
76
- align="start"
77
- className="!rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden"
78
- >
79
- <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
80
- <div className="flex items-center justify-center -space-x-4 mb-3">
81
- <div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
82
- 🎨
83
- </div>
84
- <div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
85
- 🥳
86
- </div>
87
- <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
88
- 💎
89
- </div>
90
- </div>
91
- <p className="text-xl font-semibold text-neutral-950">
92
- Redesign your Site!
93
- </p>
94
- <p className="text-sm text-neutral-500 mt-1.5">
95
- Try our new Redesign feature to give your site a fresh look.
96
- </p>
97
- </header>
98
- <main className="space-y-4 p-6">
99
- <div>
100
- <p className="text-sm text-neutral-700 mb-2">
101
- Enter your website URL to get started:
102
- </p>
103
- <Input
104
- type="text"
105
- placeholder="https://example.com"
106
- value={url}
107
- onChange={(e) => setUrl(e.target.value)}
108
- onBlur={(e) => {
109
- const inputUrl = e.target.value.trim();
110
- if (!inputUrl) {
111
- setUrl("");
112
- return;
113
- }
114
- if (!checkIfUrlIsValid(inputUrl)) {
115
- toast.error("Please enter a valid URL.");
116
- return;
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>
123
- <div>
124
- <p className="text-sm text-neutral-700 mb-2">
125
- Then, let&apos;s redesign it!
126
- </p>
127
- <Button
128
- variant="black"
129
- onClick={handleClick}
130
- className="relative w-full"
131
- >
132
- {isLoading ? (
133
- <>
134
- <Loading
135
- overlay={false}
136
- className="ml-2 size-4 animate-spin"
137
- />
138
- Fetching your site...
139
- </>
140
- ) : (
141
- <>
142
- Redesign <Paintbrush className="size-4" />
143
- </>
144
- )}
145
- </Button>
146
- </div>
147
- </main>
148
- </PopoverContent>
149
- </form>
150
- </Popover>
151
- );
152
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/selected-files.tsx DELETED
@@ -1,47 +0,0 @@
1
- import Image from "next/image";
2
-
3
- import { Button } from "@/components/ui/button";
4
- import { Minus } from "lucide-react";
5
-
6
- export const SelectedFiles = ({
7
- files,
8
- isAiWorking,
9
- onDelete,
10
- }: {
11
- files: string[];
12
- isAiWorking: boolean;
13
- onDelete: (file: string) => void;
14
- }) => {
15
- if (files.length === 0) return null;
16
- return (
17
- <div className="px-4 pt-3">
18
- <div className="flex items-center justify-start gap-2">
19
- {files.map((file) => (
20
- <div
21
- key={file}
22
- className="flex items-center relative justify-start gap-2 p-1 bg-neutral-700 rounded-md"
23
- >
24
- <Image
25
- src={file}
26
- alt="uploaded image"
27
- className="size-12 rounded-md object-cover"
28
- width={40}
29
- height={40}
30
- />
31
- <Button
32
- size="iconXsss"
33
- variant="secondary"
34
- className={`absolute top-0.5 right-0.5 ${
35
- isAiWorking ? "opacity-50 !cursor-not-allowed" : ""
36
- }`}
37
- disabled={isAiWorking}
38
- onClick={() => onDelete(file)}
39
- >
40
- <Minus className="size-4" />
41
- </Button>
42
- </div>
43
- ))}
44
- </div>
45
- </div>
46
- );
47
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/selected-html-element.tsx DELETED
@@ -1,57 +0,0 @@
1
- import classNames from "classnames";
2
- import { Code, XCircle } from "lucide-react";
3
-
4
- import { Collapsible, CollapsibleTrigger } from "@/components/ui/collapsible";
5
- import { htmlTagToText } from "@/lib/html-tag-to-text";
6
-
7
- export const SelectedHtmlElement = ({
8
- element,
9
- isAiWorking = false,
10
- onDelete,
11
- }: {
12
- element: HTMLElement | null;
13
- isAiWorking: boolean;
14
- onDelete?: () => void;
15
- }) => {
16
- if (!element) return null;
17
-
18
- const tagName = element.tagName.toLowerCase();
19
- return (
20
- <Collapsible
21
- className={classNames(
22
- "border border-neutral-700 rounded-xl p-1.5 pr-3 max-w-max hover:brightness-110 transition-all duration-200 ease-in-out !cursor-pointer",
23
- {
24
- "!cursor-pointer": !isAiWorking,
25
- "opacity-50 !cursor-not-allowed": isAiWorking,
26
- }
27
- )}
28
- disabled={isAiWorking}
29
- onClick={() => {
30
- if (!isAiWorking && onDelete) {
31
- onDelete();
32
- }
33
- }}
34
- >
35
- <CollapsibleTrigger className="flex items-center justify-start gap-2 cursor-pointer">
36
- <div className="rounded-lg bg-neutral-700 size-6 flex items-center justify-center">
37
- <Code className="text-neutral-300 size-3.5" />
38
- </div>
39
- <p className="text-sm font-semibold text-neutral-300">
40
- {element.textContent?.trim().split(/\s+/)[0]} {htmlTagToText(tagName)}
41
- </p>
42
- <XCircle className="text-neutral-300 size-4" />
43
- </CollapsibleTrigger>
44
- {/* <CollapsibleContent className="border-t border-neutral-700 pt-2 mt-2">
45
- <div className="text-xs text-neutral-400">
46
- <p>
47
- <span className="font-semibold">ID:</span> {element.id || "No ID"}
48
- </p>
49
- <p>
50
- <span className="font-semibold">Classes:</span>{" "}
51
- {element.className || "No classes"}
52
- </p>
53
- </div>
54
- </CollapsibleContent> */}
55
- </Collapsible>
56
- );
57
- };