messiasads commited on
Commit
8185bfc
·
1 Parent(s): 829f9f6

refactor to local use only

Browse files
README.md CHANGED
@@ -1,22 +1,55 @@
 
 
 
 
 
 
 
 
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
+ # DeepSite v2 🚀
3
+
4
+ ![Banner Screenshot](./localconfig.png)
5
+
6
+ Run **DeepSite** in your own environment, without relying on external services!
7
+ Perfect for those who want to customize, integrate, or have full control over the platform.
8
+
9
  ---
10
+
11
+ ## How to run DeepSite v2 locally
12
+
13
+ ### 1. Clone the repository
14
+ ```bash
15
+ git clone https://github.com/MartinsMessias/deepsite-locally.git
16
+ cd deepsite-locally
17
+ ```
18
+
19
+ ### 2. Install dependencies
20
+ Make sure you have **Node.js** installed (recommended v18+).
21
+ ```bash
22
+ npm install
23
+ ```
24
+
25
+ ### 3. Run in development mode
26
+ ```bash
27
+ npm run dev
28
+ ```
29
+
30
+ ### 4. For build and production
31
+ ```bash
32
+ npm run build
33
+ npm run start
34
+ ```
35
+
36
  ---
37
 
38
+ ## Available scripts
39
+
40
+ - `npm run dev` — Starts the development environment (Next.js + Turbopack)
41
+ - `npm run build` — Builds for production
42
+ - `npm run start` — Runs the server in production mode
43
+ - `npm run lint` — Runs the linter
44
+
45
+ ## Main dependencies
46
 
47
+ Next.js, React 19, Mongoose, TailwindCSS, Radix UI, Lucide, Monaco Editor, React Query, Zod, Axios, Sonner, and more.
48
+
49
+ See all dependencies in [`package.json`](./package.json).
50
+
51
+ ---
52
 
53
+ ## Keywords
54
+ deepsite local hosting, deepsite run locally, deepsite self-hosted, how to run deepsite locally, install deepsite on your machine, deepsite local server setup, deepsite offline mode, deepsite localhost tutorial, deploy deepsite on your own server, deepsite self-install guide, how to host deepsite on localhost step-by-step, can deepsite run offline on my computer, deepsite docker installation guide, full guide to running deepsite locally without internet, deepsite self-host vs cloud hosting comparison, deepsite performance tips when running locally, requirements to run deepsite on local environment, best practices for self-hosting deepsite platform, how to speed up deepsite in a local environment, common errors when running deepsite locally and how to fix, deepsite vs other ai site builders local run comparison, top reasons to run deepsite on your own server, is deepsite open-source and local-friendly
55
 
 
app/(public)/projects/page.tsx CHANGED
@@ -1,10 +1,10 @@
1
  import { redirect } from "next/navigation";
2
 
3
  import { MyProjects } from "@/components/my-projects";
4
- import { getProjects } from "@/app/actions/projects";
5
 
