+
+
+ {icon}
+ {title}
-
setIsCompletionOutputExpanded(!isCompletionOutputExpanded)}
+ quoteButtonState={quoteButtonState}
text={text || ""}
/>
diff --git a/webview-ui/src/components/chat/CompletionOutputRow.tsx b/webview-ui/src/components/chat/CompletionOutputRow.tsx
index e681832133..d5d3d8414d 100644
--- a/webview-ui/src/components/chat/CompletionOutputRow.tsx
+++ b/webview-ui/src/components/chat/CompletionOutputRow.tsx
@@ -2,30 +2,47 @@ import { memo } from "react"
import { cn } from "@/lib/utils"
import { MarkdownRow } from "./MarkdownRow"
import "./CompletionOutputRow.css"
+import { QuoteButtonState } from "./ChatRow"
import ExpandHandle from "./ExpandHandle"
+import QuoteButton from "./QuoteButton"
export const CompletionOutputRow = memo(
- ({ text, isOutputFullyExpanded, onToggle }: { text: string; isOutputFullyExpanded: boolean; onToggle: () => void }) => {
+ ({
+ text,
+ isOutputFullyExpanded,
+ onToggle,
+ quoteButtonState,
+ handleQuoteClick,
+ }: {
+ text: string
+ isOutputFullyExpanded: boolean
+ onToggle: () => void
+ quoteButtonState: QuoteButtonState
+ handleQuoteClick: () => void
+ }) => {
const outputLines = text.split("\n")
const lineCount = outputLines.length
const shouldAutoShow = lineCount <= 5
return (
5,
+ className={cn("w-full relative pb-0 overflow-visible border-t-1 border-description/20 rounded-b-sm", {
+ "pb-2": !shouldAutoShow,
})}>
+ className={cn("completion-output-content", "scroll-smooth p-2 overflow-y-auto w-full", {
+ "max-h-[400px]": !shouldAutoShow && isOutputFullyExpanded,
+ "max-h-[150px]": !shouldAutoShow && !isOutputFullyExpanded,
+ "overflow-y-visible": shouldAutoShow,
+ })}>
+ {quoteButtonState.visible && (
+
+ )}
{/* Show notch only if there's more than 5 lines */}
- {lineCount > 5 ? (
-
+ {!shouldAutoShow ? (
+
) : null}
)
diff --git a/webview-ui/src/components/chat/MarkdownRow.tsx b/webview-ui/src/components/chat/MarkdownRow.tsx
index a2ef4c5e4e..48ce9c39fe 100644
--- a/webview-ui/src/components/chat/MarkdownRow.tsx
+++ b/webview-ui/src/components/chat/MarkdownRow.tsx
@@ -3,7 +3,7 @@ import MarkdownBlock from "../common/MarkdownBlock"
export const MarkdownRow = memo(({ markdown, showCursor }: { markdown?: string; showCursor?: boolean }) => {
return (
-
+
)
diff --git a/webview-ui/src/components/chat/ThinkingRow.tsx b/webview-ui/src/components/chat/ThinkingRow.tsx
index bd94ae2669..f0c3fd186f 100644
--- a/webview-ui/src/components/chat/ThinkingRow.tsx
+++ b/webview-ui/src/components/chat/ThinkingRow.tsx
@@ -58,7 +58,7 @@ export const ThinkingRow = memo(
)}
{reasoningContent}
@@ -69,3 +69,5 @@ export const ThinkingRow = memo(
)
},
)
+
+ThinkingRow.displayName = "ThinkingRow"
diff --git a/webview-ui/src/components/chat/TypewriterText.tsx b/webview-ui/src/components/chat/TypewriterText.tsx
index 85cd8c385c..3171dae8ad 100644
--- a/webview-ui/src/components/chat/TypewriterText.tsx
+++ b/webview-ui/src/components/chat/TypewriterText.tsx
@@ -25,7 +25,7 @@ export const TypewriterText = memo(({ text, speed = 30 }: { text: string; speed?
// After typing completes, show shimmer effect instead of blinking cursor
if (isComplete) {
return (
-
+
{text}
)
diff --git a/webview-ui/src/components/chat/chat-view/components/messages/MessageRenderer.tsx b/webview-ui/src/components/chat/chat-view/components/messages/MessageRenderer.tsx
index e0dbc50423..9e53af328c 100644
--- a/webview-ui/src/components/chat/chat-view/components/messages/MessageRenderer.tsx
+++ b/webview-ui/src/components/chat/chat-view/components/messages/MessageRenderer.tsx
@@ -38,6 +38,7 @@ export const MessageRenderer: React.FC = ({
messageHandlers,
}) => {
const { mode } = useExtensionState()
+ const isLastMessage = index === groupedMessages?.length - 1
// Get reasoning content and response status for api_req_started messages
const reasoningData = useMemo(() => {
@@ -78,7 +79,7 @@ export const MessageRenderer: React.FC = ({
return (
= ({
)
}
+ // Determine if this is the last message for status display purposes
+ const nextMessage = index < groupedMessages.length - 1 && groupedMessages[index + 1]
+ const isNextCheckpoint = !Array.isArray(nextMessage) && nextMessage && nextMessage?.say === "checkpoint_created"
+ const isLastMessageGroup = isNextCheckpoint && index === groupedMessages.length - 2
+ const isLastMessageOrGroup = isLastMessage || isLastMessageGroup
+
// Deterministic flash fix:
// If this api_req_started is meant to be absorbed into a low-stakes tool group,
// never render it as a standalone row.
// BUT: Only absorb if this isn't the last/only message (to avoid hiding completed task api_reqs)
if (
messageOrGroup.say === "api_req_started" &&
- index < groupedMessages.length - 1 &&
+ !isLastMessageOrGroup &&
isApiReqAbsorbable(messageOrGroup.ts, modifiedMessages)
) {
return null
}
- // Determine if this is the last message for status display purposes
- const nextMessage = index < groupedMessages.length - 1 && groupedMessages[index + 1]
- const isNextCheckpoint = !Array.isArray(nextMessage) && nextMessage && nextMessage?.say === "checkpoint_created"
- const isLastMessageGroup = isNextCheckpoint && index === groupedMessages.length - 2
- const isLast = index === groupedMessages.length - 1 || isLastMessageGroup
// Regular message
return (
- Reason: {reasoning}
+ {reasoning && Reason: {reasoning}}
{/* Expanded content for folders/search/definitions - raw text */}
@@ -252,18 +251,20 @@ function getToolGroupSummary(messages: ClineMessage[]): string {
}
const parts: string[] = []
+ const action = counts.read > 0 || counts.list > 0 ? " read " : " "
+
if (counts.read > 0) {
parts.push(`${counts.read} file${counts.read > 1 ? "s" : ""}`)
}
if (counts.list > 0) {
parts.push(`${counts.list} folder${counts.list > 1 ? "s" : ""}`)
}
- if (counts.search > 0) {
- parts.push(`${counts.search} search${counts.search > 1 ? "es" : ""}`)
- }
if (counts.def > 0) {
parts.push(`${counts.def} definition${counts.def > 1 ? "s" : ""}`)
}
+ if (counts.search > 0) {
+ parts.push(`performed ${counts.search} search${counts.search > 1 ? "es" : ""}`)
+ }
- return parts.length === 0 ? "Context" : parts.join(", ") + " in context"
+ return parts.length === 0 ? "Context" : "Cline" + action + parts.join(", ")
}