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 +3 -10
  5. app/(public)/layout.tsx +0 -15
  6. app/(public)/page.tsx +0 -44
  7. app/(public)/projects/page.tsx +0 -33
  8. app/actions/auth.ts +0 -15
  9. app/api/ask-ai/route.ts +0 -411
  10. app/api/auth/route.ts +0 -82
  11. app/api/me/projects/[namespace]/[repoId]/route.ts +0 -235
  12. app/api/me/projects/route.ts +0 -126
  13. app/api/me/route.ts +0 -25
  14. app/api/re-design/route.ts +0 -39
  15. app/auth/callback/page.tsx +0 -72
  16. app/auth/page.tsx +0 -28
  17. app/favicon.ico +0 -0
  18. app/layout.tsx +0 -102
  19. app/projects/[namespace]/[repoId]/page.tsx +0 -40
  20. app/projects/new/page.tsx +0 -5
  21. assets/globals.css +0 -146
  22. assets/logo.svg +0 -316
  23. components.json +0 -21
  24. components/contexts/app-context.tsx +0 -57
  25. components/contexts/user-context.tsx +0 -8
  26. components/editor/ask-ai/follow-up-tooltip.tsx +0 -36
  27. components/editor/ask-ai/index.tsx +0 -474
  28. components/editor/ask-ai/re-imagine.tsx +0 -146
  29. components/editor/ask-ai/selected-html-element.tsx +0 -57
  30. components/editor/ask-ai/settings.tsx +0 -212
  31. components/editor/deploy-button/index.tsx +0 -171
  32. components/editor/footer/index.tsx +0 -118
  33. components/editor/header/index.tsx +0 -69
  34. components/editor/history/index.tsx +0 -72
  35. components/editor/preview/index.tsx +0 -172
  36. components/editor/save-button/index.tsx +0 -76
  37. components/invite-friends/index.tsx +0 -85
  38. components/login-modal/index.tsx +0 -61
  39. components/magic-ui/grid-pattern.tsx +0 -69
  40. components/my-projects/index.tsx +0 -57
  41. components/my-projects/load-project.tsx +0 -196
  42. components/my-projects/project-card.tsx +0 -74
  43. components/pro-modal/index.tsx +0 -92
  44. components/providers/tanstack-query-provider.tsx +0 -18
  45. components/public/navigation/index.tsx +0 -156
  46. components/space/ask-ai/index.tsx +0 -43
  47. components/ui/avatar.tsx +0 -53
  48. components/ui/button.tsx +0 -67
  49. components/ui/checkbox.tsx +0 -32
  50. components/ui/collapsible.tsx +0 -33
.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,22 +1,15 @@
1
  ---
2
- title: DeepSite v2
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 with DeepSeek
11
  models:
12
  - deepseek-ai/DeepSeek-V3-0324
13
- - deepseek-ai/DeepSeek-R1-0528
14
  ---
15
 
16
- # DeepSite 🐳
17
-
18
- DeepSite is a coding platform powered by DeepSeek AI, 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.
19
-
20
- ## How to use it locally
21
-
22
- 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
  models:
12
  - deepseek-ai/DeepSeek-V3-0324
 
13
  ---
14
 
15
+ 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="min-h-screen bg-black z-1 relative">
10
- <div className="background__noisy" />
11
- <Navigation />
12
- {children}
13
- </div>
14
- );
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/(public)/page.tsx DELETED
@@ -1,44 +0,0 @@
1
- import { AskAi } from "@/components/space/ask-ai";
2
- import { redirect } from "next/navigation";
3
- export default function Home() {
4
- redirect("/projects/new");
5
- return (
6
- <>
7
- <header className="container mx-auto pt-20 px-6 relative flex flex-col items-center justify-center text-center">
8
- <div className="rounded-full border border-neutral-100/10 bg-neutral-100/5 text-xs text-neutral-300 px-3 py-1 max-w-max mx-auto mb-2">
9
- ✨ DeepSite Public Beta
10
- </div>
11
- <h1 className="text-8xl font-semibold text-white font-mono max-w-4xl">
12
- Code your website with AI in seconds
13
- </h1>
14
- <p className="text-2xl text-neutral-300/80 mt-4 text-center max-w-2xl">
15
- Vibe Coding has never been so easy.
16
- </p>
17
- <div className="mt-14 max-w-2xl w-full mx-auto">
18
- <AskAi />
19
- </div>
20
- <div className="absolute inset-0 pointer-events-none -z-[1]">
21
- <div className="w-full h-full bg-gradient-to-r from-purple-500 to-pink-500 opacity-10 blur-3xl rounded-full" />
22
- <div 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 transform rotate-12" />
23
- <div 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 rounded-3xl" />
24
- <div 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 rounded-lg transform -rotate-15" />
25
- </div>
26
- </header>
27
- <div id="community" className="h-screen flex items-center justify-center">
28
- <h1 className="text-7xl font-extrabold text-white font-mono">
29
- Community Driven
30
- </h1>
31
- </div>
32
- <div id="deploy" className="h-screen flex items-center justify-center">
33
- <h1 className="text-7xl font-extrabold text-white font-mono">
34
- Deploy your website in seconds
35
- </h1>
36
- </div>
37
- <div id="features" className="h-screen flex items-center justify-center">
38
- <h1 className="text-7xl font-extrabold text-white font-mono">
39
- Features that make you smile
40
- </h1>
41
- </div>
42
- </>
43
- );
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/(public)/projects/page.tsx DELETED
@@ -1,33 +0,0 @@
1
- import { cookies } from "next/headers";
2
- import { redirect } from "next/navigation";
3
-
4
- import { apiServer } from "@/lib/api";
5
- import MY_TOKEN_KEY from "@/lib/get-cookie-name";
6
- import { MyProjects } from "@/components/my-projects";
7
-
8
- async function getMyProjects() {
9
- const cookieStore = await cookies();
10
- const token = cookieStore.get(MY_TOKEN_KEY())?.value;
11
- if (!token) return { redirectUrl: true, projects: [] };
12
- try {
13
- const { data } = await apiServer.get("/me/projects", {
14
- headers: {
15
- Authorization: `Bearer ${token}`,
16
- },
17
- });
18
-
19
- return {
20
- projects: data.projects,
21
- };
22
- } catch {
23
- return { projects: [] };
24
- }
25
- }
26
- export default async function ProjectsPage() {
27
- const { redirectUrl, projects } = await getMyProjects();
28
- if (redirectUrl) {
29
- redirect("/");
30
- }
31
-
32
- return <MyProjects projects={projects} />;
33
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/actions/auth.ts DELETED
@@ -1,15 +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 redirect_uri =
9
- `${host.includes("localhost") ? "http://" : "https://"}` +
10
- host +
11
- "/auth/callback";
12
-
13
- 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`;
14
- return loginRedirectUrl;
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/ask-ai/route.ts DELETED
@@ -1,411 +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, PROVIDERS } from "@/lib/providers";
8
- import {
9
- DIVIDER,
10
- FOLLOW_UP_SYSTEM_PROMPT,
11
- INITIAL_SYSTEM_PROMPT,
12
- MAX_REQUESTS_PER_IP,
13
- REPLACE_END,
14
- SEARCH_START,
15
- } from "@/lib/prompts";
16
- import MY_TOKEN_KEY from "@/lib/get-cookie-name";
17
-
18
- const ipAddresses = new Map();
19
-
20
- export async function POST(request: NextRequest) {
21
- const authHeaders = await headers();
22
- const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
23
-
24
- const body = await request.json();
25
- const { prompt, provider, model, redesignMarkdown, html } = body;
26
-
27
- if (!model || (!prompt && !redesignMarkdown)) {
28
- return NextResponse.json(
29
- { ok: false, error: "Missing required fields" },
30
- { status: 400 }
31
- );
32
- }
33
-
34
- const selectedModel = MODELS.find(
35
- (m) => m.value === model || m.label === model
36
- );
37
- if (!selectedModel) {
38
- return NextResponse.json(
39
- { ok: false, error: "Invalid model selected" },
40
- { status: 400 }
41
- );
42
- }
43
-
44
- if (!selectedModel.providers.includes(provider) && provider !== "auto") {
45
- return NextResponse.json(
46
- {
47
- ok: false,
48
- error: `The selected model does not support the ${provider} provider.`,
49
- openSelectProvider: true,
50
- },
51
- { status: 400 }
52
- );
53
- }
54
-
55
- let token = userToken;
56
- let billTo: string | null = null;
57
-
58
- /**
59
- * Handle local usage token, this bypass the need for a user token
60
- * and allows local testing without authentication.
61
- * This is useful for development and testing purposes.
62
- */
63
- if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
64
- token = process.env.HF_TOKEN;
65
- }
66
-
67
- const ip = authHeaders.get("x-forwarded-for")?.includes(",")
68
- ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
69
- : authHeaders.get("x-forwarded-for");
70
-
71
- if (!token) {
72
- ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
73
- if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
74
- return NextResponse.json(
75
- {
76
- ok: false,
77
- openLogin: true,
78
- message: "Log In to continue using the service",
79
- },
80
- { status: 429 }
81
- );
82
- }
83
-
84
- token = process.env.DEFAULT_HF_TOKEN as string;
85
- billTo = "huggingface";
86
- }
87
-
88
- const DEFAULT_PROVIDER = PROVIDERS.novita;
89
- const selectedProvider =
90
- provider === "auto"
91
- ? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS]
92
- : PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
93
-
94
- try {
95
- // Create a stream response
96
- const encoder = new TextEncoder();
97
- const stream = new TransformStream();
98
- const writer = stream.writable.getWriter();
99
-
100
- // Start the response
101
- const response = new NextResponse(stream.readable, {
102
- headers: {
103
- "Content-Type": "text/plain; charset=utf-8",
104
- "Cache-Control": "no-cache",
105
- Connection: "keep-alive",
106
- },
107
- });
108
-
109
- (async () => {
110
- let completeResponse = "";
111
- try {
112
- const client = new InferenceClient(token);
113
- const chatCompletion = client.chatCompletionStream(
114
- {
115
- model: selectedModel.value,
116
- provider: selectedProvider.id as any,
117
- messages: [
118
- {
119
- role: "system",
120
- content: INITIAL_SYSTEM_PROMPT,
121
- },
122
- {
123
- role: "user",
124
- content: redesignMarkdown
125
- ? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.`
126
- : html
127
- ? `Here is my current HTML code:\n\n\`\`\`html\n${html}\n\`\`\`\n\nNow, please create a new design based on this HTML.`
128
- : prompt,
129
- },
130
- ],
131
- max_tokens: selectedProvider.max_tokens,
132
- },
133
- billTo ? { billTo } : {}
134
- );
135
-
136
- while (true) {
137
- const { done, value } = await chatCompletion.next();
138
- if (done) {
139
- break;
140
- }
141
-
142
- const chunk = value.choices[0]?.delta?.content;
143
- if (chunk) {
144
- let newChunk = chunk;
145
- if (!selectedModel?.isThinker) {
146
- if (provider !== "sambanova") {
147
- await writer.write(encoder.encode(chunk));
148
- completeResponse += chunk;
149
-
150
- if (completeResponse.includes("</html>")) {
151
- break;
152
- }
153
- } else {
154
- if (chunk.includes("</html>")) {
155
- newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
156
- }
157
- completeResponse += newChunk;
158
- await writer.write(encoder.encode(newChunk));
159
- if (newChunk.includes("</html>")) {
160
- break;
161
- }
162
- }
163
- } else {
164
- const lastThinkTagIndex =
165
- completeResponse.lastIndexOf("</think>");
166
- completeResponse += newChunk;
167
- await writer.write(encoder.encode(newChunk));
168
- if (lastThinkTagIndex !== -1) {
169
- const afterLastThinkTag = completeResponse.slice(
170
- lastThinkTagIndex + "</think>".length
171
- );
172
- if (afterLastThinkTag.includes("</html>")) {
173
- break;
174
- }
175
- }
176
- }
177
- }
178
- }
179
- } catch (error: any) {
180
- if (error.message?.includes("exceeded your monthly included credits")) {
181
- await writer.write(
182
- encoder.encode(
183
- JSON.stringify({
184
- ok: false,
185
- openProModal: true,
186
- message: error.message,
187
- })
188
- )
189
- );
190
- } else {
191
- await writer.write(
192
- encoder.encode(
193
- JSON.stringify({
194
- ok: false,
195
- openSelectProvider: true,
196
- message:
197
- error.message ||
198
- "An error occurred while processing your request.",
199
- })
200
- )
201
- );
202
- }
203
- } finally {
204
- await writer?.close();
205
- }
206
- })();
207
-
208
- return response;
209
- } catch (error: any) {
210
- return NextResponse.json(
211
- {
212
- ok: false,
213
- openSelectProvider: true,
214
- message:
215
- error?.message || "An error occurred while processing your request.",
216
- },
217
- { status: 500 }
218
- );
219
- }
220
- }
221
-
222
- export async function PUT(request: NextRequest) {
223
- const authHeaders = await headers();
224
- const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
225
-
226
- const body = await request.json();
227
- const { prompt, html, previousPrompt, provider, selectedElementHtml } = body;
228
-
229
- if (!prompt || !html) {
230
- return NextResponse.json(
231
- { ok: false, error: "Missing required fields" },
232
- { status: 400 }
233
- );
234
- }
235
-
236
- const selectedModel = MODELS[0];
237
-
238
- let token = userToken;
239
- let billTo: string | null = null;
240
-
241
- /**
242
- * Handle local usage token, this bypass the need for a user token
243
- * and allows local testing without authentication.
244
- * This is useful for development and testing purposes.
245
- */
246
- if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
247
- token = process.env.HF_TOKEN;
248
- }
249
-
250
- const ip = authHeaders.get("x-forwarded-for")?.includes(",")
251
- ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
252
- : authHeaders.get("x-forwarded-for");
253
-
254
- if (!token) {
255
- ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
256
- if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
257
- return NextResponse.json(
258
- {
259
- ok: false,
260
- openLogin: true,
261
- message: "Log In to continue using the service",
262
- },
263
- { status: 429 }
264
- );
265
- }
266
-
267
- token = process.env.DEFAULT_HF_TOKEN as string;
268
- billTo = "huggingface";
269
- }
270
-
271
- const client = new InferenceClient(token);
272
-
273
- const DEFAULT_PROVIDER = PROVIDERS.novita;
274
- const selectedProvider =
275
- provider === "auto"
276
- ? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS]
277
- : PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
278
-
279
- try {
280
- const response = await client.chatCompletion(
281
- {
282
- model: selectedModel.value,
283
- provider: selectedProvider.id as any,
284
- messages: [
285
- {
286
- role: "system",
287
- content: FOLLOW_UP_SYSTEM_PROMPT,
288
- },
289
- {
290
- role: "user",
291
- content: previousPrompt
292
- ? previousPrompt
293
- : "You are modifying the HTML file based on the user's request.",
294
- },
295
- {
296
- role: "assistant",
297
-
298
- content: `The current code is: \n\`\`\`html\n${html}\n\`\`\` ${
299
- selectedElementHtml
300
- ? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\``
301
- : ""
302
- }`,
303
- },
304
- {
305
- role: "user",
306
- content: prompt,
307
- },
308
- ],
309
- ...(selectedProvider.id !== "sambanova"
310
- ? {
311
- max_tokens: selectedProvider.max_tokens,
312
- }
313
- : {}),
314
- },
315
- billTo ? { billTo } : {}
316
- );
317
-
318
- const chunk = response.choices[0]?.message?.content;
319
- if (!chunk) {
320
- return NextResponse.json(
321
- { ok: false, message: "No content returned from the model" },
322
- { status: 400 }
323
- );
324
- }
325
-
326
- if (chunk) {
327
- const updatedLines: number[][] = [];
328
- let newHtml = html;
329
- let position = 0;
330
- let moreBlocks = true;
331
-
332
- while (moreBlocks) {
333
- const searchStartIndex = chunk.indexOf(SEARCH_START, position);
334
- if (searchStartIndex === -1) {
335
- moreBlocks = false;
336
- continue;
337
- }
338
-
339
- const dividerIndex = chunk.indexOf(DIVIDER, searchStartIndex);
340
- if (dividerIndex === -1) {
341
- moreBlocks = false;
342
- continue;
343
- }
344
-
345
- const replaceEndIndex = chunk.indexOf(REPLACE_END, dividerIndex);
346
- if (replaceEndIndex === -1) {
347
- moreBlocks = false;
348
- continue;
349
- }
350
-
351
- const searchBlock = chunk.substring(
352
- searchStartIndex + SEARCH_START.length,
353
- dividerIndex
354
- );
355
- const replaceBlock = chunk.substring(
356
- dividerIndex + DIVIDER.length,
357
- replaceEndIndex
358
- );
359
-
360
- if (searchBlock.trim() === "") {
361
- newHtml = `${replaceBlock}\n${newHtml}`;
362
- updatedLines.push([1, replaceBlock.split("\n").length]);
363
- } else {
364
- const blockPosition = newHtml.indexOf(searchBlock);
365
- if (blockPosition !== -1) {
366
- const beforeText = newHtml.substring(0, blockPosition);
367
- const startLineNumber = beforeText.split("\n").length;
368
- const replaceLines = replaceBlock.split("\n").length;
369
- const endLineNumber = startLineNumber + replaceLines - 1;
370
-
371
- updatedLines.push([startLineNumber, endLineNumber]);
372
- newHtml = newHtml.replace(searchBlock, replaceBlock);
373
- }
374
- }
375
-
376
- position = replaceEndIndex + REPLACE_END.length;
377
- }
378
-
379
- return NextResponse.json({
380
- ok: true,
381
- html: newHtml,
382
- updatedLines,
383
- });
384
- } else {
385
- return NextResponse.json(
386
- { ok: false, message: "No content returned from the model" },
387
- { status: 400 }
388
- );
389
- }
390
- } catch (error: any) {
391
- if (error.message?.includes("exceeded your monthly included credits")) {
392
- return NextResponse.json(
393
- {
394
- ok: false,
395
- openProModal: true,
396
- message: error.message,
397
- },
398
- { status: 402 }
399
- );
400
- }
401
- return NextResponse.json(
402
- {
403
- ok: false,
404
- openSelectProvider: true,
405
- message:
406
- error.message || "An error occurred while processing your request.",
407
- },
408
- { status: 500 }
409
- );
410
- }
411
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/auth/route.ts DELETED
@@ -1,82 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
-
3
- export async function POST(req: NextRequest) {
4
- const body = await req.json();
5
- const { code } = body;
6
-
7
- if (!code) {
8
- return NextResponse.json(
9
- { error: "Code is required" },
10
- {
11
- status: 400,
12
- headers: {
13
- "Content-Type": "application/json",
14
- },
15
- }
16
- );
17
- }
18
-
19
- const Authorization = `Basic ${Buffer.from(
20
- `${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
21
- ).toString("base64")}`;
22
-
23
- const host =
24
- req.headers.get("host") ?? req.headers.get("origin") ?? "localhost:3000";
25
- const redirect_uri =
26
- `${host.includes("localhost") ? "http://" : "https://"}` +
27
- host +
28
- "/auth/callback";
29
- const request_auth = await fetch("https://huggingface.co/oauth/token", {
30
- method: "POST",
31
- headers: {
32
- "Content-Type": "application/x-www-form-urlencoded",
33
- Authorization,
34
- },
35
- body: new URLSearchParams({
36
- grant_type: "authorization_code",
37
- code,
38
- redirect_uri,
39
- }),
40
- });
41
-
42
- const response = await request_auth.json();
43
- if (!response.access_token) {
44
- return NextResponse.json(
45
- { error: "Failed to retrieve access token" },
46
- {
47
- status: 400,
48
- headers: {
49
- "Content-Type": "application/json",
50
- },
51
- }
52
- );
53
- }
54
-
55
- const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
56
- headers: {
57
- Authorization: `Bearer ${response.access_token}`,
58
- },
59
- });
60
-
61
- if (!userResponse.ok) {
62
- return NextResponse.json(
63
- { user: null, errCode: userResponse.status },
64
- { status: userResponse.status }
65
- );
66
- }
67
- const user = await userResponse.json();
68
-
69
- return NextResponse.json(
70
- {
71
- access_token: response.access_token,
72
- expires_in: response.expires_in,
73
- user,
74
- },
75
- {
76
- status: 200,
77
- headers: {
78
- "Content-Type": "application/json",
79
- },
80
- }
81
- );
82
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/route.ts DELETED
@@ -1,235 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, spaceInfo, uploadFile } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import Project from "@/models/Project";
6
- import dbConnect from "@/lib/mongodb";
7
- import { getPTag } from "@/lib/utils";
8
-
9
- export async function GET(
10
- req: NextRequest,
11
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
12
- ) {
13
- const user = await isAuthenticated();
14
-
15
- if (user instanceof NextResponse || !user) {
16
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- await dbConnect();
20
- const param = await params;
21
- const { namespace, repoId } = param;
22
-
23
- const project = await Project.findOne({
24
- user_id: user.id,
25
- space_id: `${namespace}/${repoId}`,
26
- }).lean();
27
- if (!project) {
28
- return NextResponse.json(
29
- {
30
- ok: false,
31
- error: "Project not found",
32
- },
33
- { status: 404 }
34
- );
35
- }
36
- const space_url = `https://huggingface.co/spaces/${namespace}/${repoId}/raw/main/index.html`;
37
- try {
38
- const space = await spaceInfo({
39
- name: namespace + "/" + repoId,
40
- accessToken: user.token as string,
41
- additionalFields: ["author"],
42
- });
43
-
44
- if (!space || space.sdk !== "static") {
45
- return NextResponse.json(
46
- {
47
- ok: false,
48
- error: "Space is not a static space",
49
- },
50
- { status: 404 }
51
- );
52
- }
53
- if (space.author !== user.name) {
54
- return NextResponse.json(
55
- {
56
- ok: false,
57
- error: "Space does not belong to the authenticated user",
58
- },
59
- { status: 403 }
60
- );
61
- }
62
-
63
- const response = await fetch(space_url);
64
- if (!response.ok) {
65
- return NextResponse.json(
66
- {
67
- ok: false,
68
- error: "Failed to fetch space HTML",
69
- },
70
- { status: 404 }
71
- );
72
- }
73
- let html = await response.text();
74
- // remove the last p tag including this url https://enzostvs-deepsite.hf.space
75
- html = html.replace(getPTag(namespace + "/" + repoId), "");
76
-
77
- return NextResponse.json(
78
- {
79
- project: {
80
- ...project,
81
- html,
82
- },
83
- ok: true,
84
- },
85
- { status: 200 }
86
- );
87
-
88
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
- } catch (error: any) {
90
- if (error.statusCode === 404) {
91
- await Project.deleteOne({
92
- user_id: user.id,
93
- space_id: `${namespace}/${repoId}`,
94
- });
95
- return NextResponse.json(
96
- { error: "Space not found", ok: false },
97
- { status: 404 }
98
- );
99
- }
100
- return NextResponse.json(
101
- { error: error.message, ok: false },
102
- { status: 500 }
103
- );
104
- }
105
- }
106
-
107
- export async function PUT(
108
- req: NextRequest,
109
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
110
- ) {
111
- const user = await isAuthenticated();
112
-
113
- if (user instanceof NextResponse || !user) {
114
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
115
- }
116
-
117
- await dbConnect();
118
- const param = await params;
119
- const { namespace, repoId } = param;
120
- const { html, prompts } = await req.json();
121
-
122
- const project = await Project.findOne({
123
- user_id: user.id,
124
- space_id: `${namespace}/${repoId}`,
125
- }).lean();
126
- if (!project) {
127
- return NextResponse.json(
128
- {
129
- ok: false,
130
- error: "Project not found",
131
- },
132
- { status: 404 }
133
- );
134
- }
135
-
136
- const repo: RepoDesignation = {
137
- type: "space",
138
- name: `${namespace}/${repoId}`,
139
- };
140
-
141
- const newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);
142
- const file = new File([newHtml], "index.html", { type: "text/html" });
143
- await uploadFile({
144
- repo,
145
- file,
146
- accessToken: user.token as string,
147
- commitTitle: `${prompts[prompts.length - 1]} - Follow Up Deployment`,
148
- });
149
-
150
- await Project.updateOne(
151
- { user_id: user.id, space_id: `${namespace}/${repoId}` },
152
- {
153
- $set: {
154
- prompts: [
155
- ...(project && "prompts" in project ? project.prompts : []),
156
- ...prompts,
157
- ],
158
- },
159
- }
160
- );
161
- return NextResponse.json({ ok: true }, { status: 200 });
162
- }
163
-
164
- export async function POST(
165
- req: NextRequest,
166
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
167
- ) {
168
- const user = await isAuthenticated();
169
-
170
- if (user instanceof NextResponse || !user) {
171
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
172
- }
173
-
174
- await dbConnect();
175
- const param = await params;
176
- const { namespace, repoId } = param;
177
-
178
- const space = await spaceInfo({
179
- name: namespace + "/" + repoId,
180
- accessToken: user.token as string,
181
- additionalFields: ["author"],
182
- });
183
-
184
- if (!space || space.sdk !== "static") {
185
- return NextResponse.json(
186
- {
187
- ok: false,
188
- error: "Space is not a static space",
189
- },
190
- { status: 404 }
191
- );
192
- }
193
- if (space.author !== user.name) {
194
- return NextResponse.json(
195
- {
196
- ok: false,
197
- error: "Space does not belong to the authenticated user",
198
- },
199
- { status: 403 }
200
- );
201
- }
202
-
203
- const project = await Project.findOne({
204
- user_id: user.id,
205
- space_id: `${namespace}/${repoId}`,
206
- }).lean();
207
- if (project) {
208
- return NextResponse.json(
209
- {
210
- ok: false,
211
- error: "Project already exists",
212
- },
213
- { status: 400 }
214
- );
215
- }
216
-
217
- const newProject = new Project({
218
- user_id: user.id,
219
- space_id: `${namespace}/${repoId}`,
220
- prompts: [],
221
- });
222
-
223
- await newProject.save();
224
- return NextResponse.json(
225
- {
226
- ok: true,
227
- project: {
228
- id: newProject._id,
229
- space_id: newProject.space_id,
230
- prompts: newProject.prompts,
231
- },
232
- },
233
- { status: 201 }
234
- );
235
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/route.ts DELETED
@@ -1,126 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import Project from "@/models/Project";
6
- import dbConnect from "@/lib/mongodb";
7
- import { COLORS, getPTag } from "@/lib/utils";
8
- // import type user
9
- export async function GET() {
10
- const user = await isAuthenticated();
11
-
12
- if (user instanceof NextResponse || !user) {
13
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
14
- }
15
-
16
- await dbConnect();
17
-
18
- const projects = await Project.find({
19
- user_id: user?.id,
20
- })
21
- .sort({ _createdAt: -1 })
22
- .limit(100)
23
- .lean();
24
- if (!projects) {
25
- return NextResponse.json(
26
- {
27
- ok: false,
28
- projects: [],
29
- },
30
- { status: 404 }
31
- );
32
- }
33
- return NextResponse.json(
34
- {
35
- ok: true,
36
- projects,
37
- },
38
- { status: 200 }
39
- );
40
- }
41
-
42
- /**
43
- * This API route creates a new project in Hugging Face Spaces.
44
- * It requires an Authorization header with a valid token and a JSON body with the project details.
45
- */
46
- export async function POST(request: NextRequest) {
47
- const user = await isAuthenticated();
48
-
49
- if (user instanceof NextResponse || !user) {
50
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
51
- }
52
-
53
- const { title, html, prompts } = await request.json();
54
-
55
- if (!title || !html) {
56
- return NextResponse.json(
57
- { message: "Title and HTML content are required.", ok: false },
58
- { status: 400 }
59
- );
60
- }
61
-
62
- await dbConnect();
63
-
64
- try {
65
- let readme = "";
66
- let newHtml = html;
67
-
68
- const newTitle = title
69
- .toLowerCase()
70
- .replace(/[^a-z0-9]+/g, "-")
71
- .split("-")
72
- .filter(Boolean)
73
- .join("-")
74
- .slice(0, 96);
75
-
76
- const repo: RepoDesignation = {
77
- type: "space",
78
- name: `${user.name}/${newTitle}`,
79
- };
80
-
81
- const { repoUrl } = await createRepo({
82
- repo,
83
- accessToken: user.token as string,
84
- });
85
- const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
86
- const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
87
- readme = `---
88
- title: ${newTitle}
89
- emoji: 🐳
90
- colorFrom: ${colorFrom}
91
- colorTo: ${colorTo}
92
- sdk: static
93
- pinned: false
94
- tags:
95
- - deepsite
96
- ---
97
-
98
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`;
99
-
100
- newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);
101
- const file = new File([newHtml], "index.html", { type: "text/html" });
102
- const readmeFile = new File([readme], "README.md", {
103
- type: "text/markdown",
104
- });
105
- const files = [file, readmeFile];
106
- await uploadFiles({
107
- repo,
108
- files,
109
- accessToken: user.token as string,
110
- commitTitle: `${prompts[prompts.length - 1]} - Initial Deployment`,
111
- });
112
- const path = repoUrl.split("/").slice(-2).join("/");
113
- const project = await Project.create({
114
- user_id: user.id,
115
- space_id: path,
116
- prompts,
117
- });
118
- return NextResponse.json({ project, path, ok: true }, { status: 201 });
119
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
- } catch (err: any) {
121
- return NextResponse.json(
122
- { error: err.message, ok: false },
123
- { status: 500 }
124
- );
125
- }
126
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/route.ts DELETED
@@ -1,25 +0,0 @@
1
- import { headers } from "next/headers";
2
- import { NextResponse } from "next/server";
3
-
4
- export async function GET() {
5
- const authHeaders = await headers();
6
- const token = authHeaders.get("Authorization");
7
- if (!token) {
8
- return NextResponse.json({ user: null, errCode: 401 }, { status: 401 });
9
- }
10
-
11
- const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
12
- headers: {
13
- Authorization: `${token}`,
14
- },
15
- });
16
-
17
- if (!userResponse.ok) {
18
- return NextResponse.json(
19
- { user: null, errCode: userResponse.status },
20
- { status: userResponse.status }
21
- );
22
- }
23
- const user = await userResponse.json();
24
- return NextResponse.json({ user, errCode: null }, { status: 200 });
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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,72 +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
- export default function AuthCallback({
9
- searchParams,
10
- }: {
11
- searchParams: Promise<{ code: string }>;
12
- }) {
13
- const [showButton, setShowButton] = useState(false);
14
- const { code } = use(searchParams);
15
- const { loginFromCode } = useUser();
16
-
17
- useMount(async () => {
18
- if (code) {
19
- await loginFromCode(code);
20
- }
21
- });
22
-
23
- useTimeoutFn(
24
- () => setShowButton(true),
25
- 7000 // Show button after 5 seconds
26
- );
27
-
28
- return (
29
- <div className="h-screen flex flex-col justify-center items-center">
30
- <div className="!rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden ring-[8px] ring-white/20">
31
- <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
32
- <div className="flex items-center justify-center -space-x-4 mb-3">
33
- <div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
34
- 🚀
35
- </div>
36
- <div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
37
- 👋
38
- </div>
39
- <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
40
- 🙌
41
- </div>
42
- </div>
43
- <p className="text-xl font-semibold text-neutral-950">
44
- Login In Progress...
45
- </p>
46
- <p className="text-sm text-neutral-500 mt-1.5">
47
- Wait a moment while we log you in with your code.
48
- </p>
49
- </header>
50
- <main className="space-y-4 p-6">
51
- <div>
52
- <p className="text-sm text-neutral-700 mb-4 max-w-xs">
53
- If you are not redirected automatically in the next 5 seconds,
54
- please click the button below
55
- </p>
56
- {showButton ? (
57
- <Link href="/">
58
- <Button variant="black" className="relative">
59
- Go to Home
60
- </Button>
61
- </Link>
62
- ) : (
63
- <p className="text-xs text-neutral-500">
64
- Please wait, we are logging you in...
65
- </p>
66
- )}
67
- </div>
68
- </main>
69
- </div>
70
- </div>
71
- );
72
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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,102 +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
-
6
- import TanstackProvider from "@/components/providers/tanstack-query-provider";
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 AppContext from "@/components/contexts/app-context";
12
-
13
- const inter = Inter({
14
- variable: "--font-inter-sans",
15
- subsets: ["latin"],
16
- });
17
-
18
- const ptSans = PT_Sans({
19
- variable: "--font-ptSans-mono",
20
- subsets: ["latin"],
21
- weight: ["400", "700"],
22
- });
23
-
24
- export const metadata: Metadata = {
25
- title: "DeepSite | Build with AI ✨",
26
- description:
27
- "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.",
28
- openGraph: {
29
- title: "DeepSite | Build with AI ✨",
30
- description:
31
- "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.",
32
- url: "https://deepsite.hf.co",
33
- siteName: "DeepSite",
34
- images: [
35
- {
36
- url: "https://deepsite.hf.co/banner.png",
37
- width: 1200,
38
- height: 630,
39
- alt: "DeepSite Open Graph Image",
40
- },
41
- ],
42
- },
43
- twitter: {
44
- card: "summary_large_image",
45
- title: "DeepSite | Build with AI ✨",
46
- description:
47
- "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.",
48
- images: ["https://deepsite.hf.co/banner.png"],
49
- },
50
- appleWebApp: {
51
- capable: true,
52
- title: "DeepSite",
53
- statusBarStyle: "black-translucent",
54
- },
55
- icons: {
56
- icon: "/logo.svg",
57
- shortcut: "/logo.svg",
58
- apple: "/logo.svg",
59
- },
60
- };
61
-
62
- export const viewport: Viewport = {
63
- initialScale: 1,
64
- maximumScale: 1,
65
- themeColor: "#000000",
66
- };
67
-
68
- async function getMe() {
69
- const cookieStore = await cookies();
70
- const token = cookieStore.get(MY_TOKEN_KEY())?.value;
71
- if (!token) return { user: null, errCode: null };
72
- try {
73
- const res = await apiServer.get("/me", {
74
- headers: {
75
- Authorization: `Bearer ${token}`,
76
- },
77
- });
78
- return { user: res.data.user, errCode: null };
79
- } catch (err: any) {
80
- return { user: null, errCode: err.status };
81
- }
82
- }
83
-
84
- export default async function RootLayout({
85
- children,
86
- }: Readonly<{
87
- children: React.ReactNode;
88
- }>) {
89
- const data = await getMe();
90
- return (
91
- <html lang="en">
92
- <body
93
- className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
94
- >
95
- <Toaster richColors position="bottom-center" />
96
- <TanstackProvider>
97
- <AppContext me={data}>{children}</AppContext>
98
- </TanstackProvider>
99
- </body>
100
- </html>
101
- );
102
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/projects/[namespace]/[repoId]/page.tsx DELETED
@@ -1,40 +0,0 @@
1
- import { cookies } from "next/headers";
2
- import { redirect } from "next/navigation";
3
-
4
- import { apiServer } from "@/lib/api";
5
- import MY_TOKEN_KEY from "@/lib/get-cookie-name";
6
- import { AppEditor } from "@/components/editor";
7
-
8
- async function getProject(namespace: string, repoId: string) {
9
- // TODO replace with a server action
10
- const cookieStore = await cookies();
11
- const token = cookieStore.get(MY_TOKEN_KEY())?.value;
12
- if (!token) return {};
13
- try {
14
- const { data } = await apiServer.get(
15
- `/me/projects/${namespace}/${repoId}`,
16
- {
17
- headers: {
18
- Authorization: `Bearer ${token}`,
19
- },
20
- }
21
- );
22
-
23
- return data.project;
24
- } catch {
25
- return {};
26
- }
27
- }
28
-
29
- export default async function ProjectNamespacePage({
30
- params,
31
- }: {
32
- params: Promise<{ namespace: string; repoId: string }>;
33
- }) {
34
- const { namespace, repoId } = await params;
35
- const project = await getProject(namespace, repoId);
36
- if (!project?.html) {
37
- redirect("/projects");
38
- }
39
- return <AppEditor project={project} />;
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/projects/new/page.tsx DELETED
@@ -1,5 +0,0 @@
1
- import { AppEditor } from "@/components/editor";
2
-
3
- export default function ProjectsNewPage() {
4
- return <AppEditor />;
5
- }
 
 
 
 
 
 
assets/globals.css DELETED
@@ -1,146 +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
- @layer base {
116
- * {
117
- @apply border-border outline-ring/50;
118
- }
119
- body {
120
- @apply bg-background text-foreground;
121
- }
122
- html {
123
- @apply scroll-smooth;
124
- }
125
- }
126
-
127
- .background__noisy {
128
- @apply bg-blend-normal pointer-events-none opacity-90;
129
- background-size: 25ww auto;
130
- background-image: url("/background_noisy.webp");
131
- @apply fixed w-screen h-screen -z-1 top-0 left-0;
132
- }
133
-
134
- .monaco-editor .margin {
135
- @apply !bg-neutral-900;
136
- }
137
- .monaco-editor .monaco-editor-background {
138
- @apply !bg-neutral-900;
139
- }
140
- .monaco-editor .line-numbers {
141
- @apply !text-neutral-500;
142
- }
143
-
144
- .matched-line {
145
- @apply bg-sky-500/30;
146
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/logo.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": "app/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/contexts/app-context.tsx DELETED
@@ -1,57 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- "use client";
3
-
4
- import { useUser } from "@/hooks/useUser";
5
- import { usePathname, useRouter } from "next/navigation";
6
- import { useMount } from "react-use";
7
- import { UserContext } from "@/components/contexts/user-context";
8
- import { User } from "@/types";
9
- import { toast } from "sonner";
10
- import { useBroadcastChannel } from "@/lib/useBroadcastChannel";
11
-
12
- export default function AppContext({
13
- children,
14
- me: initialData,
15
- }: {
16
- children: React.ReactNode;
17
- me?: {
18
- user: User | null;
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 (
53
- <UserContext value={{ user, loading, logout } as any}>
54
- {children}
55
- </UserContext>
56
- );
57
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/follow-up-tooltip.tsx DELETED
@@ -1,36 +0,0 @@
1
- import {
2
- Popover,
3
- PopoverContent,
4
- PopoverTrigger,
5
- } from "@/components/ui/popover";
6
- import { Info } from "lucide-react";
7
-
8
- export const FollowUpTooltip = () => {
9
- return (
10
- <Popover>
11
- <PopoverTrigger asChild>
12
- <Info className="size-3 text-neutral-300 cursor-pointer" />
13
- </PopoverTrigger>
14
- <PopoverContent
15
- align="start"
16
- className="!rounded-2xl !p-0 min-w-xs text-center overflow-hidden"
17
- >
18
- <header className="bg-neutral-950 px-4 py-3 border-b border-neutral-700/70">
19
- <p className="text-base text-neutral-200 font-semibold">
20
- ⚡ Faster, Smarter Updates
21
- </p>
22
- </header>
23
- <main className="p-4">
24
- <p className="text-neutral-300 text-sm">
25
- Using the Diff-Patch system, allow DeepSite to intelligently update
26
- your project without rewritting the entire codebase.
27
- </p>
28
- <p className="text-neutral-500 text-sm mt-2">
29
- This means faster updates, less data usage, and a more efficient
30
- development process.
31
- </p>
32
- </main>
33
- </PopoverContent>
34
- </Popover>
35
- );
36
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/index.tsx DELETED
@@ -1,474 +0,0 @@
1
- "use client";
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
- import { useState, useRef, useMemo } from "react";
4
- import classNames from "classnames";
5
- import { toast } from "sonner";
6
- import { useLocalStorage, useUpdateEffect } from "react-use";
7
- import { ArrowUp, ChevronDown, Crosshair } from "lucide-react";
8
- import { FaStopCircle } from "react-icons/fa";
9
-
10
- import ProModal from "@/components/pro-modal";
11
- import { Button } from "@/components/ui/button";
12
- import { MODELS } from "@/lib/providers";
13
- import { HtmlHistory } from "@/types";
14
- import { InviteFriends } from "@/components/invite-friends";
15
- import { Settings } from "@/components/editor/ask-ai/settings";
16
- import { LoginModal } from "@/components/login-modal";
17
- import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
18
- import Loading from "@/components/loading";
19
- import { Checkbox } from "@/components/ui/checkbox";
20
- import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
21
- import { TooltipContent } from "@radix-ui/react-tooltip";
22
- import { SelectedHtmlElement } from "./selected-html-element";
23
- import { FollowUpTooltip } from "./follow-up-tooltip";
24
- import { isTheSameHtml } from "@/lib/compare-html-diff";
25
-
26
- export function AskAI({
27
- html,
28
- setHtml,
29
- onScrollToBottom,
30
- isAiWorking,
31
- setisAiWorking,
32
- isEditableModeEnabled = false,
33
- selectedElement,
34
- setSelectedElement,
35
- setIsEditableModeEnabled,
36
- onNewPrompt,
37
- onSuccess,
38
- }: {
39
- html: string;
40
- setHtml: (html: string) => void;
41
- onScrollToBottom: () => void;
42
- isAiWorking: boolean;
43
- onNewPrompt: (prompt: string) => void;
44
- htmlHistory?: HtmlHistory[];
45
- setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
46
- onSuccess: (h: string, p: string, n?: number[][]) => void;
47
- isEditableModeEnabled: boolean;
48
- setIsEditableModeEnabled: React.Dispatch<React.SetStateAction<boolean>>;
49
- selectedElement?: HTMLElement | null;
50
- setSelectedElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
51
- }) {
52
- const refThink = useRef<HTMLDivElement | null>(null);
53
- const audio = useRef<HTMLAudioElement | null>(null);
54
-
55
- const [open, setOpen] = useState(false);
56
- const [prompt, setPrompt] = useState("");
57
- const [hasAsked, setHasAsked] = useState(false);
58
- const [previousPrompt, setPreviousPrompt] = useState("");
59
- const [provider, setProvider] = useLocalStorage("provider", "auto");
60
- const [model, setModel] = useLocalStorage("model", MODELS[0].value);
61
- const [openProvider, setOpenProvider] = useState(false);
62
- const [providerError, setProviderError] = useState("");
63
- const [openProModal, setOpenProModal] = useState(false);
64
- const [think, setThink] = useState<string | undefined>(undefined);
65
- const [openThink, setOpenThink] = useState(false);
66
- const [isThinking, setIsThinking] = useState(true);
67
- const [controller, setController] = useState<AbortController | null>(null);
68
- const [isFollowUp, setIsFollowUp] = useState(true);
69
-
70
- const callAi = async (redesignMarkdown?: string) => {
71
- if (isAiWorking) return;
72
- if (!redesignMarkdown && !prompt.trim()) return;
73
- setisAiWorking(true);
74
- setProviderError("");
75
- setThink("");
76
- setOpenThink(false);
77
- setIsThinking(true);
78
-
79
- let contentResponse = "";
80
- let thinkResponse = "";
81
- let lastRenderTime = 0;
82
-
83
- const abortController = new AbortController();
84
- setController(abortController);
85
- try {
86
- onNewPrompt(prompt);
87
- if (isFollowUp && !redesignMarkdown && !isSameHtml) {
88
- const selectedElementHtml = selectedElement
89
- ? selectedElement.outerHTML
90
- : "";
91
- const request = await fetch("/api/ask-ai", {
92
- method: "PUT",
93
- body: JSON.stringify({
94
- prompt,
95
- provider,
96
- previousPrompt,
97
- model,
98
- html,
99
- selectedElementHtml,
100
- }),
101
- headers: {
102
- "Content-Type": "application/json",
103
- "x-forwarded-for": window.location.hostname,
104
- },
105
- signal: abortController.signal,
106
- });
107
- if (request && request.body) {
108
- const res = await request.json();
109
- if (!request.ok) {
110
- if (res.openLogin) {
111
- setOpen(true);
112
- } else if (res.openSelectProvider) {
113
- setOpenProvider(true);
114
- setProviderError(res.message);
115
- } else if (res.openProModal) {
116
- setOpenProModal(true);
117
- } else {
118
- toast.error(res.message);
119
- }
120
- setisAiWorking(false);
121
- return;
122
- }
123
- setHtml(res.html);
124
- toast.success("AI responded successfully");
125
- setPreviousPrompt(prompt);
126
- setPrompt("");
127
- setisAiWorking(false);
128
- onSuccess(res.html, prompt, res.updatedLines);
129
- if (audio.current) audio.current.play();
130
- }
131
- } else {
132
- const request = await fetch("/api/ask-ai", {
133
- method: "POST",
134
- body: JSON.stringify({
135
- prompt,
136
- provider,
137
- model,
138
- html: isSameHtml ? "" : html,
139
- redesignMarkdown,
140
- }),
141
- headers: {
142
- "Content-Type": "application/json",
143
- "x-forwarded-for": window.location.hostname,
144
- },
145
- signal: abortController.signal,
146
- });
147
- if (request && request.body) {
148
- // if (!request.ok) {
149
- // const res = await request.json();
150
- // if (res.openLogin) {
151
- // setOpen(true);
152
- // } else if (res.openSelectProvider) {
153
- // setOpenProvider(true);
154
- // setProviderError(res.message);
155
- // } else if (res.openProModal) {
156
- // setOpenProModal(true);
157
- // } else {
158
- // toast.error(res.message);
159
- // }
160
- // setisAiWorking(false);
161
- // return;
162
- // }
163
- const reader = request.body.getReader();
164
- const decoder = new TextDecoder("utf-8");
165
- const selectedModel = MODELS.find(
166
- (m: { value: string }) => m.value === model
167
- );
168
- let contentThink: string | undefined = undefined;
169
- const read = async () => {
170
- const { done, value } = await reader.read();
171
- if (done) {
172
- toast.success("AI responded successfully");
173
- setPreviousPrompt(prompt);
174
- setPrompt("");
175
- setisAiWorking(false);
176
- setHasAsked(true);
177
- setModel(MODELS[0].value);
178
- if (audio.current) audio.current.play();
179
-
180
- // Now we have the complete HTML including </html>, so set it to be sure
181
- const finalDoc = contentResponse.match(
182
- /<!DOCTYPE html>[\s\S]*<\/html>/
183
- )?.[0];
184
- if (finalDoc) {
185
- setHtml(finalDoc);
186
- }
187
- onSuccess(finalDoc ?? contentResponse, prompt);
188
-
189
- return;
190
- }
191
-
192
- const chunk = decoder.decode(value, { stream: true });
193
- try {
194
- const res = JSON.parse(chunk);
195
- if (res.openLogin) {
196
- setOpen(true);
197
- } else if (res.openSelectProvider) {
198
- setOpenProvider(true);
199
- setProviderError(res.message);
200
- } else if (res.openProModal) {
201
- setOpenProModal(true);
202
- } else {
203
- toast.error(res.message);
204
- }
205
- setisAiWorking(false);
206
- return;
207
- } catch {
208
- thinkResponse += chunk;
209
- if (selectedModel?.isThinker) {
210
- const thinkMatch = thinkResponse.match(/<think>[\s\S]*/)?.[0];
211
- if (thinkMatch && !thinkResponse?.includes("</think>")) {
212
- if ((contentThink?.length ?? 0) < 3) {
213
- setOpenThink(true);
214
- }
215
- setThink(thinkMatch.replace("<think>", "").trim());
216
- contentThink += chunk;
217
- return read();
218
- }
219
- }
220
-
221
- contentResponse += chunk;
222
-
223
- const newHtml = contentResponse.match(
224
- /<!DOCTYPE html>[\s\S]*/
225
- )?.[0];
226
- if (newHtml) {
227
- setIsThinking(false);
228
- let partialDoc = newHtml;
229
- if (
230
- partialDoc.includes("<head>") &&
231
- !partialDoc.includes("</head>")
232
- ) {
233
- partialDoc += "\n</head>";
234
- }
235
- if (
236
- partialDoc.includes("<body") &&
237
- !partialDoc.includes("</body>")
238
- ) {
239
- partialDoc += "\n</body>";
240
- }
241
- if (!partialDoc.includes("</html>")) {
242
- partialDoc += "\n</html>";
243
- }
244
-
245
- // Throttle the re-renders to avoid flashing/flicker
246
- const now = Date.now();
247
- if (now - lastRenderTime > 300) {
248
- setHtml(partialDoc);
249
- lastRenderTime = now;
250
- }
251
-
252
- if (partialDoc.length > 200) {
253
- onScrollToBottom();
254
- }
255
- }
256
- read();
257
- }
258
- };
259
-
260
- read();
261
- }
262
- }
263
- } catch (error: any) {
264
- setisAiWorking(false);
265
- toast.error(error.message);
266
- if (error.openLogin) {
267
- setOpen(true);
268
- }
269
- }
270
- };
271
-
272
- const stopController = () => {
273
- if (controller) {
274
- controller.abort();
275
- setController(null);
276
- setisAiWorking(false);
277
- setThink("");
278
- setOpenThink(false);
279
- setIsThinking(false);
280
- }
281
- };
282
-
283
- useUpdateEffect(() => {
284
- if (refThink.current) {
285
- refThink.current.scrollTop = refThink.current.scrollHeight;
286
- }
287
- }, [think]);
288
-
289
- useUpdateEffect(() => {
290
- if (!isThinking) {
291
- setOpenThink(false);
292
- }
293
- }, [isThinking]);
294
-
295
- const isSameHtml = useMemo(() => {
296
- return isTheSameHtml(html);
297
- }, [html]);
298
-
299
- return (
300
- <div className="px-3">
301
- <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-10 w-full group">
302
- {think && (
303
- <div className="w-full border-b border-neutral-700 relative overflow-hidden">
304
- <header
305
- className="flex items-center justify-between px-5 py-2.5 group hover:bg-neutral-600/20 transition-colors duration-200 cursor-pointer"
306
- onClick={() => {
307
- setOpenThink(!openThink);
308
- }}
309
- >
310
- <p className="text-sm font-medium text-neutral-300 group-hover:text-neutral-200 transition-colors duration-200">
311
- {isThinking ? "DeepSite is thinking..." : "DeepSite's plan"}
312
- </p>
313
- <ChevronDown
314
- className={classNames(
315
- "size-4 text-neutral-400 group-hover:text-neutral-300 transition-all duration-200",
316
- {
317
- "rotate-180": openThink,
318
- }
319
- )}
320
- />
321
- </header>
322
- <main
323
- ref={refThink}
324
- className={classNames(
325
- "overflow-y-auto transition-all duration-200 ease-in-out",
326
- {
327
- "max-h-[0px]": !openThink,
328
- "min-h-[250px] max-h-[250px] border-t border-neutral-700":
329
- openThink,
330
- }
331
- )}
332
- >
333
- <p className="text-[13px] text-neutral-400 whitespace-pre-line px-5 pb-4 pt-3">
334
- {think}
335
- </p>
336
- </main>
337
- </div>
338
- )}
339
- {selectedElement && (
340
- <div className="px-4 pt-3">
341
- <SelectedHtmlElement
342
- element={selectedElement}
343
- isAiWorking={isAiWorking}
344
- onDelete={() => setSelectedElement(null)}
345
- />
346
- </div>
347
- )}
348
- <div className="w-full relative flex items-center justify-between">
349
- {isAiWorking && (
350
- <div className="absolute bg-neutral-800 rounded-lg bottom-0 left-4 w-[calc(100%-30px)] h-full z-1 flex items-center justify-between max-lg:text-sm">
351
- <div className="flex items-center justify-start gap-2">
352
- <Loading overlay={false} className="!size-4" />
353
- <p className="text-neutral-400 text-sm">
354
- AI is {isThinking ? "thinking" : "coding"}...{" "}
355
- </p>
356
- </div>
357
- <div
358
- className="text-xs text-neutral-400 px-1 py-0.5 rounded-md border border-neutral-600 flex items-center justify-center gap-1.5 bg-neutral-800 hover:brightness-110 transition-all duration-200 cursor-pointer"
359
- onClick={stopController}
360
- >
361
- <FaStopCircle />
362
- Stop generation
363
- </div>
364
- </div>
365
- )}
366
- <input
367
- type="text"
368
- disabled={isAiWorking}
369
- className={classNames(
370
- "w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4",
371
- {
372
- "!pt-2.5": selectedElement && !isAiWorking,
373
- }
374
- )}
375
- placeholder={
376
- selectedElement
377
- ? `Ask DeepSite about ${selectedElement.tagName.toLowerCase()}...`
378
- : hasAsked
379
- ? "Ask DeepSite for edits"
380
- : "Ask DeepSite anything..."
381
- }
382
- value={prompt}
383
- onChange={(e) => setPrompt(e.target.value)}
384
- onKeyDown={(e) => {
385
- if (e.key === "Enter" && !e.shiftKey) {
386
- callAi();
387
- }
388
- }}
389
- />
390
- </div>
391
- <div className="flex items-center justify-between gap-2 px-4 pb-3">
392
- <div className="flex-1 flex items-center justify-start gap-1.5">
393
- <ReImagine onRedesign={(md) => callAi(md)} />
394
- {!isSameHtml && (
395
- <Tooltip>
396
- <TooltipTrigger asChild>
397
- <Button
398
- size="xs"
399
- variant={isEditableModeEnabled ? "default" : "outline"}
400
- onClick={() => {
401
- setIsEditableModeEnabled?.(!isEditableModeEnabled);
402
- }}
403
- className={classNames("h-[28px]", {
404
- "!text-neutral-400 hover:!text-neutral-200 !border-neutral-600 !hover:!border-neutral-500":
405
- !isEditableModeEnabled,
406
- })}
407
- >
408
- <Crosshair className="size-4" />
409
- Edit
410
- </Button>
411
- </TooltipTrigger>
412
- <TooltipContent
413
- align="start"
414
- className="bg-neutral-950 text-xs text-neutral-200 py-1 px-2 rounded-md -translate-y-0.5"
415
- >
416
- Select an element on the page to ask DeepSite edit it
417
- directly.
418
- </TooltipContent>
419
- </Tooltip>
420
- )}
421
- <InviteFriends />
422
- </div>
423
- <div className="flex items-center justify-end gap-2">
424
- <Settings
425
- provider={provider as string}
426
- model={model as string}
427
- onChange={setProvider}
428
- onModelChange={setModel}
429
- open={openProvider}
430
- error={providerError}
431
- isFollowUp={!isSameHtml}
432
- onClose={setOpenProvider}
433
- />
434
- <Button
435
- size="iconXs"
436
- disabled={isAiWorking || !prompt.trim()}
437
- onClick={() => callAi()}
438
- >
439
- <ArrowUp className="size-4" />
440
- </Button>
441
- </div>
442
- </div>
443
- <LoginModal open={open} onClose={() => setOpen(false)} html={html} />
444
- <ProModal
445
- html={html}
446
- open={openProModal}
447
- onClose={() => setOpenProModal(false)}
448
- />
449
- {!isSameHtml && (
450
- <div className="absolute top-0 right-0 -translate-y-[calc(100%+8px)] select-none text-xs text-neutral-400 flex items-center justify-center gap-2 bg-neutral-800 border border-neutral-700 rounded-md p-1 pr-2.5">
451
- <label
452
- htmlFor="diff-patch-checkbox"
453
- className="flex items-center gap-1.5 cursor-pointer"
454
- >
455
- <Checkbox
456
- id="diff-patch-checkbox"
457
- checked={isFollowUp}
458
- onCheckedChange={(e) => {
459
- setIsFollowUp(e === true);
460
- }}
461
- />
462
- Diff-Patch Update
463
- </label>
464
- <FollowUpTooltip />
465
- </div>
466
- )}
467
- </div>
468
- <audio ref={audio} id="audio" className="hidden">
469
- <source src="/success.mp3" type="audio/mpeg" />
470
- Your browser does not support the audio element.
471
- </audio>
472
- </div>
473
- );
474
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/re-imagine.tsx DELETED
@@ -1,146 +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
-
15
- export function ReImagine({
16
- onRedesign,
17
- }: {
18
- onRedesign: (md: string) => void;
19
- }) {
20
- const [url, setUrl] = useState<string>("");
21
- const [open, setOpen] = useState(false);
22
- const [isLoading, setIsLoading] = useState(false);
23
-
24
- const checkIfUrlIsValid = (url: string) => {
25
- const urlPattern = new RegExp(
26
- /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/,
27
- "i"
28
- );
29
- return urlPattern.test(url);
30
- };
31
-
32
- const handleClick = async () => {
33
- if (isLoading) return; // Prevent multiple clicks while loading
34
- if (!url) {
35
- toast.error("Please enter a URL.");
36
- return;
37
- }
38
- if (!checkIfUrlIsValid(url)) {
39
- toast.error("Please enter a valid URL.");
40
- return;
41
- }
42
- setIsLoading(true);
43
- const response = await api.put("/re-design", {
44
- url: url.trim(),
45
- });
46
- if (response?.data?.ok) {
47
- setOpen(false);
48
- setUrl("");
49
- onRedesign(response.data.markdown);
50
- toast.success("DeepSite is redesigning your site! Let him cook... 🔥");
51
- } else {
52
- toast.error(response?.data?.error || "Failed to redesign the site.");
53
- }
54
- setIsLoading(false);
55
- };
56
-
57
- return (
58
- <Popover open={open} onOpenChange={setOpen}>
59
- <form>
60
- <PopoverTrigger asChild>
61
- <Button
62
- size="iconXs"
63
- variant="outline"
64
- className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
65
- >
66
- <Paintbrush className="size-4" />
67
- </Button>
68
- </PopoverTrigger>
69
- <PopoverContent
70
- align="start"
71
- className="!rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden"
72
- >
73
- <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
74
- <div className="flex items-center justify-center -space-x-4 mb-3">
75
- <div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
76
- 🎨
77
- </div>
78
- <div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
79
- 🥳
80
- </div>
81
- <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
82
- 💎
83
- </div>
84
- </div>
85
- <p className="text-xl font-semibold text-neutral-950">
86
- Redesign your Site!
87
- </p>
88
- <p className="text-sm text-neutral-500 mt-1.5">
89
- Try our new Redesign feature to give your site a fresh look.
90
- </p>
91
- </header>
92
- <main className="space-y-4 p-6">
93
- <div>
94
- <p className="text-sm text-neutral-700 mb-2">
95
- Enter your website URL to get started:
96
- </p>
97
- <Input
98
- type="text"
99
- placeholder="https://example.com"
100
- value={url}
101
- onChange={(e) => setUrl(e.target.value)}
102
- onBlur={(e) => {
103
- const inputUrl = e.target.value.trim();
104
- if (!inputUrl) {
105
- setUrl("");
106
- return;
107
- }
108
- if (!checkIfUrlIsValid(inputUrl)) {
109
- toast.error("Please enter a valid URL.");
110
- return;
111
- }
112
- setUrl(inputUrl);
113
- }}
114
- className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
115
- />
116
- </div>
117
- <div>
118
- <p className="text-sm text-neutral-700 mb-2">
119
- Then, let&apos;s redesign it!
120
- </p>
121
- <Button
122
- variant="black"
123
- onClick={handleClick}
124
- className="relative w-full"
125
- >
126
- {isLoading ? (
127
- <>
128
- <Loading
129
- overlay={false}
130
- className="ml-2 size-4 animate-spin"
131
- />
132
- Fetching your site...
133
- </>
134
- ) : (
135
- <>
136
- Redesign <Paintbrush className="size-4" />
137
- </>
138
- )}
139
- </Button>
140
- </div>
141
- </main>
142
- </PopoverContent>
143
- </form>
144
- </Popover>
145
- );
146
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/ask-ai/settings.tsx DELETED
@@ -1,212 +0,0 @@
1
- import classNames from "classnames";
2
- import { PiGearSixFill } from "react-icons/pi";
3
- import { RiCheckboxCircleFill } from "react-icons/ri";
4
-
5
- import {
6
- Popover,
7
- PopoverContent,
8
- PopoverTrigger,
9
- } from "@/components/ui/popover";
10
- import { PROVIDERS, MODELS } from "@/lib/providers";
11
- import { Button } from "@/components/ui/button";
12
- import {
13
- Select,
14
- SelectContent,
15
- SelectGroup,
16
- SelectItem,
17
- SelectLabel,
18
- SelectTrigger,
19
- SelectValue,
20
- } from "@/components/ui/select";
21
- import { useMemo } from "react";
22
- import { useUpdateEffect } from "react-use";
23
- import Image from "next/image";
24
-
25
- export function Settings({
26
- open,
27
- onClose,
28
- provider,
29
- model,
30
- error,
31
- isFollowUp = false,
32
- onChange,
33
- onModelChange,
34
- }: {
35
- open: boolean;
36
- provider: string;
37
- model: string;
38
- error?: string;
39
- isFollowUp?: boolean;
40
- onClose: React.Dispatch<React.SetStateAction<boolean>>;
41
- onChange: (provider: string) => void;
42
- onModelChange: (model: string) => void;
43
- }) {
44
- const modelAvailableProviders = useMemo(() => {
45
- const availableProviders = MODELS.find(
46
- (m: { value: string }) => m.value === model
47
- )?.providers;
48
- if (!availableProviders) return Object.keys(PROVIDERS);
49
- return Object.keys(PROVIDERS).filter((id) =>
50
- availableProviders.includes(id)
51
- );
52
- }, [model]);
53
-
54
- useUpdateEffect(() => {
55
- if (provider !== "auto" && !modelAvailableProviders.includes(provider)) {
56
- onChange("auto");
57
- }
58
- }, [model, provider]);
59
-
60
- return (
61
- <div className="">
62
- <Popover open={open} onOpenChange={onClose}>
63
- <PopoverTrigger asChild>
64
- <Button variant="black" size="sm">
65
- <PiGearSixFill className="size-4" />
66
- Settings
67
- </Button>
68
- </PopoverTrigger>
69
- <PopoverContent
70
- className="!rounded-2xl p-0 !w-96 overflow-hidden !bg-neutral-900"
71
- align="center"
72
- >
73
- <header className="flex items-center justify-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
74
- Customize Settings
75
- </header>
76
- <main className="px-4 pt-5 pb-6 space-y-5">
77
- {/* <a
78
- href="https://huggingface.co/spaces/enzostvs/deepsite/discussions/74"
79
- target="_blank"
80
- className="w-full flex items-center justify-between text-neutral-300 bg-neutral-300/15 border border-neutral-300/15 pl-4 p-1.5 rounded-full text-sm font-medium hover:brightness-95"
81
- >
82
- How to use it locally?
83
- <Button size="xs">See guide</Button>
84
- </a> */}
85
- {error !== "" && (
86
- <p className="text-red-500 text-sm font-medium mb-2 flex items-center justify-between bg-red-500/10 p-2 rounded-md">
87
- {error}
88
- </p>
89
- )}
90
- <label className="block">
91
- <p className="text-neutral-300 text-sm mb-2.5">
92
- Choose a DeepSeek model
93
- </p>
94
- <Select defaultValue={model} onValueChange={onModelChange}>
95
- <SelectTrigger className="w-full">
96
- <SelectValue placeholder="Select a DeepSeek model" />
97
- </SelectTrigger>
98
- <SelectContent>
99
- <SelectGroup>
100
- <SelectLabel>DeepSeek models</SelectLabel>
101
- {MODELS.map(
102
- ({
103
- value,
104
- label,
105
- isNew = false,
106
- isThinker = false,
107
- }: {
108
- value: string;
109
- label: string;
110
- isNew?: boolean;
111
- isThinker?: boolean;
112
- }) => (
113
- <SelectItem
114
- key={value}
115
- value={value}
116
- className=""
117
- disabled={isThinker && isFollowUp}
118
- >
119
- {label}
120
- {isNew && (
121
- <span className="text-xs bg-gradient-to-br from-sky-400 to-sky-600 text-white rounded-full px-1.5 py-0.5">
122
- New
123
- </span>
124
- )}
125
- </SelectItem>
126
- )
127
- )}
128
- </SelectGroup>
129
- </SelectContent>
130
- </Select>
131
- </label>
132
- {isFollowUp && (
133
- <div className="bg-amber-500/10 border-amber-500/10 p-3 text-xs text-amber-500 border rounded-lg">
134
- Note: You can&apos;t use a Thinker model for follow-up requests.
135
- We automatically switch to the default model for you.
136
- </div>
137
- )}
138
- <div className="flex flex-col gap-3">
139
- <div className="flex items-center justify-between">
140
- <div>
141
- <p className="text-neutral-300 text-sm mb-1.5">
142
- Use auto-provider
143
- </p>
144
- <p className="text-xs text-neutral-400/70">
145
- We&apos;ll automatically select the best provider for you
146
- based on your prompt.
147
- </p>
148
- </div>
149
- <div
150
- className={classNames(
151
- "bg-neutral-700 rounded-full min-w-10 w-10 h-6 flex items-center justify-between p-1 cursor-pointer transition-all duration-200",
152
- {
153
- "!bg-sky-500": provider === "auto",
154
- }
155
- )}
156
- onClick={() => {
157
- const foundModel = MODELS.find(
158
- (m: { value: string }) => m.value === model
159
- );
160
- if (provider === "auto" && foundModel?.autoProvider) {
161
- onChange(foundModel.autoProvider);
162
- } else {
163
- onChange("auto");
164
- }
165
- }}
166
- >
167
- <div
168
- className={classNames(
169
- "w-4 h-4 rounded-full shadow-md transition-all duration-200 bg-neutral-200",
170
- {
171
- "translate-x-4": provider === "auto",
172
- }
173
- )}
174
- />
175
- </div>
176
- </div>
177
- <label className="block">
178
- <p className="text-neutral-300 text-sm mb-2">
179
- Inference Provider
180
- </p>
181
- <div className="grid grid-cols-2 gap-1.5">
182
- {modelAvailableProviders.map((id: string) => (
183
- <Button
184
- key={id}
185
- variant={id === provider ? "default" : "secondary"}
186
- size="sm"
187
- onClick={() => {
188
- onChange(id);
189
- }}
190
- >
191
- <Image
192
- src={`/providers/${id}.svg`}
193
- alt={PROVIDERS[id as keyof typeof PROVIDERS].name}
194
- className="size-5 mr-2"
195
- width={20}
196
- height={20}
197
- />
198
- {PROVIDERS[id as keyof typeof PROVIDERS].name}
199
- {id === provider && (
200
- <RiCheckboxCircleFill className="ml-2 size-4 text-blue-500" />
201
- )}
202
- </Button>
203
- ))}
204
- </div>
205
- </label>
206
- </div>
207
- </main>
208
- </PopoverContent>
209
- </Popover>
210
- </div>
211
- );
212
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/deploy-button/index.tsx DELETED
@@ -1,171 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { useState } from "react";
3
- import { toast } from "sonner";
4
- import Image from "next/image";
5
- import { useRouter } from "next/navigation";
6
- import { MdSave } from "react-icons/md";
7
- import { Rocket } from "lucide-react";
8
-
9
- import SpaceIcon from "@/assets/space.svg";
10
- import Loading from "@/components/loading";
11
- import { Button } from "@/components/ui/button";
12
- import {
13
- Popover,
14
- PopoverContent,
15
- PopoverTrigger,
16
- } from "@/components/ui/popover";
17
- import { Input } from "@/components/ui/input";
18
- import { api } from "@/lib/api";
19
- import { LoginModal } from "@/components/login-modal";
20
- import { useUser } from "@/hooks/useUser";
21
-
22
- export function DeployButton({
23
- html,
24
- prompts,
25
- }: {
26
- html: string;
27
- prompts: string[];
28
- }) {
29
- const router = useRouter();
30
- const { user } = useUser();
31
- const [loading, setLoading] = useState(false);
32
- const [open, setOpen] = useState(false);
33
-
34
- const [config, setConfig] = useState({
35
- title: "",
36
- });
37
-
38
- const createSpace = async () => {
39
- if (!config.title) {
40
- toast.error("Please enter a title for your space.");
41
- return;
42
- }
43
- setLoading(true);
44
-
45
- try {
46
- const res = await api.post("/me/projects", {
47
- title: config.title,
48
- html,
49
- prompts,
50
- });
51
- if (res.data.ok) {
52
- router.push(`/projects/${res.data.path}?deploy=true`);
53
- } else {
54
- toast.error(res?.data?.error || "Failed to create space");
55
- }
56
- } catch (err: any) {
57
- toast.error(err.response?.data?.error || err.message);
58
- } finally {
59
- setLoading(false);
60
- }
61
- };
62
-
63
- return (
64
- <div className="flex items-center justify-end gap-5">
65
- <div className="relative flex items-center justify-end">
66
- {user?.id ? (
67
- <Popover>
68
- <PopoverTrigger asChild>
69
- <div>
70
- <Button variant="default" className="max-lg:hidden !px-4">
71
- <MdSave className="size-4" />
72
- Save your Project
73
- </Button>
74
- <Button variant="default" size="sm" className="lg:hidden">
75
- Deploy
76
- </Button>
77
- </div>
78
- </PopoverTrigger>
79
- <PopoverContent
80
- className="!rounded-2xl !p-0 !bg-white !border-neutral-200 min-w-xs text-center overflow-hidden"
81
- align="end"
82
- >
83
- <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
84
- <div className="flex items-center justify-center -space-x-4 mb-3">
85
- <div className="size-9 rounded-full bg-amber-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
86
- 🚀
87
- </div>
88
- <div className="size-11 rounded-full bg-red-200 shadow-2xl flex items-center justify-center z-2">
89
- <Image
90
- src={SpaceIcon}
91
- alt="Space Icon"
92
- className="size-7"
93
- />
94
- </div>
95
- <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
96
- 👻
97
- </div>
98
- </div>
99
- <p className="text-xl font-semibold text-neutral-950">
100
- Deploy as Space!
101
- </p>
102
- <p className="text-sm text-neutral-500 mt-1.5">
103
- Save and Deploy your project to a Space on the Hub. Spaces are
104
- a way to share your project with the world.
105
- </p>
106
- </header>
107
- <main className="space-y-4 p-6">
108
- <div>
109
- <p className="text-sm text-neutral-700 mb-2">
110
- Choose a title for your space:
111
- </p>
112
- <Input
113
- type="text"
114
- placeholder="My Awesome Website"
115
- value={config.title}
116
- onChange={(e) =>
117
- setConfig({ ...config, title: e.target.value })
118
- }
119
- className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
120
- />
121
- </div>
122
- <div>
123
- <p className="text-sm text-neutral-700 mb-2">
124
- Then, let&apos;s deploy it!
125
- </p>
126
- <Button
127
- variant="black"
128
- onClick={createSpace}
129
- className="relative w-full"
130
- disabled={loading}
131
- >
132
- Deploy Space <Rocket className="size-4" />
133
- {loading && (
134
- <Loading className="ml-2 size-4 animate-spin" />
135
- )}
136
- </Button>
137
- </div>
138
- </main>
139
- </PopoverContent>
140
- </Popover>
141
- ) : (
142
- <>
143
- <Button
144
- variant="default"
145
- className="max-lg:hidden !px-4"
146
- onClick={() => setOpen(true)}
147
- >
148
- <MdSave className="size-4" />
149
- Save your Project
150
- </Button>
151
- <Button
152
- variant="default"
153
- size="sm"
154
- className="lg:hidden"
155
- onClick={() => setOpen(true)}
156
- >
157
- Save
158
- </Button>
159
- </>
160
- )}
161
- <LoginModal
162
- open={open}
163
- onClose={() => setOpen(false)}
164
- html={html}
165
- title="Log In to save your Project"
166
- description="Log In through your Hugging Face account to save your project and increase your monthly free limit."
167
- />
168
- </div>
169
- </div>
170
- );
171
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/footer/index.tsx DELETED
@@ -1,118 +0,0 @@
1
- import classNames from "classnames";
2
- import { FaMobileAlt } from "react-icons/fa";
3
- import { RefreshCcw, SparkleIcon } from "lucide-react";
4
- import { FaLaptopCode } from "react-icons/fa6";
5
- import { HtmlHistory } from "@/types";
6
- import { Button } from "@/components/ui/button";
7
- import { MdAdd } from "react-icons/md";
8
- import { History } from "@/components/editor/history";
9
- import { UserMenu } from "@/components/user-menu";
10
- import { useUser } from "@/hooks/useUser";
11
-
12
- const DEVICES = [
13
- {
14
- name: "desktop",
15
- icon: FaLaptopCode,
16
- },
17
- {
18
- name: "mobile",
19
- icon: FaMobileAlt,
20
- },
21
- ];
22
-
23
- export function Footer({
24
- onReset,
25
- htmlHistory,
26
- setHtml,
27
- device,
28
- setDevice,
29
- iframeRef,
30
- }: {
31
- onReset: () => void;
32
- htmlHistory?: HtmlHistory[];
33
- device: "desktop" | "mobile";
34
- setHtml: (html: string) => void;
35
- iframeRef?: React.RefObject<HTMLIFrameElement | null>;
36
- setDevice: React.Dispatch<React.SetStateAction<"desktop" | "mobile">>;
37
- }) {
38
- const { user } = useUser();
39
-
40
- const handleRefreshIframe = () => {
41
- if (iframeRef?.current) {
42
- const iframe = iframeRef.current;
43
- const content = iframe.srcdoc;
44
- iframe.srcdoc = "";
45
- setTimeout(() => {
46
- iframe.srcdoc = content;
47
- }, 10);
48
- }
49
- };
50
-
51
- return (
52
- <footer className="border-t bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 py-2 flex items-center justify-between sticky bottom-0 z-20">
53
- <div className="flex items-center gap-2">
54
- {user &&
55
- (user?.isLocalUse ? (
56
- <>
57
- <div className="max-w-max bg-amber-500/10 rounded-full px-3 py-1 text-amber-500 border border-amber-500/20 text-sm font-semibold">
58
- Local Usage
59
- </div>
60
- </>
61
- ) : (
62
- <UserMenu className="!p-1 !pr-3 !h-auto" />
63
- ))}
64
- {user && <p className="text-neutral-700">|</p>}
65
- <Button size="sm" variant="secondary" onClick={onReset}>
66
- <MdAdd className="text-sm" />
67
- New <span className="max-lg:hidden">Project</span>
68
- </Button>
69
- {htmlHistory && htmlHistory.length > 0 && (
70
- <>
71
- <p className="text-neutral-700">|</p>
72
- <History history={htmlHistory} setHtml={setHtml} />
73
- </>
74
- )}
75
- </div>
76
- <div className="flex justify-end items-center gap-2.5">
77
- <a
78
- href="https://huggingface.co/spaces/victor/deepsite-gallery"
79
- target="_blank"
80
- >
81
- <Button size="sm" variant="ghost">
82
- <SparkleIcon className="size-3.5" />
83
- <span className="max-lg:hidden">DeepSite Gallery</span>
84
- </Button>
85
- </a>
86
- <Button size="sm" variant="outline" onClick={handleRefreshIframe}>
87
- <RefreshCcw className="size-3.5" />
88
- <span className="max-lg:hidden">Refresh Preview</span>
89
- </Button>
90
- <div className="flex items-center rounded-full p-0.5 bg-neutral-700/70 relative overflow-hidden z-0 max-lg:hidden gap-0.5">
91
- <div
92
- className={classNames(
93
- "absolute left-0.5 top-0.5 rounded-full bg-white size-7 -z-[1] transition-all duration-200",
94
- {
95
- "translate-x-[calc(100%+2px)]": device === "mobile",
96
- }
97
- )}
98
- />
99
- {DEVICES.map((deviceItem) => (
100
- <button
101
- key={deviceItem.name}
102
- className={classNames(
103
- "rounded-full text-neutral-300 size-7 flex items-center justify-center cursor-pointer",
104
- {
105
- "!text-black": device === deviceItem.name,
106
- "hover:bg-neutral-800": device !== deviceItem.name,
107
- }
108
- )}
109
- onClick={() => setDevice(deviceItem.name as "desktop" | "mobile")}
110
- >
111
- <deviceItem.icon className="text-sm" />
112
- </button>
113
- ))}
114
- </div>
115
- </div>
116
- </footer>
117
- );
118
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/header/index.tsx DELETED
@@ -1,69 +0,0 @@
1
- import { ReactNode } from "react";
2
- import { Eye, MessageCircleCode } from "lucide-react";
3
-
4
- import Logo from "@/assets/logo.svg";
5
-
6
- import { Button } from "@/components/ui/button";
7
- import classNames from "classnames";
8
- import Image from "next/image";
9
-
10
- const TABS = [
11
- {
12
- value: "chat",
13
- label: "Chat",
14
- icon: MessageCircleCode,
15
- },
16
- {
17
- value: "preview",
18
- label: "Preview",
19
- icon: Eye,
20
- },
21
- ];
22
-
23
- export function Header({
24
- tab,
25
- onNewTab,
26
- children,
27
- }: {
28
- tab: string;
29
- onNewTab: (tab: string) => void;
30
- children?: ReactNode;
31
- }) {
32
- return (
33
- <header className="border-b bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 lg:px-6 py-2 flex items-center max-lg:gap-3 justify-between lg:grid lg:grid-cols-3 z-20">
34
- <div className="flex items-center justify-start gap-3">
35
- <h1 className="text-neutral-900 dark:text-white text-lg lg:text-xl font-bold flex items-center justify-start">
36
- <Image
37
- src={Logo}
38
- alt="DeepSite Logo"
39
- className="size-6 lg:size-8 mr-2 invert-100 dark:invert-0"
40
- />
41
- <p className="max-md:hidden flex items-center justify-start">
42
- DeepSite
43
- <span className="font-mono bg-gradient-to-br from-sky-500 to-emerald-500 text-neutral-950 rounded-full text-xs ml-2 px-1.5 py-0.5">
44
- {" "}
45
- v2
46
- </span>
47
- </p>
48
- </h1>
49
- </div>
50
- <div className="flex items-center justify-start lg:justify-center gap-1 max-lg:pl-3 flex-1 max-lg:border-l max-lg:border-l-neutral-800">
51
- {TABS.map((item) => (
52
- <Button
53
- key={item.value}
54
- variant={tab === item.value ? "secondary" : "ghost"}
55
- className={classNames("", {
56
- "opacity-60": tab !== item.value,
57
- })}
58
- size="sm"
59
- onClick={() => onNewTab(item.value)}
60
- >
61
- <item.icon className="size-4" />
62
- <span className="hidden md:inline">{item.label}</span>
63
- </Button>
64
- ))}
65
- </div>
66
- <div className="flex items-center justify-end gap-3">{children}</div>
67
- </header>
68
- );
69
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/history/index.tsx DELETED
@@ -1,72 +0,0 @@
1
- import { History as HistoryIcon } from "lucide-react";
2
- import { HtmlHistory } from "@/types";
3
- import {
4
- Popover,
5
- PopoverContent,
6
- PopoverTrigger,
7
- } from "@/components/ui/popover";
8
- import { Button } from "@/components/ui/button";
9
-
10
- export function History({
11
- history,
12
- setHtml,
13
- }: {
14
- history: HtmlHistory[];
15
- setHtml: (html: string) => void;
16
- }) {
17
- return (
18
- <Popover>
19
- <PopoverTrigger asChild>
20
- <Button variant="ghost" size="sm" className="max-lg:hidden">
21
- <HistoryIcon className="size-4 text-neutral-300" />
22
- {history?.length} edit{history.length !== 1 ? "s" : ""}
23
- </Button>
24
- </PopoverTrigger>
25
- <PopoverContent
26
- className="!rounded-2xl !p-0 overflow-hidden !bg-neutral-900"
27
- align="start"
28
- >
29
- <header className="text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
30
- History
31
- </header>
32
- <main className="px-4 space-y-3">
33
- <ul className="max-h-[250px] overflow-y-auto">
34
- {history?.map((item, index) => (
35
- <li
36
- key={index}
37
- className="text-gray-300 text-xs py-2 border-b border-gray-800 last:border-0 flex items-center justify-between gap-2"
38
- >
39
- <div className="">
40
- <span className="line-clamp-1">{item.prompt}</span>
41
- <span className="text-gray-500 text-[10px]">
42
- {new Date(item.createdAt).toLocaleDateString("en-US", {
43
- month: "2-digit",
44
- day: "2-digit",
45
- year: "2-digit",
46
- }) +
47
- " " +
48
- new Date(item.createdAt).toLocaleTimeString("en-US", {
49
- hour: "2-digit",
50
- minute: "2-digit",
51
- second: "2-digit",
52
- hour12: false,
53
- })}
54
- </span>
55
- </div>
56
- <Button
57
- variant="sky"
58
- size="xs"
59
- onClick={() => {
60
- setHtml(item.html);
61
- }}
62
- >
63
- Select
64
- </Button>
65
- </li>
66
- ))}
67
- </ul>
68
- </main>
69
- </PopoverContent>
70
- </Popover>
71
- );
72
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/preview/index.tsx DELETED
@@ -1,172 +0,0 @@
1
- "use client";
2
- import { useUpdateEffect } from "react-use";
3
- import { useMemo, useState } from "react";
4
- import classNames from "classnames";
5
- import { toast } from "sonner";
6
-
7
- import { cn } from "@/lib/utils";
8
- import { GridPattern } from "@/components/magic-ui/grid-pattern";
9
- import { htmlTagToText } from "@/lib/html-tag-to-text";
10
-
11
- export const Preview = ({
12
- html,
13
- isResizing,
14
- isAiWorking,
15
- ref,
16
- device,
17
- currentTab,
18
- iframeRef,
19
- isEditableModeEnabled,
20
- onClickElement,
21
- }: {
22
- html: string;
23
- isResizing: boolean;
24
- isAiWorking: boolean;
25
- ref: React.RefObject<HTMLDivElement | null>;
26
- iframeRef?: React.RefObject<HTMLIFrameElement | null>;
27
- device: "desktop" | "mobile";
28
- currentTab: string;
29
- isEditableModeEnabled?: boolean;
30
- onClickElement?: (element: HTMLElement) => void;
31
- }) => {
32
- const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(
33
- null
34
- );
35
-
36
- // add event listener to the iframe to track hovered elements
37
- const handleMouseOver = (event: MouseEvent) => {
38
- if (iframeRef?.current) {
39
- const iframeDocument = iframeRef.current.contentDocument;
40
- if (iframeDocument) {
41
- const targetElement = event.target as HTMLElement;
42
- if (
43
- hoveredElement !== targetElement &&
44
- targetElement !== iframeDocument.body
45
- ) {
46
- setHoveredElement(targetElement);
47
- targetElement.classList.add("hovered-element");
48
- } else {
49
- return setHoveredElement(null);
50
- }
51
- }
52
- }
53
- };
54
- const handleMouseOut = () => {
55
- setHoveredElement(null);
56
- };
57
- const handleClick = (event: MouseEvent) => {
58
- if (iframeRef?.current) {
59
- const iframeDocument = iframeRef.current.contentDocument;
60
- if (iframeDocument) {
61
- const targetElement = event.target as HTMLElement;
62
- if (targetElement !== iframeDocument.body) {
63
- onClickElement?.(targetElement);
64
- }
65
- }
66
- }
67
- };
68
-
69
- useUpdateEffect(() => {
70
- const cleanupListeners = () => {
71
- if (iframeRef?.current?.contentDocument) {
72
- const iframeDocument = iframeRef.current.contentDocument;
73
- iframeDocument.removeEventListener("mouseover", handleMouseOver);
74
- iframeDocument.removeEventListener("mouseout", handleMouseOut);
75
- iframeDocument.removeEventListener("click", handleClick);
76
- }
77
- };
78
-
79
- if (iframeRef?.current) {
80
- const iframeDocument = iframeRef.current.contentDocument;
81
- if (iframeDocument) {
82
- // Clean up existing listeners first
83
- cleanupListeners();
84
-
85
- if (isEditableModeEnabled) {
86
- iframeDocument.addEventListener("mouseover", handleMouseOver);
87
- iframeDocument.addEventListener("mouseout", handleMouseOut);
88
- iframeDocument.addEventListener("click", handleClick);
89
- }
90
- }
91
- }
92
-
93
- // Clean up when component unmounts or dependencies change
94
- return cleanupListeners;
95
- }, [iframeRef, isEditableModeEnabled]);
96
-
97
- const selectedElement = useMemo(() => {
98
- if (!isEditableModeEnabled) return null;
99
- if (!hoveredElement) return null;
100
- return hoveredElement;
101
- }, [hoveredElement, isEditableModeEnabled]);
102
-
103
- return (
104
- <div
105
- ref={ref}
106
- className={classNames(
107
- "w-full border-l border-gray-900 h-full relative z-0 flex items-center justify-center",
108
- {
109
- "lg:p-4": currentTab !== "preview",
110
- "max-lg:h-0": currentTab === "chat",
111
- "max-lg:h-full": currentTab === "preview",
112
- }
113
- )}
114
- onClick={(e) => {
115
- if (isAiWorking) {
116
- e.preventDefault();
117
- e.stopPropagation();
118
- toast.warning("Please wait for the AI to finish working.");
119
- }
120
- }}
121
- >
122
- <GridPattern
123
- x={-1}
124
- y={-1}
125
- strokeDasharray={"4 2"}
126
- className={cn(
127
- "[mask-image:radial-gradient(900px_circle_at_center,white,transparent)]"
128
- )}
129
- />
130
- {!isAiWorking && hoveredElement && selectedElement && (
131
- <div
132
- className="cursor-pointer absolute bg-sky-500/10 border-[2px] border-dashed border-sky-500 rounded-r-lg rounded-b-lg p-3 z-10 pointer-events-none"
133
- style={{
134
- top: selectedElement.getBoundingClientRect().top + 24,
135
- left: selectedElement.getBoundingClientRect().left + 24,
136
- width: selectedElement.getBoundingClientRect().width,
137
- height: selectedElement.getBoundingClientRect().height,
138
- }}
139
- >
140
- <span className="bg-sky-500 rounded-t-md text-sm text-neutral-100 px-2 py-0.5 -translate-y-7 absolute top-0 left-0">
141
- {htmlTagToText(selectedElement.tagName.toLowerCase())}
142
- </span>
143
- </div>
144
- )}
145
- <iframe
146
- id="preview-iframe"
147
- ref={iframeRef}
148
- title="output"
149
- className={classNames(
150
- "w-full select-none transition-all duration-200 bg-black h-full",
151
- {
152
- "pointer-events-none": isResizing || isAiWorking,
153
- "lg:max-w-md lg:mx-auto lg:!rounded-[42px] lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:h-[80dvh] lg:max-h-[996px]":
154
- device === "mobile",
155
- "lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:rounded-[24px]":
156
- currentTab !== "preview" && device === "desktop",
157
- }
158
- )}
159
- srcDoc={html}
160
- onLoad={() => {
161
- if (iframeRef?.current?.contentWindow?.document?.body) {
162
- iframeRef.current.contentWindow.document.body.scrollIntoView({
163
- block: isAiWorking ? "end" : "start",
164
- inline: "nearest",
165
- behavior: isAiWorking ? "instant" : "smooth",
166
- });
167
- }
168
- }}
169
- />
170
- </div>
171
- );
172
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/editor/save-button/index.tsx DELETED
@@ -1,76 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { useState } from "react";
3
- import { toast } from "sonner";
4
- import { MdSave } from "react-icons/md";
5
- import { useParams } from "next/navigation";
6
-
7
- import Loading from "@/components/loading";
8
- import { Button } from "@/components/ui/button";
9
- import { api } from "@/lib/api";
10
-
11
- export function SaveButton({
12
- html,
13
- prompts,
14
- }: {
15
- html: string;
16
- prompts: string[];
17
- }) {
18
- // get params from URL
19
- const { namespace, repoId } = useParams<{
20
- namespace: string;
21
- repoId: string;
22
- }>();
23
- const [loading, setLoading] = useState(false);
24
-
25
- const updateSpace = async () => {
26
- setLoading(true);
27
-
28
- try {
29
- const res = await api.put(`/me/projects/${namespace}/${repoId}`, {
30
- html,
31
- prompts,
32
- });
33
- if (res.data.ok) {
34
- toast.success("Your space is updated! 🎉", {
35
- action: {
36
- label: "See Space",
37
- onClick: () => {
38
- window.open(
39
- `https://huggingface.co/spaces/${namespace}/${repoId}`,
40
- "_blank"
41
- );
42
- },
43
- },
44
- });
45
- } else {
46
- toast.error(res?.data?.error || "Failed to update space");
47
- }
48
- } catch (err: any) {
49
- toast.error(err.response?.data?.error || err.message);
50
- } finally {
51
- setLoading(false);
52
- }
53
- };
54
-
55
- return (
56
- <>
57
- <Button
58
- variant="default"
59
- className="max-lg:hidden !px-4"
60
- onClick={updateSpace}
61
- >
62
- <MdSave className="size-4" />
63
- Save your Project{" "}
64
- {loading && <Loading className="ml-2 size-4 animate-spin" />}
65
- </Button>
66
- <Button
67
- variant="default"
68
- size="sm"
69
- className="lg:hidden"
70
- onClick={updateSpace}
71
- >
72
- Save {loading && <Loading className="ml-2 size-4 animate-spin" />}
73
- </Button>
74
- </>
75
- );
76
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/invite-friends/index.tsx DELETED
@@ -1,85 +0,0 @@
1
- import { TiUserAdd } from "react-icons/ti";
2
- import { Link } from "lucide-react";
3
- import { FaXTwitter } from "react-icons/fa6";
4
- import { useCopyToClipboard } from "react-use";
5
- import { toast } from "sonner";
6
-
7
- import { Button } from "@/components/ui/button";
8
- import {
9
- Dialog,
10
- DialogContent,
11
- DialogTitle,
12
- DialogTrigger,
13
- } from "@/components/ui/dialog";
14
-
15
- export function InviteFriends() {
16
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
- const [_, copyToClipboard] = useCopyToClipboard();
18
-
19
- return (
20
- <Dialog>
21
- <form>
22
- <DialogTrigger asChild>
23
- <Button
24
- size="iconXs"
25
- variant="outline"
26
- className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
27
- >
28
- <TiUserAdd className="size-4" />
29
- </Button>
30
- </DialogTrigger>
31
- <DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
32
- <DialogTitle className="hidden" />
33
- <main>
34
- <div className="flex items-center justify-start -space-x-4 mb-5">
35
- <div className="size-11 rounded-full bg-pink-300 shadow-2xs flex items-center justify-center text-2xl">
36
- 😎
37
- </div>
38
- <div className="size-11 rounded-full bg-amber-300 shadow-2xs flex items-center justify-center text-2xl z-2">
39
- 😇
40
- </div>
41
- <div className="size-11 rounded-full bg-sky-300 shadow-2xs flex items-center justify-center text-2xl">
42
- 😜
43
- </div>
44
- </div>
45
- <p className="text-xl font-semibold text-neutral-950 max-w-[200px]">
46
- Invite your friends to join us!
47
- </p>
48
- <p className="text-sm text-neutral-500 mt-2 max-w-sm">
49
- Support us and share the love and let them know about our awesome
50
- platform.
51
- </p>
52
- <div className="mt-4 space-x-3.5">
53
- <a
54
- href="https://x.com/intent/post?url=https://enzostvs-deepsite.hf.space/&text=Checkout%20this%20awesome%20Ai%20Tool!%20Vibe%20coding%20has%20never%20been%20so%20easy✨"
55
- target="_blank"
56
- rel="noopener noreferrer"
57
- >
58
- <Button
59
- variant="lightGray"
60
- size="sm"
61
- className="!text-neutral-700"
62
- >
63
- <FaXTwitter className="size-4" />
64
- Share on
65
- </Button>
66
- </a>
67
- <Button
68
- variant="lightGray"
69
- size="sm"
70
- className="!text-neutral-700"
71
- onClick={() => {
72
- copyToClipboard("https://enzostvs-deepsite.hf.space/");
73
- toast.success("Invite link copied to clipboard!");
74
- }}
75
- >
76
- <Link className="size-4" />
77
- Copy Invite Link
78
- </Button>
79
- </div>
80
- </main>
81
- </DialogContent>
82
- </form>
83
- </Dialog>
84
- );
85
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/login-modal/index.tsx DELETED
@@ -1,61 +0,0 @@
1
- import { useLocalStorage } from "react-use";
2
- import { Button } from "@/components/ui/button";
3
- import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
4
- import { useUser } from "@/hooks/useUser";
5
- import { isTheSameHtml } from "@/lib/compare-html-diff";
6
-
7
- export const LoginModal = ({
8
- open,
9
- html,
10
- onClose,
11
- title = "Log In to use DeepSite for free",
12
- description = "Log In through your Hugging Face account to continue using DeepSite and increase your monthly free limit.",
13
- }: {
14
- open: boolean;
15
- html?: string;
16
- onClose: React.Dispatch<React.SetStateAction<boolean>>;
17
- title?: string;
18
- description?: string;
19
- }) => {
20
- const { openLoginWindow } = useUser();
21
- const [, setStorage] = useLocalStorage("html_content");
22
- const handleClick = async () => {
23
- if (html && !isTheSameHtml(html)) {
24
- setStorage(html);
25
- }
26
- openLoginWindow();
27
- onClose(false);
28
- };
29
- return (
30
- <Dialog open={open} onOpenChange={onClose}>
31
- <DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
32
- <DialogTitle className="hidden" />
33
- <main className="flex flex-col items-start text-left relative pt-2">
34
- <div className="flex items-center justify-start -space-x-4 mb-5">
35
- <div className="size-14 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
36
- 💪
37
- </div>
38
- <div className="size-16 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-4xl z-2">
39
- 😎
40
- </div>
41
- <div className="size-14 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
42
- 🙌
43
- </div>
44
- </div>
45
- <p className="text-2xl font-bold text-neutral-950">{title}</p>
46
- <p className="text-neutral-500 text-base mt-2 max-w-sm">
47
- {description}
48
- </p>
49
- <Button
50
- variant="black"
51
- size="lg"
52
- className="w-full !text-base !h-11 mt-8"
53
- onClick={handleClick}
54
- >
55
- Log In to Continue
56
- </Button>
57
- </main>
58
- </DialogContent>
59
- </Dialog>
60
- );
61
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/magic-ui/grid-pattern.tsx DELETED
@@ -1,69 +0,0 @@
1
- import { useId } from "react";
2
- import { cn } from "@/lib/utils";
3
-
4
- interface GridPatternProps extends React.SVGProps<SVGSVGElement> {
5
- width?: number;
6
- height?: number;
7
- x?: number;
8
- y?: number;
9
- squares?: Array<[x: number, y: number]>;
10
- strokeDasharray?: string;
11
- className?: string;
12
- [key: string]: unknown;
13
- }
14
-
15
- export function GridPattern({
16
- width = 40,
17
- height = 40,
18
- x = -1,
19
- y = -1,
20
- strokeDasharray = "0",
21
- squares,
22
- className,
23
- ...props
24
- }: GridPatternProps) {
25
- const id = useId();
26
-
27
- return (
28
- <svg
29
- aria-hidden="true"
30
- className={cn(
31
- "pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-neutral-700 -z-[1]",
32
- className
33
- )}
34
- {...props}
35
- >
36
- <defs>
37
- <pattern
38
- id={id}
39
- width={width}
40
- height={height}
41
- patternUnits="userSpaceOnUse"
42
- x={x}
43
- y={y}
44
- >
45
- <path
46
- d={`M.5 ${height}V.5H${width}`}
47
- fill="none"
48
- strokeDasharray={strokeDasharray}
49
- />
50
- </pattern>
51
- </defs>
52
- <rect width="100%" height="100%" strokeWidth={0} fill={`url(#${id})`} />
53
- {squares && (
54
- <svg x={x} y={y} className="overflow-visible">
55
- {squares.map(([x, y]) => (
56
- <rect
57
- strokeWidth="0"
58
- key={`${x}-${y}`}
59
- width={width - 1}
60
- height={height - 1}
61
- x={x * width + 1}
62
- y={y * height + 1}
63
- />
64
- ))}
65
- </svg>
66
- )}
67
- </svg>
68
- );
69
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/my-projects/index.tsx DELETED
@@ -1,57 +0,0 @@
1
- "use client";
2
- import { Plus } from "lucide-react";
3
- import Link from "next/link";
4
- import { useState } from "react";
5
-
6
- import { useUser } from "@/hooks/useUser";
7
- import { Project } from "@/types";
8
- import { redirect } from "next/navigation";
9
- import { ProjectCard } from "./project-card";
10
- import { LoadProject } from "./load-project";
11
-
12
- export function MyProjects({
13
- projects: initialProjects,
14
- }: {
15
- projects: Project[];
16
- }) {
17
- const { user } = useUser();
18
- if (!user) {
19
- redirect("/");
20
- }
21
- const [projects, setProjects] = useState<Project[]>(initialProjects || []);
22
- return (
23
- <>
24
- <section className="max-w-[86rem] py-12 px-4 mx-auto">
25
- <header className="flex items-center justify-between max-lg:flex-col gap-4">
26
- <div className="text-left">
27
- <h1 className="text-3xl font-bold text-white">
28
- <span className="capitalize">{user.fullname}</span>&apos;s
29
- DeepSite Projects
30
- </h1>
31
- <p className="text-muted-foreground text-base mt-1 max-w-xl">
32
- Create, manage, and explore your DeepSite projects.
33
- </p>
34
- </div>
35
- <LoadProject
36
- fullXsBtn
37
- onSuccess={(project: Project) => {
38
- setProjects((prev) => [...prev, project]);
39
- }}
40
- />
41
- </header>
42
- <div className="mt-8 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
43
- <Link
44
- href="/projects/new"
45
- className="bg-neutral-900 rounded-xl h-44 flex items-center justify-center text-neutral-300 border border-neutral-800 hover:brightness-110 transition-all duration-200"
46
- >
47
- <Plus className="size-5 mr-1.5" />
48
- Create Project
49
- </Link>
50
- {projects.map((project: Project) => (
51
- <ProjectCard key={project._id} project={project} />
52
- ))}
53
- </div>
54
- </section>
55
- </>
56
- );
57
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/my-projects/load-project.tsx DELETED
@@ -1,196 +0,0 @@
1
- "use client";
2
- import { useState } from "react";
3
- import { Import } from "lucide-react";
4
-
5
- import { Project } from "@/types";
6
- import { Button } from "@/components/ui/button";
7
- import {
8
- Dialog,
9
- DialogContent,
10
- DialogTitle,
11
- DialogTrigger,
12
- } from "@/components/ui/dialog";
13
- import Loading from "@/components/loading";
14
- import { Input } from "../ui/input";
15
- import { toast } from "sonner";
16
- import { api } from "@/lib/api";
17
- import { useUser } from "@/hooks/useUser";
18
- import { LoginModal } from "../login-modal";
19
-
20
- export const LoadProject = ({
21
- fullXsBtn = false,
22
- onSuccess,
23
- }: {
24
- fullXsBtn?: boolean;
25
- onSuccess: (project: Project) => void;
26
- }) => {
27
- const { user } = useUser();
28
-
29
- const [openLoginModal, setOpenLoginModal] = useState(false);
30
- const [open, setOpen] = useState(false);
31
- const [url, setUrl] = useState<string>("");
32
- const [isLoading, setIsLoading] = useState(false);
33
-
34
- const checkIfUrlIsValid = (url: string) => {
35
- // should match a hugging face spaces URL like: https://huggingface.co/spaces/username/project or https://hf.co/spaces/username/project
36
- const urlPattern = new RegExp(
37
- /^(https?:\/\/)?(huggingface\.co|hf\.co)\/spaces\/([\w-]+)\/([\w-]+)$/,
38
- "i"
39
- );
40
- return urlPattern.test(url);
41
- };
42
-
43
- const handleClick = async () => {
44
- if (isLoading) return; // Prevent multiple clicks while loading
45
- if (!url) {
46
- toast.error("Please enter a URL.");
47
- return;
48
- }
49
- if (!checkIfUrlIsValid(url)) {
50
- toast.error("Please enter a valid Hugging Face Spaces URL.");
51
- return;
52
- }
53
-
54
- const [username, namespace] = url
55
- .replace("https://huggingface.co/spaces/", "")
56
- .replace("https://hf.co/spaces/", "")
57
- .split("/");
58
-
59
- setIsLoading(true);
60
- try {
61
- const response = await api.post(`/me/projects/${username}/${namespace}`);
62
- console.log("response", response);
63
- toast.success("Project imported successfully!");
64
- setOpen(false);
65
- setUrl("");
66
- onSuccess(response.data.project);
67
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
- } catch (error: any) {
69
- toast.error(
70
- error?.response?.data?.error ?? "Failed to import the project."
71
- );
72
- } finally {
73
- setIsLoading(false);
74
- }
75
- };
76
-
77
- return (
78
- <>
79
- {!user ? (
80
- <>
81
- <Button
82
- variant="outline"
83
- className="max-lg:hidden"
84
- onClick={() => setOpenLoginModal(true)}
85
- >
86
- <Import className="size-4 mr-1.5" />
87
- Load existing Project
88
- </Button>
89
- <Button
90
- variant="outline"
91
- size="sm"
92
- className="lg:hidden"
93
- onClick={() => setOpenLoginModal(true)}
94
- >
95
- {fullXsBtn && <Import className="size-3.5 mr-1" />}
96
- Load
97
- {fullXsBtn && " existing Project"}
98
- </Button>
99
- <LoginModal
100
- open={openLoginModal}
101
- onClose={setOpenLoginModal}
102
- title="Log In to load your Project"
103
- description="Log In through Hugging Face to load an existing project and increase your free limit!"
104
- />
105
- </>
106
- ) : (
107
- <Dialog open={open} onOpenChange={setOpen}>
108
- <DialogTrigger asChild>
109
- <div>
110
- <Button variant="outline" className="max-lg:hidden">
111
- <Import className="size-4 mr-1.5" />
112
- Load existing Project
113
- </Button>
114
- <Button variant="outline" size="sm" className="lg:hidden">
115
- {fullXsBtn && <Import className="size-3.5 mr-1" />}
116
- Load
117
- {fullXsBtn && " existing Project"}
118
- </Button>
119
- </div>
120
- </DialogTrigger>
121
- <DialogContent className="sm:max-w-md !p-0 !rounded-3xl !bg-white !border-neutral-100 overflow-hidden text-center">
122
- <DialogTitle className="hidden" />
123
- <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
124
- <div className="flex items-center justify-center -space-x-4 mb-3">
125
- <div className="size-11 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-2xl opacity-50">
126
- 🎨
127
- </div>
128
- <div className="size-13 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-3xl z-2">
129
- 🥳
130
- </div>
131
- <div className="size-11 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-2xl opacity-50">
132
- 💎
133
- </div>
134
- </div>
135
- <p className="text-2xl font-semibold text-neutral-950">
136
- Import a Project
137
- </p>
138
- <p className="text-base text-neutral-500 mt-1.5">
139
- Enter the URL of your Hugging Face Space to import an existing
140
- project.
141
- </p>
142
- </header>
143
- <main className="space-y-4 px-9 pb-9 pt-2">
144
- <div>
145
- <p className="text-sm text-neutral-700 mb-2">
146
- Enter your Hugging Face Space
147
- </p>
148
- <Input
149
- type="text"
150
- placeholder="https://huggingface.com/spaces/username/project"
151
- value={url}
152
- onChange={(e) => setUrl(e.target.value)}
153
- onBlur={(e) => {
154
- const inputUrl = e.target.value.trim();
155
- if (!inputUrl) {
156
- setUrl("");
157
- return;
158
- }
159
- if (!checkIfUrlIsValid(inputUrl)) {
160
- toast.error("Please enter a valid URL.");
161
- return;
162
- }
163
- setUrl(inputUrl);
164
- }}
165
- className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
166
- />
167
- </div>
168
- <div>
169
- <p className="text-sm text-neutral-700 mb-2">
170
- Then, let&apos;s import it!
171
- </p>
172
- <Button
173
- variant="black"
174
- onClick={handleClick}
175
- className="relative w-full"
176
- >
177
- {isLoading ? (
178
- <>
179
- <Loading
180
- overlay={false}
181
- className="ml-2 size-4 animate-spin"
182
- />
183
- Fetching your Space...
184
- </>
185
- ) : (
186
- <>Import your Space</>
187
- )}
188
- </Button>
189
- </div>
190
- </main>
191
- </DialogContent>
192
- </Dialog>
193
- )}
194
- </>
195
- );
196
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/my-projects/project-card.tsx DELETED
@@ -1,74 +0,0 @@
1
- import Link from "next/link";
2
- import { formatDistance } from "date-fns";
3
- import { EllipsisVertical, Settings } from "lucide-react";
4
-
5
- import { Project } from "@/types";
6
- import { Button } from "@/components/ui/button";
7
- import {
8
- DropdownMenu,
9
- DropdownMenuContent,
10
- DropdownMenuGroup,
11
- DropdownMenuItem,
12
- DropdownMenuTrigger,
13
- } from "@/components/ui/dropdown-menu";
14
-
15
- export function ProjectCard({ project }: { project: Project }) {
16
- return (
17
- <div className="text-neutral-200 space-y-4 group cursor-pointer">
18
- <Link
19
- href={`/projects/${project.space_id}`}
20
- className="relative bg-neutral-900 rounded-2xl overflow-hidden h-44 w-full flex items-center justify-end flex-col px-3 border border-neutral-800"
21
- >
22
- <iframe
23
- src={`https://${project.space_id.replace("/", "-")}.static.hf.space/`}
24
- frameBorder="0"
25
- className="absolute inset-0 w-full h-full top-0 left-0 group-hover:brightness-75 transition-all duration-200 pointer-events-none"
26
- ></iframe>
27
-
28
- <Button
29
- variant="default"
30
- className="w-full transition-all duration-200 translate-y-full group-hover:-translate-y-3"
31
- >
32
- Open project
33
- </Button>
34
- </Link>
35
- <div className="flex items-start justify-between gap-3">
36
- <div>
37
- <p className="text-neutral-200 text-base font-semibold line-clamp-1">
38
- {project.space_id}
39
- </p>
40
- <p className="text-sm text-neutral-500">
41
- Updated{" "}
42
- {formatDistance(
43
- new Date(project._updatedAt || Date.now()),
44
- new Date(),
45
- {
46
- addSuffix: true,
47
- }
48
- )}
49
- </p>
50
- </div>
51
- <DropdownMenu>
52
- <DropdownMenuTrigger asChild>
53
- <Button variant="ghost" size="icon">
54
- <EllipsisVertical className="text-neutral-400 size-5 hover:text-neutral-300 transition-colors duration-200 cursor-pointer" />
55
- </Button>
56
- </DropdownMenuTrigger>
57
- <DropdownMenuContent className="w-56" align="start">
58
- <DropdownMenuGroup>
59
- <a
60
- href={`https://huggingface.co/spaces/${project.space_id}/settings`}
61
- target="_blank"
62
- >
63
- <DropdownMenuItem>
64
- <Settings className="size-4 text-neutral-100" />
65
- Project Settings
66
- </DropdownMenuItem>
67
- </a>
68
- </DropdownMenuGroup>
69
- </DropdownMenuContent>
70
- </DropdownMenu>
71
- </div>
72
- </div>
73
- );
74
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/pro-modal/index.tsx DELETED
@@ -1,92 +0,0 @@
1
- import { useLocalStorage } from "react-use";
2
- import { Button } from "@/components/ui/button";
3
- import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
4
- import { CheckCheck } from "lucide-react";
5
- import { isTheSameHtml } from "@/lib/compare-html-diff";
6
-
7
- export const ProModal = ({
8
- open,
9
- html,
10
- onClose,
11
- }: {
12
- open: boolean;
13
- html: string;
14
- onClose: React.Dispatch<React.SetStateAction<boolean>>;
15
- }) => {
16
- const [, setStorage] = useLocalStorage("html_content");
17
- const handleProClick = () => {
18
- if (!isTheSameHtml(html)) {
19
- setStorage(html);
20
- }
21
- window.open("https://huggingface.co/subscribe/pro?from=DeepSite", "_blank");
22
- onClose(false);
23
- };
24
- return (
25
- <Dialog open={open} onOpenChange={onClose}>
26
- <DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
27
- <DialogTitle className="hidden" />
28
- <main className="flex flex-col items-start text-left relative pt-2">
29
- <div className="flex items-center justify-start -space-x-4 mb-5">
30
- <div className="size-14 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
31
- 🚀
32
- </div>
33
- <div className="size-16 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-4xl z-2">
34
- 🤩
35
- </div>
36
- <div className="size-14 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
37
- 🥳
38
- </div>
39
- </div>
40
- <h2 className="text-2xl font-bold text-neutral-950">
41
- Only $9 to enhance your possibilities
42
- </h2>
43
- <p className="text-neutral-500 text-base mt-2 max-w-sm">
44
- It seems like you have reached the monthly free limit of DeepSite.
45
- </p>
46
- <hr className="bg-neutral-200 w-full max-w-[150px] my-6" />
47
- <p className="text-lg mt-3 text-neutral-900 font-semibold">
48
- Upgrade to a <ProTag className="mx-1" /> Account, and unlock your
49
- DeepSite high quota access ⚡
50
- </p>
51
- <ul className="mt-3 space-y-1 text-neutral-500">
52
- <li className="text-sm text-neutral-500 space-x-2 flex items-center justify-start gap-2 mb-3">
53
- You&apos;ll also unlock some Hugging Face PRO features, like:
54
- </li>
55
- <li className="text-sm space-x-2 flex items-center justify-start gap-2">
56
- <CheckCheck className="text-emerald-500 size-4" />
57
- Get acces to thousands of AI app (ZeroGPU) with high quota
58
- </li>
59
- <li className="text-sm space-x-2 flex items-center justify-start gap-2">
60
- <CheckCheck className="text-emerald-500 size-4" />
61
- Get exclusive early access to new features and updates
62
- </li>
63
- <li className="text-sm space-x-2 flex items-center justify-start gap-2">
64
- <CheckCheck className="text-emerald-500 size-4" />
65
- Get free credits across all Inference Providers
66
- </li>
67
- <li className="text-sm text-neutral-500 space-x-2 flex items-center justify-start gap-2 mt-3">
68
- ... and lots more!
69
- </li>
70
- </ul>
71
- <Button
72
- variant="black"
73
- size="lg"
74
- className="w-full !text-base !h-11 mt-8"
75
- onClick={handleProClick}
76
- >
77
- Subscribe to PRO ($9/month)
78
- </Button>
79
- </main>
80
- </DialogContent>
81
- </Dialog>
82
- );
83
- };
84
-
85
- const ProTag = ({ className }: { className?: string }) => (
86
- <span
87
- className={`${className} bg-linear-to-br shadow-green-500/10 dark:shadow-green-500/20 inline-block -skew-x-12 border border-gray-200 from-pink-300 via-green-200 to-yellow-200 text-xs font-bold text-black shadow-lg rounded-md px-2.5 py-0.5`}
88
- >
89
- PRO
90
- </span>
91
- );
92
- export default ProModal;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/providers/tanstack-query-provider.tsx DELETED
@@ -1,18 +0,0 @@
1
- "use client";
2
-
3
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
-
5
- export default function TanstackProvider({
6
- children,
7
- }: {
8
- children: React.ReactNode;
9
- }) {
10
- const queryClient = new QueryClient();
11
-
12
- return (
13
- <QueryClientProvider client={queryClient}>
14
- {children}
15
- {/* <ReactQueryDevtools initialIsOpen={false} /> */}
16
- </QueryClientProvider>
17
- );
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/public/navigation/index.tsx DELETED
@@ -1,156 +0,0 @@
1
- "use client";
2
-
3
- import { useRef, useState } from "react";
4
- import Image from "next/image";
5
- import Link from "next/link";
6
- import { useMount, useUnmount } from "react-use";
7
- import classNames from "classnames";
8
-
9
- import { Button } from "@/components/ui/button";
10
- import Logo from "@/assets/logo.svg";
11
- import { useUser } from "@/hooks/useUser";
12
- import { UserMenu } from "@/components/user-menu";
13
-
14
- const navigationLinks = [
15
- {
16
- name: "Create Website",
17
- href: "/projects/new",
18
- },
19
- {
20
- name: "Features",
21
- href: "#features",
22
- },
23
- {
24
- name: "Community",
25
- href: "#community",
26
- },
27
- {
28
- name: "Deploy",
29
- href: "#deploy",
30
- },
31
- ];
32
-
33
- export default function Navigation() {
34
- const { openLoginWindow, user } = useUser();
35
- const [hash, setHash] = useState("");
36
-
37
- const selectorRef = useRef<HTMLDivElement>(null);
38
- const linksRef = useRef<HTMLLIElement[]>(
39
- new Array(navigationLinks.length).fill(null)
40
- );
41
- const [isScrolled, setIsScrolled] = useState(false);
42
-
43
- useMount(() => {
44
- const handleScroll = () => {
45
- const scrollTop = window.scrollY;
46
- setIsScrolled(scrollTop > 100);
47
- };
48
-
49
- const initialHash = window.location.hash;
50
- if (initialHash) {
51
- setHash(initialHash);
52
- calculateSelectorPosition(initialHash);
53
- }
54
-
55
- window.addEventListener("scroll", handleScroll);
56
- });
57
-
58
- useUnmount(() => {
59
- window.removeEventListener("scroll", () => {});
60
- });
61
-
62
- const handleClick = (href: string) => {
63
- setHash(href);
64
- calculateSelectorPosition(href);
65
- };
66
-
67
- const calculateSelectorPosition = (href: string) => {
68
- if (selectorRef.current && linksRef.current) {
69
- const index = navigationLinks.findIndex((l) => l.href === href);
70
- const targetLink = linksRef.current[index];
71
- if (targetLink) {
72
- const targetRect = targetLink.getBoundingClientRect();
73
- selectorRef.current.style.left = targetRect.left + "px";
74
- selectorRef.current.style.width = targetRect.width + "px";
75
- }
76
- }
77
- };
78
-
79
- return (
80
- <div
81
- className={classNames(
82
- "sticky top-0 z-10 transition-all duration-200 backdrop-blur-md",
83
- {
84
- "bg-black/30": isScrolled,
85
- }
86
- )}
87
- >
88
- <nav className="grid grid-cols-2 p-4 container mx-auto">
89
- <Link href="/" className="flex items-center gap-1">
90
- <Image
91
- src={Logo}
92
- className="w-9 mr-1"
93
- alt="DeepSite Logo"
94
- width={64}
95
- height={64}
96
- />
97
- <p className="font-sans text-white text-xl font-bold">DeepSite</p>
98
- </Link>
99
- <ul className="items-center justify-center gap-6 hidden">
100
- {navigationLinks.map((link) => (
101
- <li
102
- key={link.name}
103
- ref={(el) => {
104
- const index = navigationLinks.findIndex(
105
- (l) => l.href === link.href
106
- );
107
- if (el && linksRef.current[index] !== el) {
108
- linksRef.current[index] = el;
109
- }
110
- }}
111
- className="inline-block font-sans text-sm"
112
- >
113
- <Link
114
- href={link.href}
115
- className={classNames(
116
- "text-neutral-500 hover:text-primary transition-colors",
117
- {
118
- "text-primary": hash === link.href,
119
- }
120
- )}
121
- onClick={() => {
122
- handleClick(link.href);
123
- }}
124
- >
125
- {link.name}
126
- </Link>
127
- </li>
128
- ))}
129
- <div
130
- ref={selectorRef}
131
- className={classNames(
132
- "h-1 absolute bottom-4 transition-all duration-200 flex items-center justify-center",
133
- {
134
- "opacity-0": !hash,
135
- }
136
- )}
137
- >
138
- <div className="size-1 bg-white rounded-full" />
139
- </div>
140
- </ul>
141
- <div className="flex items-center justify-end gap-2">
142
- {user ? (
143
- <UserMenu className="!pl-3 !pr-4 !py-2 !h-auto !rounded-lg" />
144
- ) : (
145
- <>
146
- <Button variant="link" size={"sm"} onClick={openLoginWindow}>
147
- Log In
148
- </Button>
149
- <Button size={"sm"}>Sign Up</Button>
150
- </>
151
- )}
152
- </div>
153
- </nav>
154
- </div>
155
- );
156
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/space/ask-ai/index.tsx DELETED
@@ -1,43 +0,0 @@
1
- "use client";
2
-
3
- import { ArrowUp } from "lucide-react";
4
- import { PiGearSixFill } from "react-icons/pi";
5
- import { TiUserAdd } from "react-icons/ti";
6
-
7
- import { Button } from "@/components/ui/button";
8
-
9
- export const AskAi = () => {
10
- return (
11
- <>
12
- <div className="bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent group">
13
- <textarea
14
- rows={3}
15
- className="w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4 resize-none mb-1"
16
- placeholder="Ask DeepSite anything..."
17
- onChange={() => {}}
18
- onKeyDown={() => {}}
19
- />
20
- <div className="flex items-center justify-between gap-2 px-4 pb-3">
21
- <div className="flex-1 flex justify-start">
22
- <Button
23
- size="iconXs"
24
- variant="outline"
25
- className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
26
- >
27
- <TiUserAdd className="size-4" />
28
- </Button>
29
- </div>
30
- <div className="flex items-center justify-end gap-2">
31
- <Button variant="black" size="sm">
32
- <PiGearSixFill className="size-4" />
33
- Settings
34
- </Button>
35
- <Button size="iconXs">
36
- <ArrowUp className="size-4" />
37
- </Button>
38
- </div>
39
- </div>
40
- </div>
41
- </>
42
- );
43
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ui/avatar.tsx DELETED
@@ -1,53 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
-
6
- import { cn } from "@/lib/utils"
7
-
8
- function Avatar({
9
- className,
10
- ...props
11
- }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
12
- return (
13
- <AvatarPrimitive.Root
14
- data-slot="avatar"
15
- className={cn(
16
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
17
- className
18
- )}
19
- {...props}
20
- />
21
- )
22
- }
23
-
24
- function AvatarImage({
25
- className,
26
- ...props
27
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
28
- return (
29
- <AvatarPrimitive.Image
30
- data-slot="avatar-image"
31
- className={cn("aspect-square size-full", className)}
32
- {...props}
33
- />
34
- )
35
- }
36
-
37
- function AvatarFallback({
38
- className,
39
- ...props
40
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
41
- return (
42
- <AvatarPrimitive.Fallback
43
- data-slot="avatar-fallback"
44
- className={cn(
45
- "bg-muted flex size-full items-center justify-center rounded-full",
46
- className
47
- )}
48
- {...props}
49
- />
50
- )
51
- }
52
-
53
- export { Avatar, AvatarImage, AvatarFallback }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ui/button.tsx DELETED
@@ -1,67 +0,0 @@
1
- import * as React from "react";
2
- import { Slot } from "@radix-ui/react-slot";
3
- import { cva, type VariantProps } from "class-variance-authority";
4
-
5
- import { cn } from "@/lib/utils";
6
-
7
- const buttonVariants = cva(
8
- "inline-flex items-center cursor-pointer justify-center gap-2 whitespace-nowrap rounded-full text-sm font-sans font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
- {
10
- variants: {
11
- variant: {
12
- default:
13
- "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14
- destructive:
15
- "bg-red-500 text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 [&_svg]:!text-white",
16
- outline:
17
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18
- secondary:
19
- "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20
- ghost:
21
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22
- lightGray: "bg-neutral-200/60 hover:bg-neutral-200",
23
- link: "text-primary underline-offset-4 hover:underline",
24
- ghostDarker:
25
- "text-white shadow-xs focus-visible:ring-black/40 bg-black/40 hover:bg-black/70",
26
- black: "bg-neutral-950 text-neutral-300 hover:brightness-110",
27
- sky: "bg-sky-500 text-white hover:brightness-110",
28
- },
29
- size: {
30
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
31
- sm: "h-8 rounded-full text-[13px] gap-1.5 px-3",
32
- lg: "h-10 rounded-full px-6 has-[>svg]:px-4",
33
- icon: "size-9",
34
- iconXs: "size-7",
35
- iconXss: "size-6",
36
- xs: "h-6 text-xs rounded-full pl-2 pr-2 gap-1",
37
- },
38
- },
39
- defaultVariants: {
40
- variant: "default",
41
- size: "default",
42
- },
43
- }
44
- );
45
-
46
- function Button({
47
- className,
48
- variant,
49
- size,
50
- asChild = false,
51
- ...props
52
- }: React.ComponentProps<"button"> &
53
- VariantProps<typeof buttonVariants> & {
54
- asChild?: boolean;
55
- }) {
56
- const Comp = asChild ? Slot : "button";
57
-
58
- return (
59
- <Comp
60
- data-slot="button"
61
- className={cn(buttonVariants({ variant, size, className }))}
62
- {...props}
63
- />
64
- );
65
- }
66
-
67
- export { Button, buttonVariants };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ui/checkbox.tsx DELETED
@@ -1,32 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
5
- import { CheckIcon } from "lucide-react";
6
-
7
- import { cn } from "@/lib/utils";
8
-
9
- function Checkbox({
10
- className,
11
- ...props
12
- }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
13
- return (
14
- <CheckboxPrimitive.Root
15
- data-slot="checkbox"
16
- className={cn(
17
- "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-3.5 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
18
- className
19
- )}
20
- {...props}
21
- >
22
- <CheckboxPrimitive.Indicator
23
- data-slot="checkbox-indicator"
24
- className="flex items-center justify-center text-current transition-none"
25
- >
26
- <CheckIcon className="size-3" />
27
- </CheckboxPrimitive.Indicator>
28
- </CheckboxPrimitive.Root>
29
- );
30
- }
31
-
32
- export { Checkbox };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/ui/collapsible.tsx DELETED
@@ -1,33 +0,0 @@
1
- "use client"
2
-
3
- import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
-
5
- function Collapsible({
6
- ...props
7
- }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
8
- return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
9
- }
10
-
11
- function CollapsibleTrigger({
12
- ...props
13
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
14
- return (
15
- <CollapsiblePrimitive.CollapsibleTrigger
16
- data-slot="collapsible-trigger"
17
- {...props}
18
- />
19
- )
20
- }
21
-
22
- function CollapsibleContent({
23
- ...props
24
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
25
- return (
26
- <CollapsiblePrimitive.CollapsibleContent
27
- data-slot="collapsible-content"
28
- {...props}
29
- />
30
- )
31
- }
32
-
33
- export { Collapsible, CollapsibleTrigger, CollapsibleContent }