6
  export default async function ProjectsPage() {
7
- const { ok, projects } = await getProjects();
 
8
  if (!ok) {
9
  redirect("/");
10
  }
 
1
  import { redirect } from "next/navigation";
2
 
3
  import { MyProjects } from "@/components/my-projects";
 
4
 
5
  export default async function ProjectsPage() {
6
+ const { ok, projects } = { ok: true, projects: [] };
7
+
8
  if (!ok) {
9
  redirect("/");
10
  }
app/actions/auth.ts DELETED
@@ -1,18 +0,0 @@
1
- "use server";
2
-
3
- import { headers } from "next/headers";
4
-
5
- export async function getAuth() {
6
- const authList = await headers();
7
- const host = authList.get("host") ?? "localhost:3000";
8
- const url = host.includes("/spaces/enzostvs")
9
- ? "enzostvs-deepsite.hf.space"
10
- : host;
11
- const redirect_uri =
12
- `${host.includes("localhost") ? "http://" : "https://"}` +
13
- url +
14
- "/auth/callback";
15
-
16
- const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
17
- return loginRedirectUrl;
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/actions/projects.ts DELETED
@@ -1,63 +0,0 @@
1
- "use server";
2
-
3
- import { isAuthenticated } from "@/lib/auth";
4
- import { NextResponse } from "next/server";
5
- import dbConnect from "@/lib/mongodb";
6
- import Project from "@/models/Project";
7
- import { Project as ProjectType } from "@/types";
8
-
9
- export async function getProjects(): Promise<{
10
- ok: boolean;
11
- projects: ProjectType[];
12
- }> {
13
- const user = await isAuthenticated();
14
-
15
- if (user instanceof NextResponse || !user) {
16
- return {
17
- ok: false,
18
- projects: [],
19
- };
20
- }
21
-
22
- await dbConnect();
23
- const projects = await Project.find({
24
- user_id: user?.id,
25
- })
26
- .sort({ _createdAt: -1 })
27
- .limit(100)
28
- .lean();
29
- if (!projects) {
30
- return {
31
- ok: false,
32
- projects: [],
33
- };
34
- }
35
- return {
36
- ok: true,
37
- projects: JSON.parse(JSON.stringify(projects)) as ProjectType[],
38
- };
39
- }
40
-
41
- export async function getProject(
42
- namespace: string,
43
- repoId: string
44
- ): Promise<ProjectType | null> {
45
- const user = await isAuthenticated();
46
-
47
- if (user instanceof NextResponse || !user) {
48
- return null;
49
- }
50
-
51
- await dbConnect();
52
- const project = await Project.findOne({
53
- user_id: user.id,
54
- namespace,
55
- repoId,
56
- }).lean();
57
-
58
- if (!project) {
59
- return null;
60
- }
61
-
62
- return JSON.parse(JSON.stringify(project)) as ProjectType;
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/ask-ai/route.ts CHANGED
@@ -1,28 +1,24 @@
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(
@@ -41,56 +37,6 @@ export async function POST(request: NextRequest) {
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();
@@ -109,96 +55,44 @@ export async function POST(request: NextRequest) {
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
- message:
196
- error.message ||
197
- "An error occurred while processing your request.",
198
- })
199
- )
200
- );
201
- }
202
  } finally {
203
  await writer?.close();
204
  }
@@ -209,7 +103,6 @@ export async function POST(request: NextRequest) {
209
  return NextResponse.json(
210
  {
211
  ok: false,
212
- openSelectProvider: true,
213
  message:
214
  error?.message || "An error occurred while processing your request.",
215
  },
@@ -219,11 +112,13 @@ export async function POST(request: NextRequest) {
219
  }
220
 
221
  export async function PUT(request: NextRequest) {
222
- const authHeaders = await headers();
223
- const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
224
-
225
  const body = await request.json();
226
- const { prompt, html, previousPrompt, provider, selectedElementHtml } = body;
 
 
 
 
 
227
 
228
  if (!prompt || !html) {
229
  return NextResponse.json(
@@ -232,87 +127,43 @@ export async function PUT(request: NextRequest) {
232
  );
233
  }
234
 
235
- const selectedModel = MODELS[0];
236
-
237
- let token = userToken;
238
- let billTo: string | null = null;
239
-
240
- /**
241
- * Handle local usage token, this bypass the need for a user token
242
- * and allows local testing without authentication.
243
- * This is useful for development and testing purposes.
244
- */
245
- if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
246
- token = process.env.HF_TOKEN;
247
  }
248
 
249
- const ip = authHeaders.get("x-forwarded-for")?.includes(",")
250
- ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
251
- : authHeaders.get("x-forwarded-for");
252
-
253
- if (!token) {
254
- ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
255
- if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
256
- return NextResponse.json(
257
  {
258
- ok: false,
259
- openLogin: true,
260
- message: "Log In to continue using the service",
261
  },
262
- { status: 429 }
263
- );
264
- }
265
-
266
- token = process.env.DEFAULT_HF_TOKEN as string;
267
- billTo = "huggingface";
268
- }
269
-
270
- const client = new InferenceClient(token);
271
-
272
- const DEFAULT_PROVIDER = PROVIDERS.novita;
273
- const selectedProvider =
274
- provider === "auto"
275
- ? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS]
276
- : PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
277
-
278
- try {
279
- const response = await client.chatCompletion(
280
- {
281
- model: selectedModel.value,
282
- provider: selectedProvider.id as any,
283
- messages: [
284
- {
285
- role: "system",
286
- content: FOLLOW_UP_SYSTEM_PROMPT,
287
- },
288
- {
289
- role: "user",
290
- content: previousPrompt
291
- ? previousPrompt
292
- : "You are modifying the HTML file based on the user's request.",
293
- },
294
- {
295
- role: "assistant",
296
-
297
- content: `The current code is: \n\`\`\`html\n${html}\n\`\`\` ${
298
- selectedElementHtml
299
- ? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\``
300
- : ""
301
- }`,
302
- },
303
- {
304
- role: "user",
305
- content: prompt,
306
- },
307
- ],
308
- ...(selectedProvider.id !== "sambanova"
309
- ? {
310
- max_tokens: selectedProvider.max_tokens,
311
- }
312
- : {}),
313
- },
314
- billTo ? { billTo } : {}
315
- );
316
 
317
  const chunk = response.choices[0]?.message?.content;
318
  if (!chunk) {
@@ -387,24 +238,13 @@ export async function PUT(request: NextRequest) {
387
  );
388
  }
389
  } catch (error: any) {
390
- if (error.message?.includes("exceeded your monthly included credits")) {
391
- return NextResponse.json(
392
- {
393
- ok: false,
394
- openProModal: true,
395
- message: error.message,
396
- },
397
- { status: 402 }
398
- );
399
- }
400
  return NextResponse.json(
401
  {
402
  ok: false,
403
- openSelectProvider: true,
404
  message:
405
  error.message || "An error occurred while processing your request.",
406
  },
407
  { status: 500 }
408
  );
409
  }
410
- }
 
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
  import type { NextRequest } from "next/server";
3
  import { NextResponse } from "next/server";
4
+ import OpenAI from "openai";
 
5
 
6
+ import { MODELS } from "@/lib/providers";
7
  import {
8
  DIVIDER,
9
  FOLLOW_UP_SYSTEM_PROMPT,
10
  INITIAL_SYSTEM_PROMPT,
 
11
  REPLACE_END,
12
  SEARCH_START,
13
  } from "@/lib/prompts";
 
 
 
14
 
15
  export async function POST(request: NextRequest) {
 
 
 
16
  const body = await request.json();
17
+ const { prompt, model, redesignMarkdown, html, apiKey, customModel } = body;
18
+
19
+ const openai = new OpenAI({
20
+ apiKey: apiKey || process.env.OPENAI_API_KEY,
21
+ });
22
 
23
  if (!model || (!prompt && !redesignMarkdown)) {
24
  return NextResponse.json(
 
37
  );
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  try {
41
  // Create a stream response
42
  const encoder = new TextEncoder();
 
55
  (async () => {
56
  let completeResponse = "";
57
  try {
58
+ const chatCompletion = await openai.chat.completions.create({
59
+ model: customModel || selectedModel.value,
60
+ messages: [
61
+ {
62
+ role: "system",
63
+ content: INITIAL_SYSTEM_PROMPT,
64
+ },
65
+ {
66
+ role: "user",
67
+ content: redesignMarkdown
68
+ ? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.`
69
+ : html
70
+ ? `Here is my current HTML code:\n\n\`\`\`html\n${html}\n\`\`\`\n\nNow, please create a new design based on this HTML.`
71
+ : prompt,
72
+ },
73
+ ],
74
+ stream: true,
75
+ });
76
+
77
+ for await (const chunk of chatCompletion) {
78
+ const content = chunk.choices[0]?.delta?.content || "";
79
+ await writer.write(encoder.encode(content));
80
+ completeResponse += content;
81
+ if (completeResponse.includes("</html>")) {
 
 
 
82
  break;
83
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  }
85
  } catch (error: any) {
86
+ await writer.write(
87
+ encoder.encode(
88
+ JSON.stringify({
89
+ ok: false,
90
+ message:
91
+ error.message ||
92
+ "An error occurred while processing your request.",
93
+ })
94
+ )
95
+ );
 
 
 
 
 
 
 
 
 
 
 
 
96
  } finally {
97
  await writer?.close();
98
  }
 
103
  return NextResponse.json(
104
  {
105
  ok: false,
 
106
  message:
107
  error?.message || "An error occurred while processing your request.",
108
  },
 
112
  }
113
 
114
  export async function PUT(request: NextRequest) {
 
 
 
115
  const body = await request.json();
116
+ const { prompt, html, previousPrompt, selectedElementHtml, apiKey, model, baseUrl, customModel } = body;
117
+
118
+ const openai = new OpenAI({
119
+ apiKey: apiKey || process.env.OPENAI_API_KEY,
120
+ baseURL: baseUrl || process.env.OPENAI_BASE_URL,
121
+ });
122
 
123
  if (!prompt || !html) {
124
  return NextResponse.json(
 
127
  );
128
  }
129
 
130
+ const selectedModel = MODELS.find(
131
+ (m) => m.value === model || m.label === model
132
+ );
133
+ if (!selectedModel) {
134
+ return NextResponse.json(
135
+ { ok: false, error: "Invalid model selected" },
136
+ { status: 400 }
137
+ );
 
 
 
 
138
  }
139
 
140
+ try {
141
+ const response = await openai.chat.completions.create({
142
+ model: customModel || selectedModel.value,
143
+ messages: [
 
 
 
 
144
  {
145
+ role: "system",
146
+ content: FOLLOW_UP_SYSTEM_PROMPT,
 
147
  },
148
+ {
149
+ role: "user",
150
+ content: previousPrompt
151
+ ? previousPrompt
152
+ : "You are modifying the HTML file based on the user's request.",
153
+ },
154
+ {
155
+ role: "assistant",
156
+ content: `The current code is: \n\`\`\`html\n${html}\n\`\`\` ${selectedElementHtml
157
+ ? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\``
158
+ : ""}
159
+ `,
160
+ },
161
+ {
162
+ role: "user",
163
+ content: prompt,
164
+ },
165
+ ],
166
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
  const chunk = response.choices[0]?.message?.content;
169
  if (!chunk) {
 
238
  );
239
  }
240
  } catch (error: any) {
 
 
 
 
 
 
 
 
 
 
241
  return NextResponse.json(
242
  {
243
  ok: false,
 
244
  message:
245
  error.message || "An error occurred while processing your request.",
246
  },
247
  { status: 500 }
248
  );
249
  }
250
+ }
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
- ? "enzostvs-deepsite.hf.space"
28
- : host;
29
- const redirect_uri =
30
- `${host.includes("localhost") ? "http://" : "https://"}` +
31
- url +
32
- "/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/me/projects/[namespace]/[repoId]/route.ts DELETED
@@ -1,237 +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
- // redirect to the project page if it already exists
209
- return NextResponse.json(
210
- {
211
- ok: false,
212
- error: "Project already exists",
213
- redirect: `/projects/${namespace}/${repoId}`,
214
- },
215
- { status: 400 }
216
- );
217
- }
218
-
219
- const newProject = new Project({
220
- user_id: user.id,
221
- space_id: `${namespace}/${repoId}`,
222
- prompts: [],
223
- });
224
-
225
- await newProject.save();
226
- return NextResponse.json(
227
- {
228
- ok: true,
229
- project: {
230
- id: newProject._id,
231
- space_id: newProject.space_id,
232
- prompts: newProject.prompts,
233
- },
234
- },
235
- { status: 201 }
236
- );
237
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/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/layout.tsx CHANGED
@@ -1,13 +1,10 @@
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
  import Script from "next/script";
13
 
@@ -66,28 +63,11 @@ export const viewport: Viewport = {
66
  themeColor: "#000000",
67
  };
68
 
69
- async function getMe() {
70
- const cookieStore = await cookies();
71
- const token = cookieStore.get(MY_TOKEN_KEY())?.value;
72
- if (!token) return { user: null, errCode: null };
73
- try {
74
- const res = await apiServer.get("/me", {
75
- headers: {
76
- Authorization: `Bearer ${token}`,
77
- },
78
- });
79
- return { user: res.data.user, errCode: null };
80
- } catch (err: any) {
81
- return { user: null, errCode: err.status };
82
- }
83
- }
84
-
85
  export default async function RootLayout({
86
  children,
87
  }: Readonly<{
88
  children: React.ReactNode;
89
  }>) {
90
- const data = await getMe();
91
  return (
92
  <html lang="en">
93
  <Script
@@ -100,7 +80,7 @@ export default async function RootLayout({
100
  >
101
  <Toaster richColors position="bottom-center" />
102
  <TanstackProvider>
103
- <AppContext me={data}>{children}</AppContext>
104
  </TanstackProvider>
105
  </body>
106
  </html>
 
 
1
  import type { Metadata, Viewport } from "next";
2
  import { Inter, PT_Sans } from "next/font/google";
3
+
4
 
5
  import TanstackProvider from "@/components/providers/tanstack-query-provider";
6
  import "@/assets/globals.css";
7
  import { Toaster } from "@/components/ui/sonner";
 
 
8
  import AppContext from "@/components/contexts/app-context";
9
  import Script from "next/script";
10
 
 
63
  themeColor: "#000000",
64
  };
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  export default async function RootLayout({
67
  children,
68
  }: Readonly<{
69
  children: React.ReactNode;
70
  }>) {
 
71
  return (
72
  <html lang="en">
73
  <Script
 
80
  >
81
  <Toaster richColors position="bottom-center" />
82
  <TanstackProvider>
83
+ <AppContext>{children}</AppContext>
84
  </TanstackProvider>
85
  </body>
86
  </html>
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
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/contexts/app-context.tsx CHANGED
@@ -19,7 +19,7 @@ export default function AppContext({
19
  errCode: number | null;
20
  };
21
  }) {
22
- const { loginFromCode, user, logout, loading, errCode } =
23
  useUser(initialData);
24
  const pathname = usePathname();
25
  const router = useRouter();
@@ -45,7 +45,6 @@ export default function AppContext({
45
 
46
  if (!message.code) return;
47
  if (message.type === "user-oauth" && message?.code && !events.code) {
48
- loginFromCode(message.code);
49
  }
50
  });
51
 
 
19
  errCode: number | null;
20
  };
21
  }) {
22
+ const { user, logout, loading, errCode } =
23
  useUser(initialData);
24
  const pathname = usePathname();
25
  const router = useRouter();
 
45
 
46
  if (!message.code) return;
47
  if (message.type === "user-oauth" && message?.code && !events.code) {
 
48
  }
49
  });
50
 
components/editor/ask-ai/index.tsx CHANGED
@@ -7,13 +7,11 @@ 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";
@@ -52,7 +50,6 @@ export function AskAI({
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("");
@@ -60,7 +57,6 @@ export function AskAI({
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);
@@ -88,6 +84,9 @@ export function AskAI({
88
  const selectedElementHtml = selectedElement
89
  ? selectedElement.outerHTML
90
  : "";
 
 
 
91
  const request = await fetch("/api/ask-ai", {
92
  method: "PUT",
93
  body: JSON.stringify({
@@ -97,6 +96,9 @@ export function AskAI({
97
  model,
98
  html,
99
  selectedElementHtml,
 
 
 
100
  }),
101
  headers: {
102
  "Content-Type": "application/json",
@@ -107,16 +109,6 @@ export function AskAI({
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
  }
@@ -129,6 +121,7 @@ export function AskAI({
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({
@@ -137,6 +130,7 @@ export function AskAI({
137
  model,
138
  html: isSameHtml ? "" : html,
139
  redesignMarkdown,
 
140
  }),
141
  headers: {
142
  "Content-Type": "application/json",
@@ -159,16 +153,6 @@ export function AskAI({
159
  contentResponse.trim().endsWith("}");
160
  const jsonResponse = isJson ? JSON.parse(contentResponse) : null;
161
  if (jsonResponse && !jsonResponse.ok) {
162
- if (jsonResponse.openLogin) {
163
- setOpen(true);
164
- } else if (jsonResponse.openSelectProvider) {
165
- setOpenProvider(true);
166
- setProviderError(jsonResponse.message);
167
- } else if (jsonResponse.openProModal) {
168
- setOpenProModal(true);
169
- } else {
170
- toast.error(jsonResponse.message);
171
- }
172
  setisAiWorking(false);
173
  return;
174
  }
@@ -251,9 +235,6 @@ export function AskAI({
251
  } catch (error: any) {
252
  setisAiWorking(false);
253
  toast.error(error.message);
254
- if (error.openLogin) {
255
- setOpen(true);
256
- }
257
  }
258
  };
259
 
@@ -428,12 +409,6 @@ export function AskAI({
428
  </Button>
429
  </div>
430
  </div>
431
- <LoginModal open={open} onClose={() => setOpen(false)} html={html} />
432
- <ProModal
433
- html={html}
434
- open={openProModal}
435
- onClose={() => setOpenProModal(false)}
436
- />
437
  {!isSameHtml && (
438
  <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">
439
  <label
 
7
  import { ArrowUp, ChevronDown, Crosshair } from "lucide-react";
8
  import { FaStopCircle } from "react-icons/fa";
9
 
 
10
  import { Button } from "@/components/ui/button";
11
  import { MODELS } from "@/lib/providers";
12
  import { HtmlHistory } from "@/types";
13
  import { InviteFriends } from "@/components/invite-friends";
14
  import { Settings } from "@/components/editor/ask-ai/settings";
 
15
  import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
16
  import Loading from "@/components/loading";
17
  import { Checkbox } from "@/components/ui/checkbox";
 
50
  const refThink = useRef<HTMLDivElement | null>(null);
51
  const audio = useRef<HTMLAudioElement | null>(null);
52
 
 
53
  const [prompt, setPrompt] = useState("");
54
  const [hasAsked, setHasAsked] = useState(false);
55
  const [previousPrompt, setPreviousPrompt] = useState("");
 
57
  const [model, setModel] = useLocalStorage("model", MODELS[0].value);
58
  const [openProvider, setOpenProvider] = useState(false);
59
  const [providerError, setProviderError] = useState("");
 
60
  const [think, setThink] = useState<string | undefined>(undefined);
61
  const [openThink, setOpenThink] = useState(false);
62
  const [isThinking, setIsThinking] = useState(true);
 
84
  const selectedElementHtml = selectedElement
85
  ? selectedElement.outerHTML
86
  : "";
87
+ const apiKey = localStorage.getItem("openai_api_key");
88
+ const baseUrl = localStorage.getItem("openai_base_url");
89
+ const customModel = localStorage.getItem("openai_model");
90
  const request = await fetch("/api/ask-ai", {
91
  method: "PUT",
92
  body: JSON.stringify({
 
96
  model,
97
  html,
98
  selectedElementHtml,
99
+ apiKey,
100
+ baseUrl,
101
+ customModel,
102
  }),
103
  headers: {
104
  "Content-Type": "application/json",
 
109
  if (request && request.body) {
110
  const res = await request.json();
111
  if (!request.ok) {
 
 
 
 
 
 
 
 
 
 
112
  setisAiWorking(false);
113
  return;
114
  }
 
121
  if (audio.current) audio.current.play();
122
  }
123
  } else {
124
+ const apiKey = localStorage.getItem("openai_api_key");
125
  const request = await fetch("/api/ask-ai", {
126
  method: "POST",
127
  body: JSON.stringify({
 
130
  model,
131
  html: isSameHtml ? "" : html,
132
  redesignMarkdown,
133
+ apiKey,
134
  }),
135
  headers: {
136
  "Content-Type": "application/json",
 
153
  contentResponse.trim().endsWith("}");
154
  const jsonResponse = isJson ? JSON.parse(contentResponse) : null;
155
  if (jsonResponse && !jsonResponse.ok) {
 
 
 
 
 
 
 
 
 
 
156
  setisAiWorking(false);
157
  return;
158
  }
 
235
  } catch (error: any) {
236
  setisAiWorking(false);
237
  toast.error(error.message);
 
 
 
238
  }
239
  };
240
 
 
409
  </Button>
410
  </div>
411
  </div>
 
 
 
 
 
 
412
  {!isSameHtml && (
413
  <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">
414
  <label
components/editor/ask-ai/settings.tsx CHANGED
@@ -1,6 +1,5 @@
1
- import classNames from "classnames";
2
  import { PiGearSixFill } from "react-icons/pi";
3
- import { RiCheckboxCircleFill } from "react-icons/ri";
4
 
5
  import {
6
  Popover,
@@ -9,18 +8,10 @@ import {
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,
@@ -28,9 +19,7 @@ export function Settings({
28
  provider,
29
  model,
30
  error,
31
- isFollowUp = false,
32
  onChange,
33
- onModelChange,
34
  }: {
35
  open: boolean;
36
  provider: string;
@@ -41,6 +30,16 @@ export function Settings({
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
@@ -57,6 +56,14 @@ export function Settings({
57
  }
58
  }, [model, provider]);
59
 
 
 
 
 
 
 
 
 
60
  return (
61
  <div className="">
62
  <Popover open={open} onOpenChange={onClose}>
@@ -81,121 +88,52 @@ export function Settings({
81
  )}
82
  <label className="block">
83
  <p className="text-neutral-300 text-sm mb-2.5">
84
- Choose a DeepSeek model
85
  </p>
86
- <Select defaultValue={model} onValueChange={onModelChange}>
87
- <SelectTrigger className="w-full">
88
- <SelectValue placeholder="Select a DeepSeek model" />
89
- </SelectTrigger>
90
- <SelectContent>
91
- <SelectGroup>
92
- <SelectLabel>DeepSeek models</SelectLabel>
93
- {MODELS.map(
94
- ({
95
- value,
96
- label,
97
- isNew = false,
98
- isThinker = false,
99
- }: {
100
- value: string;
101
- label: string;
102
- isNew?: boolean;
103
- isThinker?: boolean;
104
- }) => (
105
- <SelectItem
106
- key={value}
107
- value={value}
108
- className=""
109
- disabled={isThinker && isFollowUp}
110
- >
111
- {label}
112
- {isNew && (
113
- <span className="text-xs bg-gradient-to-br from-sky-400 to-sky-600 text-white rounded-full px-1.5 py-0.5">
114
- New
115
- </span>
116
- )}
117
- </SelectItem>
118
- )
119
- )}
120
- </SelectGroup>
121
- </SelectContent>
122
- </Select>
123
  </label>
124
- {isFollowUp && (
125
- <div className="bg-amber-500/10 border-amber-500/10 p-3 text-xs text-amber-500 border rounded-lg">
126
- Note: You can&apos;t use a Thinker model for follow-up requests.
127
- We automatically switch to the default model for you.
128
- </div>
129
- )}
130
- <div className="flex flex-col gap-3">
131
- <div className="flex items-center justify-between">
132
- <div>
133
- <p className="text-neutral-300 text-sm mb-1.5">
134
- Use auto-provider
135
- </p>
136
- <p className="text-xs text-neutral-400/70">
137
- We&apos;ll automatically select the best provider for you
138
- based on your prompt.
139
- </p>
140
- </div>
141
- <div
142
- className={classNames(
143
- "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",
144
- {
145
- "!bg-sky-500": provider === "auto",
146
- }
147
- )}
148
- onClick={() => {
149
- const foundModel = MODELS.find(
150
- (m: { value: string }) => m.value === model
151
- );
152
- if (provider === "auto" && foundModel?.autoProvider) {
153
- onChange(foundModel.autoProvider);
154
- } else {
155
- onChange("auto");
156
- }
157
- }}
158
- >
159
- <div
160
- className={classNames(
161
- "w-4 h-4 rounded-full shadow-md transition-all duration-200 bg-neutral-200",
162
- {
163
- "translate-x-4": provider === "auto",
164
- }
165
- )}
166
- />
167
- </div>
168
- </div>
169
- <label className="block">
170
- <p className="text-neutral-300 text-sm mb-2">
171
- Inference Provider
172
- </p>
173
- <div className="grid grid-cols-2 gap-1.5">
174
- {modelAvailableProviders.map((id: string) => (
175
- <Button
176
- key={id}
177
- variant={id === provider ? "default" : "secondary"}
178
- size="sm"
179
- onClick={() => {
180
- onChange(id);
181
- }}
182
- >
183
- <Image
184
- src={`/providers/${id}.svg`}
185
- alt={PROVIDERS[id as keyof typeof PROVIDERS].name}
186
- className="size-5 mr-2"
187
- width={20}
188
- height={20}
189
- />
190
- {PROVIDERS[id as keyof typeof PROVIDERS].name}
191
- {id === provider && (
192
- <RiCheckboxCircleFill className="ml-2 size-4 text-blue-500" />
193
- )}
194
- </Button>
195
- ))}
196
- </div>
197
- </label>
198
  </div>
 
199
  </main>
200
  </PopoverContent>
201
  </Popover>
 
 
1
  import { PiGearSixFill } from "react-icons/pi";
2
+ import { useState, useEffect } from "react";
3
 
4
  import {
5
  Popover,
 
8
  } from "@/components/ui/popover";
9
  import { PROVIDERS, MODELS } from "@/lib/providers";
10
  import { Button } from "@/components/ui/button";
 
 
 
 
 
 
 
 
 
11
  import { useMemo } from "react";
12
  import { useUpdateEffect } from "react-use";
13
+ import { Input } from "@/components/ui/input";
14
+ import { toast } from "sonner";
15
 
16
  export function Settings({
17
  open,
 
19
  provider,
20
  model,
21
  error,
 
22
  onChange,
 
23
  }: {
24
  open: boolean;
25
  provider: string;
 
30
  onChange: (provider: string) => void;
31
  onModelChange: (model: string) => void;
32
  }) {
33
+ const [apiKey, setApiKey] = useState("");
34
+ const [baseUrl, setBaseUrl] = useState("");
35
+ const [customModel, setCustomModel] = useState("");
36
+
37
+ useEffect(() => {
38
+ setApiKey(localStorage.getItem("openai_api_key") || "");
39
+ setBaseUrl(localStorage.getItem("openai_base_url") || "");
40
+ setCustomModel(localStorage.getItem("openai_model") || "");
41
+ }, [open]);
42
+
43
  const modelAvailableProviders = useMemo(() => {
44
  const availableProviders = MODELS.find(
45
  (m: { value: string }) => m.value === model
 
56
  }
57
  }, [model, provider]);
58
 
59
+ const handleSaveSettings = () => {
60
+ localStorage.setItem("openai_api_key", apiKey);
61
+ localStorage.setItem("openai_base_url", baseUrl);
62
+ localStorage.setItem("openai_model", customModel);
63
+ toast.success("Settings saved!");
64
+ onClose(false);
65
+ };
66
+
67
  return (
68
  <div className="">
69
  <Popover open={open} onOpenChange={onClose}>
 
88
  )}
89
  <label className="block">
90
  <p className="text-neutral-300 text-sm mb-2.5">
91
+ API Key
92
  </p>
93
+ <Input
94
+ type="password"
95
+ placeholder="Enter your OpenAI API key"
96
+ value={apiKey}
97
+ onChange={(e) => setApiKey(e.target.value)}
98
+ className="!bg-neutral-800 !border-neutral-700 !text-neutral-200"
99
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  </label>
101
+ <label className="block">
102
+ <p className="text-neutral-300 text-sm mb-2.5">
103
+ Base URL (optional)
104
+ </p>
105
+ <Input
106
+ type="text"
107
+ placeholder="e.g., https://api.openai.com/v1"
108
+ value={baseUrl}
109
+ onChange={(e) => setBaseUrl(e.target.value)}
110
+ className="!bg-neutral-800 !border-neutral-700 !text-neutral-200"
111
+ />
112
+ </label>
113
+ <label className="block">
114
+ <p className="text-neutral-300 text-sm mb-2.5">
115
+ Custom Model
116
+ </p>
117
+ <Input
118
+ type="text"
119
+ placeholder="e.g., gpt-4o-mini"
120
+ value={customModel || "gpt-4.1"}
121
+ onChange={(e) => setCustomModel(e.target.value)}
122
+ className="!bg-neutral-800 !border-neutral-700 !text-neutral-200"
123
+ />
124
+ </label>
125
+ <Button
126
+ variant="default"
127
+ size="sm"
128
+ onClick={handleSaveSettings}
129
+ className="mt-2 w-full"
130
+ >
131
+ Save Settings
132
+ </Button>
133
+ <div className="bg-amber-500/10 border-amber-500/10 p-3 text-xs text-amber-500 border rounded-lg">
134
+ Accepts any OpenAI-compatible provider. Enter the corresponding API key and base URL (e.g., OpenRouter, DeepSeek, etc.).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  </div>
136
+
137
  </main>
138
  </PopoverContent>
139
  </Popover>
components/editor/deploy-button/index.tsx CHANGED
@@ -16,8 +16,6 @@ import {
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,
@@ -27,9 +25,7 @@ export function DeployButton({
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: "",
@@ -65,108 +61,80 @@ export function DeployButton({
65
  return (
66
  <div className="flex items-center justify-end gap-5">
67
  <div className="relative flex items-center justify-end">
68
- {user?.id ? (
69
- <Popover>
70
- <PopoverTrigger asChild>
71
- <div>
72
- <Button variant="default" className="max-lg:hidden !px-4">
73
- <MdSave className="size-4" />
74
- Save your Project
75
- </Button>
76
- <Button variant="default" size="sm" className="lg:hidden">
77
- Deploy
78
- </Button>
79
- </div>
80
- </PopoverTrigger>
81
- <PopoverContent
82
- className="!rounded-2xl !p-0 !bg-white !border-neutral-200 min-w-xs text-center overflow-hidden"
83
- align="end"
84
- >
85
- <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
86
- <div className="flex items-center justify-center -space-x-4 mb-3">
87
- <div className="size-9 rounded-full bg-amber-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
88
- 🚀
89
- </div>
90
- <div className="size-11 rounded-full bg-red-200 shadow-2xl flex items-center justify-center z-2">
91
- <Image
92
- src={SpaceIcon}
93
- alt="Space Icon"
94
- className="size-7"
95
- />
96
- </div>
97
- <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
98
- 👻
99
- </div>
100
  </div>
101
- <p className="text-xl font-semibold text-neutral-950">
102
- Deploy as Space!
103
- </p>
104
- <p className="text-sm text-neutral-500 mt-1.5">
105
- Save and Deploy your project to a Space on the Hub. Spaces are
106
- a way to share your project with the world.
107
- </p>
108
- </header>
109
- <main className="space-y-4 p-6">
110
- <div>
111
- <p className="text-sm text-neutral-700 mb-2">
112
- Choose a title for your space:
113
- </p>
114
- <Input
115
- type="text"
116
- placeholder="My Awesome Website"
117
- value={config.title}
118
- onChange={(e) =>
119
- setConfig({ ...config, title: e.target.value })
120
- }
121
- className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
122
  />
123
  </div>
124
- <div>
125
- <p className="text-sm text-neutral-700 mb-2">
126
- Then, let&apos;s deploy it!
127
- </p>
128
- <Button
129
- variant="black"
130
- onClick={createSpace}
131
- className="relative w-full"
132
- disabled={loading}
133
- >
134
- Deploy Space <Rocket className="size-4" />
135
- {loading && (
136
- <Loading className="ml-2 size-4 animate-spin" />
137
- )}
138
- </Button>
139
  </div>
140
- </main>
141
- </PopoverContent>
142
- </Popover>
143
- ) : (
144
- <>
145
- <Button
146
- variant="default"
147
- className="max-lg:hidden !px-4"
148
- onClick={() => setOpen(true)}
149
- >
150
- <MdSave className="size-4" />
151
- Save your Project
152
- </Button>
153
- <Button
154
- variant="default"
155
- size="sm"
156
- className="lg:hidden"
157
- onClick={() => setOpen(true)}
158
- >
159
- Save
160
- </Button>
161
- </>
162
- )}
163
- <LoginModal
164
- open={open}
165
- onClose={() => setOpen(false)}
166
- html={html}
167
- title="Log In to save your Project"
168
- description="Log In through your Hugging Face account to save your project and increase your monthly free limit."
169
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  </div>
171
  </div>
172
  );
 
16
  } from "@/components/ui/popover";
17
  import { Input } from "@/components/ui/input";
18
  import { api } from "@/lib/api";
 
 
19
 
20
  export function DeployButton({
21
  html,
 
25
  prompts: string[];
26
  }) {
27
  const router = useRouter();
28
+ const [loading, setLoading] = useState(false);
 
 
29
 
30
  const [config, setConfig] = useState({
31
  title: "",
 
61
  return (
62
  <div className="flex items-center justify-end gap-5">
63
  <div className="relative flex items-center justify-end">
64
+ <Popover>
65
+ <PopoverTrigger asChild>
66
+ <div>
67
+ <Button variant="default" className="max-lg:hidden !px-4">
68
+ <MdSave className="size-4" />
69
+ Save your Project
70
+ </Button>
71
+ <Button variant="default" size="sm" className="lg:hidden">
72
+ Deploy
73
+ </Button>
74
+ </div>
75
+ </PopoverTrigger>
76
+ <PopoverContent
77
+ className="!rounded-2xl !p-0 !bg-white !border-neutral-200 min-w-xs text-center overflow-hidden"
78
+ align="end"
79
+ >
80
+ <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
81
+ <div className="flex items-center justify-center -space-x-4 mb-3">
82
+ <div className="size-9 rounded-full bg-amber-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
83
+ 🚀
 
 
 
 
 
 
 
 
 
 
 
 
84
  </div>
85
+ <div className="size-11 rounded-full bg-red-200 shadow-2xl flex items-center justify-center z-2">
86
+ <Image
87
+ src={SpaceIcon}
88
+ alt="Space Icon"
89
+ className="size-7"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  />
91
  </div>
92
+ <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
93
+ 👻
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  </div>
95
+ </div>
96
+ <p className="text-xl font-semibold text-neutral-950">
97
+ Deploy as Space!
98
+ </p>
99
+ <p className="text-sm text-neutral-500 mt-1.5">
100
+ Save and Deploy your project to a Space on the Hub. Spaces are
101
+ a way to share your project with the world.
102
+ </p>
103
+ </header>
104
+ <main className="space-y-4 p-6">
105
+ <div>
106
+ <p className="text-sm text-neutral-700 mb-2">
107
+ Choose a title for your space:
108
+ </p>
109
+ <Input
110
+ type="text"
111
+ placeholder="My Awesome Website"
112
+ value={config.title}
113
+ onChange={(e) =>
114
+ setConfig({ ...config, title: e.target.value })
115
+ }
116
+ className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
117
+ />
118
+ </div>
119
+ <div>
120
+ <p className="text-sm text-neutral-700 mb-2">
121
+ Then, let&apos;s deploy it!
122
+ </p>
123
+ <Button
124
+ variant="black"
125
+ onClick={createSpace}
126
+ className="relative w-full"
127
+ disabled={loading}
128
+ >
129
+ Deploy Space <Rocket className="size-4" />
130
+ {loading && (
131
+ <Loading className="ml-2 size-4 animate-spin" />
132
+ )}
133
+ </Button>
134
+ </div>
135
+ </main>
136
+ </PopoverContent>
137
+ </Popover>
138
  </div>
139
  </div>
140
  );
components/editor/footer/index.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import classNames from "classnames";
2
  import { FaMobileAlt } from "react-icons/fa";
3
- import { HelpCircle, RefreshCcw, SparkleIcon } from "lucide-react";
4
  import { FaLaptopCode } from "react-icons/fa6";
5
  import { HtmlHistory } from "@/types";
6
  import { Button } from "@/components/ui/button";
@@ -74,24 +74,20 @@ export function Footer({
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
- <a
87
- target="_blank"
88
- href="https://huggingface.co/spaces/enzostvs/deepsite/discussions/157"
89
- >
90
- <Button size="sm" variant="outline">
91
- <HelpCircle className="size-3.5" />
92
- <span className="max-lg:hidden">Help</span>
93
- </Button>
94
- </a>
95
  <Button size="sm" variant="outline" onClick={handleRefreshIframe}>
96
  <RefreshCcw className="size-3.5" />
97
  <span className="max-lg:hidden">Refresh Preview</span>
 
1
  import classNames from "classnames";
2
  import { FaMobileAlt } from "react-icons/fa";
3
+ import { RefreshCcw } from "lucide-react";
4
  import { FaLaptopCode } from "react-icons/fa6";
5
  import { HtmlHistory } from "@/types";
6
  import { Button } from "@/components/ui/button";
 
74
  )}
75
  </div>
76
  <div className="flex justify-end items-center gap-2.5">
77
+
78
+ <style>{`
79
+ @keyframes blink-star {
80
+ 0%, 100% { opacity: 1; }
81
+ 50% { opacity: 0.2; }
82
+ }
83
+ `}</style>
84
+ <span className="max-lg:hidden"><a
85
+ href="https://github.com/MartinsMessias/deepsite-locally"
86
+ target="_blank"
87
+ className="text-xs lg:text-sm font-medium py-2 px-3 lg:px-4 rounded-lg flex items-center transition-all duration-100 cursor-pointer"
88
+ >
89
+ <span style={{ animation: 'blink-star 2s infinite' }}>⭐</span> <span>Give a Star on GitHub</span>
90
+ </a></span>
 
 
 
 
91
  <Button size="sm" variant="outline" onClick={handleRefreshIframe}>
92
  <RefreshCcw className="size-3.5" />
93
  <span className="max-lg:hidden">Refresh Preview</span>
components/editor/index.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  "use client";
2
  import { useRef, useState } from "react";
3
  import { toast } from "sonner";
@@ -13,7 +14,7 @@ import {
13
  useUpdateEffect,
14
  } from "react-use";
15
  import classNames from "classnames";
16
- import { useRouter, useSearchParams } from "next/navigation";
17
 
18
  import { Header } from "@/components/editor/header";
19
  import { Footer } from "@/components/editor/footer";
@@ -21,7 +22,6 @@ import { defaultHTML } from "@/lib/consts";
21
  import { Preview } from "@/components/editor/preview";
22
  import { useEditor } from "@/hooks/useEditor";
23
  import { AskAI } from "@/components/editor/ask-ai";
24
- import { DeployButton } from "./deploy-button";
25
  import { Project } from "@/types";
26
  import { SaveButton } from "./save-button";
27
  import { LoadProject } from "../my-projects/load-project";
@@ -33,16 +33,13 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
33
  const { html, setHtml, htmlHistory, setHtmlHistory, prompts, setPrompts } =
34
  useEditor(project?.html ?? (htmlStorage as string) ?? defaultHTML);
35
  // get query params from URL
36
- const searchParams = useSearchParams();
37
  const router = useRouter();
38
- const deploy = searchParams.get("deploy") === "true";
39
 
40
  const iframeRef = useRef<HTMLIFrameElement | null>(null);
41
  const preview = useRef<HTMLDivElement>(null);
42
  const editor = useRef<HTMLDivElement>(null);
43
  const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
44
  const resizer = useRef<HTMLDivElement>(null);
45
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
  const monacoRef = useRef<any>(null);
47
 
48
  const [currentTab, setCurrentTab] = useState("chat");
@@ -114,20 +111,7 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
114
  };
115
 
116
  useMount(() => {
117
- if (deploy && project?._id) {
118
- toast.success("Your project is deployed! 🎉", {
119
- action: {
120
- label: "See Project",
121
- onClick: () => {
122
- window.open(
123
- `https://huggingface.co/spaces/${project?.space_id}`,
124
- "_blank"
125
- );
126
- },
127
- },
128
- });
129
- router.replace(`/projects/${project?.space_id}`);
130
- }
131
  if (htmlStorage) {
132
  removeHtmlStorage();
133
  toast.warning("Previous HTML content restored from local storage.");
@@ -180,14 +164,16 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
180
  <Header tab={currentTab} onNewTab={setCurrentTab}>
181
  <LoadProject
182
  onSuccess={(project: Project) => {
183
- router.push(`/projects/${project.space_id}`);
 
 
 
 
 
 
184
  }}
185
  />
186
- {project?._id ? (
187
- <SaveButton html={html} prompts={prompts} />
188
- ) : (
189
- <DeployButton html={html} prompts={prompts} />
190
- )}
191
  </Header>
192
  <main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full max-lg:h-[calc(100%-82px)] relative">
193
  {currentTab === "chat" && (
 
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
  "use client";
3
  import { useRef, useState } from "react";
4
  import { toast } from "sonner";
 
14
  useUpdateEffect,
15
  } from "react-use";
16
  import classNames from "classnames";
17
+ import { useRouter } from "next/navigation";
18
 
19
  import { Header } from "@/components/editor/header";
20
  import { Footer } from "@/components/editor/footer";
 
22
  import { Preview } from "@/components/editor/preview";
23
  import { useEditor } from "@/hooks/useEditor";
24
  import { AskAI } from "@/components/editor/ask-ai";
 
25
  import { Project } from "@/types";
26
  import { SaveButton } from "./save-button";
27
  import { LoadProject } from "../my-projects/load-project";
 
33
  const { html, setHtml, htmlHistory, setHtmlHistory, prompts, setPrompts } =
34
  useEditor(project?.html ?? (htmlStorage as string) ?? defaultHTML);
35
  // get query params from URL
 
36
  const router = useRouter();
 
37
 
38
  const iframeRef = useRef<HTMLIFrameElement | null>(null);
39
  const preview = useRef<HTMLDivElement>(null);
40
  const editor = useRef<HTMLDivElement>(null);
41
  const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
42
  const resizer = useRef<HTMLDivElement>(null);
 
43
  const monacoRef = useRef<any>(null);
44
 
45
  const [currentTab, setCurrentTab] = useState("chat");
 
111
  };
112
 
113
  useMount(() => {
114
+
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  if (htmlStorage) {
116
  removeHtmlStorage();
117
  toast.warning("Previous HTML content restored from local storage.");
 
164
  <Header tab={currentTab} onNewTab={setCurrentTab}>
165
  <LoadProject
166
  onSuccess={(project: Project) => {
167
+ if (project.space_id && project.space_id !== "local") {
168
+ router.push(`/projects/${project.space_id}`);
169
+ } else {
170
+ setHtml(project.html);
171
+ setPrompts(project.prompts || []);
172
+ toast.success("Projeto HTML carregado.");
173
+ }
174
  }}
175
  />
176
+ <SaveButton html={html} prompts={prompts} />
 
 
 
 
177
  </Header>
178
  <main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full max-lg:h-[calc(100%-82px)] relative">
179
  {currentTab === "chat" && (
components/editor/preview/index.tsx CHANGED
@@ -129,7 +129,7 @@ export const Preview = ({
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:
135
  selectedElement.getBoundingClientRect().top +
@@ -141,36 +141,97 @@ export const Preview = ({
141
  height: selectedElement.getBoundingClientRect().height,
142
  }}
143
  >
144
- <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">
145
  {htmlTagToText(selectedElement.tagName.toLowerCase())}
146
  </span>
147
  </div>
148
  )}
149
- <iframe
150
- id="preview-iframe"
151
- ref={iframeRef}
152
- title="output"
153
- className={classNames(
154
- "w-full select-none transition-all duration-200 bg-black h-full",
155
- {
156
- "pointer-events-none": isResizing || isAiWorking,
157
- "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]":
158
- device === "mobile",
159
- "lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:rounded-[24px]":
160
- currentTab !== "preview" && device === "desktop",
161
- }
162
- )}
163
- srcDoc={html}
164
- onLoad={() => {
165
- if (iframeRef?.current?.contentWindow?.document?.body) {
166
- iframeRef.current.contentWindow.document.body.scrollIntoView({
167
- block: isAiWorking ? "end" : "start",
168
- inline: "nearest",
169
- behavior: isAiWorking ? "instant" : "smooth",
170
- });
171
- }
172
- }}
173
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  </div>
175
  );
176
  };
 
129
  />
130
  {!isAiWorking && hoveredElement && selectedElement && (
131
  <div
132
+ className="cursor-pointer absolute bg-sky-500/10 border-[2px] border-dashed border-sky-500 p-3 z-10 pointer-events-none"
133
  style={{
134
  top:
135
  selectedElement.getBoundingClientRect().top +
 
141
  height: selectedElement.getBoundingClientRect().height,
142
  }}
143
  >
144
+ <span className="bg-sky-500 text-sm text-neutral-100 px-2 py-0.5 -translate-y-7 absolute top-0 left-0">
145
  {htmlTagToText(selectedElement.tagName.toLowerCase())}
146
  </span>
147
  </div>
148
  )}
149
+ {device === "mobile" ? (
150
+ <div
151
+ style={{
152
+ width: 320,
153
+ height: 684,
154
+ border: "16px solid #222",
155
+ borderRadius: 40,
156
+ boxShadow: "0 8px 40px #0008",
157
+ position: "relative",
158
+ background: "#111",
159
+ display: "flex",
160
+ alignItems: "center",
161
+ justifyContent: "center",
162
+ padding: 0,
163
+ }}
164
+ >
165
+ <div
166
+ style={{
167
+ position: "absolute",
168
+ top: 10,
169
+ left: "50%",
170
+ transform: "translateX(-50%)",
171
+ width: 60,
172
+ height: 5,
173
+ background: "#444",
174
+ borderRadius: 5,
175
+ zIndex: 3,
176
+ }}
177
+ />
178
+ <iframe
179
+ id="preview-iframe"
180
+ ref={iframeRef}
181
+ title="output"
182
+ className={classNames(
183
+ "w-full h-full select-none transition-all duration-200 bg-black",
184
+ {
185
+ "pointer-events-none": isResizing || isAiWorking,
186
+ "rounded-[32px]": false,
187
+ }
188
+ )}
189
+ style={{
190
+ border: "none",
191
+ width: 288,
192
+ height: 608,
193
+ borderRadius: 28,
194
+ marginTop: 24,
195
+ marginBottom: 24,
196
+ background: "#000",
197
+ }}
198
+ srcDoc={html}
199
+ onLoad={() => {
200
+ if (iframeRef?.current?.contentWindow?.document?.body) {
201
+ iframeRef.current.contentWindow.document.body.scrollIntoView({
202
+ block: isAiWorking ? "end" : "start",
203
+ inline: "nearest",
204
+ behavior: isAiWorking ? "instant" : "smooth",
205
+ });
206
+ }
207
+ }}
208
+ />
209
+ </div>
210
+ ) : (
211
+ <iframe
212
+ id="preview-iframe"
213
+ ref={iframeRef}
214
+ title="output"
215
+ className={classNames(
216
+ "w-full select-none transition-all duration-200 bg-black h-full",
217
+ {
218
+ "pointer-events-none": isResizing || isAiWorking,
219
+ "lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl":
220
+ currentTab !== "preview" && device === "desktop",
221
+ }
222
+ )}
223
+ srcDoc={html}
224
+ onLoad={() => {
225
+ if (iframeRef?.current?.contentWindow?.document?.body) {
226
+ iframeRef.current.contentWindow.document.body.scrollIntoView({
227
+ block: isAiWorking ? "end" : "start",
228
+ inline: "nearest",
229
+ behavior: isAiWorking ? "instant" : "smooth",
230
+ });
231
+ }
232
+ }}
233
+ />
234
+ )}
235
  </div>
236
  );
237
  };
components/editor/save-button/index.tsx CHANGED
@@ -31,17 +31,7 @@ export function SaveButton({
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
  }
@@ -55,20 +45,33 @@ export function SaveButton({
55
  <>
56
  <Button
57
  variant="default"
58
- className="max-lg:hidden !px-4 relative"
 
59
  onClick={updateSpace}
60
  >
61
- <MdSave className="size-4" />
62
- Save your Project{" "}
63
- {loading && <Loading className="ml-2 size-4 animate-spin" />}
64
  </Button>
65
  <Button
66
  variant="default"
67
- size="sm"
68
- className="lg:hidden relative"
69
- onClick={updateSpace}
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  >
71
- Save {loading && <Loading className="ml-2 size-4 animate-spin" />}
 
72
  </Button>
73
  </>
74
  );
 
31
  prompts,
32
  });
33
  if (res.data.ok) {
34
+ toast.success("Your space is updated! 🎉");
 
 
 
 
 
 
 
 
 
 
35
  } else {
36
  toast.error(res?.data?.error || "Failed to update space");
37
  }
 
45
  <>
46
  <Button
47
  variant="default"
48
+ size="sm"
49
+ className="lg:hidden relative"
50
  onClick={updateSpace}
51
  >
52
+ Save {loading && <Loading className="ml-2 size-4 animate-spin" />}
 
 
53
  </Button>
54
  <Button
55
  variant="default"
56
+ className="max-lg:hidden !px-4 relative"
57
+ onClick={() => {
58
+ let filename = prompt("Nome do arquivo .html:", "index.html");
59
+ if (!filename) return;
60
+ if (!filename.endsWith('.html')) filename += '.html';
61
+ const blob = new Blob([html], { type: "text/html" });
62
+ const url = URL.createObjectURL(blob);
63
+ const a = document.createElement("a");
64
+ a.href = url;
65
+ a.download = filename;
66
+ document.body.appendChild(a);
67
+ a.click();
68
+ document.body.removeChild(a);
69
+ URL.revokeObjectURL(url);
70
+ toast.success("HTML baixado com sucesso!");
71
+ }}
72
  >
73
+ Save to File
74
+ <MdSave className="ml-2" />
75
  </Button>
76
  </>
77
  );
components/login-modal/index.tsx CHANGED
@@ -1,15 +1,11 @@
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;
@@ -17,15 +13,6 @@ export const LoginModal = ({
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">
@@ -50,12 +37,12 @@ export const LoginModal = ({
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
- };
 
 
1
  import { Button } from "@/components/ui/button";
2
  import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
 
 
3
 
4
  export const LoginModal = ({
5
  open,
 
6
  onClose,
7
+ title = "Login is not available",
8
+ description = "This feature is currently disabled as the application no longer relies on Hugging Face authentication.",
9
  }: {
10
  open: boolean;
11
  html?: string;
 
13
  title?: string;
14
  description?: string;
15
  }) => {
 
 
 
 
 
 
 
 
 
16
  return (
17
  <Dialog open={open} onOpenChange={onClose}>
18
  <DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
 
37
  variant="black"
38
  size="lg"
39
  className="w-full !text-base !h-11 mt-8"
40
+ onClick={() => onClose(false)}
41
  >
42
+ Close
43
  </Button>
44
  </main>
45
  </DialogContent>
46
  </Dialog>
47
  );
48
+ };
components/my-projects/load-project.tsx CHANGED
@@ -13,9 +13,6 @@ import {
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
  import { useRouter } from "next/navigation";
20
 
21
  export const LoadProject = ({
@@ -25,46 +22,28 @@ export const LoadProject = ({
25
  fullXsBtn?: boolean;
26
  onSuccess: (project: Project) => void;
27
  }) => {
28
- const { user } = useUser();
29
- const router = useRouter();
30
 
31
- const [openLoginModal, setOpenLoginModal] = useState(false);
32
  const [open, setOpen] = useState(false);
33
  const [url, setUrl] = useState<string>("");
34
  const [isLoading, setIsLoading] = useState(false);
35
 
36
- const checkIfUrlIsValid = (url: string) => {
37
- // should match a hugging face spaces URL like: https://huggingface.co/spaces/username/project or https://hf.co/spaces/username/project
38
- const urlPattern = new RegExp(
39
- /^(https?:\/\/)?(huggingface\.co|hf\.co)\/spaces\/([\w-]+)\/([\w-]+)$/,
40
- "i"
41
- );
42
- return urlPattern.test(url);
43
- };
44
-
45
  const handleClick = async () => {
46
  if (isLoading) return; // Prevent multiple clicks while loading
47
  if (!url) {
48
  toast.error("Please enter a URL.");
49
  return;
50
  }
51
- if (!checkIfUrlIsValid(url)) {
52
- toast.error("Please enter a valid Hugging Face Spaces URL.");
53
- return;
54
- }
55
 
56
- const [username, namespace] = url
57
- .replace("https://huggingface.co/spaces/", "")
58
- .replace("https://hf.co/spaces/", "")
59
- .split("/");
60
 
61
  setIsLoading(true);
62
  try {
63
- const response = await api.post(`/me/projects/${username}/${namespace}`);
 
64
  toast.success("Project imported successfully!");
65
  setOpen(false);
66
  setUrl("");
67
- onSuccess(response.data.project);
68
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
  } catch (error: any) {
70
  if (error?.response?.data?.redirect) {
@@ -79,122 +58,98 @@ export const LoadProject = ({
79
  };
80
 
81
  return (
82
- <>
83
- {!user ? (
84
- <>
85
- <Button
86
- variant="outline"
87
- className="max-lg:hidden"
88
- onClick={() => setOpenLoginModal(true)}
89
- >
90
  <Import className="size-4 mr-1.5" />
91
  Load existing Project
92
  </Button>
93
- <Button
94
- variant="outline"
95
- size="sm"
96
- className="lg:hidden"
97
- onClick={() => setOpenLoginModal(true)}
98
- >
99
  {fullXsBtn && <Import className="size-3.5 mr-1" />}
100
  Load
101
  {fullXsBtn && " existing Project"}
102
  </Button>
103
- <LoginModal
104
- open={openLoginModal}
105
- onClose={setOpenLoginModal}
106
- title="Log In to load your Project"
107
- description="Log In through Hugging Face to load an existing project and increase your free limit!"
108
- />
109
- </>
110
- ) : (
111
- <Dialog open={open} onOpenChange={setOpen}>
112
- <DialogTrigger asChild>
113
- <div>
114
- <Button variant="outline" className="max-lg:hidden">
115
- <Import className="size-4 mr-1.5" />
116
- Load existing Project
117
- </Button>
118
- <Button variant="outline" size="sm" className="lg:hidden">
119
- {fullXsBtn && <Import className="size-3.5 mr-1" />}
120
- Load
121
- {fullXsBtn && " existing Project"}
122
- </Button>
123
- </div>
124
- </DialogTrigger>
125
- <DialogContent className="sm:max-w-md !p-0 !rounded-3xl !bg-white !border-neutral-100 overflow-hidden text-center">
126
- <DialogTitle className="hidden" />
127
- <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
128
- <div className="flex items-center justify-center -space-x-4 mb-3">
129
- <div className="size-11 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-2xl opacity-50">
130
- 🎨
131
- </div>
132
- <div className="size-13 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-3xl z-2">
133
- 🥳
134
- </div>
135
- <div className="size-11 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-2xl opacity-50">
136
- 💎
137
- </div>
138
- </div>
139
- <p className="text-2xl font-semibold text-neutral-950">
140
- Import a Project
141
- </p>
142
- <p className="text-base text-neutral-500 mt-1.5">
143
- Enter the URL of your Hugging Face Space to import an existing
144
- project.
145
- </p>
146
- </header>
147
- <main className="space-y-4 px-9 pb-9 pt-2">
148
- <div>
149
- <p className="text-sm text-neutral-700 mb-2">
150
- Enter your Hugging Face Space
151
- </p>
152
- <Input
153
- type="text"
154
- placeholder="https://huggingface.com/spaces/username/project"
155
- value={url}
156
- onChange={(e) => setUrl(e.target.value)}
157
- onBlur={(e) => {
158
- const inputUrl = e.target.value.trim();
159
- if (!inputUrl) {
160
- setUrl("");
161
- return;
162
- }
163
- if (!checkIfUrlIsValid(inputUrl)) {
164
- toast.error("Please enter a valid URL.");
165
- return;
166
- }
167
- setUrl(inputUrl);
168
- }}
169
- className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
170
- />
171
- </div>
172
- <div>
173
- <p className="text-sm text-neutral-700 mb-2">
174
- Then, let&apos;s import it!
175
- </p>
176
- <Button
177
- variant="black"
178
- onClick={handleClick}
179
- className="relative w-full"
180
- >
181
- {isLoading ? (
182
- <>
183
- <Loading
184
- overlay={false}
185
- className="ml-2 size-4 animate-spin"
186
- />
187
- Fetching your Space...
188
- </>
189
- ) : (
190
- <>Import your Space</>
191
- )}
192
- </Button>
193
- </div>
194
- </main>
195
- </DialogContent>
196
- </Dialog>
197
- )}
198
- </>
199
  );
200
  };
 
13
  import Loading from "@/components/loading";
14
  import { Input } from "../ui/input";
15
  import { toast } from "sonner";
 
 
 
16
  import { useRouter } from "next/navigation";
17
 
18
  export const LoadProject = ({
 
22
  fullXsBtn?: boolean;
23
  onSuccess: (project: Project) => void;
24
  }) => {
25
+ const router = useRouter();
 
26
 
 
27
  const [open, setOpen] = useState(false);
28
  const [url, setUrl] = useState<string>("");
29
  const [isLoading, setIsLoading] = useState(false);
30
 
 
 
 
 
 
 
 
 
 
31
  const handleClick = async () => {
32
  if (isLoading) return; // Prevent multiple clicks while loading
33
  if (!url) {
34
  toast.error("Please enter a URL.");
35
  return;
36
  }
 
 
 
 
37
 
38
+ // The URL validation and parsing logic is removed as we are no longer using Hugging Face
 
 
 
39
 
40
  setIsLoading(true);
41
  try {
42
+ // You will need to implement your own logic to import a project from a URL
43
+ // For now, this will just display a success message
44
  toast.success("Project imported successfully!");
45
  setOpen(false);
46
  setUrl("");
 
47
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
  } catch (error: any) {
49
  if (error?.response?.data?.redirect) {
 
58
  };
59
 
60
  return (
61
+ <Dialog open={open} onOpenChange={setOpen}>
62
+ <DialogTrigger asChild>
63
+ <div>
64
+ <Button variant="outline" className="max-lg:hidden">
 
 
 
 
65
  <Import className="size-4 mr-1.5" />
66
  Load existing Project
67
  </Button>
68
+ <Button variant="outline" size="sm" className="lg:hidden">
 
 
 
 
 
69
  {fullXsBtn && <Import className="size-3.5 mr-1" />}
70
  Load
71
  {fullXsBtn && " existing Project"}
72
  </Button>
73
+ </div>
74
+ </DialogTrigger>
75
+ <DialogContent className="sm:max-w-md !p-0 !rounded-3xl !bg-white !border-neutral-100 overflow-hidden text-center">
76
+ <DialogTitle className="hidden" />
77
+ <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
78
+ <p className="text-2xl font-semibold text-neutral-950">
79
+ Import a Project
80
+ </p>
81
+ <p className="text-base text-neutral-500 mt-1.5">
82
+ Enter the URL of your project to import it.
83
+ </p>
84
+ </header>
85
+ <main className="space-y-4 px-9 pb-9 pt-2">
86
+ <div>
87
+ <p className="text-sm text-neutral-700 mb-2">
88
+ Load HTML from your computer
89
+ </p>
90
+ <Input
91
+ type="file"
92
+ accept=".html"
93
+ onChange={(e) => {
94
+ const file = e.target.files?.[0];
95
+ if (file) {
96
+ const reader = new FileReader();
97
+ reader.onload = (event) => {
98
+ const htmlContent = event.target?.result as string;
99
+ onSuccess({
100
+ html: htmlContent,
101
+ prompts: [],
102
+ title: "Imported Project",
103
+ user_id: "local",
104
+ space_id: "local"
105
+ });
106
+ setOpen(false);
107
+ };
108
+ reader.readAsText(file);
109
+ }
110
+ }}
111
+ className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
112
+ />
113
+ </div>
114
+ <div className="text-sm text-neutral-700 mb-2">
115
+ OR
116
+ </div>
117
+ <div>
118
+ <p className="text-sm text-neutral-700 mb-2">
119
+ Enter your Project URL
120
+ </p>
121
+ <Input
122
+ type="text"
123
+ placeholder="https://example.com/my-project"
124
+ value={url}
125
+ onChange={(e) => setUrl(e.target.value)}
126
+ className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
127
+ />
128
+ </div>
129
+ <div>
130
+ <p className="text-sm text-neutral-700 mb-2">
131
+ Then, let&apos;s import it!
132
+ </p>
133
+ <Button
134
+ variant="black"
135
+ onClick={handleClick}
136
+ className="relative w-full"
137
+ >
138
+ {isLoading ? (
139
+ <>
140
+ <Loading
141
+ overlay={false}
142
+ className="ml-2 size-4 animate-spin"
143
+ />
144
+ Importing...
145
+ </>
146
+ ) : (
147
+ <>Import Project</>
148
+ )}
149
+ </Button>
150
+ </div>
151
+ </main>
152
+ </DialogContent>
153
+ </Dialog>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  );
155
  };
components/my-projects/project-card.tsx CHANGED
@@ -1,6 +1,6 @@
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";
@@ -8,8 +8,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 }) {
@@ -56,15 +55,7 @@ export function ProjectCard({ project }: { project: Project }) {
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>
 
1
  import Link from "next/link";
2
  import { formatDistance } from "date-fns";
3
+ import { EllipsisVertical } from "lucide-react";
4
 
5
  import { Project } from "@/types";
6
  import { Button } from "@/components/ui/button";
 
8
  DropdownMenu,
9
  DropdownMenuContent,
10
  DropdownMenuGroup,
11
+ DropdownMenuTrigger,
 
12
  } from "@/components/ui/dropdown-menu";
13
 
14
  export function ProjectCard({ project }: { project: Project }) {
 
55
  </DropdownMenuTrigger>
56
  <DropdownMenuContent className="w-56" align="start">
57
  <DropdownMenuGroup>
58
+
 
 
 
 
 
 
 
 
59
  </DropdownMenuGroup>
60
  </DropdownMenuContent>
61
  </DropdownMenu>
components/pro-modal/index.tsx CHANGED
@@ -1,24 +1,15 @@
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 (
@@ -26,55 +17,19 @@ export const ProModal = ({
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>
@@ -82,11 +37,4 @@ export const ProModal = ({
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;
 
 
1
  import { Button } from "@/components/ui/button";
2
  import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
 
 
3
 
4
  export const ProModal = ({
5
  open,
 
6
  onClose,
7
  }: {
8
  open: boolean;
 
9
  onClose: React.Dispatch<React.SetStateAction<boolean>>;
10
  }) => {
 
11
  const handleProClick = () => {
12
+ // Replace with your own subscription logic
 
 
 
13
  onClose(false);
14
  };
15
  return (
 
17
  <DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
18
  <DialogTitle className="hidden" />
19
  <main className="flex flex-col items-start text-left relative pt-2">
 
 
 
 
 
 
 
 
 
 
 
20
  <h2 className="text-2xl font-bold text-neutral-950">
21
+ Upgrade to Pro
22
  </h2>
23
  <p className="text-neutral-500 text-base mt-2 max-w-sm">
24
+ You have reached the monthly free limit.
25
  </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  <Button
27
  variant="black"
28
  size="lg"
29
  className="w-full !text-base !h-11 mt-8"
30
  onClick={handleProClick}
31
  >
32
+ Upgrade
33
  </Button>
34
  </main>
35
  </DialogContent>
 
37
  );
38
  };
39
 
 
 
 
 
 
 
 
40
  export default ProModal;
components/public/navigation/index.tsx CHANGED
@@ -6,10 +6,7 @@ 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
  {
@@ -31,7 +28,6 @@ const navigationLinks = [
31
  ];
32
 
33
  export default function Navigation() {
34
- const { openLoginWindow, user } = useUser();
35
  const [hash, setHash] = useState("");
36
 
37
  const selectorRef = useRef<HTMLDivElement>(null);
@@ -138,18 +134,6 @@ export default function Navigation() {
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
  );
 
6
  import { useMount, useUnmount } from "react-use";
7
  import classNames from "classnames";
8
 
 
9
  import Logo from "@/assets/logo.svg";
 
 
10
 
11
  const navigationLinks = [
12
  {
 
28
  ];
29
 
30
  export default function Navigation() {
 
31
  const [hash, setHash] = useState("");
32
 
33
  const selectorRef = useRef<HTMLDivElement>(null);
 
134
  <div className="size-1 bg-white rounded-full" />
135
  </div>
136
  </ul>
 
 
 
 
 
 
 
 
 
 
 
 
137
  </nav>
138
  </div>
139
  );
components/user-menu/index.tsx CHANGED
@@ -1,6 +1,5 @@
1
  import {
2
- ChartSpline,
3
- CirclePlus,
4
  FolderCode,
5
  Import,
6
  LogOut,
@@ -63,12 +62,7 @@ export const UserMenu = ({ className }: { className?: string }) => {
63
  View Projects
64
  </DropdownMenuItem>
65
  </Link>
66
- <a href="https://huggingface.co/settings/billing" target="_blank">
67
- <DropdownMenuItem>
68
- <ChartSpline className="size-4 text-neutral-100" />
69
- Usage Quota
70
- </DropdownMenuItem>
71
- </a>
72
  </DropdownMenuGroup>
73
  <DropdownMenuSeparator />
74
  <DropdownMenuItem
 
1
  import {
2
+ CirclePlus,
 
3
  FolderCode,
4
  Import,
5
  LogOut,
 
62
  View Projects
63
  </DropdownMenuItem>
64
  </Link>
65
+
 
 
 
 
 
66
  </DropdownMenuGroup>
67
  <DropdownMenuSeparator />
68
  <DropdownMenuItem
hooks/useUser.ts CHANGED
@@ -1,29 +1,29 @@
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
  "use client";
3
  import { useQuery, useQueryClient } from "@tanstack/react-query";
4
- import { useCookie } from "react-use";
5
  import { useRouter } from "next/navigation";
6
 
7
  import { User } from "@/types";
8
- import MY_TOKEN_KEY from "@/lib/get-cookie-name";
9
- import { api } from "@/lib/api";
10
  import { toast } from "sonner";
11
 
12
  export const useUser = (initialData?: {
13
  user: User | null;
14
  errCode: number | null;
15
  }) => {
16
- const cookie_name = MY_TOKEN_KEY();
17
  const client = useQueryClient();
18
  const router = useRouter();
19
- const [, setCookie, removeCookie] = useCookie(cookie_name);
20
- const [currentRoute, setCurrentRoute] = useCookie("deepsite-currentRoute");
21
 
22
  const { data: { user, errCode } = { user: null, errCode: null }, isLoading } =
23
  useQuery({
24
  queryKey: ["user.me"],
25
  queryFn: async () => {
26
- return { user: initialData?.user, errCode: initialData?.errCode };
 
 
 
 
27
  },
28
  refetchOnWindowFocus: false,
29
  refetchOnReconnect: false,
@@ -32,63 +32,11 @@ export const useUser = (initialData?: {
32
  initialData: initialData
33
  ? { user: initialData?.user, errCode: initialData?.errCode }
34
  : undefined,
35
- enabled: false,
36
  });
37
 
38
- const { data: loadingAuth } = useQuery({
39
- queryKey: ["loadingAuth"],
40
- queryFn: async () => false,
41
- refetchOnWindowFocus: false,
42
- refetchOnReconnect: false,
43
- refetchOnMount: false,
44
- });
45
- const setLoadingAuth = (value: boolean) => {
46
- client.setQueryData(["setLoadingAuth"], value);
47
- };
48
-
49
- const openLoginWindow = async () => {
50
- setCurrentRoute(window.location.pathname);
51
- return router.push("/auth");
52
- };
53
-
54
- const loginFromCode = async (code: string) => {
55
- setLoadingAuth(true);
56
- if (loadingAuth) return;
57
- await api
58
- .post("/auth", { code })
59
- .then(async (res: any) => {
60
- if (res.data) {
61
- setCookie(res.data.access_token, {
62
- expires: res.data.expires_in
63
- ? new Date(Date.now() + res.data.expires_in * 1000)
64
- : undefined,
65
- sameSite: "none",
66
- secure: true,
67
- });
68
- client.setQueryData(["user.me"], {
69
- user: res.data.user,
70
- errCode: null,
71
- });
72
- if (currentRoute) {
73
- router.push(currentRoute);
74
- setCurrentRoute("");
75
- } else {
76
- router.push("/projects");
77
- }
78
- toast.success("Login successful");
79
- }
80
- })
81
- .catch((err: any) => {
82
- toast.error(err?.data?.message ?? err.message ?? "An error occurred");
83
- })
84
- .finally(() => {
85
- setLoadingAuth(false);
86
- });
87
- };
88
-
89
  const logout = async () => {
90
- removeCookie();
91
- router.push("/");
92
  toast.success("Logout successful");
93
  client.setQueryData(["user.me"], {
94
  user: null,
@@ -100,9 +48,7 @@ export const useUser = (initialData?: {
100
  return {
101
  user,
102
  errCode,
103
- loading: isLoading || loadingAuth,
104
- openLoginWindow,
105
- loginFromCode,
106
  logout,
107
  };
108
- };
 
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
  "use client";
3
  import { useQuery, useQueryClient } from "@tanstack/react-query";
4
+
5
  import { useRouter } from "next/navigation";
6
 
7
  import { User } from "@/types";
8
+ import { isAuthenticated } from "@/lib/auth";
 
9
  import { toast } from "sonner";
10
 
11
  export const useUser = (initialData?: {
12
  user: User | null;
13
  errCode: number | null;
14
  }) => {
 
15
  const client = useQueryClient();
16
  const router = useRouter();
 
 
17
 
18
  const { data: { user, errCode } = { user: null, errCode: null }, isLoading } =
19
  useQuery({
20
  queryKey: ["user.me"],
21
  queryFn: async () => {
22
+ const authResult = await isAuthenticated();
23
+ if (authResult && "id" in authResult) {
24
+ return { user: authResult as User, errCode: null };
25
+ }
26
+ return { user: null, errCode: 401 };
27
  },
28
  refetchOnWindowFocus: false,
29
  refetchOnReconnect: false,
 
32
  initialData: initialData
33
  ? { user: initialData?.user, errCode: initialData?.errCode }
34
  : undefined,
35
+ enabled: true, // Enable the query to fetch the mock user
36
  });
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  const logout = async () => {
39
+ // Since authentication is removed, just reload the page
 
40
  toast.success("Logout successful");
41
  client.setQueryData(["user.me"], {
42
  user: null,
 
48
  return {
49
  user,
50
  errCode,
51
+ loading: isLoading,
 
 
52
  logout,
53
  };
54
+ };
lib/api.ts CHANGED
@@ -1,5 +1,5 @@
1
  import axios from "axios";
2
- import MY_TOKEN_KEY from "./get-cookie-name";
3
 
4
  export const api = axios.create({
5
  baseURL: `/api`,
@@ -15,21 +15,6 @@ export const apiServer = axios.create({
15
  },
16
  });
17
 
18
- api.interceptors.request.use(
19
- async (config) => {
20
- // get the token from cookies
21
- const cookie_name = MY_TOKEN_KEY();
22
- const token = document.cookie
23
- .split("; ")
24
- .find((row) => row.startsWith(`${cookie_name}=`))
25
- ?.split("=")[1];
26
- if (token) {
27
- config.headers.Authorization = `Bearer ${token}`;
28
- }
29
- return config;
30
- },
31
- (error) => {
32
- // Handle the error
33
- return Promise.reject(error);
34
- }
35
- );
 
1
  import axios from "axios";
2
+
3
 
4
  export const api = axios.create({
5
  baseURL: `/api`,
 
15
  },
16
  });
17
 
18
+ api.interceptors.request.use((config) => {
19
+ return config;
20
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/auth.ts CHANGED
@@ -1,72 +1,22 @@
1
  import { User } from "@/types";
2
  import { NextResponse } from "next/server";
3
- import { cookies, headers } from "next/headers";
4
- import MY_TOKEN_KEY from "./get-cookie-name";
5
 
6
  // UserResponse = type User & { token: string };
7
  type UserResponse = User & { token: string };
8
 
9
- export const isAuthenticated = async (): // req: NextRequest
10
- Promise<UserResponse | NextResponse<unknown> | undefined> => {
11
- const authHeaders = await headers();
12
- const cookieStore = await cookies();
13
- const token = cookieStore.get(MY_TOKEN_KEY())?.value
14
- ? `Bearer ${cookieStore.get(MY_TOKEN_KEY())?.value}`
15
- : authHeaders.get("Authorization");
16
-
17
- if (!token) {
18
- return NextResponse.json(
19
- {
20
- ok: false,
21
- message: "Wrong castle fam :(",
22
- },
23
- {
24
- status: 401,
25
- headers: {
26
- "Content-Type": "application/json",
27
- },
28
- }
29
- );
30
- }
31
-
32
- const user = await fetch("https://huggingface.co/api/whoami-v2", {
33
- headers: {
34
- Authorization: token,
35
- },
36
- method: "GET",
37
- })
38
- .then((res) => res.json())
39
- .catch(() => {
40
- return NextResponse.json(
41
- {
42
- ok: false,
43
- message: "Invalid token",
44
- },
45
- {
46
- status: 401,
47
- headers: {
48
- "Content-Type": "application/json",
49
- },
50
- }
51
- );
52
- });
53
- if (!user || !user.id) {
54
- return NextResponse.json(
55
- {
56
- ok: false,
57
- message: "Invalid token",
58
- },
59
- {
60
- status: 401,
61
- headers: {
62
- "Content-Type": "application/json",
63
- },
64
- }
65
- );
66
- }
67
 
68
  return {
69
  ...user,
70
- token: token.replace("Bearer ", ""),
71
  };
72
- };
 
1
  import { User } from "@/types";
2
  import { NextResponse } from "next/server";
 
 
3
 
4
  // UserResponse = type User & { token: string };
5
  type UserResponse = User & { token: string };
6
 
7
+ export const isAuthenticated = async (): Promise<UserResponse | NextResponse<unknown> | undefined> => {
8
+ // Mock user for now, as Hugging Face authentication is removed
9
+ const user: User = {
10
+ id: "mock-user-id",
11
+ name: "Mock User",
12
+ fullname: "Mock User",
13
+ avatarUrl: "https://www.gravatar.com/avatar/?d=mp",
14
+ isPro: false,
15
+ isLocalUse: true,
16
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  return {
19
  ...user,
20
+ token: "mock-token",
21
  };
22
+ };
lib/consts.ts CHANGED
@@ -5,16 +5,21 @@ export const defaultHTML = `<!DOCTYPE html>
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <meta charset="utf-8">
7
  <script src="https://cdn.tailwindcss.com"></script>
 
 
 
 
 
 
8
  </head>
9
- <body class="flex justify-center items-center h-screen overflow-hidden bg-white font-sans text-center px-6">
10
- <div class="w-full">
11
- <span class="text-xs rounded-full mb-2 inline-block px-2 py-1 border border-amber-500/15 bg-amber-500/15 text-amber-500">🔥 New version dropped!</span>
12
- <h1 class="text-4xl lg:text-6xl font-bold font-sans">
13
- <span class="text-2xl lg:text-4xl text-gray-400 block font-medium">I'm ready to work,</span>
14
  Ask me anything.
15
  </h1>
16
  </div>
17
- <img src="https://enzostvs-deepsite.hf.space/arrow.svg" class="absolute bottom-8 left-0 w-[100px] transform rotate-[30deg]" />
18
  <script></script>
19
  </body>
20
  </html>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <meta charset="utf-8">
7
  <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ body { background: #181a20; }
10
+ .glass { background: #23242a; border-radius: 14px; }
11
+ .vibe-badge { border: 1px solid #ffe06650; background: #23242a; color: #ffe066; font-weight: 600; }
12
+ .neon { text-shadow: 0 0 1.5px #ffe066; }
13
+ </style>
14
  </head>
15
+ <body class="flex justify-center items-center h-screen overflow-hidden font-sans text-center px-6 text-white">
16
+ <div class="w-full glass p-8">
17
+ <span class="text-xs rounded-full mb-4 inline-block px-3 py-1 vibe-badge">🔥 New version dropped!</span>
18
+ <h1 class="text-4xl lg:text-7xl font-black font-sans tracking-tight neon">
19
+ <span class="text-2xl lg:text-4xl block font-bold text-neutral-400 mb-1">I'm ready to work,</span>
20
  Ask me anything.
21
  </h1>
22
  </div>
 
23
  <script></script>
24
  </body>
25
  </html>
lib/get-cookie-name.ts DELETED
@@ -1,3 +0,0 @@
1
- export default function MY_TOKEN_KEY() {
2
- return "deepsite-auth-token";
3
- }
 
 
 
 
lib/mongodb.ts DELETED
@@ -1,28 +0,0 @@
1
- import mongoose from "mongoose";
2
-
3
- const MONGODB_URI = process.env.MONGODB_URI;
4
- // @ts-expect-error iknown issue with mongoose types
5
- let cached = global.mongoose;
6
-
7
- if (!cached) {
8
- // @ts-expect-error iknown issue with mongoose types
9
- cached = global.mongoose = { conn: null, promise: null };
10
- }
11
-
12
- async function dbConnect() {
13
- if (cached.conn) {
14
- return cached.conn;
15
- }
16
-
17
- if (!cached.promise) {
18
- cached.promise = mongoose
19
- .connect(MONGODB_URI as string)
20
- .then((mongoose) => {
21
- return mongoose;
22
- });
23
- }
24
- cached.conn = await cached.promise;
25
- return cached.conn;
26
- }
27
-
28
- export default dbConnect;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/providers.ts CHANGED
@@ -1,56 +1,17 @@
1
  export const PROVIDERS = {
2
- "fireworks-ai": {
3
- name: "Fireworks AI",
4
- max_tokens: 131_000,
5
- id: "fireworks-ai",
6
- },
7
- nebius: {
8
- name: "Nebius AI Studio",
9
- max_tokens: 131_000,
10
- id: "nebius",
11
- },
12
- sambanova: {
13
- name: "SambaNova",
14
- max_tokens: 32_000,
15
- id: "sambanova",
16
- },
17
- novita: {
18
- name: "NovitaAI",
19
- max_tokens: 16_000,
20
- id: "novita",
21
- },
22
- hyperbolic: {
23
- name: "Hyperbolic",
24
- max_tokens: 131_000,
25
- id: "hyperbolic",
26
- },
27
- together: {
28
- name: "Together AI",
29
  max_tokens: 128_000,
30
- id: "together",
31
  },
32
  };
33
 
34
  export const MODELS = [
35
  {
36
- value: "deepseek-ai/DeepSeek-V3-0324",
37
- label: "DeepSeek V3 O324",
38
- providers: ["fireworks-ai", "nebius", "sambanova", "novita", "hyperbolic"],
39
- autoProvider: "novita",
40
- },
41
- {
42
- value: "deepseek-ai/DeepSeek-R1-0528",
43
- label: "DeepSeek R1 0528",
44
- providers: [
45
- "fireworks-ai",
46
- "novita",
47
- "hyperbolic",
48
- "nebius",
49
- "together",
50
- "sambanova",
51
- ],
52
- autoProvider: "novita",
53
- isNew: true,
54
- isThinker: true,
55
  },
56
  ];
 
1
  export const PROVIDERS = {
2
+ "openai-compatible": {
3
+ name: "OpenAI Compatible",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  max_tokens: 128_000,
5
+ id: "openai-compatible",
6
  },
7
  };
8
 
9
  export const MODELS = [
10
  {
11
+ value: "gpt-4o-mini", // Default model, can be overridden by user
12
+ label: "GPT-4o Mini (Default)",
13
+ providers: ["openai-compatible"],
14
+ autoProvider: "openai-compatible",
15
+ isThinker: false,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  },
17
  ];
package-lock.json CHANGED
@@ -8,8 +8,6 @@
8
  "name": "deepsite-v2",
9
  "version": "0.1.0",
10
  "dependencies": {
11
- "@huggingface/hub": "^2.2.0",
12
- "@huggingface/inference": "^4.0.3",
13
  "@monaco-editor/react": "^4.7.0-rc.0",
14
  "@radix-ui/react-avatar": "^1.1.10",
15
  "@radix-ui/react-checkbox": "^1.3.2",
@@ -37,6 +35,7 @@
37
  "mongoose": "^8.15.1",
38
  "next": "15.3.3",
39
  "next-themes": "^0.4.6",
 
40
  "react": "^19.0.0",
41
  "react-dom": "^19.0.0",
42
  "react-icons": "^5.5.0",
@@ -299,49 +298,6 @@
299
  "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
300
  "license": "MIT"
301
  },
302
- "node_modules/@huggingface/hub": {
303
- "version": "2.2.0",
304
- "resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-2.2.0.tgz",
305
- "integrity": "sha512-G+VS1eMp80KovIHBlsiEigS6I6qmI4j+VQ1UZ8CaXT+pw2A7tj6e/crfxFdKNE2uOK5oQkRFiCBJykMwrWQ8OA==",
306
- "license": "MIT",
307
- "dependencies": {
308
- "@huggingface/tasks": "^0.19.11"
309
- },
310
- "bin": {
311
- "hfjs": "dist/cli.js"
312
- },
313
- "engines": {
314
- "node": ">=18"
315
- }
316
- },
317
- "node_modules/@huggingface/inference": {
318
- "version": "4.0.3",
319
- "resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-4.0.3.tgz",
320
- "integrity": "sha512-TE1IvY+4DQJhjpJsFdBqDHu0Jg+p/+9G5UmW3hmLBcUJzy7P4J0yZky+VFniSVxI8dFA4eKKcW8/uYD+yZM9NA==",
321
- "license": "MIT",
322
- "dependencies": {
323
- "@huggingface/jinja": "^0.5.0",
324
- "@huggingface/tasks": "^0.19.12"
325
- },
326
- "engines": {
327
- "node": ">=18"
328
- }
329
- },
330
- "node_modules/@huggingface/jinja": {
331
- "version": "0.5.0",
332
- "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.0.tgz",
333
- "integrity": "sha512-Ptc03/jGRiYRoi0bUYKZ14MkDslsBRT24oxmsvUlfYrvQMldrxCevhPnT+hfX8awKTT8/f/0ZBBWldoeAcMHdQ==",
334
- "license": "MIT",
335
- "engines": {
336
- "node": ">=18"
337
- }
338
- },
339
- "node_modules/@huggingface/tasks": {
340
- "version": "0.19.12",
341
- "resolved": "https://registry.npmjs.org/@huggingface/tasks/-/tasks-0.19.12.tgz",
342
- "integrity": "sha512-rg5+phoJg7SCqbUx9O9I+bfZkZBf6rqf2XGuN7M+uuwBSO4zO3V9s/Iqein1QSEhktR4g5OVBUZimodkX4ISkg==",
343
- "license": "MIT"
344
- },
345
  "node_modules/@humanfs/core": {
346
  "version": "0.19.1",
347
  "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -6885,6 +6841,26 @@
6885
  "url": "https://github.com/sponsors/ljharb"
6886
  }
6887
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6888
  "node_modules/optionator": {
6889
  "version": "0.9.4",
6890
  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
 
8
  "name": "deepsite-v2",
9
  "version": "0.1.0",
10
  "dependencies": {
 
 
11
  "@monaco-editor/react": "^4.7.0-rc.0",
12
  "@radix-ui/react-avatar": "^1.1.10",
13
  "@radix-ui/react-checkbox": "^1.3.2",
 
35
  "mongoose": "^8.15.1",
36
  "next": "15.3.3",
37
  "next-themes": "^0.4.6",
38
+ "openai": "^5.9.0",
39
  "react": "^19.0.0",
40
  "react-dom": "^19.0.0",
41
  "react-icons": "^5.5.0",
 
298
  "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
299
  "license": "MIT"
300
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  "node_modules/@humanfs/core": {
302
  "version": "0.19.1",
303
  "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
 
6841
  "url": "https://github.com/sponsors/ljharb"
6842
  }
6843
  },
6844
+ "node_modules/openai": {
6845
+ "version": "5.9.0",
6846
+ "resolved": "https://registry.npmjs.org/openai/-/openai-5.9.0.tgz",
6847
+ "integrity": "sha512-cmLC0pfqLLhBGxE4aZPyRPjydgYCncppV2ClQkKmW79hNjCvmzkfhz8rN5/YVDmjVQlFV+UsF1JIuNjNgeagyQ==",
6848
+ "bin": {
6849
+ "openai": "bin/cli"
6850
+ },
6851
+ "peerDependencies": {
6852
+ "ws": "^8.18.0",
6853
+ "zod": "^3.23.8"
6854
+ },
6855
+ "peerDependenciesMeta": {
6856
+ "ws": {
6857
+ "optional": true
6858
+ },
6859
+ "zod": {
6860
+ "optional": true
6861
+ }
6862
+ }
6863
+ },
6864
  "node_modules/optionator": {
6865
  "version": "0.9.4",
6866
  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
package.json CHANGED
@@ -9,8 +9,6 @@
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
12
- "@huggingface/hub": "^2.2.0",
13
- "@huggingface/inference": "^4.0.3",
14
  "@monaco-editor/react": "^4.7.0-rc.0",
15
  "@radix-ui/react-avatar": "^1.1.10",
16
  "@radix-ui/react-checkbox": "^1.3.2",
@@ -38,6 +36,7 @@
38
  "mongoose": "^8.15.1",
39
  "next": "15.3.3",
40
  "next-themes": "^0.4.6",
 
41
  "react": "^19.0.0",
42
  "react-dom": "^19.0.0",
43
  "react-icons": "^5.5.0",
 
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
 
 
12
  "@monaco-editor/react": "^4.7.0-rc.0",
13
  "@radix-ui/react-avatar": "^1.1.10",
14
  "@radix-ui/react-checkbox": "^1.3.2",
 
36
  "mongoose": "^8.15.1",
37
  "next": "15.3.3",
38
  "next-themes": "^0.4.6",
39
+ "openai": "^5.9.0",
40
  "react": "^19.0.0",
41
  "react-dom": "^19.0.0",
42
  "react-icons": "^5.5.0",