mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
UI: Render reasoning as plain italic (match <thinking>) (#7752)
Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: Hannes Rudolph <hrudolph@gmail.com> Co-authored-by: daniel-lxs <ricciodaniel98@gmail.com>
This commit is contained in:
parent
39030bf273
commit
bbd3d9883b
2 changed files with 42 additions and 81 deletions
|
|
@ -118,7 +118,6 @@ export const ChatRowContent = ({
|
|||
|
||||
const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration } = useExtensionState()
|
||||
const { info: model } = useSelectedModel(apiConfiguration)
|
||||
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
|
||||
const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false)
|
||||
const [showCopySuccess, setShowCopySuccess] = useState(false)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
|
|
@ -1087,9 +1086,10 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<ReasoningBlock
|
||||
content={message.text || ""}
|
||||
elapsed={isLast && isStreaming ? Date.now() - message.ts : undefined}
|
||||
isCollapsed={reasoningCollapsed}
|
||||
onToggleCollapse={() => setReasoningCollapsed(!reasoningCollapsed)}
|
||||
ts={message.ts}
|
||||
isStreaming={isStreaming}
|
||||
isLast={isLast}
|
||||
metadata={message.metadata as any}
|
||||
/>
|
||||
)
|
||||
case "api_req_started":
|
||||
|
|
|
|||
|
|
@ -1,96 +1,57 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { CaretDownIcon, CaretUpIcon, CounterClockwiseClockIcon } from "@radix-ui/react-icons"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
import { useMount } from "react-use"
|
||||
import { Clock, Lightbulb } from "lucide-react"
|
||||
|
||||
interface ReasoningBlockProps {
|
||||
content: string
|
||||
elapsed?: number
|
||||
isCollapsed?: boolean
|
||||
onToggleCollapse?: () => void
|
||||
ts: number
|
||||
isStreaming: boolean
|
||||
isLast: boolean
|
||||
metadata?: any
|
||||
}
|
||||
|
||||
export const ReasoningBlock = ({ content, elapsed, isCollapsed = false, onToggleCollapse }: ReasoningBlockProps) => {
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const elapsedRef = useRef<number>(0)
|
||||
const { t } = useTranslation("chat")
|
||||
const [thought, setThought] = useState<string>()
|
||||
const [prevThought, setPrevThought] = useState<string>(t("chat:reasoning.thinking"))
|
||||
const [isTransitioning, setIsTransitioning] = useState<boolean>(false)
|
||||
const cursorRef = useRef<number>(0)
|
||||
const queueRef = useRef<string[]>([])
|
||||
/**
|
||||
* Render reasoning with a heading and a simple timer.
|
||||
* - Heading uses i18n key chat:reasoning.thinking
|
||||
* - Timer runs while reasoning is active (no persistence)
|
||||
*/
|
||||
export const ReasoningBlock = ({ content, isStreaming, isLast }: ReasoningBlockProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const startTimeRef = useRef<number>(Date.now())
|
||||
const [elapsed, setElapsed] = useState<number>(0)
|
||||
|
||||
// Simple timer that runs while streaming
|
||||
useEffect(() => {
|
||||
if (contentRef.current && !isCollapsed) {
|
||||
contentRef.current.scrollTop = contentRef.current.scrollHeight
|
||||
if (isLast && isStreaming) {
|
||||
const tick = () => setElapsed(Date.now() - startTimeRef.current)
|
||||
tick()
|
||||
const id = setInterval(tick, 1000)
|
||||
return () => clearInterval(id)
|
||||
}
|
||||
}, [content, isCollapsed])
|
||||
}, [isLast, isStreaming])
|
||||
|
||||
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])
|
||||
const seconds = Math.floor(elapsed / 1000)
|
||||
const secondsLabel = t("chat:reasoning.seconds", { count: seconds })
|
||||
|
||||
return (
|
||||
<div className="bg-vscode-editor-background border border-vscode-border rounded-xs overflow-hidden">
|
||||
<div
|
||||
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>{t("reasoning.seconds", { count: Math.round(elapsedRef.current / 1000) })}</div>
|
||||
</>
|
||||
)}
|
||||
{isCollapsed ? <CaretDownIcon /> : <CaretUpIcon />}
|
||||
<div className="py-1">
|
||||
<div className="flex items-center justify-between mb-2.5 pr-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Lightbulb className="w-4" />
|
||||
<span className="font-bold text-vscode-foreground">{t("chat:reasoning.thinking")}</span>
|
||||
</div>
|
||||
{elapsed > 0 && (
|
||||
<span className="text-vscode-foreground tabular-nums flex items-center gap-1">
|
||||
<Clock className="w-4" />
|
||||
{secondsLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{!isCollapsed && (
|
||||
<div ref={contentRef} className="px-3 max-h-[160px] overflow-y-auto">
|
||||
{(content?.trim()?.length ?? 0) > 0 && (
|
||||
<div className="px-3 italic text-vscode-descriptionForeground">
|
||||
<MarkdownBlock markdown={content} />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue