Skip to content

Commit 8f14912

Browse files
roomote[bot]roomote-agenthannesrudolphdaniel-lxs
authored andcommitted
UI: Render reasoning as plain italic (match <thinking>) (#7752)
Co-authored-by: Roo Code <[email protected]> Co-authored-by: Hannes Rudolph <[email protected]> Co-authored-by: daniel-lxs <[email protected]>
1 parent 3a589f4 commit 8f14912

File tree

2 files changed

+41
-80
lines changed

2 files changed

+41
-80
lines changed

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ export const ChatRowContent = ({
118118

119119
const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration } = useExtensionState()
120120
const { info: model } = useSelectedModel(apiConfiguration)
121-
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
122121
const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false)
123122
const [showCopySuccess, setShowCopySuccess] = useState(false)
124123
const [isEditing, setIsEditing] = useState(false)
@@ -1087,9 +1086,10 @@ export const ChatRowContent = ({
10871086
return (
10881087
<ReasoningBlock
10891088
content={message.text || ""}
1090-
elapsed={isLast && isStreaming ? Date.now() - message.ts : undefined}
1091-
isCollapsed={reasoningCollapsed}
1092-
onToggleCollapse={() => setReasoningCollapsed(!reasoningCollapsed)}
1089+
ts={message.ts}
1090+
isStreaming={isStreaming}
1091+
isLast={isLast}
1092+
metadata={message.metadata as any}
10931093
/>
10941094
)
10951095
case "api_req_started":

webview-ui/src/components/chat/ReasoningBlock.tsx

Lines changed: 37 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,57 @@
1-
import { useCallback, useEffect, useRef, useState } from "react"
2-
import { CaretDownIcon, CaretUpIcon, CounterClockwiseClockIcon } from "@radix-ui/react-icons"
1+
import React, { useEffect, useRef, useState } from "react"
32
import { useTranslation } from "react-i18next"
43

54
import MarkdownBlock from "../common/MarkdownBlock"
6-
import { useMount } from "react-use"
5+
import { Clock, Lightbulb } from "lucide-react"
76

87
interface ReasoningBlockProps {
98
content: string
10-
elapsed?: number
11-
isCollapsed?: boolean
12-
onToggleCollapse?: () => void
9+
ts: number
10+
isStreaming: boolean
11+
isLast: boolean
12+
metadata?: any
1313
}
1414

15-
export const ReasoningBlock = ({ content, elapsed, isCollapsed = false, onToggleCollapse }: ReasoningBlockProps) => {
16-
const contentRef = useRef<HTMLDivElement>(null)
17-
const elapsedRef = useRef<number>(0)
18-
const { t } = useTranslation("chat")
19-
const [thought, setThought] = useState<string>()
20-
const [prevThought, setPrevThought] = useState<string>(t("chat:reasoning.thinking"))
21-
const [isTransitioning, setIsTransitioning] = useState<boolean>(false)
22-
const cursorRef = useRef<number>(0)
23-
const queueRef = useRef<string[]>([])
15+
/**
16+
* Render reasoning with a heading and a simple timer.
17+
* - Heading uses i18n key chat:reasoning.thinking
18+
* - Timer runs while reasoning is active (no persistence)
19+
*/
20+
export const ReasoningBlock = ({ content, isStreaming, isLast }: ReasoningBlockProps) => {
21+
const { t } = useTranslation()
2422

25-
useEffect(() => {
26-
if (contentRef.current && !isCollapsed) {
27-
contentRef.current.scrollTop = contentRef.current.scrollHeight
28-
}
29-
}, [content, isCollapsed])
30-
31-
useEffect(() => {
32-
if (elapsed) {
33-
elapsedRef.current = elapsed
34-
}
35-
}, [elapsed])
36-
37-
// Process the transition queue.
38-
const processNextTransition = useCallback(() => {
39-
const nextThought = queueRef.current.pop()
40-
queueRef.current = []
41-
42-
if (nextThought) {
43-
setIsTransitioning(true)
44-
}
45-
46-
setTimeout(() => {
47-
if (nextThought) {
48-
setPrevThought(nextThought)
49-
setIsTransitioning(false)
50-
}
51-
52-
setTimeout(() => processNextTransition(), 500)
53-
}, 200)
54-
}, [])
55-
56-
useMount(() => {
57-
processNextTransition()
58-
})
23+
const startTimeRef = useRef<number>(Date.now())
24+
const [elapsed, setElapsed] = useState<number>(0)
5925

26+
// Simple timer that runs while streaming
6027
useEffect(() => {
61-
if (content.length - cursorRef.current > 160) {
62-
setThought("... " + content.slice(cursorRef.current))
63-
cursorRef.current = content.length
28+
if (isLast && isStreaming) {
29+
const tick = () => setElapsed(Date.now() - startTimeRef.current)
30+
tick()
31+
const id = setInterval(tick, 1000)
32+
return () => clearInterval(id)
6433
}
65-
}, [content])
34+
}, [isLast, isStreaming])
6635

67-
useEffect(() => {
68-
if (thought && thought !== prevThought) {
69-
queueRef.current.push(thought)
70-
}
71-
}, [thought, prevThought])
36+
const seconds = Math.floor(elapsed / 1000)
37+
const secondsLabel = t("chat:reasoning.seconds", { count: seconds })
7238

7339
return (
74-
<div className="bg-vscode-editor-background border border-vscode-border rounded-xs overflow-hidden">
75-
<div
76-
className="flex items-center justify-between gap-1 px-3 py-2 cursor-pointer text-muted-foreground"
77-
onClick={onToggleCollapse}>
78-
<div
79-
className={`truncate flex-1 transition-opacity duration-200 ${isTransitioning ? "opacity-0" : "opacity-100"}`}>
80-
{prevThought}
81-
</div>
82-
<div className="flex flex-row items-center gap-1">
83-
{elapsedRef.current > 1000 && (
84-
<>
85-
<CounterClockwiseClockIcon className="scale-80" />
86-
<div>{t("reasoning.seconds", { count: Math.round(elapsedRef.current / 1000) })}</div>
87-
</>
88-
)}
89-
{isCollapsed ? <CaretDownIcon /> : <CaretUpIcon />}
40+
<div className="py-1">
41+
<div className="flex items-center justify-between mb-2.5 pr-2">
42+
<div className="flex items-center gap-2">
43+
<Lightbulb className="w-4" />
44+
<span className="font-bold text-vscode-foreground">{t("chat:reasoning.thinking")}</span>
9045
</div>
46+
{elapsed > 0 && (
47+
<span className="text-vscode-foreground tabular-nums flex items-center gap-1">
48+
<Clock className="w-4" />
49+
{secondsLabel}
50+
</span>
51+
)}
9152
</div>
92-
{!isCollapsed && (
93-
<div ref={contentRef} className="px-3 max-h-[160px] overflow-y-auto">
53+
{(content?.trim()?.length ?? 0) > 0 && (
54+
<div className="px-3 italic text-vscode-descriptionForeground">
9455
<MarkdownBlock markdown={content} />
9556
</div>
9657
)}

0 commit comments

Comments
 (0)