enzostvs HF Staff commited on
Commit
0b02673
·
1 Parent(s): be253a1
app/api/ask/route.ts CHANGED
@@ -31,6 +31,10 @@ import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
31
 
32
  const ipAddresses = new Map();
33
 
 
 
 
 
34
  export async function POST(request: NextRequest) {
35
  const authHeaders = await headers();
36
  const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
@@ -113,6 +117,9 @@ export async function POST(request: NextRequest) {
113
 
114
  (async () => {
115
  // let completeResponse = "";
 
 
 
116
  try {
117
  const client = new InferenceClient(token);
118
 
@@ -144,22 +151,51 @@ export async function POST(request: NextRequest) {
144
  billTo ? { billTo } : {}
145
  );
146
 
147
- while (true) {
148
- const { done, value } = await chatCompletion.next()
149
- if (done) {
150
- break;
151
- }
 
 
152
 
153
- const chunk = value.choices[0]?.delta?.content;
154
- if (chunk) {
155
- await writer.write(encoder.encode(chunk));
156
- }
157
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  // Explicitly close the writer after successful completion
160
  await writer.close();
161
  } catch (error: any) {
162
- if (error.message?.includes("exceeded your monthly included credits")) {
 
 
 
 
 
 
 
 
 
 
 
 
163
  await writer.write(
164
  encoder.encode(
165
  JSON.stringify({
@@ -341,17 +377,52 @@ export async function PUT(request: NextRequest) {
341
  billTo ? { billTo } : {}
342
  );
343
 
 
344
  let chunk = "";
345
- while (true) {
346
- const { done, value } = await chatCompletion.next();
347
- if (done) {
348
- break;
349
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
- const deltaContent = value.choices[0]?.delta?.content;
352
- if (deltaContent) {
353
- chunk += deltaContent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  }
 
355
  }
356
  if (!chunk) {
357
  return NextResponse.json(
@@ -616,6 +687,15 @@ This project was created with [DeepSite](https://huggingface.co/deepsite).
616
  );
617
  }
618
  } catch (error: any) {
 
 
 
 
 
 
 
 
 
619
  if (error.message?.includes("exceeded your monthly included credits")) {
620
  return NextResponse.json(
621
  {
 
31
 
32
  const ipAddresses = new Map();
33
 
34
+ const STREAMING_TIMEOUT = 180000;
35
+ const REQUEST_TIMEOUT = 240000;
36
+ export const maxDuration = 300;
37
+
38
  export async function POST(request: NextRequest) {
39
  const authHeaders = await headers();
40
  const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
 
117
 
118
  (async () => {
119
  // let completeResponse = "";
120
+ let timeoutId: NodeJS.Timeout | null = null;
121
+ let isTimedOut = false;
122
+
123
  try {
124
  const client = new InferenceClient(token);
125
 
 
151
  billTo ? { billTo } : {}
152
  );
153
 
154
+ // Set up timeout
155
+ const timeoutPromise = new Promise((_, reject) => {
156
+ timeoutId = setTimeout(() => {
157
+ isTimedOut = true;
158
+ reject(new Error('Request timeout: The AI model took too long to respond. Please try again with a simpler prompt or try a different model.'));
159
+ }, STREAMING_TIMEOUT);
160
+ });
161
 
162
+ // Race between streaming and timeout
163
+ await Promise.race([
164
+ (async () => {
165
+ while (true) {
166
+ const { done, value } = await chatCompletion.next()
167
+ if (done) {
168
+ break;
169
+ }
170
+
171
+ const chunk = value.choices[0]?.delta?.content;
172
+ if (chunk) {
173
+ await writer.write(encoder.encode(chunk));
174
+ }
175
+ }
176
+ })(),
177
+ timeoutPromise
178
+ ]);
179
+
180
+ // Clear timeout if successful
181
+ if (timeoutId) clearTimeout(timeoutId);
182
 
183
  // Explicitly close the writer after successful completion
184
  await writer.close();
185
  } catch (error: any) {
186
+ // Clear timeout on error
187
+ if (timeoutId) clearTimeout(timeoutId);
188
+
189
+ if (isTimedOut || error.message?.includes('timeout') || error.message?.includes('Request timeout')) {
190
+ await writer.write(
191
+ encoder.encode(
192
+ JSON.stringify({
193
+ ok: false,
194
+ message: "Request timeout: The AI model took too long to respond. Please try again with a simpler prompt or try a different model.",
195
+ })
196
+ )
197
+ );
198
+ } else if (error.message?.includes("exceeded your monthly included credits")) {
199
  await writer.write(
200
  encoder.encode(
201
  JSON.stringify({
 
377
  billTo ? { billTo } : {}
378
  );
379
 
380
+ // Set up timeout for AI streaming
381
  let chunk = "";
382
+ let timeoutId: NodeJS.Timeout | null = null;
383
+ let isTimedOut = false;
384
+
385
+ const timeoutPromise = new Promise<never>((_, reject) => {
386
+ timeoutId = setTimeout(() => {
387
+ isTimedOut = true;
388
+ reject(new Error('Request timeout: The AI model took too long to respond. Please try again with a simpler prompt or try a different model.'));
389
+ }, REQUEST_TIMEOUT);
390
+ });
391
+
392
+ try {
393
+ await Promise.race([
394
+ (async () => {
395
+ while (true) {
396
+ const { done, value } = await chatCompletion.next();
397
+ if (done) {
398
+ break;
399
+ }
400
 
401
+ const deltaContent = value.choices[0]?.delta?.content;
402
+ if (deltaContent) {
403
+ chunk += deltaContent;
404
+ }
405
+ }
406
+ })(),
407
+ timeoutPromise
408
+ ]);
409
+
410
+ // Clear timeout if successful
411
+ if (timeoutId) clearTimeout(timeoutId);
412
+ } catch (timeoutError: any) {
413
+ // Clear timeout on error
414
+ if (timeoutId) clearTimeout(timeoutId);
415
+
416
+ if (isTimedOut || timeoutError.message?.includes('timeout') || timeoutError.message?.includes('Request timeout')) {
417
+ return NextResponse.json(
418
+ {
419
+ ok: false,
420
+ message: "Request timeout: The AI model took too long to respond. Please try again with a simpler prompt or try a different model.",
421
+ },
422
+ { status: 504 }
423
+ );
424
  }
425
+ throw timeoutError;
426
  }
427
  if (!chunk) {
428
  return NextResponse.json(
 
687
  );
688
  }
689
  } catch (error: any) {
690
+ if (error.message?.includes('timeout') || error.message?.includes('Request timeout')) {
691
+ return NextResponse.json(
692
+ {
693
+ ok: false,
694
+ message: "Request timeout: The operation took too long to complete. Please try again with a simpler request or try a different model.",
695
+ },
696
+ { status: 504 }
697
+ );
698
+ }
699
  if (error.message?.includes("exceeded your monthly included credits")) {
700
  return NextResponse.json(
701
  {
app/api/auth/login-url/route.ts CHANGED
@@ -15,7 +15,7 @@ export async function GET(req: NextRequest) {
15
  url +
16
  "/deepsite/auth/callback";
17
 
18
- const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20contribute-repos%20inference-api&prompt=consent&state=1234567890`;
19
 
20
  return NextResponse.json({ loginUrl: loginRedirectUrl });
21
  }
 
15
  url +
16
  "/deepsite/auth/callback";
17
 
18
+ const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
19
 
20
  return NextResponse.json({ loginUrl: loginRedirectUrl });
21
  }
app/api/mcp/route.ts CHANGED
@@ -4,6 +4,36 @@ import { COLORS } from "@/lib/utils";
4
  import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
5
  import { Commit, Page } from "@/types";
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  interface MCPRequest {
8
  jsonrpc: "2.0";
9
  id: number | string;
@@ -228,11 +258,15 @@ async function handleCreateProject(params: CreateProjectParams) {
228
  // Get user info from HF token
229
  let username: string;
230
  try {
231
- const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
232
- headers: {
233
- Authorization: `Bearer ${hf_token}`,
234
- },
235
- });
 
 
 
 
236
 
237
  if (!userResponse.ok) {
238
  throw new Error("Invalid Hugging Face token");
@@ -241,6 +275,9 @@ async function handleCreateProject(params: CreateProjectParams) {
241
  const userData = await userResponse.json();
242
  username = userData.name;
243
  } catch (error: any) {
 
 
 
244
  throw new Error(`Authentication failed: ${error.message}`);
245
  }
246
 
@@ -300,38 +337,67 @@ This project was created with [DeepSite](https://huggingface.co/deepsite).
300
  });
301
 
302
  try {
303
- const { repoUrl } = await createRepo({
304
- repo,
305
- accessToken: hf_token,
306
- });
 
 
 
 
307
 
308
  const commitTitle = !prompt || prompt.trim() === "" ? "Initial project creation via MCP" : prompt;
309
 
310
- await uploadFiles({
311
- repo,
312
- files,
313
- accessToken: hf_token,
314
- commitTitle,
315
- });
 
 
 
 
316
 
317
  const path = repoUrl.split("/").slice(-2).join("/");
318
 
319
  const commits: Commit[] = [];
320
- for await (const commit of listCommits({ repo, accessToken: hf_token })) {
321
- if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Promote version")) {
322
- continue;
323
- }
324
- commits.push({
325
- title: commit.title,
326
- oid: commit.oid,
327
- date: commit.date,
328
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  }
330
 
331
- const space = await spaceInfo({
332
- name: repo.name,
333
- accessToken: hf_token,
334
- });
 
 
 
 
335
 
336
  const projectUrl = `https://huggingface.co/deepsite/${path}`;
337
  const spaceUrl = `https://huggingface.co/spaces/${path}`;
@@ -360,6 +426,9 @@ This project was created with [DeepSite](https://huggingface.co/deepsite).
360
  ],
361
  };
362
  } catch (err: any) {
 
 
 
363
  throw new Error(err.message || "Failed to create project");
364
  }
365
  }
 
4
  import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
5
  import { Commit, Page } from "@/types";
6
 
7
+ // Timeout configuration (in milliseconds)
8
+ const OPERATION_TIMEOUT = 120000; // 2 minutes for HF operations
9
+
10
+ // Extend the maximum execution time for this route
11
+ export const maxDuration = 180; // 3 minutes
12
+
13
+ // Utility function to wrap promises with timeout
14
+ async function withTimeout<T>(
15
+ promise: Promise<T>,
16
+ timeoutMs: number,
17
+ errorMessage: string = "Operation timed out"
18
+ ): Promise<T> {
19
+ let timeoutId: NodeJS.Timeout;
20
+
21
+ const timeoutPromise = new Promise<never>((_, reject) => {
22
+ timeoutId = setTimeout(() => {
23
+ reject(new Error(errorMessage));
24
+ }, timeoutMs);
25
+ });
26
+
27
+ try {
28
+ const result = await Promise.race([promise, timeoutPromise]);
29
+ clearTimeout(timeoutId!);
30
+ return result;
31
+ } catch (error) {
32
+ clearTimeout(timeoutId!);
33
+ throw error;
34
+ }
35
+ }
36
+
37
  interface MCPRequest {
38
  jsonrpc: "2.0";
39
  id: number | string;
 
258
  // Get user info from HF token
259
  let username: string;
260
  try {
261
+ const userResponse = await withTimeout(
262
+ fetch("https://huggingface.co/api/whoami-v2", {
263
+ headers: {
264
+ Authorization: `Bearer ${hf_token}`,
265
+ },
266
+ }),
267
+ 30000, // 30 seconds for authentication
268
+ "Authentication timeout: Unable to verify Hugging Face token"
269
+ );
270
 
271
  if (!userResponse.ok) {
272
  throw new Error("Invalid Hugging Face token");
 
275
  const userData = await userResponse.json();
276
  username = userData.name;
277
  } catch (error: any) {
278
+ if (error.message?.includes('timeout')) {
279
+ throw new Error(`Authentication timeout: ${error.message}`);
280
+ }
281
  throw new Error(`Authentication failed: ${error.message}`);
282
  }
283
 
 
337
  });
338
 
339
  try {
340
+ const { repoUrl } = await withTimeout(
341
+ createRepo({
342
+ repo,
343
+ accessToken: hf_token,
344
+ }),
345
+ 60000, // 1 minute for repo creation
346
+ "Timeout creating repository. Please try again."
347
+ );
348
 
349
  const commitTitle = !prompt || prompt.trim() === "" ? "Initial project creation via MCP" : prompt;
350
 
351
+ await withTimeout(
352
+ uploadFiles({
353
+ repo,
354
+ files,
355
+ accessToken: hf_token,
356
+ commitTitle,
357
+ }),
358
+ OPERATION_TIMEOUT,
359
+ "Timeout uploading files. The repository was created but files may not have been uploaded."
360
+ );
361
 
362
  const path = repoUrl.split("/").slice(-2).join("/");
363
 
364
  const commits: Commit[] = [];
365
+ const commitIterator = listCommits({ repo, accessToken: hf_token });
366
+
367
+ // Wrap the commit listing with a timeout
368
+ const commitTimeout = new Promise<void>((_, reject) => {
369
+ setTimeout(() => reject(new Error("Timeout listing commits")), 30000);
370
+ });
371
+
372
+ try {
373
+ await Promise.race([
374
+ (async () => {
375
+ for await (const commit of commitIterator) {
376
+ if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Promote version")) {
377
+ continue;
378
+ }
379
+ commits.push({
380
+ title: commit.title,
381
+ oid: commit.oid,
382
+ date: commit.date,
383
+ });
384
+ }
385
+ })(),
386
+ commitTimeout
387
+ ]);
388
+ } catch (error: any) {
389
+ // If listing commits times out, continue with empty commits array
390
+ console.error("Failed to list commits:", error.message);
391
  }
392
 
393
+ const space = await withTimeout(
394
+ spaceInfo({
395
+ name: repo.name,
396
+ accessToken: hf_token,
397
+ }),
398
+ 30000, // 30 seconds for space info
399
+ "Timeout fetching space information"
400
+ );
401
 
402
  const projectUrl = `https://huggingface.co/deepsite/${path}`;
403
  const spaceUrl = `https://huggingface.co/spaces/${path}`;
 
426
  ],
427
  };
428
  } catch (err: any) {
429
+ if (err.message?.includes('timeout') || err.message?.includes('Timeout')) {
430
+ throw new Error(err.message || "Operation timed out. Please try again.");
431
+ }
432
  throw new Error(err.message || "Failed to create project");
433
  }
434
  }
app/api/re-design/route.ts CHANGED
@@ -1,5 +1,11 @@
1
  import { NextRequest, NextResponse } from "next/server";
2
 
 
 
 
 
 
 
3
  export async function PUT(request: NextRequest) {
4
  const body = await request.json();
5
  const { url } = body;
@@ -9,28 +15,54 @@ export async function PUT(request: NextRequest) {
9
  }
10
 
11
  try {
12
- const response = await fetch(
13
- `https://r.jina.ai/${encodeURIComponent(url)}`,
14
- {
15
- method: "POST",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  }
17
- );
18
- if (!response.ok) {
19
  return NextResponse.json(
20
- { error: "Failed to fetch redesign" },
21
- { status: 500 }
 
 
 
22
  );
 
 
 
 
 
 
 
 
 
 
23
  }
24
- const markdown = await response.text();
25
- return NextResponse.json(
26
- {
27
- ok: true,
28
- markdown,
29
- },
30
- { status: 200 }
31
- );
32
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
  } catch (error: any) {
 
 
 
 
 
 
34
  return NextResponse.json(
35
  { error: error.message || "An error occurred" },
36
  { status: 500 }
 
1
  import { NextRequest, NextResponse } from "next/server";
2
 
3
+ // Timeout configuration (in milliseconds)
4
+ const FETCH_TIMEOUT = 30000; // 30 seconds for external fetch
5
+
6
+ // Extend the maximum execution time for this route
7
+ export const maxDuration = 60; // 1 minute
8
+
9
  export async function PUT(request: NextRequest) {
10
  const body = await request.json();
11
  const { url } = body;
 
15
  }
16
 
17
  try {
18
+ // Create an AbortController for timeout
19
+ const controller = new AbortController();
20
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
21
+
22
+ try {
23
+ const response = await fetch(
24
+ `https://r.jina.ai/${encodeURIComponent(url)}`,
25
+ {
26
+ method: "POST",
27
+ signal: controller.signal,
28
+ }
29
+ );
30
+
31
+ clearTimeout(timeoutId);
32
+
33
+ if (!response.ok) {
34
+ return NextResponse.json(
35
+ { error: "Failed to fetch redesign" },
36
+ { status: 500 }
37
+ );
38
  }
39
+ const markdown = await response.text();
 
40
  return NextResponse.json(
41
+ {
42
+ ok: true,
43
+ markdown,
44
+ },
45
+ { status: 200 }
46
  );
47
+ } catch (fetchError: any) {
48
+ clearTimeout(timeoutId);
49
+
50
+ if (fetchError.name === 'AbortError') {
51
+ return NextResponse.json(
52
+ { error: "Request timeout: The external service took too long to respond. Please try again." },
53
+ { status: 504 }
54
+ );
55
+ }
56
+ throw fetchError;
57
  }
 
 
 
 
 
 
 
 
58
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
  } catch (error: any) {
60
+ if (error.name === 'AbortError' || error.message?.includes('timeout')) {
61
+ return NextResponse.json(
62
+ { error: "Request timeout: The external service took too long to respond. Please try again." },
63
+ { status: 504 }
64
+ );
65
+ }
66
  return NextResponse.json(
67
  { error: error.message || "An error occurred" },
68
  { status: 500 }