enzostvs HF Staff commited on
Commit
accd8f2
·
1 Parent(s): 05ce0d5

collapsable thinking

Browse files
Files changed (2) hide show
  1. server.js +0 -2
  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 (!openThink && (think?.length ?? 0) < 3) {
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 px-5 pt-4 border-b border-neutral-700 max-h-[300px] overflow-y-auto relative"
176
  >
177
- <div className="sticky top-0 z-1 flex items-center justify-end">
178
- <Button
179
- size="xs"
180
- onClick={() => {
181
- setThink("");
182
- setOpenThink(false);
183
- }}
184
- >
185
- <X />
186
- Close
187
- </Button>
188
- </div>
189
- <div className="-translate-y-5">
190
- <p className="text-xs text-neutral-400">AI is thinking...</p>
191
- <p className="text-[13px] text-neutral-300 mt-1 whitespace-pre-line">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  {think}
193
  </p>
194
- </div>
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