enzostvs HF Staff commited on
Commit
4060ddc
·
1 Parent(s): 6b93566

✨ add patch stuffs

Browse files
public/providers/together.svg ADDED
server.js CHANGED
@@ -32,6 +32,10 @@ const REDIRECT_URI =
32
  process.env.REDIRECT_URI || `http://localhost:${PORT}/auth/login`;
33
  const MAX_REQUESTS_PER_IP = 2;
34
 
 
 
 
 
35
  app.use(cookieParser());
36
  app.use(bodyParser.json());
37
  app.use(express.static(path.join(__dirname, "dist")));
@@ -212,23 +216,15 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
212
  });
213
 
214
  app.post("/api/ask-ai", async (req, res) => {
215
- const { prompt, html, previousPrompt, provider, model } = req.body;
216
- if (!prompt) {
217
  return res.status(400).send({
218
  ok: false,
219
  message: "Missing required fields",
220
  });
221
  }
222
 
223
- if (!model) {
224
- return res.status(400).send({
225
- ok: false,
226
- message: "Missing model field",
227
- });
228
- }
229
-
230
- const isFollowUp =
231
- previousPrompt && previousPrompt.length > 0 && html && html.length > 0;
232
 
233
  const selectedModel = MODELS.find(
234
  (m) => m.value === model || m.label === model
@@ -283,8 +279,6 @@ app.post("/api/ask-ai", async (req, res) => {
283
  let completeResponse = "";
284
 
285
  let TOKENS_USED = prompt?.length;
286
- if (previousPrompt) TOKENS_USED += previousPrompt.length;
287
- if (html) TOKENS_USED += html.length;
288
 
289
  const DEFAULT_PROVIDER = PROVIDERS.novita;
290
  const selectedProvider =
@@ -307,25 +301,8 @@ app.post("/api/ask-ai", async (req, res) => {
307
  messages: [
308
  {
309
  role: "system",
310
- content: `ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. MAKE IT RESPONSIVE USING TAILWINDCSS. Use as much as you can TailwindCSS for the CSS, if you can't do something with TailwindCSS, then use custom CSS (make sure to import <script src="https://cdn.tailwindcss.com"></script> in the head). Also, try to ellaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE`,
311
  },
312
- ...(previousPrompt
313
- ? [
314
- {
315
- role: "user",
316
- content: previousPrompt,
317
- },
318
- ]
319
- : []),
320
- ...(isFollowUp
321
- ? // TODO try to do the git diff thing to only generate the changes and not the whole html
322
- [
323
- {
324
- role: "assistant",
325
- content: `The current code is: ${html}.`,
326
- },
327
- ]
328
- : []),
329
  {
330
  role: "user",
331
  content: prompt,
@@ -357,7 +334,6 @@ app.post("/api/ask-ai", async (req, res) => {
357
  } else {
358
  let newChunk = chunk;
359
  if (chunk.includes("</html>")) {
360
- // Replace everything after the last </html> tag with an empty string
361
  newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
362
  }
363
  completeResponse += newChunk;
@@ -367,7 +343,6 @@ app.post("/api/ask-ai", async (req, res) => {
367
  }
368
  }
369
  } else {
370
- // check if in the completeResponse there is already a </html> tag but after the last </think> tag, if yes break the loop
371
  const lastThinkTagIndex = completeResponse.lastIndexOf("</think>");
372
  completeResponse += newChunk;
373
  res.write(newChunk);
@@ -405,6 +380,203 @@ app.post("/api/ask-ai", async (req, res) => {
405
  }
406
  });
407
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  app.get("/api/remix/:username/:repo", async (req, res) => {
409
  const { username, repo } = req.params;
410
  const { hf_token } = req.cookies;
 
32
  process.env.REDIRECT_URI || `http://localhost:${PORT}/auth/login`;
33
  const MAX_REQUESTS_PER_IP = 2;
34
 
35
+ const SEARCH_START = "<<<<<<< SEARCH";
36
+ const DIVIDER = "=======";
37
+ const REPLACE_END = ">>>>>>> REPLACE";
38
+
39
  app.use(cookieParser());
40
  app.use(bodyParser.json());
41
  app.use(express.static(path.join(__dirname, "dist")));
 
216
  });
217
 
218
  app.post("/api/ask-ai", async (req, res) => {
219
+ const { prompt, provider, model } = req.body;
220
+ if (!prompt || !model) {
221
  return res.status(400).send({
222
  ok: false,
223
  message: "Missing required fields",
224
  });
225
  }
226
 
227
+ const initialSystemPrompt = `ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. MAKE IT RESPONSIVE USING TAILWINDCSS. Use as much as you can TailwindCSS for the CSS, if you can't do something with TailwindCSS, then use custom CSS (make sure to import <script src="https://cdn.tailwindcss.com"></script> in the head). Also, try to ellaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE`;
 
 
 
 
 
 
 
 
228
 
229
  const selectedModel = MODELS.find(
230
  (m) => m.value === model || m.label === model
 
279
  let completeResponse = "";
280
 
281
  let TOKENS_USED = prompt?.length;
 
 
282
 
283
  const DEFAULT_PROVIDER = PROVIDERS.novita;
284
  const selectedProvider =
 
301
  messages: [
302
  {
303
  role: "system",
304
+ content: initialSystemPrompt,
305
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  {
307
  role: "user",
308
  content: prompt,
 
334
  } else {
335
  let newChunk = chunk;
336
  if (chunk.includes("</html>")) {
 
337
  newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
338
  }
339
  completeResponse += newChunk;
 
343
  }
344
  }
345
  } else {
 
346
  const lastThinkTagIndex = completeResponse.lastIndexOf("</think>");
347
  completeResponse += newChunk;
348
  res.write(newChunk);
 
380
  }
381
  });
382
 
383
+ app.put("/api/ask-ai", async (req, res) => {
384
+ const { prompt, html, previousPrompt, provider } = req.body;
385
+ if (!prompt || !html || !previousPrompt) {
386
+ return res.status(400).send({
387
+ ok: false,
388
+ message: "Missing required fields",
389
+ });
390
+ }
391
+ const followUpSystemPrompt = `You are an expert web developer modifying an existing HTML file.
392
+ The user wants to apply changes based on their request.
393
+ You MUST output ONLY the changes required using the following SEARCH/REPLACE block format. Do NOT output the entire file.
394
+ Explain the changes briefly *before* the blocks if necessary, but the code changes THEMSELVES MUST be within the blocks.
395
+ Format Rules:
396
+ 1. Start with ${SEARCH_START}
397
+ 2. Provide the exact lines from the current code that need to be replaced.
398
+ 3. Use ${DIVIDER} to separate the search block from the replacement.
399
+ 4. Provide the new lines that should replace the original lines.
400
+ 5. End with ${REPLACE_END}
401
+ 6. You can use multiple SEARCH/REPLACE blocks if changes are needed in different parts of the file.
402
+ 7. To insert code, use an empty SEARCH block (only ${SEARCH_START} and ${DIVIDER} on their lines) if inserting at the very beginning, otherwise provide the line *before* the insertion point in the SEARCH block and include that line plus the new lines in the REPLACE block.
403
+ 8. To delete code, provide the lines to delete in the SEARCH block and leave the REPLACE block empty (only ${DIVIDER} and ${REPLACE_END} on their lines).
404
+ 9. IMPORTANT: The SEARCH block must *exactly* match the current code, including indentation and whitespace.
405
+ Example Modifying Code:
406
+ \`\`\`
407
+ Some explanation...
408
+ ${SEARCH_START}
409
+ <h1>Old Title</h1>
410
+ ${DIVIDER}
411
+ <h1>New Title</h1>
412
+ ${REPLACE_END}
413
+ ${SEARCH_START}
414
+ </body>
415
+ ${DIVIDER}
416
+ <script>console.log("Added script");</script>
417
+ </body>
418
+ ${REPLACE_END}
419
+ \`\`\`
420
+ Example Deleting Code:
421
+ \`\`\`
422
+ Removing the paragraph...
423
+ ${SEARCH_START}
424
+ <p>This paragraph will be deleted.</p>
425
+ ${DIVIDER}
426
+ ${REPLACE_END}
427
+ \`\`\``;
428
+
429
+ // force to use deepseek-ai/DeepSeek-V3-0324 model, to avoid thinker models.
430
+ const selectedModel = MODELS[0];
431
+ if (!selectedModel.providers.includes(provider) && provider !== "auto") {
432
+ return res.status(400).send({
433
+ ok: false,
434
+ openSelectProvider: true,
435
+ message: `The selected model does not support the ${provider} provider.`,
436
+ });
437
+ }
438
+
439
+ let { hf_token } = req.cookies;
440
+ let token = hf_token;
441
+
442
+ if (process.env.HF_TOKEN && process.env.HF_TOKEN !== "") {
443
+ token = process.env.HF_TOKEN;
444
+ }
445
+
446
+ const ip =
447
+ req.headers["x-forwarded-for"]?.split(",")[0].trim() ||
448
+ req.headers["x-real-ip"] ||
449
+ req.socket.remoteAddress ||
450
+ req.ip ||
451
+ "0.0.0.0";
452
+
453
+ if (!token) {
454
+ ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
455
+ if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
456
+ return res.status(429).send({
457
+ ok: false,
458
+ openLogin: true,
459
+ message: "Log In to continue using the service",
460
+ });
461
+ }
462
+
463
+ token = process.env.DEFAULT_HF_TOKEN;
464
+ }
465
+
466
+ const client = new InferenceClient(token);
467
+
468
+ let TOKENS_USED = prompt?.length;
469
+ if (previousPrompt) TOKENS_USED += previousPrompt.length;
470
+ if (html) TOKENS_USED += html.length;
471
+
472
+ const DEFAULT_PROVIDER = PROVIDERS.novita;
473
+ const selectedProvider =
474
+ provider === "auto"
475
+ ? PROVIDERS[selectedModel.autoProvider]
476
+ : PROVIDERS[provider] ?? DEFAULT_PROVIDER;
477
+
478
+ if (provider !== "auto" && TOKENS_USED >= selectedProvider.max_tokens) {
479
+ return res.status(400).send({
480
+ ok: false,
481
+ openSelectProvider: true,
482
+ message: `Context is too long. ${selectedProvider.name} allow ${selectedProvider.max_tokens} max tokens.`,
483
+ });
484
+ }
485
+
486
+ try {
487
+ const response = await client.chatCompletion({
488
+ model: selectedModel.value,
489
+ provider: selectedProvider.id,
490
+ messages: [
491
+ {
492
+ role: "system",
493
+ content: followUpSystemPrompt,
494
+ },
495
+ {
496
+ role: "user",
497
+ content: previousPrompt,
498
+ },
499
+ {
500
+ role: "assistant",
501
+ content: `The current code is: \n\`\`\`html\n${html}\n\`\`\``,
502
+ },
503
+ {
504
+ role: "user",
505
+ content: prompt,
506
+ },
507
+ ],
508
+ ...(selectedProvider.id !== "sambanova"
509
+ ? {
510
+ max_tokens: selectedProvider.max_tokens,
511
+ }
512
+ : {}),
513
+ });
514
+
515
+ const chunk = response.choices[0]?.message?.content;
516
+
517
+ // return the new HTML using the html and the chunk
518
+ if (chunk) {
519
+ const searchStartIndex = chunk.indexOf(SEARCH_START);
520
+ const replaceEndIndex = chunk.indexOf(REPLACE_END);
521
+
522
+ if (
523
+ searchStartIndex === -1 ||
524
+ replaceEndIndex === -1 ||
525
+ replaceEndIndex <= searchStartIndex
526
+ ) {
527
+ return res.status(400).send({
528
+ ok: false,
529
+ message: "Invalid response format",
530
+ });
531
+ }
532
+
533
+ const searchBlock = chunk.substring(
534
+ searchStartIndex + SEARCH_START.length,
535
+ chunk.indexOf(DIVIDER)
536
+ );
537
+ const replaceBlock = chunk.substring(
538
+ chunk.indexOf(DIVIDER) + DIVIDER.length,
539
+ replaceEndIndex
540
+ );
541
+
542
+ let newHtml = html;
543
+
544
+ if (searchBlock.trim() === "") {
545
+ // Inserting at the beginning
546
+ newHtml = `${replaceBlock}\n${newHtml}`;
547
+ } else {
548
+ // Replacing existing code
549
+ newHtml = newHtml.replace(searchBlock, replaceBlock);
550
+ }
551
+
552
+ return res.status(200).send({
553
+ ok: true,
554
+ html: newHtml,
555
+ });
556
+ } else {
557
+ return res.status(400).send({
558
+ ok: false,
559
+ message: "No content returned from the model",
560
+ });
561
+ }
562
+ } catch (error) {
563
+ if (error.message.includes("exceeded your monthly included credits")) {
564
+ return res.status(402).send({
565
+ ok: false,
566
+ openProModal: true,
567
+ message: error.message,
568
+ });
569
+ }
570
+ if (!res.headersSent) {
571
+ res.status(500).send({
572
+ ok: false,
573
+ message:
574
+ error.message || "An error occurred while processing your request.",
575
+ });
576
+ }
577
+ }
578
+ });
579
+
580
  app.get("/api/remix/:username/:repo", async (req, res) => {
581
  const { username, repo } = req.params;
582
  const { hf_token } = req.cookies;
src/components/ask-ai/ask-ai.tsx CHANGED
@@ -62,104 +62,145 @@ function AskAI({
62
  let contentResponse = "";
63
  let thinkResponse = "";
64
  let lastRenderTime = 0;
 
 
 
65
  try {
66
  onNewPrompt(prompt);
67
- const request = await fetch("/api/ask-ai", {
68
- method: "POST",
69
- body: JSON.stringify({
70
- prompt,
71
- provider,
72
- model,
73
- ...(html === defaultHTML ? {} : { html }),
74
- ...(previousPrompt ? { previousPrompt } : {}),
75
- }),
76
- headers: {
77
- "Content-Type": "application/json",
78
- },
79
- });
80
- if (request && request.body) {
81
- if (!request.ok) {
82
  const res = await request.json();
83
- if (res.openLogin) {
84
- setOpen(true);
85
- } else if (res.openSelectProvider) {
86
- setOpenProvider(true);
87
- setProviderError(res.message);
88
- } else if (res.openProModal) {
89
- setOpenProModal(true);
90
- } else {
91
- toast.error(res.message);
 
 
 
 
92
  }
 
 
 
 
93
  setisAiWorking(false);
94
- return;
95
  }
96
- const reader = request.body.getReader();
97
- const decoder = new TextDecoder("utf-8");
98
- const selectedModel = MODELS.find(
99
- (m: { value: string }) => m.value === model
100
- );
101
- let contentThink: string | undefined = undefined;
102
- const read = async () => {
103
- const { done, value } = await reader.read();
104
- if (done) {
105
- toast.success("AI responded successfully");
106
- setPreviousPrompt(prompt);
107
- setPrompt("");
108
- setisAiWorking(false);
109
- setHasAsked(true);
110
- audio.play();
111
-
112
- // Now we have the complete HTML including </html>, so set it to be sure
113
- const finalDoc = contentResponse.match(
114
- /<!DOCTYPE html>[\s\S]*<\/html>/
115
- )?.[0];
116
- if (finalDoc) {
117
- setHtml(finalDoc);
 
 
118
  }
119
- onSuccess(finalDoc ?? contentResponse, prompt);
120
-
121
  return;
122
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
- const chunk = decoder.decode(value, { stream: true });
125
- thinkResponse += chunk;
126
- if (selectedModel?.isThinker) {
127
- const thinkMatch = thinkResponse.match(/<think>[\s\S]*/)?.[0];
128
- if (thinkMatch && !thinkResponse?.includes("</think>")) {
129
- if ((contentThink?.length ?? 0) < 3) {
130
- setOpenThink(true);
131
  }
132
- setThink(thinkMatch.replace("<think>", "").trim());
133
- contentThink += chunk;
134
- return read();
135
- }
136
- }
137
-
138
- contentResponse += chunk;
139
 
140
- const newHtml = contentResponse.match(/<!DOCTYPE html>[\s\S]*/)?.[0];
141
- if (newHtml) {
142
- setIsThinking(false);
143
- let partialDoc = newHtml;
144
- if (!partialDoc.includes("</html>")) {
145
- partialDoc += "\n</html>";
146
  }
147
 
148
- // Throttle the re-renders to avoid flashing/flicker
149
- const now = Date.now();
150
- if (now - lastRenderTime > 300) {
151
- setHtml(partialDoc);
152
- lastRenderTime = now;
 
 
 
 
 
 
 
153
  }
154
 
155
- if (partialDoc.length > 200) {
156
- onScrollToBottom();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  }
158
- }
159
- read();
160
- };
161
 
162
- read();
 
163
  }
164
  } catch (error: any) {
165
  setisAiWorking(false);
 
62
  let contentResponse = "";
63
  let thinkResponse = "";
64
  let lastRenderTime = 0;
65
+
66
+ const isFollowUp =
67
+ previousPrompt && previousPrompt.length > 0 && html !== defaultHTML;
68
  try {
69
  onNewPrompt(prompt);
70
+ if (isFollowUp) {
71
+ const request = await fetch("/api/ask-ai", {
72
+ method: "PUT",
73
+ body: JSON.stringify({
74
+ prompt,
75
+ provider,
76
+ previousPrompt,
77
+ html,
78
+ }),
79
+ headers: {
80
+ "Content-Type": "application/json",
81
+ },
82
+ });
83
+ if (request && request.body) {
 
84
  const res = await request.json();
85
+ if (!request.ok) {
86
+ if (res.openLogin) {
87
+ setOpen(true);
88
+ } else if (res.openSelectProvider) {
89
+ setOpenProvider(true);
90
+ setProviderError(res.message);
91
+ } else if (res.openProModal) {
92
+ setOpenProModal(true);
93
+ } else {
94
+ toast.error(res.message);
95
+ }
96
+ setisAiWorking(false);
97
+ return;
98
  }
99
+ setHtml(res.html);
100
+ toast.success("AI responded successfully");
101
+ setPreviousPrompt(prompt);
102
+ setPrompt("");
103
  setisAiWorking(false);
104
+ onSuccess(res.html, prompt);
105
  }
106
+ } else {
107
+ const request = await fetch("/api/ask-ai", {
108
+ method: "POST",
109
+ body: JSON.stringify({
110
+ prompt,
111
+ provider,
112
+ model,
113
+ }),
114
+ headers: {
115
+ "Content-Type": "application/json",
116
+ },
117
+ });
118
+ if (request && request.body) {
119
+ if (!request.ok) {
120
+ const res = await request.json();
121
+ if (res.openLogin) {
122
+ setOpen(true);
123
+ } else if (res.openSelectProvider) {
124
+ setOpenProvider(true);
125
+ setProviderError(res.message);
126
+ } else if (res.openProModal) {
127
+ setOpenProModal(true);
128
+ } else {
129
+ toast.error(res.message);
130
  }
131
+ setisAiWorking(false);
 
132
  return;
133
  }
134
+ const reader = request.body.getReader();
135
+ const decoder = new TextDecoder("utf-8");
136
+ const selectedModel = MODELS.find(
137
+ (m: { value: string }) => m.value === model
138
+ );
139
+ let contentThink: string | undefined = undefined;
140
+ const read = async () => {
141
+ const { done, value } = await reader.read();
142
+ if (done) {
143
+ toast.success("AI responded successfully");
144
+ setPreviousPrompt(prompt);
145
+ setPrompt("");
146
+ setisAiWorking(false);
147
+ setHasAsked(true);
148
+ audio.play();
149
 
150
+ // Now we have the complete HTML including </html>, so set it to be sure
151
+ const finalDoc = contentResponse.match(
152
+ /<!DOCTYPE html>[\s\S]*<\/html>/
153
+ )?.[0];
154
+ if (finalDoc) {
155
+ setHtml(finalDoc);
 
156
  }
157
+ onSuccess(finalDoc ?? contentResponse, prompt);
 
 
 
 
 
 
158
 
159
+ return;
 
 
 
 
 
160
  }
161
 
162
+ const chunk = decoder.decode(value, { stream: true });
163
+ thinkResponse += chunk;
164
+ if (selectedModel?.isThinker) {
165
+ const thinkMatch = thinkResponse.match(/<think>[\s\S]*/)?.[0];
166
+ if (thinkMatch && !thinkResponse?.includes("</think>")) {
167
+ if ((contentThink?.length ?? 0) < 3) {
168
+ setOpenThink(true);
169
+ }
170
+ setThink(thinkMatch.replace("<think>", "").trim());
171
+ contentThink += chunk;
172
+ return read();
173
+ }
174
  }
175
 
176
+ contentResponse += chunk;
177
+
178
+ const newHtml = contentResponse.match(
179
+ /<!DOCTYPE html>[\s\S]*/
180
+ )?.[0];
181
+ if (newHtml) {
182
+ setIsThinking(false);
183
+ let partialDoc = newHtml;
184
+ if (!partialDoc.includes("</html>")) {
185
+ partialDoc += "\n</html>";
186
+ }
187
+
188
+ // Throttle the re-renders to avoid flashing/flicker
189
+ const now = Date.now();
190
+ if (now - lastRenderTime > 300) {
191
+ setHtml(partialDoc);
192
+ lastRenderTime = now;
193
+ }
194
+
195
+ if (partialDoc.length > 200) {
196
+ onScrollToBottom();
197
+ }
198
  }
199
+ read();
200
+ };
 
201
 
202
+ read();
203
+ }
204
  }
205
  } catch (error: any) {
206
  setisAiWorking(false);
utils/providers.js CHANGED
@@ -11,7 +11,7 @@ export const PROVIDERS = {
11
  },
12
  sambanova: {
13
  name: "SambaNova",
14
- max_tokens: 8_000,
15
  id: "sambanova",
16
  },
17
  novita: {
@@ -24,6 +24,11 @@ export const PROVIDERS = {
24
  max_tokens: 131_000,
25
  id: "hyperbolic",
26
  },
 
 
 
 
 
27
  };
28
 
29
  export const MODELS = [
@@ -36,7 +41,7 @@ export const MODELS = [
36
  {
37
  value: "deepseek-ai/DeepSeek-R1-0528",
38
  label: "DeepSeek R1 0528",
39
- providers: ["fireworks-ai", "novita", "hyperbolic", "nebius"],
40
  autoProvider: "novita",
41
  isNew: true,
42
  isThinker: true,
 
11
  },
12
  sambanova: {
13
  name: "SambaNova",
14
+ max_tokens: 32_000,
15
  id: "sambanova",
16
  },
17
  novita: {
 
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 = [
 
41
  {
42
  value: "deepseek-ai/DeepSeek-R1-0528",
43
  label: "DeepSeek R1 0528",
44
+ providers: ["fireworks-ai", "novita", "hyperbolic", "nebius", "together"],
45
  autoProvider: "novita",
46
  isNew: true,
47
  isThinker: true,