feat: add collapsible reasoning blocks with auto-expand setting

- Added autoExpandReasoningBlocks configuration setting to control default expansion state
- Created CollapsibleReasoningBlock component that wraps ReasoningBlock with collapsible UI
- Updated ChatRow to use CollapsibleReasoningBlock instead of ReasoningBlock
- Added preview text display when reasoning block is collapsed
- Integrated with ExtensionStateContext for settings management

Fixes #7873
This commit is contained in:
Roo Code 2025-09-10 23:52:30 +00:00
parent 8fee3127ff
commit 804532d4af
6 changed files with 91 additions and 2 deletions

View file

@ -67,6 +67,7 @@ export const globalSettingsSchema = z.object({
alwaysAllowFollowupQuestions: z.boolean().optional(),
followupAutoApproveTimeoutMs: z.number().optional(),
alwaysAllowUpdateTodoList: z.boolean().optional(),
autoExpandReasoningBlocks: z.boolean().optional(),
allowedCommands: z.array(z.string()).optional(),
deniedCommands: z.array(z.string()).optional(),
commandExecutionTimeout: z.number().optional(),

View file

@ -1553,6 +1553,10 @@ export const webviewMessageHandler = async (
await updateGlobalState("alwaysAllowFollowupQuestions", message.bool ?? false)
await provider.postStateToWebview()
break
case "autoExpandReasoningBlocks":
await updateGlobalState("autoExpandReasoningBlocks", message.bool ?? false)
await provider.postStateToWebview()
break
case "followupAutoApproveTimeoutMs":
await updateGlobalState("followupAutoApproveTimeoutMs", message.value)
await provider.postStateToWebview()

View file

@ -229,6 +229,7 @@ export type ExtensionState = Pick<
| "alwaysAllowExecute"
| "alwaysAllowUpdateTodoList"
| "followupAutoApproveTimeoutMs"
| "autoExpandReasoningBlocks"
| "allowedCommands"
| "deniedCommands"
| "allowedMaxRequests"

View file

@ -49,6 +49,7 @@ export interface WebviewMessage {
| "alwaysAllowFollowupQuestions"
| "alwaysAllowUpdateTodoList"
| "followupAutoApproveTimeoutMs"
| "autoExpandReasoningBlocks"
| "webviewDidLaunch"
| "newTask"
| "askResponse"

View file

@ -24,7 +24,7 @@ import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
import CodeAccordian from "../common/CodeAccordian"
import CodeBlock from "../common/CodeBlock"
import MarkdownBlock from "../common/MarkdownBlock"
import { ReasoningBlock } from "./ReasoningBlock"
import { CollapsibleReasoningBlock } from "./CollapsibleReasoningBlock"
import Thumbnails from "../common/Thumbnails"
import ImageBlock from "../common/ImageBlock"
@ -1084,7 +1084,7 @@ export const ChatRowContent = ({
)
case "reasoning":
return (
<ReasoningBlock
<CollapsibleReasoningBlock
content={message.text || ""}
ts={message.ts}
isStreaming={isStreaming}

View file

@ -0,0 +1,82 @@
import React, { useState, useEffect, useContext } from "react"
import { ChevronDown, ChevronRight } from "lucide-react"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible"
import { ExtensionStateContext } from "../../context/ExtensionStateContext"
import { ReasoningBlock } from "./ReasoningBlock"
interface CollapsibleReasoningBlockProps {
content: string
ts: number
isStreaming: boolean
isLast: boolean
metadata?: any
}
export const CollapsibleReasoningBlock: React.FC<CollapsibleReasoningBlockProps> = ({
content,
ts,
isStreaming,
isLast,
metadata,
}) => {
const extensionState = useContext(ExtensionStateContext)
const autoExpand = extensionState?.autoExpandReasoningBlocks ?? false
// Start with the configured default state
const [isOpen, setIsOpen] = useState(autoExpand)
// Update when the setting changes
useEffect(() => {
setIsOpen(autoExpand)
}, [autoExpand])
// Extract first line or preview of the reasoning content
const getPreviewText = () => {
if (!content) return "Thinking..."
const lines = content.split("\n").filter((line) => line.trim())
if (lines.length === 0) return "Thinking..."
// Get first meaningful line (skip empty lines)
const firstLine = lines[0]
const maxLength = 100
if (firstLine.length > maxLength) {
return firstLine.substring(0, maxLength) + "..."
}
return firstLine + (lines.length > 1 ? "..." : "")
}
return (
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<div className="bg-vscode-editorWidget-background border border-vscode-editorWidget-border rounded-md overflow-hidden">
<CollapsibleTrigger className="flex items-center justify-between w-full p-3 hover:bg-vscode-list-hoverBackground transition-colors">
<div className="flex items-center gap-2">
{isOpen ? (
<ChevronDown className="h-4 w-4 text-vscode-descriptionForeground" />
) : (
<ChevronRight className="h-4 w-4 text-vscode-descriptionForeground" />
)}
<span className="text-sm font-medium text-vscode-descriptionForeground">Reasoning</span>
{!isOpen && (
<span className="text-sm text-vscode-descriptionForeground ml-2 truncate max-w-[500px]">
{getPreviewText()}
</span>
)}
</div>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="border-t border-vscode-editorWidget-border">
<ReasoningBlock
content={content}
ts={ts}
isStreaming={isStreaming}
isLast={isLast}
metadata={metadata}
/>
</div>
</CollapsibleContent>
</div>
</Collapsible>
)
}