Spaces:
Running
Running
✨ add patch stuffs
Browse files- public/providers/together.svg +10 -0
- server.js +205 -33
- src/components/ask-ai/ask-ai.tsx +121 -80
- utils/providers.js +7 -2
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,
|
216 |
-
if (!prompt) {
|
217 |
return res.status(400).send({
|
218 |
ok: false,
|
219 |
message: "Missing required fields",
|
220 |
});
|
221 |
}
|
222 |
|
223 |
-
if (
|
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:
|
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 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
if (!request.ok) {
|
82 |
const res = await request.json();
|
83 |
-
if (
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
92 |
}
|
|
|
|
|
|
|
|
|
93 |
setisAiWorking(false);
|
94 |
-
|
95 |
}
|
96 |
-
|
97 |
-
const
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
if (
|
117 |
-
|
|
|
|
|
118 |
}
|
119 |
-
|
120 |
-
|
121 |
return;
|
122 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
setOpenThink(true);
|
131 |
}
|
132 |
-
|
133 |
-
contentThink += chunk;
|
134 |
-
return read();
|
135 |
-
}
|
136 |
-
}
|
137 |
-
|
138 |
-
contentResponse += chunk;
|
139 |
|
140 |
-
|
141 |
-
if (newHtml) {
|
142 |
-
setIsThinking(false);
|
143 |
-
let partialDoc = newHtml;
|
144 |
-
if (!partialDoc.includes("</html>")) {
|
145 |
-
partialDoc += "\n</html>";
|
146 |
}
|
147 |
|
148 |
-
|
149 |
-
|
150 |
-
if (
|
151 |
-
|
152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
}
|
154 |
|
155 |
-
|
156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
}
|
158 |
-
|
159 |
-
|
160 |
-
};
|
161 |
|
162 |
-
|
|
|
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:
|
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,
|