mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Prettier thinking blocks
This commit is contained in:
parent
b6a9bc9d98
commit
b3fd1a2e23
3 changed files with 89 additions and 89 deletions
|
|
@ -16,7 +16,7 @@ import { vscode } from "../../utils/vscode"
|
|||
import CodeAccordian, { removeLeadingNonAlphanumeric } from "../common/CodeAccordian"
|
||||
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
|
||||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
import ReasoningBlock from "./ReasoningBlock"
|
||||
import { ReasoningBlock } from "./ReasoningBlock"
|
||||
import Thumbnails from "../common/Thumbnails"
|
||||
import McpResourceRow from "../mcp/McpResourceRow"
|
||||
import McpToolRow from "../mcp/McpToolRow"
|
||||
|
|
@ -25,12 +25,12 @@ import { CheckpointSaved } from "./checkpoints/CheckpointSaved"
|
|||
|
||||
interface ChatRowProps {
|
||||
message: ClineMessage
|
||||
isExpanded: boolean
|
||||
onToggleExpand: () => void
|
||||
lastModifiedMessage?: ClineMessage
|
||||
isExpanded: boolean
|
||||
isLast: boolean
|
||||
onHeightChange: (isTaller: boolean) => void
|
||||
isStreaming: boolean
|
||||
onToggleExpand: () => void
|
||||
onHeightChange: (isTaller: boolean) => void
|
||||
}
|
||||
|
||||
interface ChatRowContentProps extends Omit<ChatRowProps, "onHeightChange"> {}
|
||||
|
|
@ -43,10 +43,7 @@ const ChatRow = memo(
|
|||
const prevHeightRef = useRef(0)
|
||||
|
||||
const [chatrow, { height }] = useSize(
|
||||
<div
|
||||
style={{
|
||||
padding: "10px 6px 10px 15px",
|
||||
}}>
|
||||
<div className="px-[15px] py-[10px] pr-[6px]">
|
||||
<ChatRowContent {...props} />
|
||||
</div>,
|
||||
)
|
||||
|
|
@ -75,33 +72,32 @@ export default ChatRow
|
|||
|
||||
export const ChatRowContent = ({
|
||||
message,
|
||||
isExpanded,
|
||||
onToggleExpand,
|
||||
lastModifiedMessage,
|
||||
isExpanded,
|
||||
isLast,
|
||||
isStreaming,
|
||||
onToggleExpand,
|
||||
}: ChatRowContentProps) => {
|
||||
const { mcpServers, alwaysAllowMcp, currentCheckpoint } = useExtensionState()
|
||||
const [reasoningCollapsed, setReasoningCollapsed] = useState(false)
|
||||
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
|
||||
|
||||
// Auto-collapse reasoning when new messages arrive
|
||||
useEffect(() => {
|
||||
if (!isLast && message.say === "reasoning") {
|
||||
setReasoningCollapsed(true)
|
||||
}
|
||||
}, [isLast, message.say])
|
||||
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
|
||||
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
|
||||
const info: ClineApiReqInfo = JSON.parse(message.text)
|
||||
return [info.cost, info.cancelReason, info.streamingFailedMessage]
|
||||
}
|
||||
|
||||
return [undefined, undefined, undefined]
|
||||
}, [message.text, message.say])
|
||||
// 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
|
||||
|
||||
// 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.
|
||||
const apiRequestFailedMessage =
|
||||
isLast && lastModifiedMessage?.ask === "api_req_failed" // if request is retried then the latest message is a api_req_retried
|
||||
? lastModifiedMessage?.text
|
||||
: undefined
|
||||
|
||||
const isCommandExecuting =
|
||||
isLast && lastModifiedMessage?.ask === "command" && lastModifiedMessage?.text?.includes(COMMAND_OUTPUT_STRING)
|
||||
|
||||
|
|
@ -428,32 +424,6 @@ export const ChatRowContent = ({
|
|||
/>
|
||||
</>
|
||||
)
|
||||
// case "inspectSite":
|
||||
// const isInspecting =
|
||||
// isLast && lastModifiedMessage?.say === "inspect_site_result" && !lastModifiedMessage?.images
|
||||
// return (
|
||||
// <>
|
||||
// <div style={headerStyle}>
|
||||
// {isInspecting ? <ProgressIndicator /> : toolIcon("inspect")}
|
||||
// <span style={{ fontWeight: "bold" }}>
|
||||
// {message.type === "ask" ? (
|
||||
// <>Roo wants to inspect this website:</>
|
||||
// ) : (
|
||||
// <>Roo is inspecting this website:</>
|
||||
// )}
|
||||
// </span>
|
||||
// </div>
|
||||
// <div
|
||||
// style={{
|
||||
// borderRadius: 3,
|
||||
// border: "1px solid var(--vscode-editorGroup-border)",
|
||||
// overflow: "hidden",
|
||||
// backgroundColor: CODE_BLOCK_BG_COLOR,
|
||||
// }}>
|
||||
// <CodeBlock source={`${"```"}shell\n${tool.path}\n${"```"}`} forceWrap={true} />
|
||||
// </div>
|
||||
// </>
|
||||
// )
|
||||
case "switchMode":
|
||||
return (
|
||||
<>
|
||||
|
|
@ -501,6 +471,7 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<ReasoningBlock
|
||||
content={message.text || ""}
|
||||
elapsed={isLast && isStreaming ? Date.now() - message.ts : undefined}
|
||||
isCollapsed={reasoningCollapsed}
|
||||
onToggleCollapse={() => setReasoningCollapsed(!reasoningCollapsed)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,70 +1,97 @@
|
|||
import React, { useEffect, useRef } from "react"
|
||||
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { CaretDownIcon, CaretUpIcon, CounterClockwiseClockIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
import { useMount } from "react-use"
|
||||
|
||||
interface ReasoningBlockProps {
|
||||
content: string
|
||||
elapsed?: number
|
||||
isCollapsed?: boolean
|
||||
onToggleCollapse?: () => void
|
||||
autoHeight?: boolean
|
||||
}
|
||||
|
||||
const ReasoningBlock: React.FC<ReasoningBlockProps> = ({
|
||||
content,
|
||||
isCollapsed = false,
|
||||
onToggleCollapse,
|
||||
autoHeight = false,
|
||||
}) => {
|
||||
export const ReasoningBlock = ({ content, elapsed, isCollapsed = false, onToggleCollapse }: ReasoningBlockProps) => {
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const elapsedRef = useRef<number>(0)
|
||||
const [thought, setThought] = useState<string>()
|
||||
const [prevThought, setPrevThought] = useState<string>("Thinking")
|
||||
const [isTransitioning, setIsTransitioning] = useState<boolean>(false)
|
||||
const cursorRef = useRef<number>(0)
|
||||
const queueRef = useRef<string[]>([])
|
||||
|
||||
// Scroll to bottom when content updates
|
||||
useEffect(() => {
|
||||
if (contentRef.current && !isCollapsed) {
|
||||
contentRef.current.scrollTop = contentRef.current.scrollHeight
|
||||
}
|
||||
}, [content, isCollapsed])
|
||||
|
||||
useEffect(() => {
|
||||
if (elapsed) {
|
||||
elapsedRef.current = elapsed
|
||||
}
|
||||
}, [elapsed])
|
||||
|
||||
// Process the transition queue.
|
||||
const processNextTransition = useCallback(() => {
|
||||
const nextThought = queueRef.current.pop()
|
||||
queueRef.current = []
|
||||
|
||||
if (nextThought) {
|
||||
setIsTransitioning(true)
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (nextThought) {
|
||||
setPrevThought(nextThought)
|
||||
setIsTransitioning(false)
|
||||
}
|
||||
|
||||
setTimeout(() => processNextTransition(), 500)
|
||||
}, 200)
|
||||
}, [])
|
||||
|
||||
useMount(() => {
|
||||
processNextTransition()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (content.length - cursorRef.current > 160) {
|
||||
setThought("... " + content.slice(cursorRef.current))
|
||||
cursorRef.current = content.length
|
||||
}
|
||||
}, [content])
|
||||
|
||||
useEffect(() => {
|
||||
if (thought && thought !== prevThought) {
|
||||
queueRef.current.push(thought)
|
||||
}
|
||||
}, [thought, prevThought])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: CODE_BLOCK_BG_COLOR,
|
||||
border: "1px solid var(--vscode-editorGroup-border)",
|
||||
borderRadius: "3px",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
<div className="bg-vscode-editor-background border border-vscode-border rounded-xs overflow-hidden">
|
||||
<div
|
||||
onClick={onToggleCollapse}
|
||||
style={{
|
||||
padding: "8px 12px",
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
borderBottom: isCollapsed ? "none" : "1px solid var(--vscode-editorGroup-border)",
|
||||
}}>
|
||||
<span style={{ fontWeight: "bold" }}>Reasoning</span>
|
||||
<span className={`codicon codicon-chevron-${isCollapsed ? "right" : "down"}`}></span>
|
||||
className="flex items-center justify-between gap-1 px-3 py-2 cursor-pointer text-muted-foreground"
|
||||
onClick={onToggleCollapse}>
|
||||
<div
|
||||
className={`truncate flex-1 transition-opacity duration-200 ${isTransitioning ? "opacity-0" : "opacity-100"}`}>
|
||||
{prevThought}
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
{elapsedRef.current > 1000 && (
|
||||
<>
|
||||
<CounterClockwiseClockIcon className="scale-80" />
|
||||
<div>{Math.round(elapsedRef.current / 1000)}s</div>
|
||||
</>
|
||||
)}
|
||||
{isCollapsed ? <CaretDownIcon /> : <CaretUpIcon />}
|
||||
</div>
|
||||
</div>
|
||||
{!isCollapsed && (
|
||||
<div
|
||||
ref={contentRef}
|
||||
style={{
|
||||
padding: "8px 12px",
|
||||
maxHeight: autoHeight ? "none" : "160px",
|
||||
overflowY: "auto",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
opacity: 0.9,
|
||||
}}>
|
||||
<MarkdownBlock markdown={content} />
|
||||
</div>
|
||||
<div ref={contentRef} className="px-3 max-h-[160px] overflow-y-auto">
|
||||
<MarkdownBlock markdown={content} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReasoningBlock
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@
|
|||
--color-vscode-editor-foreground: var(--vscode-editor-foreground);
|
||||
--color-vscode-editor-background: var(--vscode-editor-background);
|
||||
|
||||
--color-vscode-editorGroup-border: var(--vscode-editorGroup-border);
|
||||
|
||||
--color-vscode-button-foreground: var(--vscode-button-foreground);
|
||||
--color-vscode-button-background: var(--vscode-button-background);
|
||||
--color-vscode-button-secondaryForeground: var(--vscode-button-secondaryForeground);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue