diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx
index 4fa921f443..4413eab33c 100644
--- a/webview-ui/src/components/chat/ChatRow.tsx
+++ b/webview-ui/src/components/chat/ChatRow.tsx
@@ -1,18 +1,14 @@
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
-import { appendImages } from "@src/utils/imageUtils"
-import { McpExecution } from "./McpExecution"
import { useSize } from "react-use"
import { useTranslation, Trans } from "react-i18next"
import deepEqual from "fast-deep-equal"
import { VSCodeBadge, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
-import type { ClineMessage } from "@roo-code/types"
-import { Mode } from "@roo/modes"
+import type { ClineMessage, FollowUpData, SuggestionItem } from "@roo-code/types"
import { ClineApiReqInfo, ClineAskUseMcpServer, ClineSayTool } from "@roo/ExtensionMessage"
import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
import { safeJsonParse } from "@roo/safeJsonParse"
-import { FollowUpData, SuggestionItem } from "@roo-code/types"
import { useCopyToClipboard } from "@src/utils/clipboard"
import { useExtensionState } from "@src/context/ExtensionStateContext"
@@ -22,9 +18,6 @@ import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanu
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
import { Button } from "@src/components/ui"
-import ChatTextArea from "./ChatTextArea"
-import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
-
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
import CodeAccordian from "../common/CodeAccordian"
@@ -32,6 +25,7 @@ import CodeBlock from "../common/CodeBlock"
import MarkdownBlock from "../common/MarkdownBlock"
import { ReasoningBlock } from "./ReasoningBlock"
import Thumbnails from "../common/Thumbnails"
+
import McpResourceRow from "../mcp/McpResourceRow"
import { Mention } from "./Mention"
@@ -46,6 +40,7 @@ import { CommandExecutionError } from "./CommandExecutionError"
import { AutoApprovedRequestLimitWarning } from "./AutoApprovedRequestLimitWarning"
import { CondenseContextErrorRow, CondensingContextRow, ContextCondenseRow } from "./ContextCondenseRow"
import CodebaseSearchResultsDisplay from "./CodebaseSearchResultsDisplay"
+import { McpExecution } from "./McpExecution"
interface ChatRowProps {
message: ClineMessage
@@ -114,69 +109,20 @@ export const ChatRowContent = ({
editable,
}: ChatRowContentProps) => {
const { t } = useTranslation()
- const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode } = useExtensionState()
+
+ const { mcpServers, alwaysAllowMcp, currentCheckpoint } = useExtensionState()
+
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false)
const [showCopySuccess, setShowCopySuccess] = useState(false)
- const [isEditing, setIsEditing] = useState(false)
- const [editedContent, setEditedContent] = useState("")
- const [editMode, setEditMode] = useState
(mode || "code")
- const [editImages, setEditImages] = useState([])
+
const { copyWithFeedback } = useCopyToClipboard()
- // Handle message events for image selection during edit mode
- useEffect(() => {
- const handleMessage = (event: MessageEvent) => {
- const msg = event.data
- if (msg.type === "selectedImages" && msg.context === "edit" && msg.messageTs === message.ts && isEditing) {
- setEditImages((prevImages) => appendImages(prevImages, msg.images, MAX_IMAGES_PER_MESSAGE))
- }
- }
-
- window.addEventListener("message", handleMessage)
- return () => window.removeEventListener("message", handleMessage)
- }, [isEditing, message.ts])
-
- // Memoized callback to prevent re-renders caused by inline arrow functions
+ // Memoized callback to prevent re-renders caused by inline arrow functions.
const handleToggleExpand = useCallback(() => {
onToggleExpand(message.ts)
}, [onToggleExpand, message.ts])
- // Handle edit button click
- const handleEditClick = useCallback(() => {
- setIsEditing(true)
- setEditedContent(message.text || "")
- setEditImages(message.images || [])
- setEditMode(mode || "code")
- // Edit mode is now handled entirely in the frontend
- // No need to notify the backend
- }, [message.text, message.images, mode])
-
- // Handle cancel edit
- const handleCancelEdit = useCallback(() => {
- setIsEditing(false)
- setEditedContent(message.text || "")
- setEditImages(message.images || [])
- setEditMode(mode || "code")
- }, [message.text, message.images, mode])
-
- // Handle save edit
- const handleSaveEdit = useCallback(() => {
- setIsEditing(false)
- // Send edited message to backend
- vscode.postMessage({
- type: "submitEditedMessage",
- value: message.ts,
- editedMessageContent: editedContent,
- images: editImages,
- })
- }, [message.ts, editedContent, editImages])
-
- // Handle image selection for editing
- const handleSelectImages = useCallback(() => {
- vscode.postMessage({ type: "selectImages", context: "edit", messageTs: message.ts })
- }, [message.ts])
-
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
const info = safeJsonParse(message.text)
@@ -1061,58 +1007,26 @@ export const ChatRowContent = ({
case "user_feedback":
return (
- {isEditing ? (
-
-
+
+
+
- ) : (
-
-
-
-
-
-
-
-
+
+
- )}
- {!isEditing && message.images && message.images.length > 0 && (
+
+
+ {message.images && message.images.length > 0 && (
)}
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx
index 5135eca2f2..ee2cc76ef3 100644
--- a/webview-ui/src/components/chat/ChatTextArea.tsx
+++ b/webview-ui/src/components/chat/ChatTextArea.tsx
@@ -1,15 +1,16 @@
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
import { useEvent } from "react-use"
import DynamicTextArea from "react-textarea-autosize"
+import { VolumeX, Image, WandSparkles, SendHorizontal } from "lucide-react"
import { mentionRegex, mentionRegexGlobal, commandRegexGlobal, unescapeSpaces } from "@roo/context-mentions"
import { WebviewMessage } from "@roo/WebviewMessage"
import { Mode, getAllModes } from "@roo/modes"
import { ExtensionMessage } from "@roo/ExtensionMessage"
-import { vscode } from "@/utils/vscode"
-import { useExtensionState } from "@/context/ExtensionStateContext"
-import { useAppTranslation } from "@/i18n/TranslationContext"
+import { vscode } from "@src/utils/vscode"
+import { useExtensionState } from "@src/context/ExtensionStateContext"
+import { useAppTranslation } from "@src/i18n/TranslationContext"
import {
ContextMenuOptionType,
getContextMenuOptions,
@@ -18,20 +19,18 @@ import {
shouldShowContextMenu,
SearchResult,
} from "@src/utils/context-mentions"
-import { convertToMentionPath } from "@/utils/path-mentions"
-import { StandardTooltip } from "@/components/ui"
+import { cn } from "@src/lib/utils"
+import { convertToMentionPath } from "@src/utils/path-mentions"
+import { StandardTooltip } from "@src/components/ui"
import Thumbnails from "../common/Thumbnails"
-import ModeSelector from "./ModeSelector"
+import { ModeSelector } from "./ModeSelector"
import { ApiConfigSelector } from "./ApiConfigSelector"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu"
-import { VolumeX, Image, WandSparkles, SendHorizontal } from "lucide-react"
import { IndexingStatusBadge } from "./IndexingStatusBadge"
import { SlashCommandsPopover } from "./SlashCommandsPopover"
-import { cn } from "@/lib/utils"
import { usePromptHistory } from "./hooks/usePromptHistory"
-import { EditModeControls } from "./EditModeControls"
interface ChatTextAreaProps {
inputValue: string
@@ -48,17 +47,13 @@ interface ChatTextAreaProps {
mode: Mode
setMode: (value: Mode) => void
modeShortcutText: string
- // Edit mode props
- isEditMode?: boolean
- onCancel?: () => void
}
-const ChatTextArea = forwardRef
(
+export const ChatTextArea = forwardRef(
(
{
inputValue,
setInputValue,
- sendingDisabled,
selectApiConfigDisabled,
placeholderText,
selectedImages,
@@ -70,8 +65,6 @@ const ChatTextArea = forwardRef(
mode,
setMode,
modeShortcutText,
- isEditMode = false,
- onCancel,
},
ref,
) => {
@@ -91,12 +84,12 @@ const ChatTextArea = forwardRef(
commands,
} = useExtensionState()
- // Find the ID and display text for the currently selected API configuration
+ // Find the ID and display text for the currently selected API configuration.
const { currentConfigId, displayName } = useMemo(() => {
const currentConfig = listApiConfigMeta?.find((config) => config.name === currentApiConfigName)
return {
currentConfigId: currentConfig?.id || "",
- displayName: currentApiConfigName || "", // Use the name directly for display
+ displayName: currentApiConfigName || "", // Use the name directly for display.
}
}, [listApiConfigMeta, currentApiConfigName])
@@ -888,7 +881,6 @@ const ChatTextArea = forwardRef(
const placeholderBottomText = `\n(${t("chat:addContext")}${shouldDisableImages ? `, ${t("chat:dragFiles")}` : `, ${t("chat:dragFilesImages")}`})`
- // Common mode selector handler
const handleModeChange = useCallback(
(value: Mode) => {
setMode(value)
@@ -897,261 +889,10 @@ const ChatTextArea = forwardRef(
[setMode],
)
- // Helper function to render mode selector
- const renderModeSelector = () => (
-
- )
-
- // Helper function to handle API config change
const handleApiConfigChange = useCallback((value: string) => {
vscode.postMessage({ type: "loadApiConfigurationById", text: value })
}, [])
- // Helper function to render non-edit mode controls
- const renderNonEditModeControls = () => (
-
-
-
{renderModeSelector()}
-
-
-
-
-
- {isTtsPlaying && (
-
-
-
- )}
-
-
-
-
-
-
-
- )
-
- // Helper function to render the text area section
- const renderTextAreaSection = () => (
-
-
-
{
- if (typeof ref === "function") {
- ref(el)
- } else if (ref) {
- ref.current = el
- }
- textAreaRef.current = el
- }}
- value={inputValue}
- onChange={(e) => {
- handleInputChange(e)
- updateHighlights()
- }}
- onFocus={() => setIsFocused(true)}
- onKeyDown={handleKeyDown}
- onKeyUp={handleKeyUp}
- onBlur={handleBlur}
- onPaste={handlePaste}
- onSelect={updateCursorPosition}
- onMouseUp={updateCursorPosition}
- onHeightChange={(height) => {
- if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) {
- setTextAreaBaseHeight(height)
- }
-
- onHeightChange?.(height)
- }}
- placeholder={placeholderText}
- minRows={3}
- maxRows={15}
- autoFocus={true}
- className={cn(
- "w-full",
- "text-vscode-input-foreground",
- "font-vscode-font-family",
- "text-vscode-editor-font-size",
- "leading-vscode-editor-line-height",
- "cursor-text",
- isEditMode ? "pt-1.5 pb-10 px-2" : "py-1.5 px-2",
- isFocused
- ? "border border-vscode-focusBorder outline outline-vscode-focusBorder"
- : isDraggingOver
- ? "border-2 border-dashed border-vscode-focusBorder"
- : "border border-transparent",
- isDraggingOver
- ? "bg-[color-mix(in_srgb,var(--vscode-input-background)_95%,var(--vscode-focusBorder))]"
- : "bg-vscode-input-background",
- "transition-background-color duration-150 ease-in-out",
- "will-change-background-color",
- "min-h-[90px]",
- "box-border",
- "rounded",
- "resize-none",
- "overflow-x-hidden",
- "overflow-y-auto",
- "pr-9",
- "flex-none flex-grow",
- "z-[2]",
- "scrollbar-none",
- "scrollbar-hide",
- )}
- onScroll={() => updateHighlights()}
- />
-
-
-
-
-
-
-
- {!isEditMode && (
-
-
-
-
-
- )}
-
- {!inputValue && !isEditMode && (
-
- {placeholderBottomText}
-
- )}
-
- )
-
return (
(
"flex-col",
"gap-1",
"bg-editor-background",
- isEditMode ? "px-0" : "px-1.5",
+ "px-1.5",
"pb-1",
"outline-none",
"border",
"border-none",
- isEditMode ? "w-full" : "w-[calc(100%-16px)]",
+ "w-[calc(100%-16px)]",
"ml-auto",
"mr-auto",
"box-border",
@@ -1228,23 +969,168 @@ const ChatTextArea = forwardRef(
)}
- {renderTextAreaSection()}
-
+
+
+
{
+ if (typeof ref === "function") {
+ ref(el)
+ } else if (ref) {
+ ref.current = el
+ }
+ textAreaRef.current = el
+ }}
+ value={inputValue}
+ onChange={(e) => {
+ handleInputChange(e)
+ updateHighlights()
+ }}
+ onFocus={() => setIsFocused(true)}
+ onKeyDown={handleKeyDown}
+ onKeyUp={handleKeyUp}
+ onBlur={handleBlur}
+ onPaste={handlePaste}
+ onSelect={updateCursorPosition}
+ onMouseUp={updateCursorPosition}
+ onHeightChange={(height) => {
+ if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) {
+ setTextAreaBaseHeight(height)
+ }
- {isEditMode && (
-
- )}
+ onHeightChange?.(height)
+ }}
+ placeholder={placeholderText}
+ minRows={3}
+ maxRows={15}
+ autoFocus={true}
+ className={cn(
+ "w-full",
+ "text-vscode-input-foreground",
+ "font-vscode-font-family",
+ "text-vscode-editor-font-size",
+ "leading-vscode-editor-line-height",
+ "cursor-text",
+ "py-1.5 px-2",
+ isFocused
+ ? "border border-vscode-focusBorder outline outline-vscode-focusBorder"
+ : isDraggingOver
+ ? "border-2 border-dashed border-vscode-focusBorder"
+ : "border border-transparent",
+ isDraggingOver
+ ? "bg-[color-mix(in_srgb,var(--vscode-input-background)_95%,var(--vscode-focusBorder))]"
+ : "bg-vscode-input-background",
+ "transition-background-color duration-150 ease-in-out",
+ "will-change-background-color",
+ "min-h-[90px]",
+ "box-border",
+ "rounded",
+ "resize-none",
+ "overflow-x-hidden",
+ "overflow-y-auto",
+ "pr-9",
+ "flex-none flex-grow",
+ "z-[2]",
+ "scrollbar-none",
+ "scrollbar-hide",
+ )}
+ onScroll={() => updateHighlights()}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!inputValue && (
+
+ {placeholderBottomText}
+
+ )}
+
+
{selectedImages.length > 0 && (
@@ -1259,10 +1145,81 @@ const ChatTextArea = forwardRef
(
/>
)}
- {!isEditMode && renderNonEditModeControls()}
+
+
+
+ {isTtsPlaying && (
+
+
+
+ )}
+
+
+
+
+
+
+