fix: keep streamed reasoning visible
This commit is contained in:
@@ -1325,6 +1325,36 @@ describe('App', () => {
|
||||
.closest('details')
|
||||
expect(streamingReasoning).toHaveAttribute('open')
|
||||
expect(screen.getByText('先检查项目结构')).toBeInTheDocument()
|
||||
const reasoningContent =
|
||||
streamingReasoning!.querySelector<HTMLElement>(
|
||||
'.message-reasoning__content'
|
||||
)
|
||||
if (!reasoningContent) {
|
||||
throw new Error('Missing reasoning content')
|
||||
}
|
||||
const reasoningScrollTo = vi.fn()
|
||||
reasoningContent.scrollTo = reasoningScrollTo
|
||||
Object.defineProperty(reasoningContent, 'scrollHeight', {
|
||||
configurable: true,
|
||||
value: 640
|
||||
})
|
||||
|
||||
act(() => {
|
||||
if (!request) {
|
||||
throw new Error('Missing request')
|
||||
}
|
||||
agentListener?.({
|
||||
requestId: request.requestId,
|
||||
type: 'reasoning',
|
||||
delta: ',再确认依赖'
|
||||
})
|
||||
})
|
||||
|
||||
expect(reasoningScrollTo).toHaveBeenLastCalledWith({
|
||||
top: 640,
|
||||
behavior: 'auto'
|
||||
})
|
||||
expect(screen.getByText('先检查项目结构,再确认依赖')).toBeInTheDocument()
|
||||
|
||||
act(() => {
|
||||
if (!request) {
|
||||
|
||||
+45
-30
@@ -524,6 +524,43 @@ function groupMessageBlocks(
|
||||
return items
|
||||
}
|
||||
|
||||
function MessageReasoning({
|
||||
content,
|
||||
streaming
|
||||
}: {
|
||||
content: string
|
||||
streaming: boolean
|
||||
}): React.JSX.Element {
|
||||
const { t } = useTranslation('app')
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!streaming || !contentRef.current) {
|
||||
return
|
||||
}
|
||||
contentRef.current.scrollTo({
|
||||
top: contentRef.current.scrollHeight,
|
||||
behavior: 'auto'
|
||||
})
|
||||
}, [content, streaming])
|
||||
|
||||
return (
|
||||
<details className="message-reasoning" open={streaming}>
|
||||
<summary>
|
||||
{streaming
|
||||
? t('chat.reasoning.streaming')
|
||||
: t('chat.reasoning.complete')}
|
||||
</summary>
|
||||
<div
|
||||
className="markdown-content message-reasoning__content"
|
||||
ref={contentRef}
|
||||
>
|
||||
<MarkdownRenderer>{content}</MarkdownRenderer>
|
||||
</div>
|
||||
</details>
|
||||
)
|
||||
}
|
||||
|
||||
function ToolExecutionList({
|
||||
tools
|
||||
}: {
|
||||
@@ -5380,22 +5417,11 @@ function App(): React.JSX.Element {
|
||||
tools={item.tools}
|
||||
/>
|
||||
) : item.block.type === 'reasoning' ? (
|
||||
<details
|
||||
className="message-reasoning"
|
||||
<MessageReasoning
|
||||
content={item.block.content}
|
||||
key={item.block.id}
|
||||
open={message.state === 'streaming'}
|
||||
>
|
||||
<summary>
|
||||
{message.state === 'streaming'
|
||||
? t('chat.reasoning.streaming')
|
||||
: t('chat.reasoning.complete')}
|
||||
</summary>
|
||||
<div className="markdown-content message-reasoning__content">
|
||||
<MarkdownRenderer>
|
||||
{item.block.content}
|
||||
</MarkdownRenderer>
|
||||
</div>
|
||||
</details>
|
||||
streaming={message.state === 'streaming'}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="markdown-content message__content"
|
||||
@@ -5411,22 +5437,11 @@ function App(): React.JSX.Element {
|
||||
) : (
|
||||
<>
|
||||
{message.reasoning && (
|
||||
<details
|
||||
className="message-reasoning"
|
||||
<MessageReasoning
|
||||
content={message.reasoning}
|
||||
key={`${message.id}-${message.state}`}
|
||||
open={message.state === 'streaming'}
|
||||
>
|
||||
<summary>
|
||||
{message.state === 'streaming'
|
||||
? t('chat.reasoning.streaming')
|
||||
: t('chat.reasoning.complete')}
|
||||
</summary>
|
||||
<div className="markdown-content message-reasoning__content">
|
||||
<MarkdownRenderer>
|
||||
{message.reasoning}
|
||||
</MarkdownRenderer>
|
||||
</div>
|
||||
</details>
|
||||
streaming={message.state === 'streaming'}
|
||||
/>
|
||||
)}
|
||||
{message.content && (
|
||||
<div className="markdown-content message__content">
|
||||
|
||||
Reference in New Issue
Block a user