Skip to content

Commit fd8a349

Browse files
committed
Thinking is now working as expected
1 parent 7f55232 commit fd8a349

File tree

5 files changed

+84
-10
lines changed

5 files changed

+84
-10
lines changed

components/chat/ChatMessages.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { RefObject, useState } from 'react';
88
interface ChatMessagesProps {
99
messages: Message[];
1010
streamingContent: string;
11+
thinkingContent: string;
1112
editingMessageIndex: number | null;
1213
editingContent: string;
1314
setEditingContent: (content: string) => void;
@@ -22,6 +23,7 @@ interface ChatMessagesProps {
2223
export default function ChatMessages({
2324
messages,
2425
streamingContent,
26+
thinkingContent,
2527
editingMessageIndex,
2628
editingContent,
2729
setEditingContent,
@@ -148,7 +150,7 @@ export default function ChatMessages({
148150
</div>
149151
) : (
150152
<div className="flex flex-col items-start mb-6 group">
151-
{message.thinking && (
153+
{(message.thinking) && (
152154
<ThinkingSection thinking={message.thinking} />
153155
)}
154156
<div className="max-w-[95%] text-gray-100 py-2 px-0.5">
@@ -201,6 +203,10 @@ export default function ChatMessages({
201203
))
202204
)}
203205

206+
{thinkingContent && (
207+
<ThinkingSection thinkingContent={thinkingContent} isStreaming={streamingContent==''}/>
208+
)}
209+
204210
{streamingContent && (
205211
<div className="flex flex-col items-start mb-6">
206212
<div className="max-w-[95%] text-gray-100 py-2 px-0.5">

components/chat/MainChatArea.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const MainChatArea: React.FC = () => {
1919
messages,
2020
setMessages,
2121
streamingContent,
22+
thinkingContent,
2223
editingMessageIndex,
2324
editingContent,
2425
setEditingContent,
@@ -101,6 +102,7 @@ const MainChatArea: React.FC = () => {
101102
<ChatMessages
102103
messages={messages}
103104
streamingContent={streamingContent}
105+
thinkingContent={thinkingContent}
104106
editingMessageIndex={editingMessageIndex}
105107
editingContent={editingContent}
106108
setEditingContent={setEditingContent}

components/ui/ThinkingSection.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,41 @@
11
'use client';
22

3-
import { useState } from 'react';
3+
import { useState, useEffect } from 'react';
44
import { motion, AnimatePresence } from 'motion/react';
55
import { ChevronDown, Brain, Copy, Check } from 'lucide-react';
66
import MarkdownRenderer from '@/components/MarkdownRenderer';
77

88
interface ThinkingSectionProps {
99
thinking?: string;
10+
thinkingContent?: string;
1011
isStreaming?: boolean;
1112
}
1213

13-
export default function ThinkingSection({ thinking, isStreaming = false }: ThinkingSectionProps) {
14+
export default function ThinkingSection({ thinking, thinkingContent, isStreaming = false }: ThinkingSectionProps) {
1415
const [isExpanded, setIsExpanded] = useState(false);
1516
const [copied, setCopied] = useState(false);
17+
const [thinkingText, setThinkingText] = useState('');
1618

17-
if (!thinking && !isStreaming) return null;
19+
useEffect(() => {
20+
if (thinkingContent !== '') {
21+
setThinkingText(thinkingContent!);
22+
setIsExpanded(true);
23+
} else {
24+
setThinkingText(thinking || '');
25+
setIsExpanded(false);
26+
}
27+
}, [thinking, thinkingContent]);
28+
29+
useEffect(() => {
30+
if (isStreaming){
31+
setIsExpanded(true)
32+
}
33+
else{
34+
setIsExpanded(false)
35+
}
36+
}, [isStreaming]);
37+
38+
if (!thinking && !isStreaming && !thinkingContent) return null;
1839

1940
const handleCopy = async () => {
2041
if (!thinking) return;
@@ -64,8 +85,8 @@ export default function ThinkingSection({ thinking, isStreaming = false }: Think
6485
</button>
6586
)}
6687
<div className="text-xs text-gray-300 font-mono leading-relaxed">
67-
{thinking ? (
68-
<MarkdownRenderer content={thinking} />
88+
{thinkingText ? (
89+
<MarkdownRenderer content={thinkingText} />
6990
) : (
7091
<div className="flex items-center gap-2">
7192
<div className="w-1 h-1 bg-gray-400 rounded-full animate-pulse" />

hooks/useChatActions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface UseChatActionsReturn {
1717
inputMessage: string;
1818
isLoading: boolean;
1919
streamingContent: string;
20+
thinkingContent: string;
2021
balance: number;
2122
isBalanceLoading: boolean;
2223
uploadedImages: string[];
@@ -72,6 +73,7 @@ export const useChatActions = (): UseChatActionsReturn => {
7273
const [inputMessage, setInputMessage] = useState('');
7374
const [isLoading, setIsLoading] = useState(false);
7475
const [streamingContent, setStreamingContent] = useState('');
76+
const [thinkingContent, setThinkingContent] = useState('');
7577
const [balance, setBalance] = useState(0);
7678
const [isBalanceLoading, setIsBalanceLoading] = useState(true);
7779
const [uploadedImages, setUploadedImages] = useState<string[]>([]);
@@ -180,7 +182,7 @@ export const useChatActions = (): UseChatActionsReturn => {
180182
if (messagesEndRef.current) {
181183
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
182184
}
183-
}, [streamingContent]);
185+
}, [streamingContent, thinkingContent]);
184186

185187
const setTransactionHistory = useCallback((value: React.SetStateAction<TransactionHistory[]>) => {
186188
setTransactionHistoryState(prev => {
@@ -285,6 +287,7 @@ export const useChatActions = (): UseChatActionsReturn => {
285287
) => {
286288
setIsLoading(true);
287289
setStreamingContent('');
290+
setThinkingContent('');
288291

289292
// Create a ref to track current messages during the API call
290293
let currentMessages = messageHistory;
@@ -305,6 +308,7 @@ export const useChatActions = (): UseChatActionsReturn => {
305308
receiveToken,
306309
activeMintUrl: cashuStore.activeMintUrl,
307310
onStreamingUpdate: setStreamingContent,
311+
onThinkingUpdate: setThinkingContent,
308312
onMessagesUpdate: updateMessages,
309313
onMessageAppend: (message) => {
310314
// Append to current messages state
@@ -326,13 +330,15 @@ export const useChatActions = (): UseChatActionsReturn => {
326330
} finally {
327331
setIsLoading(false);
328332
setStreamingContent('');
333+
setThinkingContent('');
329334
}
330335
}, [usingNip60, balance, sendToken, receiveToken, cashuStore.activeMintUrl, transactionHistory, setPendingCashuAmountState]);
331336

332337
return {
333338
inputMessage,
334339
isLoading,
335340
streamingContent,
341+
thinkingContent,
336342
balance,
337343
isBalanceLoading,
338344
uploadedImages,

utils/apiUtils.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface FetchAIResponseParams {
1616
receiveToken: (token: string) => Promise<any[]>;
1717
activeMintUrl?: string | null;
1818
onStreamingUpdate: (content: string) => void;
19+
onThinkingUpdate: (content: string) => void;
1920
onMessagesUpdate: (messages: Message[]) => void;
2021
onMessageAppend: (message: Message) => void;
2122
onBalanceUpdate: (balance: number) => void;
@@ -42,6 +43,7 @@ export const fetchAIResponse = async (params: FetchAIResponseParams): Promise<vo
4243
receiveToken,
4344
activeMintUrl,
4445
onStreamingUpdate,
46+
onThinkingUpdate,
4547
onMessagesUpdate,
4648
onMessageAppend,
4749
onBalanceUpdate,
@@ -126,7 +128,7 @@ export const fetchAIResponse = async (params: FetchAIResponseParams): Promise<vo
126128
throw new Error('Response body is not available');
127129
}
128130

129-
const streamingResult = await processStreamingResponse(response, onStreamingUpdate, selectedModel?.id);
131+
const streamingResult = await processStreamingResponse(response, onStreamingUpdate, onThinkingUpdate, selectedModel?.id);
130132

131133
if (streamingResult.content) {
132134
const assistantMessage = createTextMessage('assistant', streamingResult.content);
@@ -146,6 +148,7 @@ export const fetchAIResponse = async (params: FetchAIResponseParams): Promise<vo
146148
}
147149

148150
onStreamingUpdate('');
151+
onThinkingUpdate('');
149152

150153
// Handle refund and balance update
151154
await handlePostResponseRefund({
@@ -290,13 +293,15 @@ interface StreamingResult {
290293
async function processStreamingResponse(
291294
response: Response,
292295
onStreamingUpdate: (content: string) => void,
296+
onThinkingUpdate: (content: string) => void,
293297
modelId?: string
294298
): Promise<StreamingResult> {
295299
const reader = response.body!.getReader();
296300
const decoder = new TextDecoder('utf-8');
297301
let accumulatedContent = '';
298302
let accumulatedThinking = '';
299303
let isInThinking = false;
304+
let isInContent = false;
300305
let usage: StreamingResult['usage'];
301306
let model: string | undefined;
302307
let finish_reason: string | undefined;
@@ -324,18 +329,52 @@ async function processStreamingResponse(
324329
try {
325330
const parsedData = JSON.parse(jsonData);
326331

327-
// Handle content delta
332+
// Handle reasoning delta. OpenRouter does this.
328333
if (parsedData.choices &&
334+
parsedData.choices[0] &&
335+
parsedData.choices[0].delta &&
336+
parsedData.choices[0].delta.reasoning) {
337+
338+
let newContent;
339+
if (!isInThinking) {
340+
newContent = "<thinking> " + parsedData.choices[0].delta.reasoning;
341+
isInThinking = true;
342+
}
343+
else {
344+
newContent = parsedData.choices[0].delta.reasoning;
345+
}
346+
const thinkingResult = extractThinkingFromStream(newContent, accumulatedThinking);
347+
accumulatedThinking = thinkingResult.thinking;
348+
onThinkingUpdate(accumulatedThinking)
349+
}
350+
351+
// Handle content delta
352+
else if (parsedData.choices &&
329353
parsedData.choices[0] &&
330354
parsedData.choices[0].delta &&
331355
parsedData.choices[0].delta.content) {
332356

357+
if (isInThinking && !isInContent) {
358+
const newContent = "</thinking>";
359+
const thinkingResult = extractThinkingFromStream(newContent, accumulatedThinking);
360+
accumulatedThinking = thinkingResult.thinking;
361+
onThinkingUpdate(accumulatedThinking);
362+
363+
if (thinkingResult.content) {
364+
accumulatedContent += thinkingResult.content;
365+
onStreamingUpdate(accumulatedContent);
366+
}
367+
isInThinking = false;
368+
isInContent = true;
369+
}
370+
333371
const newContent = parsedData.choices[0].delta.content;
334372

335373
if (modelId && isThinkingCapableModel(modelId)) {
336374
const thinkingResult = extractThinkingFromStream(newContent, accumulatedThinking);
337375
accumulatedThinking = thinkingResult.thinking;
338376
isInThinking = thinkingResult.isInThinking;
377+
onThinkingUpdate(accumulatedThinking);
339378

340379
if (thinkingResult.content) {
341380
accumulatedContent += thinkingResult.content;
@@ -379,7 +418,7 @@ async function processStreamingResponse(
379418

380419
return {
381420
content: accumulatedContent,
382-
thinking: (modelId && isThinkingCapableModel(modelId) && accumulatedThinking) ? accumulatedThinking : undefined,
421+
thinking: (modelId && accumulatedThinking) ? accumulatedThinking : undefined,
383422
usage,
384423
model,
385424
finish_reason

0 commit comments

Comments
 (0)