diff --git a/.changeset/wicked-ways-cry.md b/.changeset/wicked-ways-cry.md new file mode 100644 index 0000000000..c347010a78 --- /dev/null +++ b/.changeset/wicked-ways-cry.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Revert "Tailwind migration" diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index 0d93d9da0c..b3a79cde7c 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -1,94 +1,113 @@ -import { useState, memo } from "react" +import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react" +import { memo } from "react" +import { useAppTranslation } from "@/i18n/TranslationContext" 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 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. - */ - +/* +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. +*/ const Announcement = ({ hideAnnouncement }: AnnouncementProps) => { const { t } = useAppTranslation() - const [open, setOpen] = useState(true) + + 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 + + ) return ( - { - setOpen(open) +
+
+ + + +

{t("chat:announcement.title")}

- if (!open) { - hideAnnouncement() - } - }}> - - - {t("chat:announcement.title")} - {t("chat:announcement.description")} - -
-

{t("chat:announcement.whatsNew")}

-
    -
  • - •{" "} - , code: }} /> -
  • -
  • - •{" "} - , code: }} /> -
  • -
  • - •{" "} - , code: }} /> -
  • -
+

{t("chat:announcement.description")}

+ +

{t("chat:announcement.whatsNew")}

+
    +
  • + •{" "} + , + code: , + }} + /> +
  • +
  • + •{" "} + , + code: , + }} + /> +
  • +
  • + •{" "} + , + code: , + }} + /> +
  • +
+ +

