diff --git a/desktop/src/components/chat/ThinkingBlock.tsx b/desktop/src/components/chat/ThinkingBlock.tsx index d43889b4..284f9a3f 100644 --- a/desktop/src/components/chat/ThinkingBlock.tsx +++ b/desktop/src/components/chat/ThinkingBlock.tsx @@ -2,20 +2,12 @@ import { useState, useEffect, useMemo, useRef } from 'react' import { useTranslation } from '../../i18n' import { MarkdownRenderer } from '../markdown/MarkdownRenderer' -const PREVIEW_LINE_LIMIT = 10 - export function ThinkingBlock({ content, isActive = false }: { content: string; isActive?: boolean }) { const t = useTranslation() const [expanded, setExpanded] = useState(false) const contentRef = useRef(null) const displayContent = useMemo(() => content.replace(/\r\n?/g, '\n').trimEnd(), [content]) const hasDisplayContent = displayContent.trim().length > 0 - const displayLines = useMemo(() => displayContent.split('\n'), [displayContent]) - const previewContent = useMemo( - () => displayLines.slice(0, PREVIEW_LINE_LIMIT).join('\n'), - [displayLines], - ) - const isPreviewTruncated = displayLines.length > PREVIEW_LINE_LIMIT useEffect(() => { if (expanded && isActive && contentRef.current) { @@ -40,29 +32,20 @@ export function ThinkingBlock({ content, isActive = false }: { content: string; {isActive && } - {hasDisplayContent && ( + {expanded && hasDisplayContent && (
setExpanded(true) : undefined} - className={`relative mt-1 rounded-lg border border-[var(--color-border)]/40 bg-[var(--color-surface-container-lowest)] p-2.5 text-[11px] text-[var(--color-text-secondary)] ${ - expanded - ? 'max-h-[300px] overflow-y-auto' - : 'thinking-preview-clamp cursor-pointer' - }`} + ref={contentRef} + data-thinking-content="expanded" + className="relative mt-1 max-h-[300px] overflow-y-auto rounded-lg border border-[var(--color-border)]/40 bg-[var(--color-surface-container-lowest)] p-2.5 text-[11px] text-[var(--color-text-secondary)]" > - {!expanded && isPreviewTruncated &&
)} @@ -89,15 +72,6 @@ const thinkingStyles = ` margin-left: 1px; animation: thinking-cursor-blink 1s step-end infinite; } -.thinking-inline-cursor { - display: inline-block; - width: 1px; - height: 0.95em; - margin-left: 3px; - vertical-align: text-bottom; - background: var(--color-text-tertiary); - animation: thinking-cursor-blink 1s step-end infinite; -} .thinking-dots::after { content: ''; animation: thinking-dots 1.4s steps(1, end) infinite; @@ -110,24 +84,4 @@ const thinkingStyles = ` .thinking-markdown > :last-child > :last-child { margin-bottom: 0; } -.thinking-preview-clamp { - max-height: calc(${PREVIEW_LINE_LIMIT} * 1.25rem + 1.25rem); - overflow: hidden; -} -.thinking-preview-fade { - position: absolute; - left: 0; - right: 0; - bottom: 0; - height: 1.75rem; - border-bottom-left-radius: 0.5rem; - border-bottom-right-radius: 0.5rem; - pointer-events: none; - background: linear-gradient(to bottom, transparent, var(--color-surface-container-lowest)); -} -.thinking-preview-active-cursor { - position: absolute; - right: 0.6rem; - bottom: 0.45rem; -} ` diff --git a/desktop/src/components/chat/chatBlocks.test.tsx b/desktop/src/components/chat/chatBlocks.test.tsx index 22985b67..25e41d3c 100644 --- a/desktop/src/components/chat/chatBlocks.test.tsx +++ b/desktop/src/components/chat/chatBlocks.test.tsx @@ -18,32 +18,42 @@ describe('chat blocks', () => { const { container } = render() expect(screen.getByText(/Thinking/)).toBeTruthy() - expect(container.textContent).toContain('this is a long internal reasoning trace') + expect(container.textContent).not.toContain('this is a long internal reasoning trace') expect(container.querySelector('.thinking-cursor')).toBeNull() }) it('does not animate inactive historical thinking blocks', () => { const { container } = render() - expect(container.querySelector('.thinking-inline-cursor')).toBeNull() + fireEvent.click(screen.getByRole('button', { name: /Thinking/ })) + + expect(container.textContent).toContain('old reasoning') + expect(container.querySelector('.thinking-cursor')).toBeNull() }) - it('renders collapsed thinking content as markdown', () => { + it('renders thinking content as markdown only after expanding', () => { const { container } = render() + expect(container.textContent).not.toContain('important') + expect(container.querySelector('strong')).toBeNull() + expect(container.querySelector('li')).toBeNull() + + fireEvent.click(screen.getByRole('button', { name: /Thinking/ })) + expect(container.querySelector('strong')?.textContent).toBe('important') expect(container.querySelector('li')?.textContent).toBe('item one') }) - it('limits collapsed thinking content to ten lines until expanded', () => { + it('hides full thinking content until expanded', () => { const content = Array.from({ length: 12 }, (_, index) => `line-${index + 1}`).join('\n') const { container } = render() - expect(container.textContent).toContain('line-10') + expect(container.textContent).not.toContain('line-1') expect(container.textContent).not.toContain('line-11') fireEvent.click(screen.getByRole('button', { name: /Thinking/ })) + expect(container.textContent).toContain('line-1') expect(container.textContent).toContain('line-11') expect(container.textContent).toContain('line-12') })