@@ -2,7 +2,7 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "
22import { useSize } from "react-use"
33import { useTranslation , Trans } from "react-i18next"
44import deepEqual from "fast-deep-equal"
5- import { VSCodeBadge , VSCodeButton } from "@vscode/webview-ui-toolkit/react"
5+ import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
66
77import type { ClineMessage , FollowUpData , SuggestionItem } from "@roo-code/types"
88import { Mode } from "@roo/modes"
@@ -11,22 +11,20 @@ import { ClineApiReqInfo, ClineAskUseMcpServer, ClineSayTool } from "@roo/Extens
1111import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
1212import { safeJsonParse } from "@roo/safeJsonParse"
1313
14- import { useCopyToClipboard } from "@src/utils/clipboard"
1514import { useExtensionState } from "@src/context/ExtensionStateContext"
1615import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
1716import { vscode } from "@src/utils/vscode"
1817import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
1918import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
20- import { Button } from "@src/components/ui"
2119
2220import { ToolUseBlock , ToolUseBlockHeader } from "../common/ToolUseBlock"
2321import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
2422import CodeAccordian from "../common/CodeAccordian"
25- import CodeBlock from "../common/CodeBlock"
2623import MarkdownBlock from "../common/MarkdownBlock"
2724import { ReasoningBlock } from "./ReasoningBlock"
2825import Thumbnails from "../common/Thumbnails"
2926import ImageBlock from "../common/ImageBlock"
27+ import ErrorRow from "./ErrorRow"
3028
3129import McpResourceRow from "../mcp/McpResourceRow"
3230
@@ -47,7 +45,8 @@ import { McpExecution } from "./McpExecution"
4745import { ChatTextArea } from "./ChatTextArea"
4846import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
4947import { useSelectedModel } from "../ui/hooks/useSelectedModel"
50- import { ChevronRight , ChevronDown , Eye , FileDiff , ListTree } from "lucide-react"
48+ import { ChevronRight , ChevronDown , Eye , FileDiff , ListTree , User , Edit , Trash2 } from "lucide-react"
49+ import { cn } from "@/lib/utils"
5150
5251interface ChatRowProps {
5352 message : ClineMessage
@@ -119,13 +118,10 @@ export const ChatRowContent = ({
119118
120119 const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration } = useExtensionState ( )
121120 const { info : model } = useSelectedModel ( apiConfiguration )
122- const [ isDiffErrorExpanded , setIsDiffErrorExpanded ] = useState ( false )
123- const [ showCopySuccess , setShowCopySuccess ] = useState ( false )
124121 const [ isEditing , setIsEditing ] = useState ( false )
125122 const [ editedContent , setEditedContent ] = useState ( "" )
126123 const [ editMode , setEditMode ] = useState < Mode > ( mode || "code" )
127124 const [ editImages , setEditImages ] = useState < string [ ] > ( [ ] )
128- const { copyWithFeedback } = useCopyToClipboard ( )
129125
130126 // Handle message events for image selection during edit mode
131127 useEffect ( ( ) => {
@@ -212,19 +208,8 @@ export const ChatRowContent = ({
212208 const [ icon , title ] = useMemo ( ( ) => {
213209 switch ( type ) {
214210 case "error" :
215- return [
216- < span
217- className = "codicon codicon-error"
218- style = { { color : errorColor , marginBottom : "-1.5px" } } > </ span > ,
219- < span style = { { color : errorColor , fontWeight : "bold" } } > { t ( "chat:error" ) } </ span > ,
220- ]
221211 case "mistake_limit_reached" :
222- return [
223- < span
224- className = "codicon codicon-error"
225- style = { { color : errorColor , marginBottom : "-1.5px" } } > </ span > ,
226- < span style = { { color : errorColor , fontWeight : "bold" } } > { t ( "chat:troubleMessage" ) } </ span > ,
227- ]
212+ return [ null , null ] // These will be handled by ErrorRow component
228213 case "command" :
229214 return [
230215 isCommandExecuting ? (
@@ -347,13 +332,6 @@ export const ChatRowContent = ({
347332 wordBreak : "break-word" ,
348333 }
349334
350- const pStyle : React . CSSProperties = {
351- margin : 0 ,
352- whiteSpace : "pre-wrap" ,
353- wordBreak : "break-word" ,
354- overflowWrap : "anywhere" ,
355- }
356-
357335 const tool = useMemo (
358336 ( ) => ( message . ask === "tool" ? safeJsonParse < ClineSayTool > ( message . text ) : null ) ,
359337 [ message . ask , message . text ] ,
@@ -997,92 +975,12 @@ export const ChatRowContent = ({
997975 switch ( message . say ) {
998976 case "diff_error" :
999977 return (
1000- < div >
1001- < div
1002- style = { {
1003- marginTop : "0px" ,
1004- overflow : "hidden" ,
1005- marginBottom : "8px" ,
1006- } } >
1007- < div
1008- style = { {
1009- borderBottom : isDiffErrorExpanded
1010- ? "1px solid var(--vscode-editorGroup-border)"
1011- : "none" ,
1012- fontWeight : "normal" ,
1013- fontSize : "var(--vscode-font-size)" ,
1014- color : "var(--vscode-editor-foreground)" ,
1015- display : "flex" ,
1016- alignItems : "center" ,
1017- justifyContent : "space-between" ,
1018- cursor : "pointer" ,
1019- } }
1020- onClick = { ( ) => setIsDiffErrorExpanded ( ! isDiffErrorExpanded ) } >
1021- < div
1022- style = { {
1023- display : "flex" ,
1024- alignItems : "center" ,
1025- gap : "10px" ,
1026- flexGrow : 1 ,
1027- } } >
1028- < span
1029- className = "codicon codicon-warning"
1030- style = { {
1031- color : "var(--vscode-editorWarning-foreground)" ,
1032- opacity : 0.8 ,
1033- fontSize : 16 ,
1034- marginBottom : "-1.5px" ,
1035- } } > </ span >
1036- < span style = { { fontWeight : "bold" } } > { t ( "chat:diffError.title" ) } </ span >
1037- </ div >
1038- < div style = { { display : "flex" , alignItems : "center" } } >
1039- < VSCodeButton
1040- appearance = "icon"
1041- style = { {
1042- padding : "3px" ,
1043- height : "24px" ,
1044- marginRight : "4px" ,
1045- color : "var(--vscode-editor-foreground)" ,
1046- display : "flex" ,
1047- alignItems : "center" ,
1048- justifyContent : "center" ,
1049- background : "transparent" ,
1050- } }
1051- onClick = { ( e ) => {
1052- e . stopPropagation ( )
1053-
1054- // Call copyWithFeedback and handle the Promise
1055- copyWithFeedback ( message . text || "" ) . then ( ( success ) => {
1056- if ( success ) {
1057- // Show checkmark
1058- setShowCopySuccess ( true )
1059-
1060- // Reset after a brief delay
1061- setTimeout ( ( ) => {
1062- setShowCopySuccess ( false )
1063- } , 1000 )
1064- }
1065- } )
1066- } } >
1067- < span
1068- className = { `codicon codicon-${ showCopySuccess ? "check" : "copy" } ` } > </ span >
1069- </ VSCodeButton >
1070- < span
1071- className = { `codicon codicon-chevron-${ isDiffErrorExpanded ? "up" : "down" } ` } > </ span >
1072- </ div >
1073- </ div >
1074- { isDiffErrorExpanded && (
1075- < div
1076- style = { {
1077- padding : "8px" ,
1078- backgroundColor : "var(--vscode-editor-background)" ,
1079- borderTop : "none" ,
1080- } } >
1081- < CodeBlock source = { message . text || "" } language = "xml" />
1082- </ div >
1083- ) }
1084- </ div >
1085- </ div >
978+ < ErrorRow
979+ type = "diff_error"
980+ message = { message . text || "" }
981+ expandable = { true }
982+ showCopyButton = { true }
983+ />
1086984 )
1087985 case "subtask_result" :
1088986 return (
@@ -1172,10 +1070,11 @@ export const ChatRowContent = ({
11721070 </ div >
11731071 { ( ( ( cost === null || cost === undefined ) && apiRequestFailedMessage ) ||
11741072 apiReqStreamingFailedMessage ) && (
1175- < >
1176- < p style = { { ...pStyle , color : "var(--vscode-errorForeground)" } } >
1177- { apiRequestFailedMessage || apiReqStreamingFailedMessage }
1178- { apiRequestFailedMessage ?. toLowerCase ( ) . includes ( "powershell" ) && (
1073+ < ErrorRow
1074+ type = "api_failure"
1075+ message = { apiRequestFailedMessage || apiReqStreamingFailedMessage || "" }
1076+ additionalContent = {
1077+ apiRequestFailedMessage ?. toLowerCase ( ) . includes ( "powershell" ) ? (
11791078 < >
11801079 < br />
11811080 < br />
@@ -1187,9 +1086,9 @@ export const ChatRowContent = ({
11871086 </ a >
11881087 .
11891088 </ >
1190- ) }
1191- </ p >
1192- < />
1089+ ) : undefined
1090+ }
1091+ />
11931092 ) }
11941093
11951094 { isExpanded && (
@@ -1221,70 +1120,77 @@ export const ChatRowContent = ({
12211120 )
12221121 case "user_feedback" :
12231122 return (
1224- < div
1225- className = { `bg-vscode-editor-background border rounded-xs overflow-hidden whitespace-pre-wrap ${ isEditing ? "p-0" : "p-1" } ` } >
1226- { isEditing ? (
1227- < div className = "flex flex-col gap-2" >
1228- < ChatTextArea
1229- inputValue = { editedContent }
1230- setInputValue = { setEditedContent }
1231- sendingDisabled = { false }
1232- selectApiConfigDisabled = { true }
1233- placeholderText = { t ( "chat:editMessage.placeholder" ) }
1234- selectedImages = { editImages }
1235- setSelectedImages = { setEditImages }
1236- onSend = { handleSaveEdit }
1237- onSelectImages = { handleSelectImages }
1238- shouldDisableImages = { ! model ?. supportsImages }
1239- mode = { editMode }
1240- setMode = { setEditMode }
1241- modeShortcutText = ""
1242- isEditMode = { true }
1243- onCancel = { handleCancelEdit }
1244- />
1245- </ div >
1246- ) : (
1247- < div className = "flex justify-between" >
1248- < div
1249- className = "flex-grow px-2 py-1 wrap-anywhere cursor-pointer hover:bg-vscode-list-hoverBackground rounded transition-colors"
1250- onClick = { ( e ) => {
1251- e . stopPropagation ( )
1252- if ( ! isStreaming ) {
1253- handleEditClick ( )
1254- }
1255- } }
1256- title = { t ( "chat:queuedMessages.clickToEdit" ) } >
1257- < Mention text = { message . text } withShadow />
1123+ < div className = "group" >
1124+ < div style = { headerStyle } >
1125+ < User className = "w-4" />
1126+ < span style = { { fontWeight : "bold" } } > { t ( "chat:feedback.youSaid" ) } </ span >
1127+ </ div >
1128+ < div
1129+ className = { cn (
1130+ "ml-6 border rounded-sm overflow-hidden whitespace-pre-wrap" ,
1131+ isEditing
1132+ ? "bg-vscode-editor-background text-vscode-editor-foreground"
1133+ : "cursor-text p-1 bg-vscode-editor-foreground text-vscode-editor-background" ,
1134+ ) } >
1135+ { isEditing ? (
1136+ < div className = "flex flex-col gap-2" >
1137+ < ChatTextArea
1138+ inputValue = { editedContent }
1139+ setInputValue = { setEditedContent }
1140+ sendingDisabled = { false }
1141+ selectApiConfigDisabled = { true }
1142+ placeholderText = { t ( "chat:editMessage.placeholder" ) }
1143+ selectedImages = { editImages }
1144+ setSelectedImages = { setEditImages }
1145+ onSend = { handleSaveEdit }
1146+ onSelectImages = { handleSelectImages }
1147+ shouldDisableImages = { ! model ?. supportsImages }
1148+ mode = { editMode }
1149+ setMode = { setEditMode }
1150+ modeShortcutText = ""
1151+ isEditMode = { true }
1152+ onCancel = { handleCancelEdit }
1153+ />
12581154 </ div >
1259- < div className = "flex" >
1260- < Button
1261- variant = "ghost"
1262- size = "icon"
1263- className = "shrink-0"
1264- disabled = { isStreaming }
1265- onClick = { ( e ) => {
1266- e . stopPropagation ( )
1267- handleEditClick ( )
1268- } } >
1269- < span className = "codicon codicon-edit" />
1270- </ Button >
1271- < Button
1272- variant = "ghost"
1273- size = "icon"
1274- className = "shrink-0"
1275- disabled = { isStreaming }
1155+ ) : (
1156+ < div className = "flex justify-between" >
1157+ < div
1158+ className = "flex-grow px-2 py-1 wrap-anywhere rounded-lg transition-colors"
12761159 onClick = { ( e ) => {
12771160 e . stopPropagation ( )
1278- vscode . postMessage ( { type : "deleteMessage" , value : message . ts } )
1279- } } >
1280- < span className = "codicon codicon-trash" />
1281- </ Button >
1161+ if ( ! isStreaming ) {
1162+ handleEditClick ( )
1163+ }
1164+ } }
1165+ title = { t ( "chat:queuedMessages.clickToEdit" ) } >
1166+ < Mention text = { message . text } withShadow />
1167+ </ div >
1168+ < div className = "flex gap-2 pr-1" >
1169+ < div
1170+ className = "cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
1171+ style = { { visibility : isStreaming ? "hidden" : "visible" } }
1172+ onClick = { ( e ) => {
1173+ e . stopPropagation ( )
1174+ handleEditClick ( )
1175+ } } >
1176+ < Edit className = "w-4" />
1177+ </ div >
1178+ < div
1179+ className = "cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
1180+ style = { { visibility : isStreaming ? "hidden" : "visible" } }
1181+ onClick = { ( e ) => {
1182+ e . stopPropagation ( )
1183+ vscode . postMessage ( { type : "deleteMessage" , value : message . ts } )
1184+ } } >
1185+ < Trash2 className = "w-4" />
1186+ </ div >
1187+ </ div >
12821188 </ div >
1283- </ div >
1284- ) }
1285- { ! isEditing && message . images && message . images . length > 0 && (
1286- < Thumbnails images = { message . images } style = { { marginTop : "8px" } } />
1287- ) }
1189+ ) }
1190+ { ! isEditing && message . images && message . images . length > 0 && (
1191+ < Thumbnails images = { message . images } style = { { marginTop : "8px" } } />
1192+ ) }
1193+ </ div >
12881194 </ div >
12891195 )
12901196 case "user_feedback_diff" :
@@ -1301,17 +1207,7 @@ export const ChatRowContent = ({
13011207 </ div >
13021208 )
13031209 case "error" :
1304- return (
1305- < >
1306- { title && (
1307- < div style = { headerStyle } >
1308- { icon }
1309- { title }
1310- </ div >
1311- ) }
1312- < p style = { { ...pStyle , color : "var(--vscode-errorForeground)" } } > { message . text } </ p >
1313- </ >
1314- )
1210+ return < ErrorRow type = "error" message = { message . text || "" } />
13151211 case "completion_result" :
13161212 return (
13171213 < >
@@ -1482,15 +1378,7 @@ export const ChatRowContent = ({
14821378 case "ask" :
14831379 switch ( message . ask ) {
14841380 case "mistake_limit_reached" :
1485- return (
1486- < >
1487- < div style = { headerStyle } >
1488- { icon }
1489- { title }
1490- </ div >
1491- < p style = { { ...pStyle , color : "var(--vscode-errorForeground)" } } > { message . text } </ p >
1492- </ >
1493- )
1381+ return < ErrorRow type = "mistake_limit" message = { message . text || "" } />
14941382 case "command" :
14951383 return (
14961384 < CommandExecution
0 commit comments