Spaces:
Running
Running
collapsable thinking
Browse files- server.js +0 -2
- src/components/ask-ai/ask-ai.tsx +44 -19
server.js
CHANGED
@@ -289,8 +289,6 @@ app.post("/api/ask-ai", async (req, res) => {
|
|
289 |
? PROVIDERS[selectedModel.autoProvider]
|
290 |
: PROVIDERS[provider] ?? DEFAULT_PROVIDER;
|
291 |
|
292 |
-
console.log(provider, selectedProvider);
|
293 |
-
|
294 |
if (provider !== "auto" && TOKENS_USED >= selectedProvider.max_tokens) {
|
295 |
return res.status(400).send({
|
296 |
ok: false,
|
|
|
289 |
? PROVIDERS[selectedModel.autoProvider]
|
290 |
: PROVIDERS[provider] ?? DEFAULT_PROVIDER;
|
291 |
|
|
|
|
|
292 |
if (provider !== "auto" && TOKENS_USED >= selectedProvider.max_tokens) {
|
293 |
return res.status(400).send({
|
294 |
ok: false,
|
src/components/ask-ai/ask-ai.tsx
CHANGED
@@ -5,6 +5,7 @@ import { GrSend } from "react-icons/gr";
|
|
5 |
import classNames from "classnames";
|
6 |
import { toast } from "sonner";
|
7 |
import { useLocalStorage, useUpdateEffect } from "react-use";
|
|
|
8 |
|
9 |
import Login from "../login/login";
|
10 |
import { defaultHTML } from "../../../utils/consts";
|
@@ -14,7 +15,6 @@ import ProModal from "../pro-modal/pro-modal";
|
|
14 |
import { Button } from "../ui/button";
|
15 |
// @ts-expect-error not needed
|
16 |
import { MODELS } from "./../../../utils/providers";
|
17 |
-
import { X } from "lucide-react";
|
18 |
|
19 |
function AskAI({
|
20 |
html,
|
@@ -55,6 +55,9 @@ function AskAI({
|
|
55 |
if (isAiWorking || !prompt.trim()) return;
|
56 |
setisAiWorking(true);
|
57 |
setProviderError("");
|
|
|
|
|
|
|
58 |
|
59 |
let contentResponse = "";
|
60 |
let lastRenderTime = 0;
|
@@ -94,6 +97,7 @@ function AskAI({
|
|
94 |
const selectedModel = MODELS.find(
|
95 |
(m: { value: string }) => m.value === model
|
96 |
);
|
|
|
97 |
const read = async () => {
|
98 |
const { done, value } = await reader.read();
|
99 |
if (done) {
|
@@ -121,10 +125,11 @@ function AskAI({
|
|
121 |
if (selectedModel?.isThinker) {
|
122 |
const thinkMatch = contentResponse.match(/<think>[\s\S]*/)?.[0];
|
123 |
if (thinkMatch && !contentResponse?.includes("</think>")) {
|
124 |
-
if (
|
125 |
setOpenThink(true);
|
126 |
}
|
127 |
setThink(thinkMatch.replace("<think>", "").trim());
|
|
|
128 |
}
|
129 |
}
|
130 |
|
@@ -167,31 +172,51 @@ function AskAI({
|
|
167 |
}
|
168 |
}, [think]);
|
169 |
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
return (
|
171 |
<div className="bg-neutral-800 border border-neutral-700 rounded-lg ring-[5px] focus-within:ring-sky-500/50 ring-transparent z-10 absolute bottom-3 left-3 w-[calc(100%-20px)] group">
|
172 |
{think && (
|
173 |
<div
|
174 |
ref={refThink}
|
175 |
-
className="w-full
|
176 |
>
|
177 |
-
<
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
{think}
|
193 |
</p>
|
194 |
-
</
|
195 |
</div>
|
196 |
)}
|
197 |
<div
|
|
|
5 |
import classNames from "classnames";
|
6 |
import { toast } from "sonner";
|
7 |
import { useLocalStorage, useUpdateEffect } from "react-use";
|
8 |
+
import { ChevronDown } from "lucide-react";
|
9 |
|
10 |
import Login from "../login/login";
|
11 |
import { defaultHTML } from "../../../utils/consts";
|
|
|
15 |
import { Button } from "../ui/button";
|
16 |
// @ts-expect-error not needed
|
17 |
import { MODELS } from "./../../../utils/providers";
|
|
|
18 |
|
19 |
function AskAI({
|
20 |
html,
|
|
|
55 |
if (isAiWorking || !prompt.trim()) return;
|
56 |
setisAiWorking(true);
|
57 |
setProviderError("");
|
58 |
+
setThink("");
|
59 |
+
setOpenThink(false);
|
60 |
+
setIsThinking(true);
|
61 |
|
62 |
let contentResponse = "";
|
63 |
let lastRenderTime = 0;
|
|
|
97 |
const selectedModel = MODELS.find(
|
98 |
(m: { value: string }) => m.value === model
|
99 |
);
|
100 |
+
let contentThink: string | undefined = undefined;
|
101 |
const read = async () => {
|
102 |
const { done, value } = await reader.read();
|
103 |
if (done) {
|
|
|
125 |
if (selectedModel?.isThinker) {
|
126 |
const thinkMatch = contentResponse.match(/<think>[\s\S]*/)?.[0];
|
127 |
if (thinkMatch && !contentResponse?.includes("</think>")) {
|
128 |
+
if ((contentThink?.length ?? 0) < 3) {
|
129 |
setOpenThink(true);
|
130 |
}
|
131 |
setThink(thinkMatch.replace("<think>", "").trim());
|
132 |
+
contentThink += chunk;
|
133 |
}
|
134 |
}
|
135 |
|
|
|
172 |
}
|
173 |
}, [think]);
|
174 |
|
175 |
+
useUpdateEffect(() => {
|
176 |
+
if (!isThinking) {
|
177 |
+
setOpenThink(false);
|
178 |
+
}
|
179 |
+
}, [isThinking]);
|
180 |
+
|
181 |
return (
|
182 |
<div className="bg-neutral-800 border border-neutral-700 rounded-lg ring-[5px] focus-within:ring-sky-500/50 ring-transparent z-10 absolute bottom-3 left-3 w-[calc(100%-20px)] group">
|
183 |
{think && (
|
184 |
<div
|
185 |
ref={refThink}
|
186 |
+
className="w-full border-b border-neutral-700 relative overflow-hidden"
|
187 |
>
|
188 |
+
<header
|
189 |
+
className="flex items-center justify-between px-5 py-2.5 group hover:bg-neutral-600/20 transition-colors duration-200 cursor-pointer"
|
190 |
+
onClick={() => {
|
191 |
+
setOpenThink(!openThink);
|
192 |
+
}}
|
193 |
+
>
|
194 |
+
<p className="text-sm font-medium text-neutral-300 group-hover:text-neutral-200 transition-colors duration-200">
|
195 |
+
{isThinking ? "AI is thinking..." : "AI's plan"}
|
196 |
+
</p>
|
197 |
+
<ChevronDown
|
198 |
+
className={classNames(
|
199 |
+
"size-4 text-neutral-400 group-hover:text-neutral-300 transition-all duration-200",
|
200 |
+
{
|
201 |
+
"rotate-180": openThink,
|
202 |
+
}
|
203 |
+
)}
|
204 |
+
/>
|
205 |
+
</header>
|
206 |
+
<main
|
207 |
+
className={classNames(
|
208 |
+
"overflow-y-auto transition-all duration-200 ease-in-out",
|
209 |
+
{
|
210 |
+
"max-h-[0px]": !openThink,
|
211 |
+
"min-h-[250px] max-h-[250px] border-t border-neutral-700":
|
212 |
+
openThink,
|
213 |
+
}
|
214 |
+
)}
|
215 |
+
>
|
216 |
+
<p className="text-[13px] text-neutral-400 whitespace-pre-line px-5 pb-4 pt-3">
|
217 |
{think}
|
218 |
</p>
|
219 |
+
</main>
|
220 |
</div>
|
221 |
)}
|
222 |
<div
|