Skip to content

Commit b710aff

Browse files
committed
Consolidates and simplifies Error messages, makes user messages more prominent
1 parent 58ce2aa commit b710aff

File tree

3 files changed

+232
-200
lines changed

3 files changed

+232
-200
lines changed

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 88 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "
22
import { useSize } from "react-use"
33
import { useTranslation, Trans } from "react-i18next"
44
import 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

77
import type { ClineMessage, FollowUpData, SuggestionItem } from "@roo-code/types"
88
import { Mode } from "@roo/modes"
@@ -11,22 +11,20 @@ import { ClineApiReqInfo, ClineAskUseMcpServer, ClineSayTool } from "@roo/Extens
1111
import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
1212
import { safeJsonParse } from "@roo/safeJsonParse"
1313

14-
import { useCopyToClipboard } from "@src/utils/clipboard"
1514
import { useExtensionState } from "@src/context/ExtensionStateContext"
1615
import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
1716
import { vscode } from "@src/utils/vscode"
1817
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
1918
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
20-
import { Button } from "@src/components/ui"
2119

2220
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
2321
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
2422
import CodeAccordian from "../common/CodeAccordian"
25-
import CodeBlock from "../common/CodeBlock"
2623
import MarkdownBlock from "../common/MarkdownBlock"
2724
import { ReasoningBlock } from "./ReasoningBlock"
2825
import Thumbnails from "../common/Thumbnails"
2926
import ImageBlock from "../common/ImageBlock"
27+
import ErrorRow from "./ErrorRow"
3028

3129
import McpResourceRow from "../mcp/McpResourceRow"
3230

@@ -47,7 +45,8 @@ import { McpExecution } from "./McpExecution"
4745
import { ChatTextArea } from "./ChatTextArea"
4846
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
4947
import { 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

5251
interface 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

Comments
 (0)