Skip to content

Commit 8fa770e

Browse files
authored
Merge pull request RooCodeInc#1256 from RooVetGit/cte/pretty-thinking
Prettier thinking blocks
2 parents b6a9bc9 + 360e47d commit 8fa770e

File tree

4 files changed

+94
-89
lines changed

4 files changed

+94
-89
lines changed

.changeset/young-hornets-taste.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Prettier thinking blocks

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

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { vscode } from "../../utils/vscode"
1616
import CodeAccordian, { removeLeadingNonAlphanumeric } from "../common/CodeAccordian"
1717
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
1818
import MarkdownBlock from "../common/MarkdownBlock"
19-
import ReasoningBlock from "./ReasoningBlock"
19+
import { ReasoningBlock } from "./ReasoningBlock"
2020
import Thumbnails from "../common/Thumbnails"
2121
import McpResourceRow from "../mcp/McpResourceRow"
2222
import McpToolRow from "../mcp/McpToolRow"
@@ -25,12 +25,12 @@ import { CheckpointSaved } from "./checkpoints/CheckpointSaved"
2525

2626
interface ChatRowProps {
2727
message: ClineMessage
28-
isExpanded: boolean
29-
onToggleExpand: () => void
3028
lastModifiedMessage?: ClineMessage
29+
isExpanded: boolean
3130
isLast: boolean
32-
onHeightChange: (isTaller: boolean) => void
3331
isStreaming: boolean
32+
onToggleExpand: () => void
33+
onHeightChange: (isTaller: boolean) => void
3434
}
3535

3636
interface ChatRowContentProps extends Omit<ChatRowProps, "onHeightChange"> {}
@@ -43,10 +43,7 @@ const ChatRow = memo(
4343
const prevHeightRef = useRef(0)
4444

4545
const [chatrow, { height }] = useSize(
46-
<div
47-
style={{
48-
padding: "10px 6px 10px 15px",
49-
}}>
46+
<div className="px-[15px] py-[10px] pr-[6px]">
5047
<ChatRowContent {...props} />
5148
</div>,
5249
)
@@ -75,33 +72,32 @@ export default ChatRow
7572

7673
export const ChatRowContent = ({
7774
message,
78-
isExpanded,
79-
onToggleExpand,
8075
lastModifiedMessage,
76+
isExpanded,
8177
isLast,
8278
isStreaming,
79+
onToggleExpand,
8380
}: ChatRowContentProps) => {
8481
const { mcpServers, alwaysAllowMcp, currentCheckpoint } = useExtensionState()
85-
const [reasoningCollapsed, setReasoningCollapsed] = useState(false)
82+
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
8683

87-
// Auto-collapse reasoning when new messages arrive
88-
useEffect(() => {
89-
if (!isLast && message.say === "reasoning") {
90-
setReasoningCollapsed(true)
91-
}
92-
}, [isLast, message.say])
9384
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
9485
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
9586
const info: ClineApiReqInfo = JSON.parse(message.text)
9687
return [info.cost, info.cancelReason, info.streamingFailedMessage]
9788
}
89+
9890
return [undefined, undefined, undefined]
9991
}, [message.text, message.say])
100-
// when resuming task, last wont be api_req_failed but a resume_task message, so api_req_started will show loading spinner. that's why we just remove the last api_req_started that failed without streaming anything
92+
93+
// When resuming task, last wont be api_req_failed but a resume_task
94+
// message, so api_req_started will show loading spinner. That's why we just
95+
// remove the last api_req_started that failed without streaming anything.
10196
const apiRequestFailedMessage =
10297
isLast && lastModifiedMessage?.ask === "api_req_failed" // if request is retried then the latest message is a api_req_retried
10398
? lastModifiedMessage?.text
10499
: undefined
100+
105101
const isCommandExecuting =
106102
isLast && lastModifiedMessage?.ask === "command" && lastModifiedMessage?.text?.includes(COMMAND_OUTPUT_STRING)
107103

