diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx
index b3a79cde7c..0d93d9da0c 100644
--- a/webview-ui/src/components/chat/Announcement.tsx
+++ b/webview-ui/src/components/chat/Announcement.tsx
@@ -1,113 +1,94 @@
-import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
-import { memo } from "react"
-import { useAppTranslation } from "@/i18n/TranslationContext"
+import { useState, memo } from "react"
import { Trans } from "react-i18next"
+import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
+
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@src/components/ui"
interface AnnouncementProps {
hideAnnouncement: () => void
}
-/*
-You must update the latestAnnouncementId in ClineProvider for new announcements to show to users. This new id will be compared with whats in state for the 'last announcement shown', and if it's different then the announcement will render. As soon as an announcement is shown, the id will be updated in state. This ensures that announcements are not shown more than once, even if the user doesn't close it themselves.
-*/
+
+/**
+ * You must update the `latestAnnouncementId` in ClineProvider for new
+ * announcements to show to users. This new id will be compared with what's in
+ * state for the 'last announcement shown', and if it's different then the
+ * announcement will render. As soon as an announcement is shown, the id will be
+ * updated in state. This ensures that announcements are not shown more than
+ * once, even if the user doesn't close it themselves.
+ */
+
const Announcement = ({ hideAnnouncement }: AnnouncementProps) => {
const { t } = useAppTranslation()
-
- const discordLink = (
- {
- e.preventDefault()
- window.postMessage(
- { type: "action", action: "openExternal", data: { url: "https://discord.gg/roocode" } },
- "*",
- )
- }}>
- Discord
-
- )
-
- const redditLink = (
- {
- e.preventDefault()
- window.postMessage(
- { type: "action", action: "openExternal", data: { url: "https://reddit.com/r/RooCode" } },
- "*",
- )
- }}>
- Reddit
-
- )
+ const [open, setOpen] = useState(true)
return (
-
-
-
-
-
-
{t("chat:announcement.title")}
+
{
+ setOpen(open)
- {t("chat:announcement.description")}
-
- {t("chat:announcement.whatsNew")}
-
-
- •{" "}
- ,
- code: ,
- }}
- />
-
-
- •{" "}
- ,
- code: ,
- }}
- />
-
-
- •{" "}
- ,
- code: ,
- }}
- />
-
-
-
-
+ if (!open) {
+ hideAnnouncement()
+ }
+ }}>
+
+
+ {t("chat:announcement.title")}
+ {t("chat:announcement.description")}
+
+
+
{t("chat:announcement.whatsNew")}
+
+
+ •{" "}
+ , code: }} />
+
+
+ •{" "}
+ , code: }} />
+
+
+ •{" "}
+ , code: }} />
+
+
, redditLink:
}}
/>
-
-
-
+
+
+
)
}
+const DiscordLink = () => (
+ {
+ e.preventDefault()
+ window.postMessage(
+ { type: "action", action: "openExternal", data: { url: "https://discord.gg/roocode" } },
+ "*",
+ )
+ }}>
+ Discord
+
+)
+
+const RedditLink = () => (
+ {
+ e.preventDefault()
+ window.postMessage(
+ { type: "action", action: "openExternal", data: { url: "https://reddit.com/r/RooCode" } },
+ "*",
+ )
+ }}>
+ Reddit
+
+)
+
export default memo(Announcement)
diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx
index aaa30a0153..2eb3a0afbf 100644
--- a/webview-ui/src/components/chat/AutoApproveMenu.tsx
+++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useState } from "react"
import { Trans } from "react-i18next"
import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
+import { cn } from "@/lib/utils"
import { vscode } from "@src/utils/vscode"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { useAppTranslation } from "@src/i18n/TranslationContext"
@@ -118,23 +119,13 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
return (
+ className={cn(
+ "px-[15px] select-none overflow-y-auto",
+ isExpanded && "border-t-[0.5px] border-vscode-titleBar-inactiveForeground-20",
+ )}
+ style={style}>
e.stopPropagation()}>
{
}}
/>
-
-
- {t("chat:autoApprove.title")}
-
-
+
+ {t("chat:autoApprove.title")}
+
{enabledActionsList || t("chat:autoApprove.none")}
{isExpanded && (
-
+
{
: displayState.mousePosition || defaultMousePosition
const [browserSessionRow, { height: rowHeight }] = useSize(
-
-
+
+
{isBrowsing ? (
) : (
-
+
)}
-
+
<>{t("chat:browser.rooWantsToUse")}>
-
+
{/* URL Bar */}
-
+ className={cn(
+ "my-[5px] mx-auto w-[calc(100%-10px)] box-border bg-vscode-input-background border border-vscode-input-border rounded p-[3px_5px] flex items-center justify-center text-xs",
+ // Conditional color class:
+ displayState.url ? "text-vscode-input-foreground" : "text-vscode-descriptionForeground",
+ )}>
+
{displayState.url || "http"}
@@ -287,25 +261,15 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
{/* Screenshot Area */}
+ className={cn(
+ "w-full relative bg-vscode-input-background",
+ `[padding-bottom:${aspectRatio}%]`, // Converted paddingBottom to arbitrary property
+ )}>
{displayState.screenshot ? (
vscode.postMessage({
type: "openImage",
@@ -314,47 +278,34 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
}
/>
) : (
-
-
+
+
)}
{displayState.mousePosition && (
)}
-
+
{
setConsoleLogsExpanded(!consoleLogsExpanded)
}}
- style={{
- display: "flex",
- alignItems: "center",
- gap: "4px",
- width: "100%",
- justifyContent: "flex-start",
- cursor: "pointer",
- padding: `9px 8px ${consoleLogsExpanded ? 0 : 8}px 8px`,
- }}>
+ className={`flex items-center gap-1 w-full justify-start cursor-pointer px-2 ${
+ consoleLogsExpanded ? "pt-[9px] pb-0" : "py-[9px]"
+ }`}>
- {t("chat:browser.consoleLogs")}
+ {t("chat:browser.consoleLogs")}
{consoleLogsExpanded && (
@@ -363,23 +314,15 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
{/* Action content with min height */}
-
{actionContent}
+
0 && `[min-height:${maxActionHeight}px]`)}>{actionContent}
{/* Pagination moved to bottom */}
{pages.length > 1 && (
-
+
{t("chat:browser.navigation.step", { current: currentPageIndex + 1, total: pages.length })}
-
+
setCurrentPageIndex((i) => i - 1)}>
@@ -426,13 +369,7 @@ const BrowserSessionRowContent = ({
isStreaming,
}: BrowserSessionRowContentProps) => {
const { t } = useTranslation()
- const headerStyle: React.CSSProperties = {
- display: "flex",
- alignItems: "center",
- gap: "10px",
- marginBottom: "10px",
- wordBreak: "break-word",
- }
+ const headerClassNames = "flex items-center gap-2.5 mb-2.5 break-words"
switch (message.type) {
case "say":
@@ -440,7 +377,7 @@ const BrowserSessionRowContent = ({
case "api_req_started":
case "text":
return (
-
+
-
-
{t("chat:browser.sessionStarted")}
+
+ {t("chat:browser.sessionStarted")}
-
+
>
@@ -526,26 +457,11 @@ const BrowserActionBox = ({
}
}
return (
-
-
-
-
- {t("chat:browser.actions.title")}
+
+
+
+
+ {t("chat:browser.actions.title")}
{getBrowserActionText(action, coordinate, text)}
@@ -554,7 +470,12 @@ const BrowserActionBox = ({
)
}
-const BrowserCursor: React.FC<{ style?: React.CSSProperties }> = ({ style }) => {
+interface BrowserCursorProps {
+ style?: React.CSSProperties
+ className?: string
+}
+
+const BrowserCursor: React.FC
= ({ style, className }) => {
const { t } = useTranslation()
// (can't use svgs in vsc extensions)
const cursorBase64 =
@@ -563,13 +484,16 @@ const BrowserCursor: React.FC<{ style?: React.CSSProperties }> = ({ style }) =>
return (
)
}
diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx
index efc720ba76..0aad4d321f 100644
--- a/webview-ui/src/components/chat/ChatRow.tsx
+++ b/webview-ui/src/components/chat/ChatRow.tsx
@@ -8,6 +8,7 @@ import { ClineApiReqInfo, ClineAskUseMcpServer, ClineMessage, ClineSayTool } fro
import { COMMAND_OUTPUT_STRING } from "@roo/shared/combineCommandSequences"
import { safeJsonParse } from "@roo/shared/safeJsonParse"
+import { cn } from "@/lib/utils"
import { useCopyToClipboard } from "@src/utils/clipboard"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
@@ -15,7 +16,7 @@ import { vscode } from "@src/utils/vscode"
import { Button } from "@src/components/ui"
import CodeAccordian, { removeLeadingNonAlphanumeric } from "../common/CodeAccordian"
-import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
+import CodeBlock from "../common/CodeBlock"
import MarkdownBlock from "../common/MarkdownBlock"
import { ReasoningBlock } from "./ReasoningBlock"
import Thumbnails from "../common/Thumbnails"
@@ -118,37 +119,26 @@ export const ChatRowContent = ({
const type = message.type === "ask" ? message.ask : message.say
- const normalColor = "var(--vscode-foreground)"
- const errorColor = "var(--vscode-errorForeground)"
- const successColor = "var(--vscode-charts-green)"
- const cancelledColor = "var(--vscode-descriptionForeground)"
-
- const [icon, title] = useMemo(() => {
+ const [icon, title] = useMemo((): [React.ReactNode | null, React.ReactNode | null] => {
switch (type) {
case "error":
return [
- ,
- {t("chat:error")} ,
+ ,
+ {t("chat:error")} ,
]
case "mistake_limit_reached":
return [
- ,
- {t("chat:troubleMessage")} ,
+ , // Self-closing
+ {t("chat:troubleMessage")} ,
]
case "command":
return [
isCommandExecuting ? (
) : (
-
+ // Self-closing
),
- {t("chat:runCommand.title")}: ,
+ {t("chat:runCommand.title")}: ,
]
case "use_mcp_server":
const mcpServerUse = safeJsonParse(message.text)
@@ -159,11 +149,9 @@ export const ChatRowContent = ({
isMcpServerResponding ? (
) : (
-
+ // Self-closing
),
-
+
{mcpServerUse.type === "use_mcp_tool"
? t("chat:mcp.wantsToUseTool", { serverName: mcpServerUse.serverName })
: t("chat:mcp.wantsToAccessResource", { serverName: mcpServerUse.serverName })}
@@ -171,88 +159,59 @@ export const ChatRowContent = ({
]
case "completion_result":
return [
- ,
- {t("chat:taskCompleted")} ,
+ , // Self-closing
+ {t("chat:taskCompleted")} ,
]
case "api_req_retry_delayed":
- return []
+ return [null, null] // Explicitly return [null, null] for this case
case "api_req_started":
- const getIconSpan = (iconName: string, color: string) => (
-
-
+ const getIconSpan = (iconName: string, colorClassName: string) => (
+
+
)
return [
apiReqCancelReason !== null && apiReqCancelReason !== undefined ? (
apiReqCancelReason === "user_cancelled" ? (
- getIconSpan("error", cancelledColor)
+ getIconSpan("error", "text-vscode-descriptionForeground")
) : (
- getIconSpan("error", errorColor)
+ getIconSpan("error", "text-vscode-errorForeground")
)
) : cost !== null && cost !== undefined ? (
- getIconSpan("check", successColor)
+ getIconSpan("check", "text-vscode-charts-green")
) : apiRequestFailedMessage ? (
- getIconSpan("error", errorColor)
+ getIconSpan("error", "text-vscode-errorForeground")
) : (
),
apiReqCancelReason !== null && apiReqCancelReason !== undefined ? (
apiReqCancelReason === "user_cancelled" ? (
-
- {t("chat:apiRequest.cancelled")}
-
+
{t("chat:apiRequest.cancelled")}
) : (
-
+
{t("chat:apiRequest.streamingFailed")}
)
) : cost !== null && cost !== undefined ? (
- {t("chat:apiRequest.title")}
+ {t("chat:apiRequest.title")}
) : apiRequestFailedMessage ? (
- {t("chat:apiRequest.failed")}
+ {t("chat:apiRequest.failed")}
) : (
- {t("chat:apiRequest.streaming")}
+ {t("chat:apiRequest.streaming")}
),
]
case "followup":
return [
- ,
- {t("chat:questions.hasQuestion")} ,
+ ,
+ {t("chat:questions.hasQuestion")} ,
]
default:
- return [null, null]
+ return [null, null] // Explicitly return [null, null] for default case
}
}, [type, isCommandExecuting, message, isMcpServerResponding, apiReqCancelReason, cost, apiRequestFailedMessage, t])
- const headerStyle: React.CSSProperties = {
- display: "flex",
- alignItems: "center",
- gap: "10px",
- marginBottom: "10px",
- wordBreak: "break-word",
- }
-
- const pStyle: React.CSSProperties = {
- margin: 0,
- whiteSpace: "pre-wrap",
- wordBreak: "break-word",
- overflowWrap: "anywhere",
- }
+ const headerClasses = "flex items-center gap-[10px] mb-[10px] break-words"
+ const pClasses = "m-0 whitespace-pre-wrap break-words overflow-anywhere"
const tool = useMemo(
() => (message.ask === "tool" ? safeJsonParse(message.text) : null),
@@ -268,9 +227,7 @@ export const ChatRowContent = ({
if (tool) {
const toolIcon = (name: string) => (
-
+ // Converted to Tailwind & self-closing
)
switch (tool.tool) {
@@ -278,9 +235,9 @@ export const ChatRowContent = ({
case "appliedDiff":
return (
<>
-
+
{toolIcon(tool.tool === "appliedDiff" ? "diff" : "edit")}
-
+
{tool.isOutsideWorkspace
? t("chat:fileOperations.wantsToEditOutsideWorkspace")
: t("chat:fileOperations.wantsToEdit")}
@@ -299,9 +256,9 @@ export const ChatRowContent = ({
case "insertContent":
return (
<>
-
+
{toolIcon("insert")}
-
+
{tool.isOutsideWorkspace
? t("chat:fileOperations.wantsToEditOutsideWorkspace")
: tool.lineNumber === 0
@@ -324,9 +281,9 @@ export const ChatRowContent = ({
case "searchAndReplace":
return (
<>
-
+
{toolIcon("replace")}
-
+
{message.type === "ask"
? t("chat:fileOperations.wantsToSearchReplace")
: t("chat:fileOperations.didSearchReplace")}
@@ -345,9 +302,9 @@ export const ChatRowContent = ({
case "newFileCreated":
return (
<>
-
+
{toolIcon("new-file")}
- {t("chat:fileOperations.wantsToCreate")}
+ {t("chat:fileOperations.wantsToCreate")}
-
+
{toolIcon("file-code")}
-
+
{message.type === "ask"
? tool.isOutsideWorkspace
? t("chat:fileOperations.wantsToReadOutsideWorkspace")
@@ -371,45 +328,19 @@ export const ChatRowContent = ({
: t("chat:fileOperations.didRead")}
-
+
{
vscode.postMessage({ type: "openFile", text: tool.content })
}}>
{tool.path?.startsWith(".") &&
. }
-
+
{removeLeadingNonAlphanumeric(tool.path ?? "") + "\u200E"}
{tool.reason}
-
-
+
+
>
@@ -417,9 +348,9 @@ export const ChatRowContent = ({
case "fetchInstructions":
return (
<>
-
+
{toolIcon("file-code")}
- {t("chat:instructions.wantsToFetch")}
+ {t("chat:instructions.wantsToFetch")}
-
+
{toolIcon("folder-opened")}
-
+
{message.type === "ask"
? t("chat:directoryOperations.wantsToViewTopLevel")
: t("chat:directoryOperations.didViewTopLevel")}
@@ -452,9 +383,9 @@ export const ChatRowContent = ({
case "listFilesRecursive":
return (
<>
-
+
{toolIcon("folder-opened")}
-
+
{message.type === "ask"
? t("chat:directoryOperations.wantsToViewRecursive")
: t("chat:directoryOperations.didViewRecursive")}
@@ -472,9 +403,9 @@ export const ChatRowContent = ({
case "listCodeDefinitionNames":
return (
<>
-
+
{toolIcon("file-code")}
-
+
{message.type === "ask"
? t("chat:directoryOperations.wantsToViewDefinitions")
: t("chat:directoryOperations.didViewDefinitions")}
@@ -491,9 +422,9 @@ export const ChatRowContent = ({
case "searchFiles":
return (
<>
-
+
{toolIcon("search")}
-
+
{message.type === "ask" ? (
-
+
{toolIcon("symbol-enum")}
-
+
{message.type === "ask" ? (
<>
{tool.reason ? (
@@ -564,9 +495,9 @@ export const ChatRowContent = ({
case "newTask":
return (
<>
-
+
{toolIcon("tasklist")}
-
+
{tool.mode} }}
@@ -607,9 +538,9 @@ export const ChatRowContent = ({
case "finishTask":
return (
<>
-
+
{toolIcon("check-all")}
- {t("chat:subtasks.wantsToFinish")}
+ {t("chat:subtasks.wantsToFinish")}
-
+
{icon}
{title}
-
+
{apiRequestFailedMessage || apiReqStreamingFailedMessage}
{apiRequestFailedMessage?.toLowerCase().includes("powershell") && (
<>
@@ -898,22 +823,22 @@ export const ChatRowContent = ({
return (
<>
{title && (
-
+
{icon}
{title}
)}
-
{message.text}
+
{message.text}
>
)
case "completion_result":
return (
<>
-
+
{icon}
{title}
-
+
>
@@ -923,16 +848,8 @@ export const ChatRowContent = ({
case "mcp_server_response":
return (
<>
-
-
- {t("chat:response")}
-
+
+
{t("chat:response")}
{title && (
-
+
{icon}
{title}
)}
-
+
>
@@ -971,17 +888,17 @@ export const ChatRowContent = ({
case "mistake_limit_reached":
return (
<>
-
+
{icon}
{title}
-
{message.text}
+
{message.text}
>
)
case "command":
return (
<>
-
+
{icon}
{title}
@@ -999,17 +916,11 @@ export const ChatRowContent = ({
return (
<>
-
+
{icon}
{title}
-
+
{useMcpServer.type === "access_mcp_resource" && (
{useMcpServer.arguments && useMcpServer.arguments !== "{}" && (
-
-
+
+
{t("chat:arguments")}
-
+
{icon}
{title}
-
@@ -1091,15 +996,15 @@ export const ChatRowContent = ({
return (
<>
{title && (
-
+
{icon}
{title}
)}
-
-
+
+
+ {message.partial === true ? message?.text : followUpData?.question}
+
)}
0 ? "mt-0" : ""} px-3.5 min-[370px]:px-10 pt-5 transition-all duration-300`}>
+ className={cn(
+ "w-full flex flex-col gap-4 m-auto px-3.5 min-[370px]:px-10 pt-5 transition-all duration-300",
+ isExpanded && tasks.length > 0 ? "mt-0" : "",
+ )}>
{telemetrySetting === "unset" &&
}
{/* Show the task history preview if expanded and tasks exist */}
{taskHistory.length > 0 && isExpanded &&
}
-
+
+
the docs
),
@@ -1320,7 +1328,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction
{
scrollToBottomSmooth()
disableAutoScrollRef.current = false
diff --git a/webview-ui/src/components/chat/CommandExecutionError.tsx b/webview-ui/src/components/chat/CommandExecutionError.tsx
index 3c8c6273c1..b049877de0 100644
--- a/webview-ui/src/components/chat/CommandExecutionError.tsx
+++ b/webview-ui/src/components/chat/CommandExecutionError.tsx
@@ -27,10 +27,7 @@ export const CommandExecutionError = () => {
}}
/>
-
+
{t("chat:shellIntegration.troubleshooting")}
diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx
index 27d53803a8..ddb11ccf0d 100644
--- a/webview-ui/src/components/chat/ContextMenu.tsx
+++ b/webview-ui/src/components/chat/ContextMenu.tsx
@@ -68,18 +68,10 @@ const ContextMenu: React.FC = ({
switch (option.type) {
case ContextMenuOptionType.Mode:
return (
-
-
{option.label}
+
+
{option.label}
{option.description && (
-
+
{option.description}
)}
@@ -96,17 +88,9 @@ const ContextMenu: React.FC = ({
case ContextMenuOptionType.Git:
if (option.value) {
return (
-
-
{option.label}
-
+
+ {option.label}
+
{option.description}
@@ -124,29 +108,9 @@ const ContextMenu: React.FC = ({
const filename = pathList.at(-1)
const folderPath = pathList.slice(0, -1).join("/")
return (
-
+
{filename}
-
+
{folderPath}
@@ -197,68 +161,32 @@ const ContextMenu: React.FC
= ({
return (
+ className="bg-vscode-dropdown-background border border-vscode-editorGroup-border rounded-[3px] shadow-[0_4px_10px_rgba(0,0,0,0.25)] z-[1000] flex flex-col max-h-[200px] overflow-y-auto">
{filteredOptions && filteredOptions.length > 0 ? (
filteredOptions.map((option, index) => (
isOptionSelectable(option) && onSelect(option.type, option.value)}
- style={{
- padding: "4px 6px",
- cursor: isOptionSelectable(option) ? "pointer" : "default",
- color: "var(--vscode-dropdown-foreground)",
- display: "flex",
- alignItems: "center",
- justifyContent: "space-between",
- ...(index === selectedIndex && isOptionSelectable(option)
- ? {
- backgroundColor: "var(--vscode-list-activeSelectionBackground)",
- color: "var(--vscode-list-activeSelectionForeground)",
- }
- : {}),
- }}
+ className={`p-[4px_6px] text-vscode-dropdown-foreground flex items-center justify-between ${
+ isOptionSelectable(option) ? "cursor-pointer" : "cursor-default"
+ } ${
+ index === selectedIndex && isOptionSelectable(option)
+ ? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground"
+ : ""
+ }`}
onMouseEnter={() => isOptionSelectable(option) && setSelectedIndex(index)}>
-
+
{(option.type === ContextMenuOptionType.File ||
option.type === ContextMenuOptionType.Folder ||
option.type === ContextMenuOptionType.OpenedFile) && (
)}
{option.type !== ContextMenuOptionType.Mode &&
@@ -267,13 +195,9 @@ const ContextMenu: React.FC
= ({
option.type !== ContextMenuOptionType.OpenedFile &&
getIconForOption(option) && (
)}
{renderOptionContent(option)}
@@ -282,23 +206,12 @@ const ContextMenu: React.FC = ({
option.type === ContextMenuOptionType.Folder ||
option.type === ContextMenuOptionType.Git) &&
!option.value && (
-
+
)}
))
) : (
-
+
No results found
)}
@@ -306,5 +219,4 @@ const ContextMenu: React.FC
= ({
)
}
-
export default ContextMenu
diff --git a/webview-ui/src/components/chat/IconButton.tsx b/webview-ui/src/components/chat/IconButton.tsx
index 80f59ee74b..31f47d844f 100644
--- a/webview-ui/src/components/chat/IconButton.tsx
+++ b/webview-ui/src/components/chat/IconButton.tsx
@@ -38,9 +38,9 @@ export const IconButton: React.FC
= ({
diff --git a/webview-ui/src/components/chat/Markdown.tsx b/webview-ui/src/components/chat/Markdown.tsx
index a209ce8723..a9bb790d88 100644
--- a/webview-ui/src/components/chat/Markdown.tsx
+++ b/webview-ui/src/components/chat/Markdown.tsx
@@ -1,12 +1,14 @@
import { memo, useState } from "react"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
+import { cn } from "@/lib/utils"
import { useCopyToClipboard } from "@src/utils/clipboard"
import MarkdownBlock from "../common/MarkdownBlock"
export const Markdown = memo(({ markdown, partial }: { markdown?: string; partial?: boolean }) => {
const [isHovering, setIsHovering] = useState(false)
+ const [copySuccess, setCopySuccess] = useState(false)
// Shorter feedback duration for copy button flash.
const { copyWithFeedback } = useCopyToClipboard(200)
@@ -16,43 +18,25 @@ export const Markdown = memo(({ markdown, partial }: { markdown?: string; partia
}
return (
- setIsHovering(true)}
- onMouseLeave={() => setIsHovering(false)}
- style={{ position: "relative" }}>
-
+
setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} className="relative">
+
{markdown && !partial && isHovering && (
-
-
+
{
const success = await copyWithFeedback(markdown)
if (success) {
- const button = document.activeElement as HTMLElement
- if (button) {
- button.style.background = "var(--vscode-button-background)"
- setTimeout(() => {
- button.style.background = ""
- }, 200)
- }
+ setCopySuccess(true)
+ setTimeout(() => {
+ setCopySuccess(false)
+ }, 200)
}
}}
title="Copy as markdown">
diff --git a/webview-ui/src/components/chat/ProgressIndicator.tsx b/webview-ui/src/components/chat/ProgressIndicator.tsx
index da35c196e1..44d6c9dca1 100644
--- a/webview-ui/src/components/chat/ProgressIndicator.tsx
+++ b/webview-ui/src/components/chat/ProgressIndicator.tsx
@@ -1,15 +1,8 @@
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
export const ProgressIndicator = () => (
-
-
+
diff --git a/webview-ui/src/components/common/CodeAccordian.tsx b/webview-ui/src/components/common/CodeAccordian.tsx
index e1ea77826e..0fe39dd239 100644
--- a/webview-ui/src/components/common/CodeAccordian.tsx
+++ b/webview-ui/src/components/common/CodeAccordian.tsx
@@ -1,6 +1,7 @@
import { memo, useMemo } from "react"
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
-import CodeBlock, { CODE_BLOCK_BG_COLOR } from "./CodeBlock"
+import { cn } from "@/lib/utils"
+import CodeBlock from "./CodeBlock"
import { ToolProgressStatus } from "@roo/shared/ExtensionMessage"
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
@@ -46,64 +47,31 @@ const CodeAccordian = ({
)
return (
-
+
{(path || isFeedback || isConsoleLogs) && (
{isLoading &&
}
{isFeedback || isConsoleLogs ? (
-
-
-
+
+
+
{isFeedback ? "User Edits" : "Console Logs"}
) : (
<>
{path?.startsWith(".") && . }
-
+
{removeLeadingNonAlphanumeric(path ?? "") + "\u200E"}
>
)}
-
+
{progressStatus && progressStatus.text && (
<>
{progressStatus.icon && }
@@ -116,12 +84,7 @@ const CodeAccordian = ({
)}
{(!(path || isFeedback || isConsoleLogs) || isExpanded) && (
-
+
void
}
-const CodeBlockButton = styled.button`
- background: transparent;
- border: none;
- color: var(--vscode-foreground);
- cursor: var(--copy-button-cursor, default);
- padding: 4px;
- margin: 0 0px;
- display: flex;
- align-items: center;
- justify-content: center;
- opacity: 0.4;
- border-radius: 3px;
- pointer-events: var(--copy-button-events, none);
- margin-left: 4px;
- height: 24px;
- width: 24px;
+interface CodeBlockButtonProps extends React.ButtonHTMLAttributes {
+ children: React.ReactNode
+}
- &:hover {
- background: var(--vscode-toolbar-hoverBackground);
- opacity: 1;
- }
+const CodeBlockButton = ({ children, ...props }: CodeBlockButtonProps) => {
+ return (
+
+ {children}
+
+ )
+}
- /* Style for Lucide icons to ensure consistent sizing and positioning */
- svg {
- display: block;
- }
-`
+interface CodeBlockButtonWrapperProps extends React.HTMLAttributes {
+ children: React.ReactNode
+}
-const CodeBlockButtonWrapper = styled.div`
- position: fixed;
- top: var(--copy-button-top);
- right: var(--copy-button-right, 8px);
- height: auto;
- z-index: 100;
- background: ${CODE_BLOCK_BG_COLOR}${WRAPPER_ALPHA};
- overflow: visible;
- pointer-events: none;
- opacity: var(--copy-button-opacity, 0);
- padding: 4px 6px;
- border-radius: 3px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
+const CodeBlockButtonWrapper = React.forwardRef(
+ ({ children, ...props }, ref) => {
+ return (
+
+ {children}
+
+ )
+ },
+)
+CodeBlockButtonWrapper.displayName = "CodeBlockButtonWrapper"
- &:hover {
- background: var(--vscode-editor-background);
- opacity: 1 !important;
- }
+interface CodeBlockContainerProps extends React.HTMLAttributes {
+ children: React.ReactNode
+ "data-partially-visible"?: boolean
+}
- ${CodeBlockButton} {
- position: relative;
- top: 0;
- right: 0;
- }
-`
+const CodeBlockContainer = React.forwardRef(
+ ({ children, "data-partially-visible": partiallyVisible, ...props }, ref) => {
+ return (
+
+ {children}
+
+ )
+ },
+)
+CodeBlockContainer.displayName = "CodeBlockContainer"
-const CodeBlockContainer = styled.div`
- position: relative;
- overflow: hidden;
- border-bottom: 4px solid var(--vscode-sideBar-background);
- background-color: ${CODE_BLOCK_BG_COLOR};
-
- ${CodeBlockButtonWrapper} {
- opacity: 0;
- pointer-events: none;
- transition: opacity 0.2s; /* Keep opacity transition for buttons */
- }
-
- &[data-partially-visible="true"]:hover ${CodeBlockButtonWrapper} {
- opacity: 1;
- pointer-events: all;
- cursor: pointer;
- }
-`
-
-export const StyledPre = styled.div<{
+interface StyledPreProps extends React.HTMLAttributes {
preStyle?: React.CSSProperties
wordwrap?: "true" | "false" | undefined
windowshade?: "true" | "false"
collapsedHeight?: number
-}>`
- background-color: ${CODE_BLOCK_BG_COLOR};
- max-height: ${({ windowshade, collapsedHeight }) =>
- windowshade === "true" ? `${collapsedHeight || WINDOW_SHADE_SETTINGS.collapsedHeight}px` : "none"};
- overflow-y: auto;
- padding: 10px;
- // transition: max-height ${WINDOW_SHADE_SETTINGS.transitionDelayS} ease-out;
- border-radius: 5px;
- ${({ preStyle }) => preStyle && { ...preStyle }}
+ children: React.ReactNode
+}
- pre {
- background-color: ${CODE_BLOCK_BG_COLOR};
- border-radius: 5px;
- margin: 0;
- padding: 10px;
- width: 100%;
- box-sizing: border-box;
- }
+export const StyledPre = React.forwardRef(
+ ({ preStyle, wordwrap, windowshade, collapsedHeight, children, className, ...props }, ref) => {
+ return (
+
+ {children}
+
+ )
+ },
+)
+StyledPre.displayName = "StyledPre"
- pre,
- code {
- /* Undefined wordwrap defaults to true (pre-wrap) behavior */
- white-space: ${({ wordwrap }) => (wordwrap === "false" ? "pre" : "pre-wrap")};
- word-break: ${({ wordwrap }) => (wordwrap === "false" ? "normal" : "normal")};
- overflow-wrap: ${({ wordwrap }) => (wordwrap === "false" ? "normal" : "break-word")};
- font-size: var(--vscode-editor-font-size, var(--vscode-font-size, 12px));
- font-family: var(--vscode-editor-font-family);
- }
-
- pre > code {
- .hljs-deletion {
- background-color: var(--vscode-diffEditor-removedTextBackground);
- display: inline-block;
- width: 100%;
- }
- .hljs-addition {
- background-color: var(--vscode-diffEditor-insertedTextBackground);
- display: inline-block;
- width: 100%;
- }
- }
-
- .hljs {
- color: var(--vscode-editor-foreground, #fff);
- background-color: ${CODE_BLOCK_BG_COLOR};
- }
-`
-
-const LanguageSelect = styled.select`
- font-size: 12px;
- color: var(--vscode-foreground);
- opacity: 0.4;
- font-family: monospace;
- appearance: none;
- background: transparent;
- border: none;
- cursor: pointer;
- padding: 4px;
- margin: 0;
- vertical-align: middle;
- height: 24px;
-
- & option {
- background: var(--vscode-editor-background);
- color: var(--vscode-foreground);
- padding: 0;
- margin: 0;
- }
-
- &::-webkit-scrollbar {
- width: 6px;
- }
-
- &::-webkit-scrollbar-thumb {
- background: var(--vscode-scrollbarSlider-background);
- }
-
- &::-webkit-scrollbar-track {
- background: var(--vscode-editor-background);
- }
-
- &:hover {
- opacity: 1;
- background: var(--vscode-toolbar-hoverBackground);
- border-radius: 3px;
- }
-
- &:focus {
- opacity: 1;
- outline: none;
- border-radius: 3px;
- }
-`
+interface LanguageSelectProps extends React.SelectHTMLAttributes {
+ children: React.ReactNode
+}
+const LanguageSelect = ({ children, className, ...props }: LanguageSelectProps) => {
+ return (
+
+ {children}
+
+ )
+}
const CodeBlock = memo(
({
@@ -243,7 +180,7 @@ const CodeBlock = memo(
// Syntax highlighting with cached Shiki instance
useEffect(() => {
- const fallback = `${source || ""} `
+ const fallback = `${source || ""} `
const highlight = async () => {
// Show plain text if language needs to be loaded
if (currentLanguage && !isLanguageLoaded(currentLanguage)) {
@@ -257,7 +194,7 @@ const CodeBlock = memo(
transformers: [
{
pre(node) {
- node.properties.style = "padding: 0; margin: 0;"
+ node.properties.class = "p-0 m-0"
return node
},
code(node) {
@@ -620,7 +557,7 @@ const CodeBlock = memo(
updateCodeBlockButtonPosition()}
- style={{ gap: 0 }}>
+ className="gap-0">
{language && (
+ className="font-bold text-left text-[1.2em]">
{normalizeLanguage(language)}
)
@@ -658,13 +595,12 @@ const CodeBlock = memo(
+ className={cn(
+ "text-left",
+ normalizedLang === currentLanguage
+ ? "font-bold text-[1.2em]"
+ : "font-normal",
+ )}>
{normalizedLang}
)
diff --git a/webview-ui/src/components/common/MarkdownBlock.tsx b/webview-ui/src/components/common/MarkdownBlock.tsx
index a9c5ada19a..de8e10110a 100644
--- a/webview-ui/src/components/common/MarkdownBlock.tsx
+++ b/webview-ui/src/components/common/MarkdownBlock.tsx
@@ -1,6 +1,5 @@
-import React, { memo, useEffect } from "react"
+import React, { memo, useEffect, FC, PropsWithChildren } from "react"
import { useRemark } from "react-remark"
-import styled from "styled-components"
import { visit } from "unist-util-visit"
import { vscode } from "@src/utils/vscode"
@@ -55,71 +54,45 @@ const remarkUrlToLink = () => {
}
}
-const StyledMarkdown = styled.div`
- code:not(pre > code) {
- font-family: var(--vscode-editor-font-family, monospace);
- filter: saturation(110%) brightness(95%);
- color: var(--vscode-textPreformat-foreground) !important;
- background-color: var(--vscode-textPreformat-background) !important;
- padding: 0px 2px;
- white-space: pre-line;
- word-break: break-word;
- overflow-wrap: anywhere;
- }
-
- /* Target only Dark High Contrast theme using the data attribute VS Code adds to the body */
- body[data-vscode-theme-kind="vscode-high-contrast"] & code:not(pre > code) {
- color: var(
- --vscode-editorInlayHint-foreground,
- var(--vscode-symbolIcon-stringForeground, var(--vscode-charts-orange, #e9a700))
- );
- }
-
- font-family:
- var(--vscode-font-family),
- system-ui,
- -apple-system,
- BlinkMacSystemFont,
- "Segoe UI",
- Roboto,
- Oxygen,
- Ubuntu,
- Cantarell,
- "Open Sans",
- "Helvetica Neue",
- sans-serif;
-
- font-size: var(--vscode-font-size, 13px);
-
- p,
- li,
- ol,
- ul {
- line-height: 1.25;
- }
-
- ol,
- ul {
- padding-left: 2.5em;
- margin-left: 0;
- }
-
- p {
- white-space: pre-wrap;
- }
-
- a {
- color: var(--vscode-textLink-foreground);
- text-decoration-line: underline;
- text-decoration-style: dotted;
- text-decoration-color: var(--vscode-textLink-foreground);
- &:hover {
- color: var(--vscode-textLink-activeForeground);
- text-decoration-style: solid;
- text-decoration-color: var(--vscode-textLink-activeForeground);
- }
- }
-`
+const StyledMarkdown: FC> = ({ children }) => {
+ // Note: Tailwind doesn't have a direct equivalent for targeting based on body data attributes like `body[data-vscode-theme-kind="vscode-high-contrast"]`.
+ // This specific high-contrast styling might need to be handled differently, possibly via a theme-aware context or by adding a class to this component when high contrast is active.
+ // For now, the general styles are applied.
+ return (
+
+ {children}
+
+ )
+}
const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
const { theme } = useExtensionState()
@@ -228,7 +201,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
}, [markdown, setMarkdown, theme])
return (
-
+
{reactContent}
)
diff --git a/webview-ui/src/components/common/MermaidBlock.tsx b/webview-ui/src/components/common/MermaidBlock.tsx
index d44d51b5f9..0c93920e62 100644
--- a/webview-ui/src/components/common/MermaidBlock.tsx
+++ b/webview-ui/src/components/common/MermaidBlock.tsx
@@ -1,11 +1,11 @@
-import { useEffect, useRef, useState } from "react"
+import React, { useEffect, useRef, useState } from "react"
import mermaid from "mermaid"
import { useDebounceEffect } from "@src/utils/useDebounceEffect"
-import styled from "styled-components"
import { vscode } from "@src/utils/vscode"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { useCopyToClipboard } from "@src/utils/clipboard"
import CodeBlock from "./CodeBlock"
+import { cn } from "@/lib/utils"
const MERMAID_THEME = {
background: "#1e1e1e", // VS Code dark theme background
@@ -152,37 +152,18 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
{isLoading &&
{t("common:mermaid.loading")} }
{error ? (
-
+
setIsErrorExpanded(!isErrorExpanded)}>
-
-
-
{t("common:mermaid.render_error")}
+
+
+ {t("common:mermaid.render_error")}
-
+
{
e.stopPropagation()
@@ -195,21 +176,14 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
{isErrorExpanded && (
-
) : (
-
+
)}
)
@@ -266,44 +240,40 @@ async function svgToPng(svgEl: SVGElement): Promise
{
})
}
-const MermaidBlockContainer = styled.div`
- position: relative;
- margin: 8px 0;
-`
-
-const LoadingMessage = styled.div`
- padding: 8px 0;
- color: var(--vscode-descriptionForeground);
- font-style: italic;
- font-size: 0.9em;
-`
-
-const CopyButton = styled.button`
- padding: 3px;
- height: 24px;
- margin-right: 4px;
- color: var(--vscode-editor-foreground);
- display: flex;
- align-items: center;
- justify-content: center;
- background: transparent;
- border: none;
- cursor: pointer;
-
- &:hover {
- opacity: 0.8;
- }
-`
-
-interface SvgContainerProps {
- $isLoading: boolean
+const MermaidBlockContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ return {children}
}
-const SvgContainer = styled.div`
- opacity: ${(props) => (props.$isLoading ? 0.3 : 1)};
- min-height: 20px;
- transition: opacity 0.2s ease;
- cursor: pointer;
- display: flex;
- justify-content: center;
-`
+const LoadingMessage: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ return {children}
+}
+
+const CopyButton: React.FC> = ({ children, ...props }) => {
+ return (
+
+ {children}
+
+ )
+}
+
+interface SvgContainerProps extends React.HTMLAttributes {
+ isLoading: boolean
+ children?: React.ReactNode
+}
+
+const SvgContainer = React.forwardRef(({ isLoading, children, ...props }, ref) => {
+ return (
+
+ {children}
+
+ )
+})
+SvgContainer.displayName = "SvgContainer"
diff --git a/webview-ui/src/components/common/TelemetryBanner.tsx b/webview-ui/src/components/common/TelemetryBanner.tsx
index dac980489c..156ec3704c 100644
--- a/webview-ui/src/components/common/TelemetryBanner.tsx
+++ b/webview-ui/src/components/common/TelemetryBanner.tsx
@@ -1,29 +1,10 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { memo, useState } from "react"
-import styled from "styled-components"
import { vscode } from "@src/utils/vscode"
import { TelemetrySetting } from "@roo/shared/TelemetrySetting"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { Trans } from "react-i18next"
-
-const BannerContainer = styled.div`
- background-color: var(--vscode-banner-background);
- padding: 12px 20px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- flex-shrink: 0;
- margin-bottom: 6px;
-`
-
-const ButtonContainer = styled.div`
- display: flex;
- gap: 8px;
- width: 100%;
- & > vscode-button {
- flex: 1;
- }
-`
+import { cn } from "@/lib/utils"
const TelemetryBanner = () => {
const { t } = useAppTranslation()
@@ -48,7 +29,7 @@ const TelemetryBanner = () => {
}
return (
-
+
{t("welcome:telemetry.title")}
@@ -64,15 +45,15 @@ const TelemetryBanner = () => {
-
-
+
+
{t("welcome:telemetry.allow")}
-
+
{t("welcome:telemetry.deny")}
-
-
+
+
)
}
diff --git a/webview-ui/src/components/common/Thumbnails.tsx b/webview-ui/src/components/common/Thumbnails.tsx
index acdf5f4295..f3097cc01f 100644
--- a/webview-ui/src/components/common/Thumbnails.tsx
+++ b/webview-ui/src/components/common/Thumbnails.tsx
@@ -37,56 +37,24 @@ const Thumbnails = ({ images, style, setImages, onHeightChange }: ThumbnailsProp
}
return (
-
+
{images.map((image, index) => (
setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}>
handleImageClick(image)}
/>
{isDeletable && hoveredIndex === index && (
handleDelete(index)}
- style={{
- position: "absolute",
- top: -4,
- right: -4,
- width: 13,
- height: 13,
- borderRadius: "50%",
- backgroundColor: "var(--vscode-badge-background)",
- display: "flex",
- justifyContent: "center",
- alignItems: "center",
- cursor: "pointer",
- }}>
-
+ className="absolute -top-1 -right-1 w-[13px] h-[13px] rounded-full bg-vscode-badge-background flex justify-center items-center cursor-pointer">
+
)}
diff --git a/webview-ui/src/components/common/VSCodeButtonLink.tsx b/webview-ui/src/components/common/VSCodeButtonLink.tsx
index d0cd6b1c97..819e8f4065 100644
--- a/webview-ui/src/components/common/VSCodeButtonLink.tsx
+++ b/webview-ui/src/components/common/VSCodeButtonLink.tsx
@@ -8,12 +8,7 @@ interface VSCodeButtonLinkProps {
}
export const VSCodeButtonLink = ({ href, children, ...props }: VSCodeButtonLinkProps) => (
-
+
{children}
)
diff --git a/webview-ui/src/components/history/HistoryPreview.tsx b/webview-ui/src/components/history/HistoryPreview.tsx
index 080cbff3e2..f290b1ae58 100644
--- a/webview-ui/src/components/history/HistoryPreview.tsx
+++ b/webview-ui/src/components/history/HistoryPreview.tsx
@@ -29,13 +29,11 @@ const HistoryPreview = () => {
{item.task}
diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx
index bcb86a8d13..2950aa8a65 100644
--- a/webview-ui/src/components/history/HistoryView.tsx
+++ b/webview-ui/src/components/history/HistoryView.tsx
@@ -99,7 +99,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
{
setSortOption("mostRelevant")
}
}}>
-
+
{searchQuery && (
setSearchQuery("")}
slot="end"
- style={{
- display: "flex",
- justifyContent: "center",
- alignItems: "center",
- height: "100%",
- }}
/>
)}
setSortOption((e.target as HTMLInputElement).value as SortOption)}>
@@ -152,7 +142,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
value="mostRelevant"
disabled={!searchQuery}
data-testid="radio-most-relevant"
- style={{ opacity: searchQuery ? 1 : 0.5 }}>
+ className={searchQuery ? "opacity-100" : "opacity-50"}>
{t("history:mostRelevant")}
@@ -197,10 +187,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
{
-
+
-
-
+ className="flex justify-between items-center">
+
+
{t("history:tokensLabel")}
-
+ className="flex items-center gap-[3px] text-vscode-descriptionForeground">
+
{formatLargeNumber(item.tokensIn || 0)}
-
+ className="flex items-center gap-[3px] text-vscode-descriptionForeground">
+
{formatLargeNumber(item.tokensOut || 0)}
@@ -358,75 +302,32 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
{!!item.cacheWrites && (
-
+ className="flex items-center gap-1 flex-wrap">
+
{t("history:cacheLabel")}
-
+ className="flex items-center gap-[3px] text-vscode-descriptionForeground">
+
+{formatLargeNumber(item.cacheWrites || 0)}
-
+ className="flex items-center gap-[3px] text-vscode-descriptionForeground">
+
{formatLargeNumber(item.cacheReads || 0)}
)}
{!!item.totalCost && (
-
-
-
+
+
+
{t("history:apiCostLabel")}
-
+
${item.totalCost?.toFixed(4)}
diff --git a/webview-ui/src/components/mcp/McpEnabledToggle.tsx b/webview-ui/src/components/mcp/McpEnabledToggle.tsx
index 648cb77142..2aae120377 100644
--- a/webview-ui/src/components/mcp/McpEnabledToggle.tsx
+++ b/webview-ui/src/components/mcp/McpEnabledToggle.tsx
@@ -16,18 +16,11 @@ const McpEnabledToggle = () => {
}
return (
-
+
- {t("mcp:enableToggle.title")}
+ {t("mcp:enableToggle.title")}
-
- {t("mcp:enableToggle.description")}
-
+
{t("mcp:enableToggle.description")}
)
}
diff --git a/webview-ui/src/components/mcp/McpResourceRow.tsx b/webview-ui/src/components/mcp/McpResourceRow.tsx
index b31082dafb..a28898bef3 100644
--- a/webview-ui/src/components/mcp/McpResourceRow.tsx
+++ b/webview-ui/src/components/mcp/McpResourceRow.tsx
@@ -9,26 +9,12 @@ const McpResourceRow = ({ item }: McpResourceRowProps) => {
const uri = hasUri ? item.uri : item.uriTemplate
return (
-
-
-
-
{uri}
+
+
+
+ {uri}
-
+
{item.name && item.description
? `${item.name}: ${item.description}`
: !item.name && item.description
@@ -37,18 +23,9 @@ const McpResourceRow = ({ item }: McpResourceRowProps) => {
? item.name
: "No description"}
-
-
Returns
-
+
+ Returns
+
{item.mimeType || "Unknown"}
diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx
index 2cab9b31ee..adce4e5324 100644
--- a/webview-ui/src/components/mcp/McpToolRow.tsx
+++ b/webview-ui/src/components/mcp/McpToolRow.tsx
@@ -24,18 +24,14 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
}
return (
-
+
e.stopPropagation()}>
-
-
-
{tool.name}
+
+
+ {tool.name}
{serverName && alwaysAllowMcp && (
@@ -43,32 +39,12 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
)}
- {tool.description && (
-
- {tool.description}
-
- )}
+ {tool.description &&
{tool.description}
}
{tool.inputSchema &&
"properties" in tool.inputSchema &&
Object.keys(tool.inputSchema.properties as Record
).length > 0 && (
-
-
- {t("mcp:tool.parameters")}
-
+
+
{t("mcp:tool.parameters")}
{Object.entries(tool.inputSchema.properties as Record
).map(
([paramName, schema]) => {
const isRequired =
@@ -78,29 +54,12 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
tool.inputSchema.required.includes(paramName)
return (
-
-
+
+
{paramName}
- {isRequired && (
- *
- )}
+ {isRequired && * }
-
+
{schema.description || t("mcp:tool.noDescription")}
@@ -112,5 +71,4 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
)
}
-
export default McpToolRow
diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx
index b03be6500b..87648b61f4 100644
--- a/webview-ui/src/components/mcp/McpView.tsx
+++ b/webview-ui/src/components/mcp/McpView.tsx
@@ -23,6 +23,7 @@ import {
DialogDescription,
DialogFooter,
} from "@src/components/ui"
+import { cn } from "@/lib/utils" // Import cn utility
import { Tab, TabContent, TabHeader } from "../common/Tab"
@@ -54,20 +55,12 @@ const McpView = ({ onDone }: McpViewProps) => {
-
+
-
+
Model Context Protocol
-
+
community-made servers
@@ -77,28 +70,23 @@ const McpView = ({ onDone }: McpViewProps) => {
{mcpEnabled && (
<>
-
+
{
setEnableMcpServerCreation(e.target.checked)
vscode.postMessage({ type: "enableMcpServerCreation", bool: e.target.checked })
}}>
- {t("mcp:enableServerCreation.title")}
+ {t("mcp:enableServerCreation.title")}
-
+
{t("mcp:enableServerCreation.description")}
{/* Server List */}
{servers.length > 0 && (
-
+
{servers.map((server) => (
{
)}
{/* Edit Settings Buttons */}
-
+
{
vscode.postMessage({ type: "openMcpSettings" })
}}>
-
+
{t("mcp:editGlobalMCP")}
{
vscode.postMessage({ type: "openProjectMcpSettings" })
}}>
-
+
{t("mcp:editProjectMCP")}
@@ -157,17 +145,6 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
{ value: 3600, label: t("mcp:networkTimeout.options.60minutes") },
]
- const getStatusColor = () => {
- switch (server.status) {
- case "connected":
- return "var(--vscode-testing-iconPassed)"
- case "connecting":
- return "var(--vscode-charts-yellow)"
- case "disconnected":
- return "var(--vscode-testing-iconFailed)"
- }
- }
-
const handleRowClick = () => {
if (server.status === "connected") {
setIsExpanded(!isExpanded)
@@ -203,74 +180,48 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
}
return (
-
+
{server.status === "connected" && (
-
+
)}
-
+
{server.name}
{server.source && (
-
+
{server.source}
)}
- e.stopPropagation()}>
-
setShowDeleteConfirm(true)}
- style={{ marginRight: "8px" }}>
-
+ e.stopPropagation()}>
+
setShowDeleteConfirm(true)} className="mr-[8px]">
+
-
+ className="mr-[8px]">
+
{
vscode.postMessage({
type: "toggleMcpServer",
@@ -291,40 +242,26 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
}
}}>
{server.status === "connected" ? (
isExpanded && (
-
-
+
+
{t("mcp:tabs.tools")} ({server.tools?.length || 0})
@@ -338,8 +275,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
{server.tools && server.tools.length > 0 ? (
-
+
{server.tools.map((tool) => (
) : (
-
+
{t("mcp:emptyState.noTools")}
)}
@@ -360,8 +296,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
{(server.resources && server.resources.length > 0) ||
(server.resourceTemplates && server.resourceTemplates.length > 0) ? (
-
+
{[...(server.resourceTemplates || []), ...(server.resources || [])].map(
(item) => (
) : (
-
+
{t("mcp:emptyState.noResources")}
)}
@@ -380,8 +315,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
{server.errorHistory && server.errorHistory.length > 0 ? (
-
+
{[...server.errorHistory]
.sort((a, b) => b.timestamp - a.timestamp)
.map((error, index) => (
@@ -389,7 +323,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
))}
) : (
-
+
{t("mcp:emptyState.noErrors")}
)}
@@ -397,28 +331,13 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
{/* Network Timeout */}
-
-
+
+
{t("mcp:networkTimeout.label")}
+ className="flex-1 p-[4px] bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border rounded-[2px] outline-none cursor-pointer">
{timeoutOptions.map((option) => (
{option.label}
@@ -426,33 +345,15 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
))}
-
+
{t("mcp:networkTimeout.description")}
)
) : (
-
-
+
+
{server.error &&
server.error.split("\n").map((item, index) => (
@@ -465,7 +366,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
appearance="secondary"
onClick={handleRestart}
disabled={server.status === "connecting"}
- style={{ width: "calc(100% - 20px)", margin: "0 10px 10px 10px" }}>
+ className="w-[calc(100%-20px)] mx-[10px] mb-[10px]">
{server.status === "connecting" ? "Retrying..." : "Retry Connection"}
diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx
index c494387042..8889591ddd 100644
--- a/webview-ui/src/components/prompts/PromptsView.tsx
+++ b/webview-ui/src/components/prompts/PromptsView.tsx
@@ -434,14 +434,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
return (
- {t("prompts:title")}
+ {t("prompts:title")}
{t("prompts:done")}
e.stopPropagation()} className="flex justify-between items-center mb-3">
-
{t("prompts:modes.title")}
+
{t("prompts:modes.title")}
{
e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
- className="absolute top-full right-0 w-[200px] mt-1 bg-vscode-editor-background border border-vscode-input-border rounded shadow-md z-[1000]">
+ className="absolute top-full right-0 w-[200px] mt-1 bg-background border border-border rounded shadow-md z-[1000]">
{
e.preventDefault() // Prevent blur
vscode.postMessage({
@@ -485,7 +485,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{t("prompts:modes.editGlobalModes")}
{
e.preventDefault() // Prevent blur
vscode.postMessage({
@@ -507,9 +507,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
-
- {t("prompts:modes.createModeHelpText")}
-
+ {t("prompts:modes.createModeHelpText")}
@@ -641,7 +639,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)}
-
+
{t("prompts:roleDefinition.description")}
{!findModeBySlug(visualMode, customModes) && (
-
+
{t("prompts:tools.builtInModesText")}
)}
@@ -750,7 +748,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
disabled={!isCustomMode}>
{t(`prompts:tools.toolNames.${group}`)}
{group === "edit" && (
-
+
{t("prompts:tools.allowedFiles")}{" "}
{(() => {
const currentMode = getCurrentMode()
@@ -773,7 +771,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
})}
) : (
-
+
{(() => {
const currentMode = getCurrentMode()
const enabledGroups = currentMode?.groups || []
@@ -821,7 +819,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)}
-
+
{t("prompts:customInstructions.description", {
modeName: getCurrentMode()?.name || "Code",
})}
@@ -861,7 +859,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
className="w-full resize-y"
data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`}
/>
-
-
+
{
setIsSystemPromptDisclosureOpen(!isSystemPromptDisclosureOpen)}
- className="flex items-center text-xs text-vscode-foreground hover:text-vscode-textLink-foreground focus:outline-none"
+ className="flex items-center text-xs text-foreground hover:text-vscode-textLink-foreground focus:outline-none"
aria-expanded={isSystemPromptDisclosureOpen}>
@@ -940,7 +938,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{isSystemPromptDisclosureOpen && (
-
-
-
{t("prompts:globalCustomInstructions.title")}
+
+
{t("prompts:globalCustomInstructions.title")}
-
+
{t("prompts:globalCustomInstructions.description", {
language: i18next.language,
})}
@@ -996,7 +994,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
className="w-full resize-y"
data-testid="global-custom-instructions-textarea"
/>
-
-
-
{t("prompts:supportPrompts.title")}
+
+
{t("prompts:supportPrompts.title")}
{
{/* Support prompt description */}
-
+
{t(`prompts:supportPrompts.types.${activeSupportOption}.description`)}
@@ -1074,13 +1072,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{activeSupportOption === "ENHANCE" && (
<>
-
+
{t("prompts:supportPrompts.enhance.apiConfiguration")}
-
+
{t("prompts:supportPrompts.enhance.apiConfigDescription")}
@@ -1143,7 +1141,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{isCreateModeDialogOpen && (
-
+
{
}}
className="w-full"
/>
- {nameError && (
- {nameError}
- )}
+ {nameError && {nameError}
}
{t("prompts:createModeDialog.slug.label")}
@@ -1177,16 +1173,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
}}
className="w-full"
/>
-
+
{t("prompts:createModeDialog.slug.description")}
- {slugError && (
-
{slugError}
- )}
+ {slugError &&
{slugError}
}
{t("prompts:createModeDialog.saveLocation.label")}
-
+
{t("prompts:createModeDialog.saveLocation.description")}
{
}}>
{t("prompts:createModeDialog.saveLocation.global.label")}
-
+
{t("prompts:createModeDialog.saveLocation.global.description")}
{t("prompts:createModeDialog.saveLocation.project.label")}
-
+
{t("prompts:createModeDialog.saveLocation.project.description")}
-
-
+
+
{t("prompts:createModeDialog.roleDefinition.label")}
-
+
{t("prompts:createModeDialog.roleDefinition.description")}
{t("prompts:createModeDialog.tools.label")}
-
+
{t("prompts:createModeDialog.tools.description")}
@@ -1263,15 +1250,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
))}
- {groupsError && (
-
{groupsError}
- )}
+ {groupsError &&
{groupsError}
}
{t("prompts:createModeDialog.customInstructions.label")}
-
+
{t("prompts:createModeDialog.customInstructions.description")}
-
+
setIsCreateModeDialogOpen(false)}>
{t("prompts:createModeDialog.buttons.cancel")}
@@ -1298,7 +1283,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{isDialogOpen && (
-
+