, redditLink: }} + components={{ + discordLink: discordLink, + redditLink: 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 2eb3a0afbf..aaa30a0153 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -2,7 +2,6 @@ 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" @@ -119,13 +118,23 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { return (
+ style={{ + padding: "0 15px", + userSelect: "none", + borderTop: isExpanded + ? `0.5px solid color-mix(in srgb, var(--vscode-titleBar-inactiveForeground) 20%, transparent)` + : "none", + overflowY: "auto", + ...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 */}
-
+ style={{ + margin: "5px auto", + width: "calc(100% - 10px)", + boxSizing: "border-box", // includes padding in width calculation + backgroundColor: "var(--vscode-input-background)", + border: "1px solid var(--vscode-input-border)", + borderRadius: "4px", + padding: "3px 5px", + display: "flex", + alignItems: "center", + justifyContent: "center", + color: displayState.url + ? "var(--vscode-input-foreground)" + : "var(--vscode-descriptionForeground)", + fontSize: "12px", + }}> +
{displayState.url || "http"}
@@ -261,15 +287,25 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => { {/* Screenshot Area */}
+ style={{ + width: "100%", + paddingBottom: `${aspectRatio}%`, // height/width ratio + position: "relative", + backgroundColor: "var(--vscode-input-background)", + }}> {displayState.screenshot ? ( {t("chat:browser.screenshot")} vscode.postMessage({ type: "openImage", @@ -278,34 +314,47 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => { } /> ) : ( -
- +
+
)} {displayState.mousePosition && ( )}
-
+
{ setConsoleLogsExpanded(!consoleLogsExpanded) }} - className={`flex items-center gap-1 w-full justify-start cursor-pointer px-2 ${ - consoleLogsExpanded ? "pt-[9px] pb-0" : "py-[9px]" - }`}> + style={{ + display: "flex", + alignItems: "center", + gap: "4px", + width: "100%", + justifyContent: "flex-start", + cursor: "pointer", + padding: `9px 8px ${consoleLogsExpanded ? 0 : 8}px 8px`, + }}> - {t("chat:browser.consoleLogs")} + {t("chat:browser.consoleLogs")}
{consoleLogsExpanded && ( @@ -314,15 +363,23 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
{/* Action content with min height */} -
0 && `[min-height:${maxActionHeight}px]`)}>{actionContent}
+
{actionContent}
{/* Pagination moved to bottom */} {pages.length > 1 && ( -
+
{t("chat:browser.navigation.step", { current: currentPageIndex + 1, total: pages.length })}
-
+
setCurrentPageIndex((i) => i - 1)}> @@ -369,7 +426,13 @@ const BrowserSessionRowContent = ({ isStreaming, }: BrowserSessionRowContentProps) => { const { t } = useTranslation() - const headerClassNames = "flex items-center gap-2.5 mb-2.5 break-words" + const headerStyle: React.CSSProperties = { + display: "flex", + alignItems: "center", + gap: "10px", + marginBottom: "10px", + wordBreak: "break-word", + } switch (message.type) { case "say": @@ -377,7 +440,7 @@ const BrowserSessionRowContent = ({ case "api_req_started": case "text": return ( -
+
-
- {t("chat:browser.sessionStarted")} +
+ {t("chat:browser.sessionStarted")}
-
+
@@ -457,11 +526,26 @@ const BrowserActionBox = ({ } } return ( -
-
-
- - {t("chat:browser.actions.title")} +
+
+
+ + {t("chat:browser.actions.title")} {getBrowserActionText(action, coordinate, text)}
@@ -470,12 +554,7 @@ const BrowserActionBox = ({ ) } -interface BrowserCursorProps { - style?: React.CSSProperties - className?: string -} - -const BrowserCursor: React.FC = ({ style, className }) => { +const BrowserCursor: React.FC<{ style?: React.CSSProperties }> = ({ style }) => { const { t } = useTranslation() // (can't use svgs in vsc extensions) const cursorBase64 = @@ -484,16 +563,13 @@ const BrowserCursor: React.FC = ({ style, className }) => { return ( {t("chat:browser.cursor")} ) } diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 0aad4d321f..efc720ba76 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -8,7 +8,6 @@ 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" @@ -16,7 +15,7 @@ import { vscode } from "@src/utils/vscode" import { Button } from "@src/components/ui" import CodeAccordian, { removeLeadingNonAlphanumeric } from "../common/CodeAccordian" -import CodeBlock from "../common/CodeBlock" +import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock" import MarkdownBlock from "../common/MarkdownBlock" import { ReasoningBlock } from "./ReasoningBlock" import Thumbnails from "../common/Thumbnails" @@ -119,26 +118,37 @@ export const ChatRowContent = ({ const type = message.type === "ask" ? message.ask : message.say - const [icon, title] = useMemo((): [React.ReactNode | null, React.ReactNode | null] => { + 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(() => { switch (type) { case "error": return [ - , - {t("chat:error")}, + , + {t("chat:error")}, ] case "mistake_limit_reached": return [ - , // Self-closing - {t("chat:troubleMessage")}, + , + {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) @@ -149,9 +159,11 @@ 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 })} @@ -159,59 +171,88 @@ export const ChatRowContent = ({ ] case "completion_result": return [ - , // Self-closing - {t("chat:taskCompleted")}, + , + {t("chat:taskCompleted")}, ] case "api_req_retry_delayed": - return [null, null] // Explicitly return [null, null] for this case + return [] case "api_req_started": - const getIconSpan = (iconName: string, colorClassName: string) => ( -
- + const getIconSpan = (iconName: string, color: string) => ( +
+
) return [ apiReqCancelReason !== null && apiReqCancelReason !== undefined ? ( apiReqCancelReason === "user_cancelled" ? ( - getIconSpan("error", "text-vscode-descriptionForeground") + getIconSpan("error", cancelledColor) ) : ( - getIconSpan("error", "text-vscode-errorForeground") + getIconSpan("error", errorColor) ) ) : cost !== null && cost !== undefined ? ( - getIconSpan("check", "text-vscode-charts-green") + getIconSpan("check", successColor) ) : apiRequestFailedMessage ? ( - getIconSpan("error", "text-vscode-errorForeground") + getIconSpan("error", errorColor) ) : ( ), 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] // Explicitly return [null, null] for default case + return [null, null] } }, [type, isCommandExecuting, message, isMcpServerResponding, apiReqCancelReason, cost, apiRequestFailedMessage, t]) - const headerClasses = "flex items-center gap-[10px] mb-[10px] break-words" - const pClasses = "m-0 whitespace-pre-wrap break-words overflow-anywhere" + 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 tool = useMemo( () => (message.ask === "tool" ? safeJsonParse(message.text) : null), @@ -227,7 +268,9 @@ export const ChatRowContent = ({ if (tool) { const toolIcon = (name: string) => ( - // Converted to Tailwind & self-closing + ) switch (tool.tool) { @@ -235,9 +278,9 @@ export const ChatRowContent = ({ case "appliedDiff": return ( <> -
+
{toolIcon(tool.tool === "appliedDiff" ? "diff" : "edit")} - + {tool.isOutsideWorkspace ? t("chat:fileOperations.wantsToEditOutsideWorkspace") : t("chat:fileOperations.wantsToEdit")} @@ -256,9 +299,9 @@ export const ChatRowContent = ({ case "insertContent": return ( <> -
+
{toolIcon("insert")} - + {tool.isOutsideWorkspace ? t("chat:fileOperations.wantsToEditOutsideWorkspace") : tool.lineNumber === 0 @@ -281,9 +324,9 @@ export const ChatRowContent = ({ case "searchAndReplace": return ( <> -
+
{toolIcon("replace")} - + {message.type === "ask" ? t("chat:fileOperations.wantsToSearchReplace") : t("chat:fileOperations.didSearchReplace")} @@ -302,9 +345,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") @@ -328,19 +371,45 @@ export const ChatRowContent = ({ : t("chat:fileOperations.didRead")}
-
+
{ vscode.postMessage({ type: "openFile", text: tool.content }) }}> {tool.path?.startsWith(".") && .} - + {removeLeadingNonAlphanumeric(tool.path ?? "") + "\u200E"} {tool.reason} -
- +
+
@@ -348,9 +417,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")} @@ -383,9 +452,9 @@ export const ChatRowContent = ({ case "listFilesRecursive": return ( <> -
+
{toolIcon("folder-opened")} - + {message.type === "ask" ? t("chat:directoryOperations.wantsToViewRecursive") : t("chat:directoryOperations.didViewRecursive")} @@ -403,9 +472,9 @@ export const ChatRowContent = ({ case "listCodeDefinitionNames": return ( <> -
+
{toolIcon("file-code")} - + {message.type === "ask" ? t("chat:directoryOperations.wantsToViewDefinitions") : t("chat:directoryOperations.didViewDefinitions")} @@ -422,9 +491,9 @@ export const ChatRowContent = ({ case "searchFiles": return ( <> -
+
{toolIcon("search")} - + {message.type === "ask" ? ( -
+
{toolIcon("symbol-enum")} - + {message.type === "ask" ? ( <> {tool.reason ? ( @@ -495,9 +564,9 @@ export const ChatRowContent = ({ case "newTask": return ( <> -
+
{toolIcon("tasklist")} - + {tool.mode} }} @@ -538,9 +607,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") && ( <> @@ -823,22 +898,22 @@ export const ChatRowContent = ({ return ( <> {title && ( -

+
{icon} {title}
)} -

{message.text}

+

{message.text}

) case "completion_result": return ( <> -
+
{icon} {title}
-
+
@@ -848,8 +923,16 @@ export const ChatRowContent = ({ case "mcp_server_response": return ( <> -
-
{t("chat:response")}
+
+
+ {t("chat:response")} +
{title && ( -
+
{icon} {title}
)} -
+
@@ -888,17 +971,17 @@ export const ChatRowContent = ({ case "mistake_limit_reached": return ( <> -
+
{icon} {title}
-

{message.text}

+

{message.text}

) case "command": return ( <> -
+
{icon} {title}
@@ -916,11 +999,17 @@ export const ChatRowContent = ({ return ( <> -
+
{icon} {title}
-
+
{useMcpServer.type === "access_mcp_resource" && (
{useMcpServer.arguments && useMcpServer.arguments !== "{}" && ( -
-
+
+
{t("chat:arguments")}
-
+
{icon} {title}
-
+
@@ -996,15 +1091,15 @@ export const ChatRowContent = ({ return ( <> {title && ( -
+
{icon} {title}
)} -
-

- {message.partial === true ? message?.text : followUpData?.question} -

+
+
)}
0 ? "mt-0" : "", - )}> + className={` w-full flex flex-col gap-4 m-auto ${isExpanded && tasks.length > 0 ? "mt-0" : ""} px-3.5 min-[370px]:px-10 pt-5 transition-all duration-300`}> {telemetrySetting === "unset" && } {/* Show the task history preview if expanded and tasks exist */} @@ -1262,11 +1258,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction + the docs ), @@ -1328,7 +1320,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 b049877de0..3c8c6273c1 100644 --- a/webview-ui/src/components/chat/CommandExecutionError.tsx +++ b/webview-ui/src/components/chat/CommandExecutionError.tsx @@ -27,7 +27,10 @@ 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 ddb11ccf0d..27d53803a8 100644 --- a/webview-ui/src/components/chat/ContextMenu.tsx +++ b/webview-ui/src/components/chat/ContextMenu.tsx @@ -68,10 +68,18 @@ const ContextMenu: React.FC = ({ switch (option.type) { case ContextMenuOptionType.Mode: return ( -
- {option.label} +
+ {option.label} {option.description && ( - + {option.description} )} @@ -88,9 +96,17 @@ const ContextMenu: React.FC = ({ case ContextMenuOptionType.Git: if (option.value) { return ( -
- {option.label} - +
+ {option.label} + {option.description}
@@ -108,9 +124,29 @@ const ContextMenu: React.FC = ({ const filename = pathList.at(-1) const folderPath = pathList.slice(0, -1).join("/") return ( -
+
{filename} - + {folderPath}
@@ -161,32 +197,68 @@ const ContextMenu: React.FC = ({ return (
+ style={{ + backgroundColor: "var(--vscode-dropdown-background)", + border: "1px solid var(--vscode-editorGroup-border)", + borderRadius: "3px", + boxShadow: "0 4px 10px rgba(0, 0, 0, 0.25)", + zIndex: 1000, + display: "flex", + flexDirection: "column", + maxHeight: "200px", + overflowY: "auto", + }}> {filteredOptions && filteredOptions.length > 0 ? ( filteredOptions.map((option, index) => (
isOptionSelectable(option) && onSelect(option.type, option.value)} - 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" - : "" - }`} + 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)", + } + : {}), + }} onMouseEnter={() => isOptionSelectable(option) && setSelectedIndex(index)}> -
+
{(option.type === ContextMenuOptionType.File || option.type === ContextMenuOptionType.Folder || option.type === ContextMenuOptionType.OpenedFile) && ( Mode )} {option.type !== ContextMenuOptionType.Mode && @@ -195,9 +267,13 @@ const ContextMenu: React.FC = ({ option.type !== ContextMenuOptionType.OpenedFile && getIconForOption(option) && ( )} {renderOptionContent(option)} @@ -206,12 +282,23 @@ const ContextMenu: React.FC = ({ option.type === ContextMenuOptionType.Folder || option.type === ContextMenuOptionType.Git) && !option.value && ( - + )}
)) ) : ( -
+
No results found
)} @@ -219,4 +306,5 @@ 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 31f47d844f..80f59ee74b 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 a9bb790d88..a209ce8723 100644 --- a/webview-ui/src/components/chat/Markdown.tsx +++ b/webview-ui/src/components/chat/Markdown.tsx @@ -1,14 +1,12 @@ 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) @@ -18,25 +16,43 @@ export const Markdown = memo(({ markdown, partial }: { markdown?: string; partia } return ( -
setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} className="relative"> -
+
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + style={{ position: "relative" }}> +
{markdown && !partial && isHovering && ( -
+
+ { const success = await copyWithFeedback(markdown) if (success) { - setCopySuccess(true) - setTimeout(() => { - setCopySuccess(false) - }, 200) + const button = document.activeElement as HTMLElement + if (button) { + button.style.background = "var(--vscode-button-background)" + setTimeout(() => { + button.style.background = "" + }, 200) + } } }} title="Copy as markdown"> diff --git a/webview-ui/src/components/chat/ProgressIndicator.tsx b/webview-ui/src/components/chat/ProgressIndicator.tsx index 44d6c9dca1..da35c196e1 100644 --- a/webview-ui/src/components/chat/ProgressIndicator.tsx +++ b/webview-ui/src/components/chat/ProgressIndicator.tsx @@ -1,8 +1,15 @@ 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 0fe39dd239..e1ea77826e 100644 --- a/webview-ui/src/components/common/CodeAccordian.tsx +++ b/webview-ui/src/components/common/CodeAccordian.tsx @@ -1,7 +1,6 @@ import { memo, useMemo } from "react" import { getLanguageFromPath } from "@src/utils/getLanguageFromPath" -import { cn } from "@/lib/utils" -import CodeBlock from "./CodeBlock" +import CodeBlock, { CODE_BLOCK_BG_COLOR } from "./CodeBlock" import { ToolProgressStatus } from "@roo/shared/ExtensionMessage" import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" @@ -47,31 +46,64 @@ const CodeAccordian = ({ ) return ( -
+
{(path || isFeedback || isConsoleLogs) && (
{isLoading && } {isFeedback || isConsoleLogs ? ( -
- - +
+ + {isFeedback ? "User Edits" : "Console Logs"}
) : ( <> {path?.startsWith(".") && .} - + {removeLeadingNonAlphanumeric(path ?? "") + "\u200E"} )} -
+
{progressStatus && progressStatus.text && ( <> {progressStatus.icon && } @@ -84,7 +116,12 @@ const CodeAccordian = ({
)} {(!(path || isFeedback || isConsoleLogs) || isExpanded) && ( -
+
void } -interface CodeBlockButtonProps extends React.ButtonHTMLAttributes { - children: React.ReactNode -} +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; -const CodeBlockButton = ({ children, ...props }: CodeBlockButtonProps) => { - return ( - - ) -} + &:hover { + background: var(--vscode-toolbar-hoverBackground); + opacity: 1; + } -interface CodeBlockButtonWrapperProps extends React.HTMLAttributes { - children: React.ReactNode -} + /* Style for Lucide icons to ensure consistent sizing and positioning */ + svg { + display: block; + } +` -const CodeBlockButtonWrapper = React.forwardRef( - ({ children, ...props }, ref) => { - return ( -
- {children} -
- ) - }, -) -CodeBlockButtonWrapper.displayName = "CodeBlockButtonWrapper" +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; -interface CodeBlockContainerProps extends React.HTMLAttributes { - children: React.ReactNode - "data-partially-visible"?: boolean -} + &:hover { + background: var(--vscode-editor-background); + opacity: 1 !important; + } -const CodeBlockContainer = React.forwardRef( - ({ children, "data-partially-visible": partiallyVisible, ...props }, ref) => { - return ( -
- {children} -
- ) - }, -) -CodeBlockContainer.displayName = "CodeBlockContainer" + ${CodeBlockButton} { + position: relative; + top: 0; + right: 0; + } +` -interface StyledPreProps extends React.HTMLAttributes { +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<{ preStyle?: React.CSSProperties wordwrap?: "true" | "false" | undefined windowshade?: "true" | "false" collapsedHeight?: number - children: React.ReactNode -} +}>` + 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 }} -export const StyledPre = React.forwardRef( - ({ preStyle, wordwrap, windowshade, collapsedHeight, children, className, ...props }, ref) => { - return ( -
- {children} -
- ) - }, -) -StyledPre.displayName = "StyledPre" + pre { + background-color: ${CODE_BLOCK_BG_COLOR}; + border-radius: 5px; + margin: 0; + padding: 10px; + width: 100%; + box-sizing: border-box; + } -interface LanguageSelectProps extends React.SelectHTMLAttributes { - children: React.ReactNode -} -const LanguageSelect = ({ children, className, ...props }: LanguageSelectProps) => { - return ( - - ) -} + 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; + } +` const CodeBlock = memo( ({ @@ -180,7 +243,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)) { @@ -194,7 +257,7 @@ const CodeBlock = memo( transformers: [ { pre(node) { - node.properties.class = "p-0 m-0" + node.properties.style = "padding: 0; margin: 0;" return node }, code(node) { @@ -557,7 +620,7 @@ const CodeBlock = memo( updateCodeBlockButtonPosition()} - className="gap-0"> + style={{ gap: 0 }}> {language && ( + style={{ fontWeight: "bold", textAlign: "left", fontSize: "1.2em" }}> {normalizeLanguage(language)} ) @@ -595,12 +658,13 @@ const CodeBlock = memo( ) diff --git a/webview-ui/src/components/common/MarkdownBlock.tsx b/webview-ui/src/components/common/MarkdownBlock.tsx index de8e10110a..a9c5ada19a 100644 --- a/webview-ui/src/components/common/MarkdownBlock.tsx +++ b/webview-ui/src/components/common/MarkdownBlock.tsx @@ -1,5 +1,6 @@ -import React, { memo, useEffect, FC, PropsWithChildren } from "react" +import React, { memo, useEffect } from "react" import { useRemark } from "react-remark" +import styled from "styled-components" import { visit } from "unist-util-visit" import { vscode } from "@src/utils/vscode" @@ -54,45 +55,71 @@ const remarkUrlToLink = () => { } } -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 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 MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => { const { theme } = useExtensionState() @@ -201,7 +228,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 0c93920e62..d44d51b5f9 100644 --- a/webview-ui/src/components/common/MermaidBlock.tsx +++ b/webview-ui/src/components/common/MermaidBlock.tsx @@ -1,11 +1,11 @@ -import React, { useEffect, useRef, useState } from "react" +import { 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,18 +152,37 @@ 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() @@ -176,14 +195,21 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
{isErrorExpanded && ( -
-
{error}
+
+
+ {error} +
)}
) : ( - + )} ) @@ -240,40 +266,44 @@ async function svgToPng(svgEl: SVGElement): Promise { }) } -const MermaidBlockContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => { - return
{children}
+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 LoadingMessage: React.FC<{ children: React.ReactNode }> = ({ children }) => { - return
{children}
-} - -const CopyButton: React.FC> = ({ children, ...props }) => { - return ( - - ) -} - -interface SvgContainerProps extends React.HTMLAttributes { - isLoading: boolean - children?: React.ReactNode -} - -const SvgContainer = React.forwardRef(({ isLoading, children, ...props }, ref) => { - return ( -
- {children} -
- ) -}) -SvgContainer.displayName = "SvgContainer" +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; +` diff --git a/webview-ui/src/components/common/TelemetryBanner.tsx b/webview-ui/src/components/common/TelemetryBanner.tsx index 156ec3704c..dac980489c 100644 --- a/webview-ui/src/components/common/TelemetryBanner.tsx +++ b/webview-ui/src/components/common/TelemetryBanner.tsx @@ -1,10 +1,29 @@ 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" -import { cn } from "@/lib/utils" + +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; + } +` const TelemetryBanner = () => { const { t } = useAppTranslation() @@ -29,7 +48,7 @@ const TelemetryBanner = () => { } return ( -
+
{t("welcome:telemetry.title")}
@@ -45,15 +64,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 f3097cc01f..acdf5f4295 100644 --- a/webview-ui/src/components/common/Thumbnails.tsx +++ b/webview-ui/src/components/common/Thumbnails.tsx @@ -37,24 +37,56 @@ const Thumbnails = ({ images, style, setImages, onHeightChange }: ThumbnailsProp } return ( -
+
{images.map((image, index) => (
setHoveredIndex(index)} onMouseLeave={() => setHoveredIndex(null)}> {`Thumbnail handleImageClick(image)} /> {isDeletable && hoveredIndex === index && (
handleDelete(index)} - className="absolute -top-1 -right-1 w-[13px] h-[13px] rounded-full bg-vscode-badge-background flex justify-center items-center cursor-pointer"> - + 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", + }}> +
)}
diff --git a/webview-ui/src/components/common/VSCodeButtonLink.tsx b/webview-ui/src/components/common/VSCodeButtonLink.tsx index 819e8f4065..d0cd6b1c97 100644 --- a/webview-ui/src/components/common/VSCodeButtonLink.tsx +++ b/webview-ui/src/components/common/VSCodeButtonLink.tsx @@ -8,7 +8,12 @@ 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 f290b1ae58..080cbff3e2 100644 --- a/webview-ui/src/components/history/HistoryPreview.tsx +++ b/webview-ui/src/components/history/HistoryPreview.tsx @@ -29,11 +29,13 @@ const HistoryPreview = () => {
{item.task}
diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index 2950aa8a65..bcb86a8d13 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)}> @@ -142,7 +152,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { value="mostRelevant" disabled={!searchQuery} data-testid="radio-most-relevant" - className={searchQuery ? "opacity-100" : "opacity-50"}> + style={{ opacity: searchQuery ? 1 : 0.5 }}> {t("history:mostRelevant")} @@ -187,7 +197,10 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { {
-
+
-
- + style={{ + display: "flex", + justifyContent: "space-between", + alignItems: "center", + }}> +
+ {t("history:tokensLabel")} - + style={{ + display: "flex", + alignItems: "center", + gap: "3px", + color: "var(--vscode-descriptionForeground)", + }}> + {formatLargeNumber(item.tokensIn || 0)} - + style={{ + display: "flex", + alignItems: "center", + gap: "3px", + color: "var(--vscode-descriptionForeground)", + }}> + {formatLargeNumber(item.tokensOut || 0)}
@@ -302,32 +358,75 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { {!!item.cacheWrites && (
- + style={{ + display: "flex", + alignItems: "center", + gap: "4px", + flexWrap: "wrap", + }}> + {t("history:cacheLabel")} - + style={{ + display: "flex", + alignItems: "center", + gap: "3px", + color: "var(--vscode-descriptionForeground)", + }}> + +{formatLargeNumber(item.cacheWrites || 0)} - + style={{ + display: "flex", + alignItems: "center", + gap: "3px", + color: "var(--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 2aae120377..648cb77142 100644 --- a/webview-ui/src/components/mcp/McpEnabledToggle.tsx +++ b/webview-ui/src/components/mcp/McpEnabledToggle.tsx @@ -16,11 +16,18 @@ 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 a28898bef3..b31082dafb 100644 --- a/webview-ui/src/components/mcp/McpResourceRow.tsx +++ b/webview-ui/src/components/mcp/McpResourceRow.tsx @@ -9,12 +9,26 @@ 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 @@ -23,9 +37,18 @@ 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 adce4e5324..2cab9b31ee 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -24,14 +24,18 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR } return ( -
+
e.stopPropagation()}> -
- - {tool.name} +
+ + {tool.name}
{serverName && alwaysAllowMcp && ( @@ -39,12 +43,32 @@ 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 = @@ -54,12 +78,29 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR tool.inputSchema.required.includes(paramName) return ( -
- +
+ {paramName} - {isRequired && *} + {isRequired && ( + * + )} - + {schema.description || t("mcp:tool.noDescription")}
@@ -71,4 +112,5 @@ 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 87648b61f4..b03be6500b 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -23,7 +23,6 @@ import { DialogDescription, DialogFooter, } from "@src/components/ui" -import { cn } from "@/lib/utils" // Import cn utility import { Tab, TabContent, TabHeader } from "../common/Tab" @@ -55,12 +54,20 @@ const McpView = ({ onDone }: McpViewProps) => { -
+
- + Model Context Protocol - + community-made servers @@ -70,23 +77,28 @@ 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 */} -
+
@@ -145,6 +157,17 @@ 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) @@ -180,48 +203,74 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM } return ( -
+
{server.status === "connected" && ( - + )} - + {server.name} {server.source && ( - + {server.source} )} -
e.stopPropagation()}> -
{ vscode.postMessage({ type: "toggleMcpServer", @@ -242,26 +291,40 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM } }}>
{server.status === "connected" ? ( isExpanded && ( -
- +
+ {t("mcp:tabs.tools")} ({server.tools?.length || 0}) @@ -275,7 +338,8 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM {server.tools && server.tools.length > 0 ? ( -
+
{server.tools.map((tool) => ( ) : ( -
+
{t("mcp:emptyState.noTools")}
)} @@ -296,7 +360,8 @@ 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")}
)} @@ -315,7 +380,8 @@ 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) => ( @@ -323,7 +389,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM ))}
) : ( -
+
{t("mcp:emptyState.noErrors")}
)} @@ -331,13 +397,28 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM {/* Network Timeout */} -
-
+
+
{t("mcp:networkTimeout.label")}
- + {t("mcp:networkTimeout.description")}
) ) : ( -
-
+
+
{server.error && server.error.split("\n").map((item, index) => ( @@ -366,7 +465,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM appearance="secondary" onClick={handleRestart} disabled={server.status === "connecting"} - className="w-[calc(100%-20px)] mx-[10px] mb-[10px]"> + style={{ width: "calc(100% - 20px)", margin: "0 10px 10px 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 8889591ddd..c494387042 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")}

e.stopPropagation()} className="flex justify-between items-center mb-3"> -

{t("prompts:modes.title")}

+

{t("prompts:modes.title")}

)}
-
+
{t("prompts:roleDefinition.description")}