@@ -428,32 +424,6 @@ export const ChatRowContent = ({
428424
/>
429425
</>
430426
)
431-
// case "inspectSite":
432-
// const isInspecting =
433-
// isLast && lastModifiedMessage?.say === "inspect_site_result" && !lastModifiedMessage?.images
434-
// return (
435-
// <>
436-
// <div style={headerStyle}>
437-
// {isInspecting ? <ProgressIndicator /> : toolIcon("inspect")}
438-
// <span style={{ fontWeight: "bold" }}>
439-
// {message.type === "ask" ? (
440-
// <>Roo wants to inspect this website:</>
441-
// ) : (
442-
// <>Roo is inspecting this website:</>
443-
// )}
444-
// </span>
445-
// </div>
446-
// <div
447-
// style={{
448-
// borderRadius: 3,
449-
// border: "1px solid var(--vscode-editorGroup-border)",
450-
// overflow: "hidden",
451-
// backgroundColor: CODE_BLOCK_BG_COLOR,
452-
// }}>
453-
// <CodeBlock source={`${"```"}shell\n${tool.path}\n${"```"}`} forceWrap={true} />
454-
// </div>
455-
// </>
456-
// )
457427
case "switchMode":
458428
return (
459429
<>
@@ -501,6 +471,7 @@ export const ChatRowContent = ({
501471
return (
502472
<ReasoningBlock
503473
content={message.text || ""}
474+
elapsed={isLast && isStreaming ? Date.now() - message.ts : undefined}
504475
isCollapsed={reasoningCollapsed}
505476
onToggleCollapse={() => setReasoningCollapsed(!reasoningCollapsed)}
506477
/>

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

Lines changed: 72 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,97 @@
1-
import React, { useEffect, useRef } from "react"
2-
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
1+
import { useCallback, useEffect, useRef, useState } from "react"
2+
import { CaretDownIcon, CaretUpIcon, CounterClockwiseClockIcon } from "@radix-ui/react-icons"
3+
34
import MarkdownBlock from "../common/MarkdownBlock"
5+
import { useMount } from "react-use"
46

57
interface ReasoningBlockProps {
68
content: string
9+
elapsed?: number
710
isCollapsed?: boolean
811
onToggleCollapse?: () => void
9-
autoHeight?: boolean
1012
}
1113

12-
const ReasoningBlock: React.FC<ReasoningBlockProps> = ({
13-
content,
14-
isCollapsed = false,
15-
onToggleCollapse,
16-
autoHeight = false,
17-
}) => {
14+
export const ReasoningBlock = ({ content, elapsed, isCollapsed = false, onToggleCollapse }: ReasoningBlockProps) => {
1815
const contentRef = useRef<HTMLDivElement>(null)
16+
const elapsedRef = useRef<number>(0)
17+
const [thought, setThought] = useState<string>()
18+
const [prevThought, setPrevThought] = useState<string>("Thinking")
19+
const [isTransitioning, setIsTransitioning] = useState<boolean>(false)
20+
const cursorRef = useRef<number>(0)
21+
const queueRef = useRef<string[]>([])
1922

20-
// Scroll to bottom when content updates
2123
useEffect(() => {
2224
if (contentRef.current && !isCollapsed) {
2325
contentRef.current.scrollTop = contentRef.current.scrollHeight
2426
}
2527
}, [content, isCollapsed])
2628

29+
useEffect(() => {
30+
if (elapsed) {
31+
elapsedRef.current = elapsed
32+
}
33+
}, [elapsed])
34+
35+
// Process the transition queue.
36+
const processNextTransition = useCallback(() => {
37+
const nextThought = queueRef.current.pop()
38+
queueRef.current = []
39+
40+
if (nextThought) {
41+
setIsTransitioning(true)
42+
}
43+
44+
setTimeout(() => {
45+
if (nextThought) {
46+
setPrevThought(nextThought)
47+
setIsTransitioning(false)
48+
}
49+
50+
setTimeout(() => processNextTransition(), 500)
51+
}, 200)
52+
}, [])
53+
54+
useMount(() => {
55+
processNextTransition()
56+
})
57+
58+
useEffect(() => {
59+
if (content.length - cursorRef.current > 160) {
60+
setThought("... " + content.slice(cursorRef.current))
61+
cursorRef.current = content.length
62+
}
63+
}, [content])
64+
65+
useEffect(() => {
66+
if (thought && thought !== prevThought) {
67+
queueRef.current.push(thought)
68+
}
69+
}, [thought, prevThought])
70+
2771
return (
28-
<div
29-
style={{
30-
backgroundColor: CODE_BLOCK_BG_COLOR,
31-
border: "1px solid var(--vscode-editorGroup-border)",
32-
borderRadius: "3px",
33-
overflow: "hidden",
34-
}}>
72+
<div className="bg-vscode-editor-background border border-vscode-border rounded-xs overflow-hidden">
3573
<div
36-
onClick={onToggleCollapse}
37-
style={{
38-
padding: "8px 12px",
39-
cursor: "pointer",
40-
userSelect: "none",
41-
display: "flex",
42-
alignItems: "center",
43-
justifyContent: "space-between",
44-
borderBottom: isCollapsed ? "none" : "1px solid var(--vscode-editorGroup-border)",
45-
}}>
46-
<span style={{ fontWeight: "bold" }}>Reasoning</span>
47-
<span className={`codicon codicon-chevron-${isCollapsed ? "right" : "down"}`}></span>
74+
className="flex items-center justify-between gap-1 px-3 py-2 cursor-pointer text-muted-foreground"
75+
onClick={onToggleCollapse}>
76+
<div
77+
className={`truncate flex-1 transition-opacity duration-200 ${isTransitioning ? "opacity-0" : "opacity-100"}`}>
78+
{prevThought}
79+
</div>
80+
<div className="flex flex-row items-center gap-1">
81+
{elapsedRef.current > 1000 && (
82+
<>
83+
<CounterClockwiseClockIcon className="scale-80" />
84+
<div>{Math.round(elapsedRef.current / 1000)}s</div>
85+
</>
86+
)}
87+
{isCollapsed ? <CaretDownIcon /> : <CaretUpIcon />}
88+
</div>
4889
</div>
4990
{!isCollapsed && (
50-
<div
51-
ref={contentRef}
52-
style={{
53-
padding: "8px 12px",
54-
maxHeight: autoHeight ? "none" : "160px",
55-
overflowY: "auto",
56-
}}>
57-
<div
58-
style={{
59-
fontSize: "13px",
60-
opacity: 0.9,
61-
}}>
62-
<MarkdownBlock markdown={content} />
63-
</div>
91+
<div ref={contentRef} className="px-3 max-h-[160px] overflow-y-auto">
92+
<MarkdownBlock markdown={content} />
6493
</div>
6594
)}
6695
</div>
6796
)
6897
}
69-
70-
export default ReasoningBlock

webview-ui/src/index.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
--color-vscode-editor-foreground: var(--vscode-editor-foreground);
6565
--color-vscode-editor-background: var(--vscode-editor-background);
6666

67+
--color-vscode-editorGroup-border: var(--vscode-editorGroup-border);
68+
6769
--color-vscode-button-foreground: var(--vscode-button-foreground);
6870
--color-vscode-button-background: var(--vscode-button-background);
6971
--color-vscode-button-secondaryForeground: var(--vscode-button-secondaryForeground);

0 commit comments

Comments
 (0)