mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat(chat): add Reasoning heading and persistent timer; persist timing in message.metadata; render timer in UI
This commit is contained in:
parent
ab958bf80b
commit
c557a2faa3
4 changed files with 119 additions and 4 deletions
|
|
@ -2865,6 +2865,35 @@ export const webviewMessageHandler = async (
|
|||
}
|
||||
break
|
||||
}
|
||||
case "updateMessageReasoningMeta": {
|
||||
// Persist reasoning timer metadata on a specific message (by ts)
|
||||
try {
|
||||
const currentCline = provider.getCurrentTask()
|
||||
if (!currentCline || !message.messageTs) {
|
||||
break
|
||||
}
|
||||
const { messageIndex } = findMessageIndices(message.messageTs, currentCline)
|
||||
if (messageIndex === -1) {
|
||||
break
|
||||
}
|
||||
const msg = currentCline.clineMessages[messageIndex] as any
|
||||
const existingMeta = (msg.metadata as any) || {}
|
||||
const existingReasoning = existingMeta.reasoning || {}
|
||||
msg.metadata = {
|
||||
...existingMeta,
|
||||
reasoning: { ...existingReasoning, ...(message.reasoningMeta || {}) },
|
||||
}
|
||||
|
||||
await saveTaskMessages({
|
||||
messages: currentCline.clineMessages,
|
||||
taskId: currentCline.taskId,
|
||||
globalStoragePath: provider.contextProxy.globalStorageUri.fsPath,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[updateMessageReasoningMeta] Failed to persist reasoning metadata:", error)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "showMdmAuthRequiredNotification": {
|
||||
// Show notification that organization requires authentication
|
||||
vscode.window.showWarningMessage(t("common:mdm.info.organization_requires_auth"))
|
||||
|
|
|
|||
|
|
@ -221,6 +221,7 @@ export interface WebviewMessage {
|
|||
| "queueMessage"
|
||||
| "removeQueuedMessage"
|
||||
| "editQueuedMessage"
|
||||
| "updateMessageReasoningMeta"
|
||||
text?: string
|
||||
editedMessageContent?: string
|
||||
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
|
||||
|
|
@ -256,6 +257,10 @@ export interface WebviewMessage {
|
|||
terminalOperation?: "continue" | "abort"
|
||||
messageTs?: number
|
||||
restoreCheckpoint?: boolean
|
||||
reasoningMeta?: {
|
||||
startedAt?: number
|
||||
elapsedMs?: number
|
||||
}
|
||||
historyPreviewCollapsed?: boolean
|
||||
filters?: { type?: string; search?: string; tags?: string[] }
|
||||
settings?: any
|
||||
|
|
|
|||
|
|
@ -1083,7 +1083,15 @@ export const ChatRowContent = ({
|
|||
</div>
|
||||
)
|
||||
case "reasoning":
|
||||
return <ReasoningBlock content={message.text || ""} />
|
||||
return (
|
||||
<ReasoningBlock
|
||||
content={message.text || ""}
|
||||
ts={message.ts}
|
||||
isStreaming={isStreaming}
|
||||
isLast={isLast}
|
||||
metadata={message.metadata as any}
|
||||
/>
|
||||
)
|
||||
case "api_req_started":
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,89 @@
|
|||
import React, { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
|
||||
interface ReasoningBlockProps {
|
||||
content: string
|
||||
ts: number
|
||||
isStreaming: boolean
|
||||
isLast: boolean
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
const totalSeconds = Math.max(0, Math.floor(ms / 1000))
|
||||
const minutes = Math.floor(totalSeconds / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
return `${minutes}:${seconds.toString().padStart(2, "0")}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Render reasoning as simple italic text, matching how <thinking> content is shown.
|
||||
* No borders, boxes, headers, timers, or collapsible behavior.
|
||||
* Render reasoning with a heading and a persistent timer.
|
||||
* - Heading uses i18n key chat:reasoning.thinking
|
||||
* - Timer persists via message.metadata.reasoning { startedAt, elapsedMs }
|
||||
*/
|
||||
export const ReasoningBlock = ({ content }: ReasoningBlockProps) => {
|
||||
export const ReasoningBlock = ({ content, ts, isStreaming, isLast, metadata }: ReasoningBlockProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const persisted = (metadata?.reasoning as { startedAt?: number; elapsedMs?: number } | undefined) || {}
|
||||
const startedAtRef = useRef<number>(persisted.startedAt ?? Date.now())
|
||||
const [elapsed, setElapsed] = useState<number>(persisted.elapsedMs ?? 0)
|
||||
|
||||
// Initialize startedAt on first mount if missing (persist to task)
|
||||
useEffect(() => {
|
||||
if (!persisted.startedAt && isLast) {
|
||||
vscode.postMessage({
|
||||
type: "updateMessageReasoningMeta",
|
||||
messageTs: ts,
|
||||
reasoningMeta: { startedAt: startedAtRef.current },
|
||||
} as any)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ts])
|
||||
|
||||
// Tick while active (last row and streaming)
|
||||
useEffect(() => {
|
||||
const active = isLast && isStreaming
|
||||
if (!active) return
|
||||
|
||||
const tick = () => setElapsed(Date.now() - startedAtRef.current)
|
||||
tick()
|
||||
const id = setInterval(tick, 1000)
|
||||
return () => clearInterval(id)
|
||||
}, [isLast, isStreaming])
|
||||
|
||||
// Persist final elapsed when streaming stops
|
||||
const wasActiveRef = useRef<boolean>(false)
|
||||
useEffect(() => {
|
||||
const active = isLast && isStreaming
|
||||
if (wasActiveRef.current && !active) {
|
||||
const finalMs = Date.now() - startedAtRef.current
|
||||
setElapsed(finalMs)
|
||||
vscode.postMessage({
|
||||
type: "updateMessageReasoningMeta",
|
||||
messageTs: ts,
|
||||
reasoningMeta: { startedAt: startedAtRef.current, elapsedMs: finalMs },
|
||||
} as any)
|
||||
}
|
||||
wasActiveRef.current = active
|
||||
}, [isLast, isStreaming, ts])
|
||||
|
||||
const displayMs = useMemo(() => {
|
||||
if (isLast && isStreaming) return elapsed
|
||||
return persisted.elapsedMs ?? elapsed
|
||||
}, [elapsed, isLast, isStreaming, persisted.elapsedMs])
|
||||
|
||||
return (
|
||||
<div className="px-3 py-1">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="codicon codicon-light-bulb text-muted-foreground" />
|
||||
<span className="font-medium text-vscode-foreground">{t("chat:reasoning.thinking")}</span>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground tabular-nums">{formatDuration(displayMs)}</span>
|
||||
</div>
|
||||
<div className="italic text-muted-foreground">
|
||||
<MarkdownBlock markdown={content} />
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue