app/(public)/layout.tsx DELETED
@@ -1,15 +0,0 @@
1
- import Navigation from "@/components/public/navigation";
2
-
3
- export default async function PublicLayout({
4
- children,
5
- }: Readonly<{
6
- children: React.ReactNode;
7
- }>) {
8
- return (
9
- <div className="h-screen bg-neutral-950 z-1 relative overflow-auto scroll-smooth">
10
- <div className="background__noisy" />
11
- <Navigation />
12
- {children}
13
- </div>
14
- );
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/(public)/page.tsx DELETED
@@ -1,5 +0,0 @@
1
- import { MyProjects } from "@/components/my-projects";
2
-
3
- export default async function HomePage() {
4
- return <MyProjects />;
5
- }
 
 
 
 
 
 
app/[namespace]/[repoId]/page.tsx DELETED
@@ -1,28 +0,0 @@
1
- import { AppEditor } from "@/components/editor";
2
- import { generateSEO } from "@/lib/seo";
3
- import { Metadata } from "next";
4
-
5
- export async function generateMetadata({
6
- params,
7
- }: {
8
- params: Promise<{ namespace: string; repoId: string }>;
9
- }): Promise<Metadata> {
10
- const { namespace, repoId } = await params;
11
-
12
- return generateSEO({
13
- title: `${namespace}/${repoId} - DeepSite Editor`,
14
- description: `Edit and build ${namespace}/${repoId} with AI-powered tools on DeepSite. Create stunning websites with no code required.`,
15
- path: `/${namespace}/${repoId}`,
16
- // Prevent indexing of individual project editor pages if they contain sensitive content
17
- noIndex: false, // Set to true if you want to keep project pages private
18
- });
19
- }
20
-
21
- export default async function ProjectNamespacePage({
22
- params,
23
- }: {
24
- params: Promise<{ namespace: string; repoId: string }>;
25
- }) {
26
- const { namespace, repoId } = await params;
27
- return <AppEditor namespace={namespace} repoId={repoId} />;
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/actions/auth.ts DELETED
@@ -1,18 +0,0 @@
1
- "use server";
2
-
3
- import { headers } from "next/headers";
4
-
5
- export async function getAuth() {
6
- const authList = await headers();
7
- const host = authList.get("host") ?? "localhost:3000";
8
- const url = host.includes("/spaces/enzostvs")
9
- ? "enzostvs-deepsite.hf.space"
10
- : host;
11
- const redirect_uri =
12
- `${host.includes("localhost") ? "http://" : "https://"}` +
13
- url +
14
- "/deepsite/auth/callback";
15
-
16
- const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
17
- return loginRedirectUrl;
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/actions/projects.ts DELETED
@@ -1,47 +0,0 @@
1
- "use server";
2
-
3
- import { isAuthenticated } from "@/lib/auth";
4
- import { NextResponse } from "next/server";
5
- import { listSpaces } from "@huggingface/hub";
6
- import { ProjectType } from "@/types";
7
-
8
- export async function getProjects(): Promise<{
9
- ok: boolean;
10
- projects: ProjectType[];
11
- isEmpty?: boolean;
12
- }> {
13
- const user = await isAuthenticated();
14
-
15
- if (user instanceof NextResponse || !user) {
16
- return {
17
- ok: false,
18
- projects: [],
19
- };
20
- }
21
-
22
- const projects = [];
23
- for await (const space of listSpaces({
24
- accessToken: user.token as string,
25
- additionalFields: ["author", "cardData"],
26
- search: {
27
- owner: user.name,
28
- }
29
- })) {
30
- if (
31
- !space.private &&
32
- space.sdk === "static" &&
33
- Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
34
- (
35
- ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite-v3")) ||
36
- ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite"))
37
- )
38
- ) {
39
- projects.push(space);
40
- }
41
- }
42
-
43
- return {
44
- ok: true,
45
- projects,
46
- };
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/ask/route.ts DELETED
@@ -1,399 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { NextRequest } from "next/server";
3
- import { NextResponse } from "next/server";
4
- import { headers } from "next/headers";
5
- import { InferenceClient } from "@huggingface/inference";
6
-
7
- import { MODELS } from "@/lib/providers";
8
- import {
9
- FOLLOW_UP_SYSTEM_PROMPT,
10
- FOLLOW_UP_SYSTEM_PROMPT_LIGHT,
11
- INITIAL_SYSTEM_PROMPT,
12
- INITIAL_SYSTEM_PROMPT_LIGHT,
13
- MAX_REQUESTS_PER_IP,
14
- PROMPT_FOR_PROJECT_NAME,
15
- } from "@/lib/prompts";
16
- import MY_TOKEN_KEY from "@/lib/get-cookie-name";
17
- import { Page } from "@/types";
18
- import { isAuthenticated } from "@/lib/auth";
19
- import { getBestProvider } from "@/lib/best-provider";
20
-
21
- const ipAddresses = new Map();
22
-
23
- export async function POST(request: NextRequest) {
24
- const authHeaders = await headers();
25
- const tokenInHeaders = authHeaders.get("Authorization");
26
- const userToken = tokenInHeaders ? tokenInHeaders.replace("Bearer ", "") : request.cookies.get(MY_TOKEN_KEY())?.value;
27
-
28
- const body = await request.json();
29
- const { prompt, provider, model, redesignMarkdown, enhancedSettings, pages } = body;
30
-
31
- if (!model || (!prompt && !redesignMarkdown)) {
32
- return NextResponse.json(
33
- { ok: false, error: "Missing required fields" },
34
- { status: 400 }
35
- );
36
- }
37
-
38
- const selectedModel = MODELS.find(
39
- (m) => m.value === model || m.label === model
40
- );
41
-
42
- if (!selectedModel) {
43
- return NextResponse.json(
44
- { ok: false, error: "Invalid model selected" },
45
- { status: 400 }
46
- );
47
- }
48
-
49
- let token: string | null = null;
50
- if (userToken) token = userToken;
51
- let billTo: string | null = null;
52
-
53
- /**
54
- * Handle local usage token, this bypass the need for a user token
55
- * and allows local testing without authentication.
56
- * This is useful for development and testing purposes.
57
- */
58
- if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
59
- token = process.env.HF_TOKEN;
60
- }
61
-
62
- const ip = authHeaders.get("x-forwarded-for")?.includes(",")
63
- ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
64
- : authHeaders.get("x-forwarded-for");
65
-
66
- if (!token || token === "null" || token === "") {
67
- ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
68
- if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
69
- return NextResponse.json(
70
- {
71
- ok: false,
72
- openLogin: true,
73
- message: "Log In to continue using the service",
74
- },
75
- { status: 429 }
76
- );
77
- }
78
-
79
- token = process.env.DEFAULT_HF_TOKEN as string;
80
- billTo = "huggingface";
81
- }
82
-
83
- try {
84
- const encoder = new TextEncoder();
85
- const stream = new TransformStream();
86
- const writer = stream.writable.getWriter();
87
-
88
- const response = new NextResponse(stream.readable, {
89
- headers: {
90
- "Content-Type": "text/plain; charset=utf-8",
91
- "Cache-Control": "no-cache",
92
- Connection: "keep-alive",
93
- },
94
- });
95
-
96
- (async () => {
97
- try {
98
- const client = new InferenceClient(token);
99
-
100
- const systemPrompt = selectedModel.value.includes('MiniMax')
101
- ? INITIAL_SYSTEM_PROMPT_LIGHT
102
- : INITIAL_SYSTEM_PROMPT;
103
-
104
- const userPrompt = prompt;
105
-
106
- const chatCompletion = client.chatCompletionStream(
107
- {
108
- model: selectedModel.value + (provider !== "auto" ? `:${provider}` : ""),
109
- messages: [
110
- {
111
- role: "system",
112
- content: systemPrompt,
113
- },
114
- ...(redesignMarkdown ? [{
115
- role: "assistant",
116
- content: `User will ask you to redesign the site based on this markdown. Use the same images as the site, but you can improve the content and the design. Here is the markdown: ${redesignMarkdown}`
117
- }] : []),
118
- {
119
- role: "user",
120
- content: userPrompt + (enhancedSettings.isActive ? `1. I want to use the following primary color: ${enhancedSettings.primaryColor} (eg: bg-${enhancedSettings.primaryColor}-500).
121
- 2. I want to use the following secondary color: ${enhancedSettings.secondaryColor} (eg: bg-${enhancedSettings.secondaryColor}-500).
122
- 3. I want to use the following theme: ${enhancedSettings.theme} mode.` : "")
123
- },
124
- ],
125
- ...(selectedModel.top_k ? { top_k: selectedModel.top_k } : {}),
126
- ...(selectedModel.temperature ? { temperature: selectedModel.temperature } : {}),
127
- ...(selectedModel.top_p ? { top_p: selectedModel.top_p } : {}),
128
- },
129
- billTo ? { billTo } : {}
130
- );
131
-
132
- while (true) {
133
- const { done, value } = await chatCompletion.next()
134
- if (done) {
135
- break;
136
- }
137
-
138
- const chunk = value.choices[0]?.delta?.content;
139
- if (chunk) {
140
- await writer.write(encoder.encode(chunk));
141
- }
142
- }
143
-
144
- await writer.close();
145
- } catch (error: any) {
146
- if (error.message?.includes("exceeded your monthly included credits")) {
147
- await writer.write(
148
- encoder.encode(
149
- JSON.stringify({
150
- ok: false,
151
- openProModal: true,
152
- message: error.message,
153
- })
154
- )
155
- );
156
- } else if (error?.message?.includes("inference provider information")) {
157
- await writer.write(
158
- encoder.encode(
159
- JSON.stringify({
160
- ok: false,
161
- openSelectProvider: true,
162
- message: error.message,
163
- })
164
- )
165
- );
166
- }
167
- else {
168
- await writer.write(
169
- encoder.encode(
170
- JSON.stringify({
171
- ok: false,
172
- message:
173
- error.message ||
174
- "An error occurred while processing your request.",
175
- })
176
- )
177
- );
178
- }
179
- } finally {
180
- try {
181
- await writer?.close();
182
- } catch {
183
- }
184
- }
185
- })();
186
-
187
- return response;
188
- } catch (error: any) {
189
- return NextResponse.json(
190
- {
191
- ok: false,
192
- openSelectProvider: true,
193
- message:
194
- error?.message || "An error occurred while processing your request.",
195
- },
196
- { status: 500 }
197
- );
198
- }
199
- }
200
-
201
- export async function PUT(request: NextRequest) {
202
- const user = await isAuthenticated();
203
- if (user instanceof NextResponse || !user) {
204
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
205
- }
206
-
207
- const authHeaders = await headers();
208
-
209
- const body = await request.json();
210
- const { prompt, provider, selectedElementHtml, model, pages, files, repoId, isNew } =
211
- body;
212
-
213
- if (!prompt || pages.length === 0) {
214
- return NextResponse.json(
215
- { ok: false, error: "Missing required fields" },
216
- { status: 400 }
217
- );
218
- }
219
-
220
- const selectedModel = MODELS.find(
221
- (m) => m.value === model || m.label === model
222
- );
223
- if (!selectedModel) {
224
- return NextResponse.json(
225
- { ok: false, error: "Invalid model selected" },
226
- { status: 400 }
227
- );
228
- }
229
-
230
- let token = user.token as string;
231
- let billTo: string | null = null;
232
-
233
- /**
234
- * Handle local usage token, this bypass the need for a user token
235
- * and allows local testing without authentication.
236
- * This is useful for development and testing purposes.
237
- */
238
- if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
239
- token = process.env.HF_TOKEN;
240
- }
241
-
242
- const ip = authHeaders.get("x-forwarded-for")?.includes(",")
243
- ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
244
- : authHeaders.get("x-forwarded-for");
245
-
246
- if (!token || token === "null" || token === "") {
247
- ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
248
- if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
249
- return NextResponse.json(
250
- {
251
- ok: false,
252
- openLogin: true,
253
- message: "Log In to continue using the service",
254
- },
255
- { status: 429 }
256
- );
257
- }
258
-
259
- token = process.env.DEFAULT_HF_TOKEN as string;
260
- billTo = "huggingface";
261
- }
262
-
263
- try {
264
- const encoder = new TextEncoder();
265
- const stream = new TransformStream();
266
- const writer = stream.writable.getWriter();
267
-
268
- const response = new NextResponse(stream.readable, {
269
- headers: {
270
- "Content-Type": "text/plain; charset=utf-8",
271
- "Cache-Control": "no-cache",
272
- Connection: "keep-alive",
273
- },
274
- });
275
-
276
- (async () => {
277
- try {
278
- const client = new InferenceClient(token);
279
-
280
- const basePrompt = selectedModel.value.includes('MiniMax')
281
- ? FOLLOW_UP_SYSTEM_PROMPT_LIGHT
282
- : FOLLOW_UP_SYSTEM_PROMPT;
283
- const systemPrompt = basePrompt + (isNew ? PROMPT_FOR_PROJECT_NAME : "");
284
- const userContext = "You are modifying the HTML file based on the user's request.";
285
-
286
- const allPages = pages || [];
287
- const pagesContext = allPages
288
- .map((p: Page) => `- ${p.path}\n${p.html}`)
289
- .join("\n\n");
290
-
291
- const assistantContext = `${selectedElementHtml
292
- ? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\` Could be in multiple pages, if so, update all the pages.`
293
- : ""
294
- }. Current pages (${allPages.length} total): ${pagesContext}. ${files?.length > 0 ? `Available images: ${files?.map((f: string) => f).join(', ')}.` : ""}`;
295
-
296
- const chatCompletion = client.chatCompletionStream(
297
- {
298
- model: selectedModel.value + (provider !== "auto" ? `:${provider}` : ""),
299
- messages: [
300
- {
301
- role: "system",
302
- content: systemPrompt,
303
- },
304
- {
305
- role: "user",
306
- content: userContext,
307
- },
308
- {
309
- role: "assistant",
310
- content: assistantContext,
311
- },
312
- {
313
- role: "user",
314
- content: prompt,
315
- },
316
- ],
317
- ...(selectedModel.top_k ? { top_k: selectedModel.top_k } : {}),
318
- ...(selectedModel.temperature ? { temperature: selectedModel.temperature } : {}),
319
- ...(selectedModel.top_p ? { top_p: selectedModel.top_p } : {}),
320
- },
321
- billTo ? { billTo } : {}
322
- );
323
-
324
- // Stream the response chunks to the client
325
- while (true) {
326
- const { done, value } = await chatCompletion.next();
327
- if (done) {
328
- break;
329
- }
330
-
331
- const chunk = value.choices[0]?.delta?.content;
332
- if (chunk) {
333
- await writer.write(encoder.encode(chunk));
334
- }
335
- }
336
-
337
- await writer.write(encoder.encode(`\n___METADATA_START___\n${JSON.stringify({
338
- repoId,
339
- isNew,
340
- userName: user.name,
341
- })}\n___METADATA_END___\n`));
342
-
343
- await writer.close();
344
- } catch (error: any) {
345
- if (error.message?.includes("exceeded your monthly included credits")) {
346
- await writer.write(
347
- encoder.encode(
348
- JSON.stringify({
349
- ok: false,
350
- openProModal: true,
351
- message: error.message,
352
- })
353
- )
354
- );
355
- } else if (error?.message?.includes("inference provider information")) {
356
- await writer.write(
357
- encoder.encode(
358
- JSON.stringify({
359
- ok: false,
360
- openSelectProvider: true,
361
- message: error.message,
362
- })
363
- )
364
- );
365
- } else {
366
- await writer.write(
367
- encoder.encode(
368
- JSON.stringify({
369
- ok: false,
370
- message:
371
- error.message ||
372
- "An error occurred while processing your request.",
373
- })
374
- )
375
- );
376
- }
377
- } finally {
378
- try {
379
- await writer?.close();
380
- } catch {
381
- // ignore
382
- }
383
- }
384
- })();
385
-
386
- return response;
387
- } catch (error: any) {
388
- return NextResponse.json(
389
- {
390
- ok: false,
391
- openSelectProvider: true,
392
- message:
393
- error.message || "An error occurred while processing your request.",
394
- },
395
- { status: 500 }
396
- );
397
- }
398
- }
399
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/auth/login-url/route.ts DELETED
@@ -1,21 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
-
3
- export async function GET(req: NextRequest) {
4
- const host = req.headers.get("host") ?? "localhost:3000";
5
-
6
- let url: string;
7
- if (host.includes("localhost")) {
8
- url = host;
9
- } else {
10
- url = "huggingface.co";
11
- }
12
-
13
- const redirect_uri =
14
- `${host.includes("localhost") ? "http://" : "https://"}` +
15
- url +
16
- "/deepsite/auth/callback";
17
-
18
- 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`;
19
-
20
- return NextResponse.json({ loginUrl: loginRedirectUrl });
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/auth/logout/route.ts DELETED
@@ -1,25 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import MY_TOKEN_KEY from "@/lib/get-cookie-name";
3
-
4
- export async function POST() {
5
- const cookieName = MY_TOKEN_KEY();
6
- const isProduction = process.env.NODE_ENV === "production";
7
-
8
- const response = NextResponse.json(
9
- { message: "Logged out successfully" },
10
- { status: 200 }
11
- );
12
-
13
- // Clear the HTTP-only cookie
14
- const cookieOptions = [
15
- `${cookieName}=`,
16
- "Max-Age=0",
17
- "Path=/",
18
- "HttpOnly",
19
- ...(isProduction ? ["Secure", "SameSite=None"] : ["SameSite=Lax"])
20
- ].join("; ");
21
-
22
- response.headers.set("Set-Cookie", cookieOptions);
23
-
24
- return response;
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/auth/route.ts DELETED
@@ -1,86 +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
-
26
- const url = host.includes("/spaces/enzostvs")
27
- ? "huggingface.co/deepsite"
28
- : host;
29
- const redirect_uri =
30
- `${host.includes("localhost") ? "http://" : "https://"}` +
31
- url +
32
- "/deepsite/auth/callback";
33
- const request_auth = await fetch("https://huggingface.co/oauth/token", {
34
- method: "POST",
35
- headers: {
36
- "Content-Type": "application/x-www-form-urlencoded",
37
- Authorization,
38
- },
39
- body: new URLSearchParams({
40
- grant_type: "authorization_code",
41
- code,
42
- redirect_uri,
43
- }),
44
- });
45
-
46
- const response = await request_auth.json();
47
- if (!response.access_token) {
48
- return NextResponse.json(
49
- { error: "Failed to retrieve access token" },
50
- {
51
- status: 400,
52
- headers: {
53
- "Content-Type": "application/json",
54
- },
55
- }
56
- );
57
- }
58
-
59
- const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
60
- headers: {
61
- Authorization: `Bearer ${response.access_token}`,
62
- },
63
- });
64
-
65
- if (!userResponse.ok) {
66
- return NextResponse.json(
67
- { user: null, errCode: userResponse.status },
68
- { status: userResponse.status }
69
- );
70
- }
71
- const user = await userResponse.json();
72
-
73
- return NextResponse.json(
74
- {
75
- access_token: response.access_token,
76
- expires_in: response.expires_in,
77
- user,
78
- },
79
- {
80
- status: 200,
81
- headers: {
82
- "Content-Type": "application/json",
83
- },
84
- }
85
- );
86
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/mcp/route.ts DELETED
@@ -1,435 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, createRepo, uploadFiles, spaceInfo, listCommits } from "@huggingface/hub";
3
- import { COLORS } from "@/lib/utils";
4
- import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
5
- import { Commit, Page } from "@/types";
6
-
7
- // Timeout configuration (in milliseconds)
8
- const OPERATION_TIMEOUT = 120000; // 2 minutes for HF operations
9
-
10
- // Extend the maximum execution time for this route
11
- export const maxDuration = 180; // 3 minutes
12
-
13
- // Utility function to wrap promises with timeout
14
- async function withTimeout<T>(
15
- promise: Promise<T>,
16
- timeoutMs: number,
17
- errorMessage: string = "Operation timed out"
18
- ): Promise<T> {
19
- let timeoutId: NodeJS.Timeout;
20
-
21
- const timeoutPromise = new Promise<never>((_, reject) => {
22
- timeoutId = setTimeout(() => {
23
- reject(new Error(errorMessage));
24
- }, timeoutMs);
25
- });
26
-
27
- try {
28
- const result = await Promise.race([promise, timeoutPromise]);
29
- clearTimeout(timeoutId!);
30
- return result;
31
- } catch (error) {
32
- clearTimeout(timeoutId!);
33
- throw error;
34
- }
35
- }
36
-
37
- interface MCPRequest {
38
- jsonrpc: "2.0";
39
- id: number | string;
40
- method: string;
41
- params?: any;
42
- }
43
-
44
- interface MCPResponse {
45
- jsonrpc: "2.0";
46
- id: number | string;
47
- result?: any;
48
- error?: {
49
- code: number;
50
- message: string;
51
- data?: any;
52
- };
53
- }
54
-
55
- interface CreateProjectParams {
56
- title?: string;
57
- pages: Page[];
58
- prompt?: string;
59
- hf_token?: string; // Optional - can come from header instead
60
- }
61
-
62
- // MCP Server over HTTP
63
- export async function POST(req: NextRequest) {
64
- try {
65
- const body: MCPRequest = await req.json();
66
- const { jsonrpc, id, method, params } = body;
67
-
68
- // Validate JSON-RPC 2.0 format
69
- if (jsonrpc !== "2.0") {
70
- return NextResponse.json({
71
- jsonrpc: "2.0",
72
- id: id || null,
73
- error: {
74
- code: -32600,
75
- message: "Invalid Request: jsonrpc must be '2.0'",
76
- },
77
- });
78
- }
79
-
80
- let response: MCPResponse;
81
-
82
- switch (method) {
83
- case "initialize":
84
- response = {
85
- jsonrpc: "2.0",
86
- id,
87
- result: {
88
- protocolVersion: "2024-11-05",
89
- capabilities: {
90
- tools: {},
91
- },
92
- serverInfo: {
93
- name: "deepsite-mcp-server",
94
- version: "1.0.0",
95
- },
96
- },
97
- };
98
- break;
99
-
100
- case "tools/list":
101
- response = {
102
- jsonrpc: "2.0",
103
- id,
104
- result: {
105
- tools: [
106
- {
107
- name: "create_project",
108
- description: `Create a new DeepSite project. This will create a new Hugging Face Space with your HTML/CSS/JS files.
109
-
110
- Example usage:
111
- - Create a simple website with HTML, CSS, and JavaScript files
112
- - Each page needs a 'path' (filename like "index.html", "styles.css", "script.js") and 'html' (the actual content)
113
- - The title will be formatted to a valid repository name
114
- - Returns the project URL and metadata`,
115
- inputSchema: {
116
- type: "object",
117
- properties: {
118
- title: {
119
- type: "string",
120
- description: "Project title (optional, defaults to 'DeepSite Project'). Will be formatted to a valid repo name.",
121
- },
122
- pages: {
123
- type: "array",
124
- description: "Array of files to include in the project",
125
- items: {
126
- type: "object",
127
- properties: {
128
- path: {
129
- type: "string",
130
- description: "File path (e.g., 'index.html', 'styles.css', 'script.js')",
131
- },
132
- html: {
133
- type: "string",
134
- description: "File content",
135
- },
136
- },
137
- required: ["path", "html"],
138
- },
139
- },
140
- prompt: {
141
- type: "string",
142
- description: "Optional prompt/description for the commit message",
143
- },
144
- hf_token: {
145
- type: "string",
146
- description: "Hugging Face API token (optional if provided via Authorization header)",
147
- },
148
- },
149
- required: ["pages"],
150
- },
151
- },
152
- ],
153
- },
154
- };
155
- break;
156
-
157
- case "tools/call":
158
- const { name, arguments: toolArgs } = params;
159
-
160
- if (name === "create_project") {
161
- try {
162
- // Extract token from Authorization header if present
163
- const authHeader = req.headers.get("authorization");
164
- let hf_token = toolArgs.hf_token;
165
-
166
- if (authHeader && authHeader.startsWith("Bearer ")) {
167
- hf_token = authHeader.substring(7); // Remove "Bearer " prefix
168
- }
169
-
170
- const result = await handleCreateProject({
171
- ...toolArgs,
172
- hf_token,
173
- } as CreateProjectParams);
174
- response = {
175
- jsonrpc: "2.0",
176
- id,
177
- result,
178
- };
179
- } catch (error: any) {
180
- response = {
181
- jsonrpc: "2.0",
182
- id,
183
- error: {
184
- code: -32000,
185
- message: error.message || "Failed to create project",
186
- data: error.data,
187
- },
188
- };
189
- }
190
- } else {
191
- response = {
192
- jsonrpc: "2.0",
193
- id,
194
- error: {
195
- code: -32601,
196
- message: `Unknown tool: ${name}`,
197
- },
198
- };
199
- }
200
- break;
201
-
202
- default:
203
- response = {
204
- jsonrpc: "2.0",
205
- id,
206
- error: {
207
- code: -32601,
208
- message: `Method not found: ${method}`,
209
- },
210
- };
211
- }
212
-
213
- return NextResponse.json(response);
214
- } catch (error: any) {
215
- return NextResponse.json({
216
- jsonrpc: "2.0",
217
- id: null,
218
- error: {
219
- code: -32700,
220
- message: "Parse error",
221
- data: error.message,
222
- },
223
- });
224
- }
225
- }
226
-
227
- // Handle OPTIONS for CORS
228
- export async function OPTIONS() {
229
- return new NextResponse(null, {
230
- status: 200,
231
- headers: {
232
- "Access-Control-Allow-Origin": "*",
233
- "Access-Control-Allow-Methods": "POST, OPTIONS",
234
- "Access-Control-Allow-Headers": "Content-Type",
235
- },
236
- });
237
- }
238
-
239
- async function handleCreateProject(params: CreateProjectParams) {
240
- const { title: titleFromRequest, pages, prompt, hf_token } = params;
241
-
242
- // Validate required parameters
243
- if (!hf_token || typeof hf_token !== "string") {
244
- throw new Error("hf_token is required and must be a string");
245
- }
246
-
247
- if (!pages || !Array.isArray(pages) || pages.length === 0) {
248
- throw new Error("At least one page is required");
249
- }
250
-
251
- // Validate that each page has required fields
252
- for (const page of pages) {
253
- if (!page.path || !page.html) {
254
- throw new Error("Each page must have 'path' and 'html' properties");
255
- }
256
- }
257
-
258
- // Get user info from HF token
259
- let username: string;
260
- try {
261
- const userResponse = await withTimeout(
262
- fetch("https://huggingface.co/api/whoami-v2", {
263
- headers: {
264
- Authorization: `Bearer ${hf_token}`,
265
- },
266
- }),
267
- 30000, // 30 seconds for authentication
268
- "Authentication timeout: Unable to verify Hugging Face token"
269
- );
270
-
271
- if (!userResponse.ok) {
272
- throw new Error("Invalid Hugging Face token");
273
- }
274
-
275
- const userData = await userResponse.json();
276
- username = userData.name;
277
- } catch (error: any) {
278
- if (error.message?.includes('timeout')) {
279
- throw new Error(`Authentication timeout: ${error.message}`);
280
- }
281
- throw new Error(`Authentication failed: ${error.message}`);
282
- }
283
-
284
- const title = titleFromRequest ?? "DeepSite Project";
285
-
286
- const formattedTitle = title
287
- .toLowerCase()
288
- .replace(/[^a-z0-9]+/g, "-")
289
- .split("-")
290
- .filter(Boolean)
291
- .join("-")
292
- .slice(0, 96);
293
-
294
- const repo: RepoDesignation = {
295
- type: "space",
296
- name: `${username}/${formattedTitle}`,
297
- };
298
-
299
- const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
300
- const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
301
- const README = `---
302
- title: ${title}
303
- colorFrom: ${colorFrom}
304
- colorTo: ${colorTo}
305
- emoji: 🐳
306
- sdk: static
307
- pinned: false
308
- tags:
309
- - deepsite-v3
310
- ---
311
-
312
- # Welcome to your new DeepSite project!
313
- This project was created with [DeepSite](https://huggingface.co/deepsite).
314
- `;
315
-
316
- const files: File[] = [];
317
- const readmeFile = new File([README], "README.md", { type: "text/markdown" });
318
- files.push(readmeFile);
319
-
320
- pages.forEach((page: Page) => {
321
- // Determine MIME type based on file extension
322
- let mimeType = "text/html";
323
- if (page.path.endsWith(".css")) {
324
- mimeType = "text/css";
325
- } else if (page.path.endsWith(".js")) {
326
- mimeType = "text/javascript";
327
- } else if (page.path.endsWith(".json")) {
328
- mimeType = "application/json";
329
- }
330
-
331
- // Inject the DeepSite badge script into index pages only
332
- const content = mimeType === "text/html" && isIndexPage(page.path)
333
- ? injectDeepSiteBadge(page.html)
334
- : page.html;
335
- const file = new File([content], page.path, { type: mimeType });
336
- files.push(file);
337
- });
338
-
339
- try {
340
- const { repoUrl } = await withTimeout(
341
- createRepo({
342
- repo,
343
- accessToken: hf_token,
344
- }),
345
- 60000, // 1 minute for repo creation
346
- "Timeout creating repository. Please try again."
347
- );
348
-
349
- const commitTitle = !prompt || prompt.trim() === "" ? "Initial project creation via MCP" : prompt;
350
-
351
- await withTimeout(
352
- uploadFiles({
353
- repo,
354
- files,
355
- accessToken: hf_token,
356
- commitTitle,
357
- }),
358
- OPERATION_TIMEOUT,
359
- "Timeout uploading files. The repository was created but files may not have been uploaded."
360
- );
361
-
362
- const path = repoUrl.split("/").slice(-2).join("/");
363
-
364
- const commits: Commit[] = [];
365
- const commitIterator = listCommits({ repo, accessToken: hf_token });
366
-
367
- // Wrap the commit listing with a timeout
368
- const commitTimeout = new Promise<void>((_, reject) => {
369
- setTimeout(() => reject(new Error("Timeout listing commits")), 30000);
370
- });
371
-
372
- try {
373
- await Promise.race([
374
- (async () => {
375
- for await (const commit of commitIterator) {
376
- if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Promote version")) {
377
- continue;
378
- }
379
- commits.push({
380
- title: commit.title,
381
- oid: commit.oid,
382
- date: commit.date,
383
- });
384
- }
385
- })(),
386
- commitTimeout
387
- ]);
388
- } catch (error: any) {
389
- // If listing commits times out, continue with empty commits array
390
- console.error("Failed to list commits:", error.message);
391
- }
392
-
393
- const space = await withTimeout(
394
- spaceInfo({
395
- name: repo.name,
396
- accessToken: hf_token,
397
- }),
398
- 30000, // 30 seconds for space info
399
- "Timeout fetching space information"
400
- );
401
-
402
- const projectUrl = `https://huggingface.co/deepsite/${path}`;
403
- const spaceUrl = `https://huggingface.co/spaces/${path}`;
404
- const liveUrl = `https://${username}-${formattedTitle}.hf.space`;
405
-
406
- return {
407
- content: [
408
- {
409
- type: "text",
410
- text: JSON.stringify(
411
- {
412
- success: true,
413
- message: "Project created successfully!",
414
- projectUrl,
415
- spaceUrl,
416
- liveUrl,
417
- spaceId: space.name,
418
- projectId: space.id,
419
- files: pages.map((p) => p.path),
420
- updatedAt: space.updatedAt,
421
- },
422
- null,
423
- 2
424
- ),
425
- },
426
- ],
427
- };
428
- } catch (err: any) {
429
- if (err.message?.includes('timeout') || err.message?.includes('Timeout')) {
430
- throw new Error(err.message || "Operation timed out. Please try again.");
431
- }
432
- throw new Error(err.message || "Failed to create project");
433
- }
434
- }
435
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/promote/route.ts DELETED
@@ -1,230 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, listFiles, spaceInfo, uploadFiles, deleteFiles, downloadFile } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import { Page } from "@/types";
6
-
7
- export async function POST(
8
- req: NextRequest,
9
- { params }: {
10
- params: Promise<{
11
- namespace: string;
12
- repoId: string;
13
- commitId: string;
14
- }>
15
- }
16
- ) {
17
- const user = await isAuthenticated();
18
-
19
- if (user instanceof NextResponse || !user) {
20
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
21
- }
22
-
23
- const param = await params;
24
- const { namespace, repoId, commitId } = param;
25
-
26
- try {
27
- const repo: RepoDesignation = {
28
- type: "space",
29
- name: `${namespace}/${repoId}`,
30
- };
31
-
32
- const space = await spaceInfo({
33
- name: `${namespace}/${repoId}`,
34
- accessToken: user.token as string,
35
- additionalFields: ["author"],
36
- });
37
-
38
- if (!space || space.sdk !== "static") {
39
- return NextResponse.json(
40
- { ok: false, error: "Space is not a static space." },
41
- { status: 404 }
42
- );
43
- }
44
-
45
- if (space.author !== user.name) {
46
- return NextResponse.json(
47
- { ok: false, error: "Space does not belong to the authenticated user." },
48
- { status: 403 }
49
- );
50
- }
51
-
52
- const files: File[] = [];
53
- const pages: Page[] = [];
54
- const mediaFiles: string[] = [];
55
- const allowedExtensions = ["html", "md", "css", "js", "json", "txt"];
56
- const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif", "mp4", "webm", "ogg", "avi", "mov", "mp3", "wav", "ogg", "aac", "m4a"];
57
- const commitFilePaths: Set<string> = new Set();
58
-
59
- for await (const fileInfo of listFiles({
60
- repo,
61
- accessToken: user.token as string,
62
- revision: commitId,
63
- })) {
64
- const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase();
65
-
66
- if (fileInfo.path.endsWith(".html") || fileInfo.path.endsWith(".css") || fileInfo.path.endsWith(".js") || fileInfo.path.endsWith(".json")) {
67
- commitFilePaths.add(fileInfo.path);
68
-
69
- const blob = await downloadFile({
70
- repo,
71
- accessToken: user.token as string,
72
- path: fileInfo.path,
73
- revision: commitId,
74
- raw: true
75
- });
76
- const content = await blob?.text();
77
-
78
- if (content) {
79
- let mimeType = "text/plain";
80
-
81
- switch (fileExtension) {
82
- case "html":
83
- mimeType = "text/html";
84
- break;
85
- case "css":
86
- mimeType = "text/css";
87
- break;
88
- case "js":
89
- mimeType = "application/javascript";
90
- break;
91
- case "json":
92
- mimeType = "application/json";
93
- break;
94
- }
95
-
96
- if (fileInfo.path === "index.html") {
97
- pages.unshift({
98
- path: fileInfo.path,
99
- html: content,
100
- });
101
- } else {
102
- pages.push({
103
- path: fileInfo.path,
104
- html: content,
105
- });
106
- }
107
-
108
- const file = new File([content], fileInfo.path, { type: mimeType });
109
- files.push(file);
110
- }
111
- }
112
- else if (fileInfo.type === "directory" && (["videos", "images", "audio"].includes(fileInfo.path) || fileInfo.path === "components")) {
113
- for await (const subFileInfo of listFiles({
114
- repo,
115
- accessToken: user.token as string,
116
- revision: commitId,
117
- path: fileInfo.path,
118
- })) {
119
- if (subFileInfo.path.includes("components")) {
120
- commitFilePaths.add(subFileInfo.path);
121
- const blob = await downloadFile({
122
- repo,
123
- accessToken: user.token as string,
124
- path: subFileInfo.path,
125
- revision: commitId,
126
- raw: true
127
- });
128
- const content = await blob?.text();
129
-
130
- if (content) {
131
- pages.push({
132
- path: subFileInfo.path,
133
- html: content,
134
- });
135
-
136
- const file = new File([content], subFileInfo.path, { type: "text/html" });
137
- files.push(file);
138
- }
139
- } else if (allowedFilesExtensions.includes(subFileInfo.path.split(".").pop() || "")) {
140
- commitFilePaths.add(subFileInfo.path);
141
- mediaFiles.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${subFileInfo.path}`);
142
- }
143
- }
144
- }
145
- else if (allowedExtensions.includes(fileExtension || "")) {
146
- commitFilePaths.add(fileInfo.path);
147
- }
148
- }
149
-
150
- const mainBranchFilePaths: Set<string> = new Set();
151
- for await (const fileInfo of listFiles({
152
- repo,
153
- accessToken: user.token as string,
154
- revision: "main",
155
- })) {
156
- const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase();
157
-
158
- if (allowedExtensions.includes(fileExtension || "")) {
159
- mainBranchFilePaths.add(fileInfo.path);
160
- }
161
- }
162
-
163
- const filesToDelete: string[] = [];
164
- for (const mainFilePath of mainBranchFilePaths) {
165
- if (!commitFilePaths.has(mainFilePath)) {
166
- filesToDelete.push(mainFilePath);
167
- }
168
- }
169
-
170
- if (files.length === 0 && filesToDelete.length === 0) {
171
- return NextResponse.json(
172
- { ok: false, error: "No files found in the specified commit and no files to delete" },
173
- { status: 404 }
174
- );
175
- }
176
-
177
- if (filesToDelete.length > 0) {
178
- await deleteFiles({
179
- repo,
180
- paths: filesToDelete,
181
- accessToken: user.token as string,
182
- commitTitle: `Removed files from promoting ${commitId.slice(0, 7)}`,
183
- commitDescription: `Removed files that don't exist in commit ${commitId}:\n${filesToDelete.map(path => `- ${path}`).join('\n')}`,
184
- });
185
- }
186
-
187
- if (files.length > 0) {
188
- await uploadFiles({
189
- repo,
190
- files,
191
- accessToken: user.token as string,
192
- commitTitle: `Promote version ${commitId.slice(0, 7)} to main`,
193
- commitDescription: `Promoted commit ${commitId} to main branch`,
194
- });
195
- }
196
-
197
- return NextResponse.json(
198
- {
199
- ok: true,
200
- message: "Version promoted successfully",
201
- promotedCommit: commitId,
202
- pages: pages,
203
- files: mediaFiles,
204
- },
205
- { status: 200 }
206
- );
207
-
208
- } catch (error: any) {
209
-
210
- // Handle specific HuggingFace API errors
211
- if (error.statusCode === 404) {
212
- return NextResponse.json(
213
- { ok: false, error: "Commit not found" },
214
- { status: 404 }
215
- );
216
- }
217
-
218
- if (error.statusCode === 403) {
219
- return NextResponse.json(
220
- { ok: false, error: "Access denied to repository" },
221
- { status: 403 }
222
- );
223
- }
224
-
225
- return NextResponse.json(
226
- { ok: false, error: error.message || "Failed to promote version" },
227
- { status: 500 }
228
- );
229
- }
230
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/route.ts DELETED
@@ -1,102 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, listFiles, spaceInfo, downloadFile } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import { Page } from "@/types";
6
-
7
- export async function GET(
8
- req: NextRequest,
9
- { params }: {
10
- params: Promise<{
11
- namespace: string;
12
- repoId: string;
13
- commitId: string;
14
- }>
15
- }
16
- ) {
17
- const user = await isAuthenticated();
18
-
19
- if (user instanceof NextResponse || !user) {
20
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
21
- }
22
-
23
- const param = await params;
24
- const { namespace, repoId, commitId } = param;
25
-
26
- try {
27
- const repo: RepoDesignation = {
28
- type: "space",
29
- name: `${namespace}/${repoId}`,
30
- };
31
-
32
- const space = await spaceInfo({
33
- name: `${namespace}/${repoId}`,
34
- accessToken: user.token as string,
35
- additionalFields: ["author"],
36
- });
37
-
38
- if (!space || space.sdk !== "static") {
39
- return NextResponse.json(
40
- { ok: false, error: "Space is not a static space." },
41
- { status: 404 }
42
- );
43
- }
44
-
45
- if (space.author !== user.name) {
46
- return NextResponse.json(
47
- { ok: false, error: "Space does not belong to the authenticated user." },
48
- { status: 403 }
49
- );
50
- }
51
-
52
- const pages: Page[] = [];
53
-
54
- for await (const fileInfo of listFiles({
55
- repo,
56
- accessToken: user.token as string,
57
- revision: commitId,
58
- })) {
59
- const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase();
60
-
61
- if (fileInfo.path.endsWith(".html") || fileInfo.path.endsWith(".css") || fileInfo.path.endsWith(".js") || fileInfo.path.endsWith(".json")) {
62
- const blob = await downloadFile({
63
- repo,
64
- accessToken: user.token as string,
65
- path: fileInfo.path,
66
- revision: commitId,
67
- raw: true
68
- });
69
- const content = await blob?.text();
70
-
71
- if (content) {
72
- if (fileInfo.path === "index.html") {
73
- pages.unshift({
74
- path: fileInfo.path,
75
- html: content,
76
- });
77
- } else {
78
- pages.push({
79
- path: fileInfo.path,
80
- html: content,
81
- });
82
- }
83
- }
84
- }
85
- }
86
-
87
- return NextResponse.json({
88
- ok: true,
89
- pages,
90
- });
91
- } catch (error: any) {
92
- console.error("Error fetching commit pages:", error);
93
- return NextResponse.json(
94
- {
95
- ok: false,
96
- error: error.message || "Failed to fetch commit pages",
97
- },
98
- { status: 500 }
99
- );
100
- }
101
- }
102
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/download/route.ts DELETED
@@ -1,105 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, listFiles, spaceInfo, downloadFile } from "@huggingface/hub";
3
- import JSZip from "jszip";
4
-
5
- import { isAuthenticated } from "@/lib/auth";
6
-
7
- export async function GET(
8
- req: NextRequest,
9
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
10
- ) {
11
- const user = await isAuthenticated();
12
-
13
- if (user instanceof NextResponse || !user) {
14
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
15
- }
16
-
17
- const param = await params;
18
- const { namespace, repoId } = param;
19
-
20
- try {
21
- const space = await spaceInfo({
22
- name: `${namespace}/${repoId}`,
23
- accessToken: user.token as string,
24
- additionalFields: ["author"],
25
- });
26
-
27
- if (!space || space.sdk !== "static") {
28
- return NextResponse.json(
29
- {
30
- ok: false,
31
- error: "Space is not a static space",
32
- },
33
- { status: 404 }
34
- );
35
- }
36
-
37
- if (space.author !== user.name) {
38
- return NextResponse.json(
39
- {
40
- ok: false,
41
- error: "Space does not belong to the authenticated user",
42
- },
43
- { status: 403 }
44
- );
45
- }
46
-
47
- const repo: RepoDesignation = {
48
- type: "space",
49
- name: `${namespace}/${repoId}`,
50
- };
51
-
52
- const zip = new JSZip();
53
-
54
- for await (const fileInfo of listFiles({
55
- repo,
56
- accessToken: user.token as string,
57
- recursive: true,
58
- })) {
59
- if (fileInfo.type === "directory" || fileInfo.path.startsWith(".")) {
60
- continue;
61
- }
62
-
63
- try {
64
- const blob = await downloadFile({
65
- repo,
66
- accessToken: user.token as string,
67
- path: fileInfo.path,
68
- raw: true
69
- });
70
-
71
- if (blob) {
72
- const arrayBuffer = await blob.arrayBuffer();
73
- zip.file(fileInfo.path, arrayBuffer);
74
- }
75
- } catch (error) {
76
- console.error(`Error downloading file ${fileInfo.path}:`, error);
77
- }
78
- }
79
-
80
- const zipBlob = await zip.generateAsync({
81
- type: "blob",
82
- compression: "DEFLATE",
83
- compressionOptions: {
84
- level: 6
85
- }
86
- });
87
-
88
- const projectName = `${namespace}-${repoId}`.replace(/[^a-zA-Z0-9-_]/g, '_');
89
- const filename = `${projectName}.zip`;
90
-
91
- return new NextResponse(zipBlob, {
92
- headers: {
93
- "Content-Type": "application/zip",
94
- "Content-Disposition": `attachment; filename="${filename}"`,
95
- "Content-Length": zipBlob.size.toString(),
96
- },
97
- });
98
- } catch (error: any) {
99
- return NextResponse.json(
100
- { ok: false, error: error.message || "Failed to create ZIP file" },
101
- { status: 500 }
102
- );
103
- }
104
- }
105
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/images/route.ts DELETED
@@ -1,125 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, spaceInfo, uploadFiles } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import Project from "@/models/Project";
6
- import dbConnect from "@/lib/mongodb";
7
-
8
- export async function POST(
9
- req: NextRequest,
10
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
11
- ) {
12
- try {
13
- const user = await isAuthenticated();
14
-
15
- if (user instanceof NextResponse || !user) {
16
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
17
- }
18
-
19
- const param = await params;
20
- const { namespace, repoId } = param;
21
-
22
- const space = await spaceInfo({
23
- name: `${namespace}/${repoId}`,
24
- accessToken: user.token as string,
25
- additionalFields: ["author"],
26
- });
27
-
28
- if (!space || space.sdk !== "static") {
29
- return NextResponse.json(
30
- { ok: false, error: "Space is not a static space." },
31
- { status: 404 }
32
- );
33
- }
34
-
35
- if (space.author !== user.name) {
36
- return NextResponse.json(
37
- { ok: false, error: "Space does not belong to the authenticated user." },
38
- { status: 403 }
39
- );
40
- }
41
-
42
- // Parse the FormData to get the media files
43
- const formData = await req.formData();
44
- const mediaFiles = formData.getAll("images") as File[];
45
-
46
- if (!mediaFiles || mediaFiles.length === 0) {
47
- return NextResponse.json(
48
- {
49
- ok: false,
50
- error: "At least one media file is required under the 'images' key",
51
- },
52
- { status: 400 }
53
- );
54
- }
55
-
56
- const files: File[] = [];
57
- for (const file of mediaFiles) {
58
- if (!(file instanceof File)) {
59
- return NextResponse.json(
60
- {
61
- ok: false,
62
- error: "Invalid file format - all items under 'images' key must be files",
63
- },
64
- { status: 400 }
65
- );
66
- }
67
-
68
- // Check if file is a supported media type
69
- const isImage = file.type.startsWith('image/');
70
- const isVideo = file.type.startsWith('video/');
71
- const isAudio = file.type.startsWith('audio/');
72
-
73
- if (!isImage && !isVideo && !isAudio) {
74
- return NextResponse.json(
75
- {
76
- ok: false,
77
- error: `File ${file.name} is not a supported media type (image, video, or audio)`,
78
- },
79
- { status: 400 }
80
- );
81
- }
82
-
83
- // Create File object with appropriate folder prefix
84
- let folderPrefix = 'images/';
85
- if (isVideo) {
86
- folderPrefix = 'videos/';
87
- } else if (isAudio) {
88
- folderPrefix = 'audio/';
89
- }
90
-
91
- const fileName = `${folderPrefix}${file.name}`;
92
- const processedFile = new File([file], fileName, { type: file.type });
93
- files.push(processedFile);
94
- }
95
-
96
- // Upload files to HuggingFace space
97
- const repo: RepoDesignation = {
98
- type: "space",
99
- name: `${namespace}/${repoId}`,
100
- };
101
-
102
- await uploadFiles({
103
- repo,
104
- files,
105
- accessToken: user.token as string,
106
- commitTitle: `Upload ${files.length} media file(s)`,
107
- });
108
-
109
- return NextResponse.json({
110
- ok: true,
111
- message: `Successfully uploaded ${files.length} media file(s) to ${namespace}/${repoId}/`,
112
- uploadedFiles: files.map((file) => `https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${file.name}`),
113
- }, { status: 200 });
114
-
115
- } catch (error) {
116
- console.error('Error uploading media files:', error);
117
- return NextResponse.json(
118
- {
119
- ok: false,
120
- error: "Failed to upload media files",
121
- },
122
- { status: 500 }
123
- );
124
- }
125
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/route.ts DELETED
@@ -1,197 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, spaceInfo, listFiles, deleteRepo, listCommits, downloadFile } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import { Commit, Page } from "@/types";
6
-
7
- export async function DELETE(
8
- req: NextRequest,
9
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
10
- ) {
11
- const user = await isAuthenticated();
12
-
13
- if (user instanceof NextResponse || !user) {
14
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
15
- }
16
-
17
- const param = await params;
18
- const { namespace, repoId } = param;
19
-
20
- try {
21
- const space = await spaceInfo({
22
- name: `${namespace}/${repoId}`,
23
- accessToken: user.token as string,
24
- additionalFields: ["author"],
25
- });
26
-
27
- if (!space || space.sdk !== "static") {
28
- return NextResponse.json(
29
- { ok: false, error: "Space is not a static space." },
30
- { status: 404 }
31
- );
32
- }
33
-
34
- if (space.author !== user.name) {
35
- return NextResponse.json(
36
- { ok: false, error: "Space does not belong to the authenticated user." },
37
- { status: 403 }
38
- );
39
- }
40
-
41
- const repo: RepoDesignation = {
42
- type: "space",
43
- name: `${namespace}/${repoId}`,
44
- };
45
-
46
- await deleteRepo({
47
- repo,
48
- accessToken: user.token as string,
49
- });
50
-
51
-
52
- return NextResponse.json({ ok: true }, { status: 200 });
53
- } catch (error: any) {
54
- return NextResponse.json(
55
- { ok: false, error: error.message },
56
- { status: 500 }
57
- );
58
- }
59
- }
60
-
61
- export async function GET(
62
- req: NextRequest,
63
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
64
- ) {
65
- const user = await isAuthenticated();
66
-
67
- if (user instanceof NextResponse || !user) {
68
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
69
- }
70
-
71
- const param = await params;
72
- const { namespace, repoId } = param;
73
-
74
- try {
75
- const space = await spaceInfo({
76
- name: namespace + "/" + repoId,
77
- accessToken: user.token as string,
78
- additionalFields: ["author"],
79
- });
80
-
81
- if (!space || space.sdk !== "static") {
82
- return NextResponse.json(
83
- {
84
- ok: false,
85
- error: "Space is not a static space",
86
- },
87
- { status: 404 }
88
- );
89
- }
90
- if (space.author !== user.name) {
91
- return NextResponse.json(
92
- {
93
- ok: false,
94
- error: "Space does not belong to the authenticated user",
95
- },
96
- { status: 403 }
97
- );
98
- }
99
-
100
- const repo: RepoDesignation = {
101
- type: "space",
102
- name: `${namespace}/${repoId}`,
103
- };
104
-
105
- const htmlFiles: Page[] = [];
106
- const files: string[] = [];
107
-
108
- const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif", "mp4", "webm", "ogg", "avi", "mov", "mp3", "wav", "ogg", "aac", "m4a"];
109
-
110
- for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) {
111
- if (fileInfo.path.endsWith(".html") || fileInfo.path.endsWith(".css") || fileInfo.path.endsWith(".js") || fileInfo.path.endsWith(".json")) {
112
- const blob = await downloadFile({ repo, accessToken: user.token as string, path: fileInfo.path, raw: true });
113
- const html = await blob?.text();
114
- if (!html) {
115
- continue;
116
- }
117
- if (fileInfo.path === "index.html") {
118
- htmlFiles.unshift({
119
- path: fileInfo.path,
120
- html,
121
- });
122
- } else {
123
- htmlFiles.push({
124
- path: fileInfo.path,
125
- html,
126
- });
127
- }
128
- }
129
- if (fileInfo.type === "directory" && (["videos", "images", "audio"].includes(fileInfo.path) || fileInfo.path === "components")) {
130
- for await (const subFileInfo of listFiles({repo, accessToken: user.token as string, path: fileInfo.path})) {
131
- if (subFileInfo.path.includes("components")) {
132
- const blob = await downloadFile({ repo, accessToken: user.token as string, path: subFileInfo.path, raw: true });
133
- const html = await blob?.text();
134
- if (!html) {
135
- continue;
136
- }
137
- htmlFiles.push({
138
- path: subFileInfo.path,
139
- html,
140
- });
141
- } else if (allowedFilesExtensions.includes(subFileInfo.path.split(".").pop() || "")) {
142
- files.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${subFileInfo.path}`);
143
- }
144
- }
145
- }
146
- }
147
- const commits: Commit[] = [];
148
- for await (const commit of listCommits({ repo, accessToken: user.token as string })) {
149
- if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Removed files from promoting")) {
150
- continue;
151
- }
152
- commits.push({
153
- title: commit.title,
154
- oid: commit.oid,
155
- date: commit.date,
156
- });
157
- }
158
-
159
- if (htmlFiles.length === 0) {
160
- return NextResponse.json(
161
- {
162
- ok: false,
163
- error: "No HTML files found",
164
- },
165
- { status: 404 }
166
- );
167
- }
168
- return NextResponse.json(
169
- {
170
- project: {
171
- id: space.id,
172
- space_id: space.name,
173
- private: space.private,
174
- _updatedAt: space.updatedAt,
175
- },
176
- pages: htmlFiles,
177
- files,
178
- commits,
179
- ok: true,
180
- },
181
- { status: 200 }
182
- );
183
-
184
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
- } catch (error: any) {
186
- if (error.statusCode === 404) {
187
- return NextResponse.json(
188
- { error: "Space not found", ok: false },
189
- { status: 404 }
190
- );
191
- }
192
- return NextResponse.json(
193
- { error: error.message, ok: false },
194
- { status: 500 }
195
- );
196
- }
197
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/save/route.ts DELETED
@@ -1,72 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { uploadFiles } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import { Page } from "@/types";
6
-
7
- export async function PUT(
8
- req: NextRequest,
9
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
10
- ) {
11
- const user = await isAuthenticated();
12
- if (user instanceof NextResponse || !user) {
13
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
14
- }
15
-
16
- const param = await params;
17
- const { namespace, repoId } = param;
18
- const { pages, commitTitle = "Manual changes saved" } = await req.json();
19
-
20
- if (!pages || !Array.isArray(pages) || pages.length === 0) {
21
- return NextResponse.json(
22
- { ok: false, error: "Pages are required" },
23
- { status: 400 }
24
- );
25
- }
26
-
27
- try {
28
- // Prepare files for upload
29
- const files: File[] = [];
30
- pages.forEach((page: Page) => {
31
- // Determine MIME type based on file extension
32
- let mimeType = "text/html";
33
- if (page.path.endsWith(".css")) {
34
- mimeType = "text/css";
35
- } else if (page.path.endsWith(".js")) {
36
- mimeType = "text/javascript";
37
- } else if (page.path.endsWith(".json")) {
38
- mimeType = "application/json";
39
- }
40
- const file = new File([page.html], page.path, { type: mimeType });
41
- files.push(file);
42
- });
43
-
44
- const response = await uploadFiles({
45
- repo: {
46
- type: "space",
47
- name: `${namespace}/${repoId}`,
48
- },
49
- files,
50
- commitTitle,
51
- accessToken: user.token as string,
52
- });
53
-
54
- return NextResponse.json({
55
- ok: true,
56
- pages,
57
- commit: {
58
- ...response.commit,
59
- title: commitTitle,
60
- }
61
- });
62
- } catch (error: any) {
63
- console.error("Error saving manual changes:", error);
64
- return NextResponse.json(
65
- {
66
- ok: false,
67
- error: error.message || "Failed to save changes",
68
- },
69
- { status: 500 }
70
- );
71
- }
72
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/[namespace]/[repoId]/update/route.ts DELETED
@@ -1,141 +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 { Page } from "@/types";
6
- import { COLORS } from "@/lib/utils";
7
- import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
8
- import { pagesToFiles } from "@/lib/format-ai-response";
9
-
10
- /**
11
- * UPDATE route - for updating existing projects or creating new ones after AI streaming
12
- * This route handles the HuggingFace upload after client-side AI response processing
13
- */
14
- export async function PUT(
15
- req: NextRequest,
16
- { params }: { params: Promise<{ namespace: string; repoId: string }> }
17
- ) {
18
- const user = await isAuthenticated();
19
- if (user instanceof NextResponse || !user) {
20
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
21
- }
22
-
23
- const param = await params;
24
- let { namespace, repoId } = param;
25
- const { pages, commitTitle = "AI-generated changes", isNew, projectName } = await req.json();
26
-
27
- if (!pages || !Array.isArray(pages) || pages.length === 0) {
28
- return NextResponse.json(
29
- { ok: false, error: "Pages are required" },
30
- { status: 400 }
31
- );
32
- }
33
-
34
- try {
35
- let files: File[];
36
-
37
- if (isNew) {
38
- // Creating a new project
39
- const title = projectName || "DeepSite Project";
40
- const formattedTitle = title
41
- .toLowerCase()
42
- .replace(/[^a-z0-9]+/g, "-")
43
- .split("-")
44
- .filter(Boolean)
45
- .join("-")
46
- .slice(0, 96);
47
-
48
- const repo: RepoDesignation = {
49
- type: "space",
50
- name: `${user.name}/${formattedTitle}`,
51
- };
52
-
53
- try {
54
- const { repoUrl } = await createRepo({
55
- repo,
56
- accessToken: user.token as string,
57
- });
58
- namespace = user.name;
59
- repoId = repoUrl.split("/").slice(-2).join("/").split("/")[1];
60
- } catch (createRepoError: any) {
61
- return NextResponse.json(
62
- {
63
- ok: false,
64
- error: `Failed to create repository: ${createRepoError.message || 'Unknown error'}`,
65
- },
66
- { status: 500 }
67
- );
68
- }
69
-
70
- // Prepare files with badge injection for new projects
71
- files = [];
72
- pages.forEach((page: Page) => {
73
- let mimeType = "text/html";
74
- if (page.path.endsWith(".css")) {
75
- mimeType = "text/css";
76
- } else if (page.path.endsWith(".js")) {
77
- mimeType = "text/javascript";
78
- } else if (page.path.endsWith(".json")) {
79
- mimeType = "application/json";
80
- }
81
- const content = (mimeType === "text/html" && isIndexPage(page.path))
82
- ? injectDeepSiteBadge(page.html)
83
- : page.html;
84
- const file = new File([content], page.path, { type: mimeType });
85
- files.push(file);
86
- });
87
-
88
- // Add README.md for new projects
89
- const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
90
- const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
91
- const README = `---
92
- title: ${title}
93
- colorFrom: ${colorFrom}
94
- colorTo: ${colorTo}
95
- emoji: 🐳
96
- sdk: static
97
- pinned: false
98
- tags:
99
- - deepsite-v3
100
- ---
101
-
102
- # Welcome to your new DeepSite project!
103
- This project was created with [DeepSite](https://huggingface.co/deepsite).
104
- `;
105
- files.push(new File([README], "README.md", { type: "text/markdown" }));
106
- } else {
107
- // Updating existing project - no badge injection
108
- files = pagesToFiles(pages);
109
- }
110
-
111
- const response = await uploadFiles({
112
- repo: {
113
- type: "space",
114
- name: `${namespace}/${repoId}`,
115
- },
116
- files,
117
- commitTitle,
118
- accessToken: user.token as string,
119
- });
120
-
121
- return NextResponse.json({
122
- ok: true,
123
- pages,
124
- repoId: `${namespace}/${repoId}`,
125
- commit: {
126
- ...response.commit,
127
- title: commitTitle,
128
- }
129
- });
130
- } catch (error: any) {
131
- console.error("Error updating project:", error);
132
- return NextResponse.json(
133
- {
134
- ok: false,
135
- error: error.message || "Failed to update project",
136
- },
137
- { status: 500 }
138
- );
139
- }
140
- }
141
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/projects/route.ts DELETED
@@ -1,121 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { RepoDesignation, createRepo, listCommits, spaceInfo, uploadFiles } from "@huggingface/hub";
3
-
4
- import { isAuthenticated } from "@/lib/auth";
5
- import { Commit, Page } from "@/types";
6
- import { COLORS } from "@/lib/utils";
7
- import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
8
-
9
- export async function POST(
10
- req: NextRequest,
11
- ) {
12
- const user = await isAuthenticated();
13
- if (user instanceof NextResponse || !user) {
14
- return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
15
- }
16
-
17
- const { title: titleFromRequest, pages, prompt } = await req.json();
18
-
19
- const title = titleFromRequest ?? "DeepSite Project";
20
-
21
- const formattedTitle = title
22
- .toLowerCase()
23
- .replace(/[^a-z0-9]+/g, "-")
24
- .split("-")
25
- .filter(Boolean)
26
- .join("-")
27
- .slice(0, 96);
28
-
29
- const repo: RepoDesignation = {
30
- type: "space",
31
- name: `${user.name}/${formattedTitle}`,
32
- };
33
- const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
34
- const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
35
- const README = `---
36
- title: ${title}
37
- colorFrom: ${colorFrom}
38
- colorTo: ${colorTo}
39
- emoji: 🐳
40
- sdk: static
41
- pinned: false
42
- tags:
43
- - deepsite-v3
44
- ---
45
-
46
- # Welcome to your new DeepSite project!
47
- This project was created with [DeepSite](https://huggingface.co/deepsite).
48
- `;
49
-
50
- const files: File[] = [];
51
- const readmeFile = new File([README], "README.md", { type: "text/markdown" });
52
- files.push(readmeFile);
53
- pages.forEach((page: Page) => {
54
- // Determine MIME type based on file extension
55
- let mimeType = "text/html";
56
- if (page.path.endsWith(".css")) {
57
- mimeType = "text/css";
58
- } else if (page.path.endsWith(".js")) {
59
- mimeType = "text/javascript";
60
- } else if (page.path.endsWith(".json")) {
61
- mimeType = "application/json";
62
- }
63
- // Inject the DeepSite badge script into index pages only (not components or other HTML files)
64
- const content = (mimeType === "text/html" && isIndexPage(page.path))
65
- ? injectDeepSiteBadge(page.html)
66
- : page.html;
67
- const file = new File([content], page.path, { type: mimeType });
68
- files.push(file);
69
- });
70
-
71
- try {
72
- const { repoUrl} = await createRepo({
73
- repo,
74
- accessToken: user.token as string,
75
- });
76
- const commitTitle = !prompt || prompt.trim() === "" ? "Redesign my website" : prompt;
77
- await uploadFiles({
78
- repo,
79
- files,
80
- accessToken: user.token as string,
81
- commitTitle
82
- });
83
-
84
- const path = repoUrl.split("/").slice(-2).join("/");
85
-
86
- const commits: Commit[] = [];
87
- for await (const commit of listCommits({ repo, accessToken: user.token as string })) {
88
- if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Promote version")) {
89
- continue;
90
- }
91
- commits.push({
92
- title: commit.title,
93
- oid: commit.oid,
94
- date: commit.date,
95
- });
96
- }
97
-
98
- const space = await spaceInfo({
99
- name: repo.name,
100
- accessToken: user.token as string,
101
- });
102
-
103
- let newProject = {
104
- files,
105
- pages,
106
- commits,
107
- project: {
108
- id: space.id,
109
- space_id: space.name,
110
- _updatedAt: space.updatedAt,
111
- }
112
- }
113
-
114
- return NextResponse.json({ space: newProject, path, ok: true }, { status: 201 });
115
- } catch (err: any) {
116
- return NextResponse.json(
117
- { error: err.message, ok: false },
118
- { status: 500 }
119
- );
120
- }
121
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/me/route.ts DELETED
@@ -1,46 +0,0 @@
1
- import { listSpaces } from "@huggingface/hub";
2
- import { headers } from "next/headers";
3
- import { NextResponse } from "next/server";
4
-
5
- export async function GET() {
6
- const authHeaders = await headers();
7
- const token = authHeaders.get("Authorization");
8
- if (!token) {
9
- return NextResponse.json({ user: null, errCode: 401 }, { status: 401 });
10
- }
11
-
12
- const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
13
- headers: {
14
- Authorization: `${token}`,
15
- },
16
- });
17
-
18
- if (!userResponse.ok) {
19
- return NextResponse.json(
20
- { user: null, errCode: userResponse.status },
21
- { status: userResponse.status }
22
- );
23
- }
24
- const user = await userResponse.json();
25
- const projects = [];
26
- for await (const space of listSpaces({
27
- accessToken: token.replace("Bearer ", "") as string,
28
- additionalFields: ["author", "cardData"],
29
- search: {
30
- owner: user.name,
31
- }
32
- })) {
33
- if (
34
- space.sdk === "static" &&
35
- Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
36
- (
37
- ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite-v3")) ||
38
- ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite"))
39
- )
40
- ) {
41
- projects.push(space);
42
- }
43
- }
44
-
45
- return NextResponse.json({ user, projects, errCode: null }, { status: 200 });
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/re-design/route.ts DELETED
@@ -1,71 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
-
3
- // Timeout configuration (in milliseconds)
4
- const FETCH_TIMEOUT = 30000; // 30 seconds for external fetch
5
-
6
- // Extend the maximum execution time for this route
7
- export const maxDuration = 60; // 1 minute
8
-
9
- export async function PUT(request: NextRequest) {
10
- const body = await request.json();
11
- const { url } = body;
12
-
13
- if (!url) {
14
- return NextResponse.json({ error: "URL is required" }, { status: 400 });
15
- }
16
-
17
- try {
18
- // Create an AbortController for timeout
19
- const controller = new AbortController();
20
- const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
21
-
22
- try {
23
- const response = await fetch(
24
- `https://r.jina.ai/${encodeURIComponent(url)}`,
25
- {
26
- method: "POST",
27
- signal: controller.signal,
28
- }
29
- );
30
-
31
- clearTimeout(timeoutId);
32
-
33
- if (!response.ok) {
34
- return NextResponse.json(
35
- { error: "Failed to fetch redesign" },
36
- { status: 500 }
37
- );
38
- }
39
- const markdown = await response.text();
40
- return NextResponse.json(
41
- {
42
- ok: true,
43
- markdown,
44
- },
45
- { status: 200 }
46
- );
47
- } catch (fetchError: any) {
48
- clearTimeout(timeoutId);
49
-
50
- if (fetchError.name === 'AbortError') {
51
- return NextResponse.json(
52
- { error: "Request timeout: The external service took too long to respond. Please try again." },
53
- { status: 504 }
54
- );
55
- }
56
- throw fetchError;
57
- }
58
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
- } catch (error: any) {
60
- if (error.name === 'AbortError' || error.message?.includes('timeout')) {
61
- return NextResponse.json(
62
- { error: "Request timeout: The external service took too long to respond. Please try again." },
63
- { status: 504 }
64
- );
65
- }
66
- return NextResponse.json(
67
- { error: error.message || "An error occurred" },
68
- { status: 500 }
69
- );
70
- }
71
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/auth/callback/page.tsx DELETED
@@ -1,97 +0,0 @@
1
- "use client";
2
- import Link from "next/link";
3
- import { useUser } from "@/hooks/useUser";
4
- import { use, useState } from "react";
5
- import { useMount, useTimeoutFn } from "react-use";
6
-
7
- import { Button } from "@/components/ui/button";
8
- import { AnimatedBlobs } from "@/components/animated-blobs";
9
- import { useBroadcastChannel } from "@/lib/useBroadcastChannel";
10
- export default function AuthCallback({
11
- searchParams,
12
- }: {
13
- searchParams: Promise<{ code: string }>;
14
- }) {
15
- const [showButton, setShowButton] = useState(false);
16
- const [isPopupAuth, setIsPopupAuth] = useState(false);
17
- const { code } = use(searchParams);
18
- const { loginFromCode } = useUser();
19
- const { postMessage } = useBroadcastChannel("auth", () => {});
20
-
21
- useMount(async () => {
22
- if (code) {
23
- const isPopup = window.opener || window.parent !== window;
24
- setIsPopupAuth(isPopup);
25
-
26
- if (isPopup) {
27
- postMessage({
28
- type: "user-oauth",
29
- code: code,
30
- });
31
-
32
- setTimeout(() => {
33
- if (window.opener) {
34
- window.close();
35
- }
36
- }, 1000);
37
- } else {
38
- await loginFromCode(code);
39
- }
40
- }
41
- });
42
-
43
- useTimeoutFn(() => setShowButton(true), 7000);
44
-
45
- return (
46
- <div className="h-screen flex flex-col justify-center items-center bg-neutral-950 z-1 relative">
47
- <div className="background__noisy" />
48
- <div className="relative max-w-4xl py-10 flex items-center justify-center w-full">
49
- <div className="max-w-lg mx-auto !rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden ring-[8px] ring-white/20">
50
- <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
51
- <div className="flex items-center justify-center -space-x-4 mb-3">
52
- <div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
53
- 🚀
54
- </div>
55
- <div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
56
- 👋
57
- </div>
58
- <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
59
- 🙌
60
- </div>
61
- </div>
62
- <p className="text-xl font-semibold text-neutral-950">
63
- {isPopupAuth
64
- ? "Authentication Complete!"
65
- : "Login In Progress..."}
66
- </p>
67
- <p className="text-sm text-neutral-500 mt-1.5">
68
- {isPopupAuth
69
- ? "You can now close this tab and return to the previous page."
70
- : "Wait a moment while we log you in with your code."}
71
- </p>
72
- </header>
73
- <main className="space-y-4 p-6">
74
- <div>
75
- <p className="text-sm text-neutral-700 mb-4 max-w-xs">
76
- If you are not redirected automatically in the next 5 seconds,
77
- please click the button below
78
- </p>
79
- {showButton ? (
80
- <Link href="/">
81
- <Button variant="black" className="relative">
82
- Go to Home
83
- </Button>
84
- </Link>
85
- ) : (
86
- <p className="text-xs text-neutral-500">
87
- Please wait, we are logging you in...
88
- </p>
89
- )}
90
- </div>
91
- </main>
92
- </div>
93
- <AnimatedBlobs />
94
- </div>
95
- </div>
96
- );
97
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/auth/page.tsx DELETED
@@ -1,28 +0,0 @@
1
- import { redirect } from "next/navigation";
2
- import { Metadata } from "next";
3
-
4
- import { getAuth } from "@/app/actions/auth";
5
-
6
- export const revalidate = 1;
7
-
8
- export const metadata: Metadata = {
9
- robots: "noindex, nofollow",
10
- };
11
-
12
- export default async function Auth() {
13
- const loginRedirectUrl = await getAuth();
14
- if (loginRedirectUrl) {
15
- redirect(loginRedirectUrl);
16
- }
17
-
18
- return (
19
- <div className="p-4">
20
- <div className="border bg-red-500/10 border-red-500/20 text-red-500 px-5 py-3 rounded-lg">
21
- <h1 className="text-xl font-bold">Error</h1>
22
- <p className="text-sm">
23
- An error occurred while trying to log in. Please try again later.
24
- </p>
25
- </div>
26
- </div>
27
- );
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/favicon.ico DELETED
Binary file (25.9 kB)
 
app/layout.tsx DELETED
@@ -1,145 +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 Script from "next/script";
5
- import { headers } from "next/headers";
6
- import { redirect } from "next/navigation";
7
-
8
- import "@/assets/globals.css";
9
- import { Toaster } from "@/components/ui/sonner";
10
- import IframeDetector from "@/components/iframe-detector";
11
- import AppContext from "@/components/contexts/app-context";
12
- import TanstackContext from "@/components/contexts/tanstack-query-context";
13
- import { LoginProvider } from "@/components/contexts/login-context";
14
- import { ProProvider } from "@/components/contexts/pro-context";
15
- import { generateSEO, generateStructuredData } from "@/lib/seo";
16
-
17
- const inter = Inter({
18
- variable: "--font-inter-sans",
19
- subsets: ["latin"],
20
- });
21
-
22
- const ptSans = PT_Sans({
23
- variable: "--font-ptSans-mono",
24
- subsets: ["latin"],
25
- weight: ["400", "700"],
26
- });
27
-
28
- export const metadata: Metadata = {
29
- ...generateSEO({
30
- title: "DeepSite | Build with AI ✨",
31
- description:
32
- "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.",
33
- path: "/",
34
- }),
35
- appleWebApp: {
36
- capable: true,
37
- title: "DeepSite",
38
- statusBarStyle: "black-translucent",
39
- },
40
- icons: {
41
- icon: "/logo.svg",
42
- shortcut: "/logo.svg",
43
- apple: "/logo.svg",
44
- },
45
- verification: {
46
- google: process.env.GOOGLE_SITE_VERIFICATION,
47
- },
48
- };
49
-
50
- export const viewport: Viewport = {
51
- initialScale: 1,
52
- maximumScale: 1,
53
- themeColor: "#000000",
54
- };
55
-
56
- // async function getMe() {
57
- // const cookieStore = await cookies();
58
- // const cookieName = MY_TOKEN_KEY();
59
- // const token = cookieStore.get(cookieName)?.value;
60
-
61
- // if (!token) return { user: null, projects: [], errCode: null };
62
- // try {
63
- // const res = await apiServer.get("/me", {
64
- // headers: {
65
- // Authorization: `Bearer ${token}`,
66
- // },
67
- // });
68
- // return { user: res.data.user, projects: res.data.projects, errCode: null };
69
- // } catch (err: any) {
70
- // return { user: null, projects: [], errCode: err.status };
71
- // }
72
- // }
73
-
74
- export default async function RootLayout({
75
- children,
76
- }: Readonly<{
77
- children: React.ReactNode;
78
- }>) {
79
- // Domain redirect check
80
- const headersList = await headers();
81
- const forwardedHost = headersList.get("x-forwarded-host");
82
- const host = headersList.get("host");
83
- const hostname = (forwardedHost || host || "").split(":")[0];
84
-
85
- const isLocalDev =
86
- hostname === "localhost" ||
87
- hostname === "127.0.0.1" ||
88
- hostname.startsWith("192.168.");
89
- const isHuggingFace =
90
- hostname === "huggingface.co" || hostname.endsWith(".huggingface.co");
91
-
92
- if (!isHuggingFace && !isLocalDev) {
93
- const pathname = headersList.get("x-invoke-path") || "/deepsite";
94
- redirect(`https://huggingface.co${pathname}`);
95
- }
96
-
97
- // const data = await getMe();
98
-
99
- // Generate structured data
100
- const structuredData = generateStructuredData("WebApplication", {
101
- name: "DeepSite",
102
- description: "Build websites with AI, no code required",
103
- url: "https://huggingface.co/deepsite",
104
- });
105
-
106
- const organizationData = generateStructuredData("Organization", {
107
- name: "DeepSite",
108
- url: "https://huggingface.co/deepsite",
109
- });
110
-
111
- return (
112
- <html lang="en">
113
- <body
114
- className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
115
- >
116
- <script
117
- type="application/ld+json"
118
- dangerouslySetInnerHTML={{
119
- __html: JSON.stringify(structuredData),
120
- }}
121
- />
122
- <script
123
- type="application/ld+json"
124
- dangerouslySetInnerHTML={{
125
- __html: JSON.stringify(organizationData),
126
- }}
127
- />
128
- <Script
129
- defer
130
- data-domain="deepsite.hf.co"
131
- src="https://plausible.io/js/script.js"
132
- />
133
- <IframeDetector />
134
- <Toaster richColors position="bottom-center" />
135
- <TanstackContext>
136
- <AppContext>
137
- <LoginProvider>
138
- <ProProvider>{children}</ProProvider>
139
- </LoginProvider>
140
- </AppContext>
141
- </TanstackContext>
142
- </body>
143
- </html>
144
- );
145
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/new/page.tsx DELETED
@@ -1,14 +0,0 @@
1
- import { AppEditor } from "@/components/editor";
2
- import { Metadata } from "next";
3
- import { generateSEO } from "@/lib/seo";
4
-
5
- export const metadata: Metadata = generateSEO({
6
- title: "Create New Project - DeepSite",
7
- description:
8
- "Start building your next website with AI. Create a new project on DeepSite and experience the power of AI-driven web development.",
9
- path: "/new",
10
- });
11
-
12
- export default function NewProjectPage() {
13
- return <AppEditor isNew />;
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/sitemap.ts DELETED
@@ -1,28 +0,0 @@
1
- import { MetadataRoute } from 'next';
2
-
3
- export default function sitemap(): MetadataRoute.Sitemap {
4
- const baseUrl = 'https://huggingface.co/deepsite';
5
-
6
- return [
7
- {
8
- url: baseUrl,
9
- lastModified: new Date(),
10
- changeFrequency: 'daily',
11
- priority: 1,
12
- },
13
- {
14
- url: `${baseUrl}/new`,
15
- lastModified: new Date(),
16
- changeFrequency: 'weekly',
17
- priority: 0.8,
18
- },
19
- {
20
- url: `${baseUrl}/auth`,
21
- lastModified: new Date(),
22
- changeFrequency: 'monthly',
23
- priority: 0.5,
24
- },
25
- // Note: Dynamic project routes will be handled by Next.js automatically
26
- // but you can add specific high-priority project pages here if needed
27
- ];
28
- }