feat: Improve the display of thinking content in the desktop UI (#8414)

Signed-off-by: jh-block <jhugo@block.xyz>
This commit is contained in:
jh-block
2026-04-08 22:35:04 +02:00
committed by GitHub
parent 8b1007eee0
commit f2f5baab74
3 changed files with 71 additions and 36 deletions
+6 -28
View File
@@ -2,6 +2,7 @@ import { useMemo, useRef } from 'react';
import ImagePreview from './ImagePreview';
import { formatMessageTimestamp } from '../utils/timeUtils';
import MarkdownContent from './MarkdownContent';
import ThinkingContent from './ThinkingContent';
import ToolCallWithResponse from './ToolCallWithResponse';
import {
getTextAndImageContent,
@@ -47,27 +48,9 @@ export default function GooseMessage({
}: GooseMessageProps) {
const contentRef = useRef<HTMLDivElement | null>(null);
let { textContent, imagePaths } = getTextAndImageContent(message);
const { textContent: displayText, imagePaths } = getTextAndImageContent(message);
const thinkingContent = getThinkingContent(message);
const splitChainOfThought = (text: string): { displayText: string; cotText: string | null } => {
const regex = /<think>([\s\S]*?)<\/think>/i;
const match = text.match(regex);
if (!match) {
return { displayText: text, cotText: null };
}
const cotRaw = match[1].trim();
const displayText = text.replace(regex, '').trim();
return {
displayText,
cotText: cotRaw || null,
};
};
const { displayText, cotText } = splitChainOfThought(textContent);
const timestamp = useMemo(() => formatMessageTimestamp(message.created), [message.created]);
const toolRequests = getToolRequests(message);
const messageIndex = messages.findIndex((msg) => msg.id === message.id);
@@ -132,15 +115,10 @@ export default function GooseMessage({
<div className="goose-message flex w-[90%] justify-start min-w-0">
<div className="flex flex-col w-full min-w-0">
{thinkingContent && (
<div className="mb-2 text-xs text-gray-400/70 italic">
<MarkdownContent content={thinkingContent} />
</div>
)}
{cotText && (
<div className="mb-2 text-sm text-gray-400 italic">
<MarkdownContent content={cotText} />
</div>
<ThinkingContent
content={thinkingContent}
isExpanded={isStreaming && !displayText.trim() && imagePaths.length === 0 && toolRequests.length === 0}
/>
)}
{(displayText.trim() || imagePaths.length > 0) && (
@@ -0,0 +1,37 @@
import { useState, useEffect, useRef } from 'react';
import MarkdownContent from './MarkdownContent';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
import Expand from './ui/Expand';
interface ThinkingContentProps {
content: string;
isExpanded: boolean;
}
export default function ThinkingContent({ content, isExpanded }: ThinkingContentProps) {
const [manualToggle, setManualToggle] = useState<boolean | null>(null);
const prevIsExpanded = useRef(isExpanded);
useEffect(() => {
if (prevIsExpanded.current && !isExpanded) {
setManualToggle(null);
}
prevIsExpanded.current = isExpanded;
}, [isExpanded]);
const expanded = manualToggle !== null ? manualToggle : isExpanded;
return (
<Collapsible open={expanded} onOpenChange={(open) => setManualToggle(open)} className="mb-2">
<CollapsibleTrigger className="flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors cursor-pointer">
<Expand size={3} isExpanded={expanded} />
<span className="italic">Thinking</span>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="mt-1 ml-[18px] text-xs text-text-secondary italic">
<MarkdownContent content={content} />
</div>
</CollapsibleContent>
</Collapsible>
);
}