diff --git a/.env.sample b/.env.sample index 6cdaa1b3b1..4d6c24ac72 100644 --- a/.env.sample +++ b/.env.sample @@ -1,2 +1 @@ -# PostHog API Keys for telemetry -POSTHOG_API_KEY=key-goes-here \ No newline at end of file +POSTHOG_API_KEY=key-goes-here diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index b537b9298e..99dda495d0 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -17,6 +17,12 @@ import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog" type Tab = "settings" | "history" | "mcp" | "prompts" | "chat" +type HumanRelayDialogState = { + isOpen: boolean + requestId: string + promptText: string +} + const tabsByMessageAction: Partial, Tab>> = { chatButtonClicked: "chat", settingsButtonClicked: "settings", @@ -24,24 +30,21 @@ const tabsByMessageAction: Partial { const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, telemetryKey, machineId } = useExtensionState() + const [showAnnouncement, setShowAnnouncement] = useState(false) const [tab, setTab] = useState("chat") - const settingsRef = useRef(null) - - // Human Relay Dialog Status - const [humanRelayDialogState, setHumanRelayDialogState] = useState<{ - isOpen: boolean - requestId: string - promptText: string - }>({ + const [humanRelayDialogState, setHumanRelayDialogState] = useState({ isOpen: false, requestId: "", promptText: "", }) + const settingsRef = useRef(null) + const switchTab = useCallback((newTab: Tab) => { if (settingsRef.current?.checkUnsaveChanges) { settingsRef.current.checkUnsaveChanges(() => setTab(newTab)) @@ -74,23 +77,6 @@ const App = () => { [switchTab], ) - // Processing Human Relay Dialog Submission - const handleHumanRelaySubmit = (requestId: string, text: string) => { - vscode.postMessage({ - type: "humanRelayResponse", - requestId, - text, - }) - } - - // Handle Human Relay dialog box cancel - const handleHumanRelayCancel = (requestId: string) => { - vscode.postMessage({ - type: "humanRelayCancel", - requestId, - }) - } - useEvent("message", onMessage) useEffect(() => { @@ -106,7 +92,7 @@ const App = () => { } }, [telemetrySetting, telemetryKey, machineId, didHydrateState]) - // Tell Extension that we are ready to receive messages + // Tell the extension that we are ready to receive messages. useEffect(() => { vscode.postMessage({ type: "webviewDidLaunch" }) }, []) @@ -121,24 +107,23 @@ const App = () => { ) : ( <> - {tab === "settings" && setTab("chat")} />} - {tab === "history" && switchTab("chat")} />} - {tab === "mcp" && switchTab("chat")} />} {tab === "prompts" && switchTab("chat")} />} + {tab === "mcp" && switchTab("chat")} />} + {tab === "history" && switchTab("chat")} />} + {tab === "settings" && setTab("chat")} />} setShowAnnouncement(false)} showHistoryView={() => switchTab("history")} /> - {/* Human Relay Dialog */} setHumanRelayDialogState((prev) => ({ ...prev, isOpen: false }))} - onSubmit={handleHumanRelaySubmit} - onCancel={handleHumanRelayCancel} + onSubmit={(requestId, text) => vscode.postMessage({ type: "humanRelayResponse", requestId, text })} + onCancel={(requestId) => vscode.postMessage({ type: "humanRelayCancel", requestId })} /> ) @@ -147,6 +132,7 @@ const App = () => { const AppWithProviders = () => ( +
) diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index 82fdb628eb..791ca26085 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -33,7 +33,7 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {

What's New

-

+

  • • Faster asynchronous checkpoints
  • • Support for .rooignore files
  • @@ -44,7 +44,7 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
  • • Updated DeepSeek provider
  • • New "Human Relay" provider
-

+

Get more details and discuss in{" "} diff --git a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx index 1910fc0bb6..63867c9858 100644 --- a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx +++ b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from "react" +import { useState, useCallback } from "react" import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons" import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui" @@ -14,7 +14,6 @@ type CheckpointMenuProps = { } export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: CheckpointMenuProps) => { - const [portalContainer, setPortalContainer] = useState() const [isOpen, setIsOpen] = useState(false) const [isConfirming, setIsConfirming] = useState(false) @@ -42,15 +41,6 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec setIsOpen(false) }, [ts, commitHash]) - useEffect(() => { - // The dropdown menu uses a portal from @shadcn/ui which by default renders - // at the document root. This causes the menu to remain visible even when - // the parent ChatView component is hidden (during settings/history view). - // By moving the portal inside ChatView, the menu will properly hide when - // its parent is hidden. - setPortalContainer(document.getElementById("chat-view-portal") || undefined) - }, []) - return (

{isDiffAvailable && ( @@ -70,7 +60,7 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec - +
{!isCurrent && (
diff --git a/webview-ui/src/components/common/Alert.tsx b/webview-ui/src/components/common/Alert.tsx new file mode 100644 index 0000000000..b16e799b91 --- /dev/null +++ b/webview-ui/src/components/common/Alert.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" +import { HTMLAttributes } from "react" + +type AlertProps = HTMLAttributes + +export const Alert = ({ className, children, ...props }: AlertProps) => ( +
+ {children} +
+) diff --git a/webview-ui/src/components/common/Tab.tsx b/webview-ui/src/components/common/Tab.tsx new file mode 100644 index 0000000000..982fb7e103 --- /dev/null +++ b/webview-ui/src/components/common/Tab.tsx @@ -0,0 +1,23 @@ +import { HTMLAttributes } from "react" + +import { cn } from "@/lib/utils" + +type TabProps = HTMLAttributes + +export const Tab = ({ className, children, ...props }: TabProps) => ( +
+ {children} +
+) + +export const TabHeader = ({ className, children, ...props }: TabProps) => ( +
+ {children} +
+) + +export const TabContent = ({ className, children, ...props }: TabProps) => ( +
+ {children} +
+) diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index e65a11a3ec..ec44f8eaca 100644 --- a/webview-ui/src/components/history/HistoryView.tsx +++ b/webview-ui/src/components/history/HistoryView.tsx @@ -9,6 +9,7 @@ import { formatLargeNumber, formatDate } from "@/utils/format" import { cn } from "@/lib/utils" import { Button } from "@/components/ui" +import { Tab, TabContent, TabHeader } from "../common/Tab" import { useTaskSearch } from "./useTaskSearch" import { ExportButton } from "./ExportButton" import { CopyButton } from "./CopyButton" @@ -25,8 +26,8 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { const [deleteTaskId, setDeleteTaskId] = useState(null) return ( -
-
+ +

History

Done @@ -81,8 +82,9 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
-
-
+ + + {
)} /> -
+ + {deleteTaskId && ( !open && setDeleteTaskId(null)} open /> )} -
+ ) } diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx index 7a24922d88..ce37a4c09d 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -1,3 +1,4 @@ +import { useState } from "react" import { VSCodeButton, VSCodeCheckbox, @@ -6,14 +7,17 @@ import { VSCodePanelTab, VSCodePanelView, } from "@vscode/webview-ui-toolkit/react" -import { useState } from "react" -import { vscode } from "../../utils/vscode" -import { useExtensionState } from "../../context/ExtensionStateContext" + import { McpServer } from "../../../../src/shared/mcp" + +import { vscode } from "@/utils/vscode" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui" + +import { useExtensionState } from "../../context/ExtensionStateContext" +import { Tab, TabContent, TabHeader } from "../common/Tab" import McpToolRow from "./McpToolRow" import McpResourceRow from "./McpResourceRow" import McpEnabledToggle from "./McpEnabledToggle" -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "../ui/dialog" type McpViewProps = { onDone: () => void @@ -29,12 +33,13 @@ const McpView = ({ onDone }: McpViewProps) => { } = useExtensionState() return ( -
-
+ +

MCP Servers

Done -
-
+ + +
{
)} -
-
+ + ) } -// Server Row Component const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowMcp?: boolean }) => { const [isExpanded, setIsExpanded] = useState(false) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index ccf1e6d700..d51d7d8909 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -42,6 +42,7 @@ import { import { TOOL_GROUPS, GROUP_DISPLAY_NAMES, ToolGroup } from "../../../../src/shared/tool-groups" import { vscode } from "../../utils/vscode" +import { Tab, TabContent, TabHeader } from "../common/Tab" // Get all available groups that should show in prompts view const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable) @@ -406,12 +407,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { } return ( -
-
+ +

Prompts

Done -
-
+ + +
Preferred Language
@@ -934,6 +936,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
+
{ )}
-
+ + {isCreateModeDialogOpen && (
{
)} + {isDialogOpen && (
{
)} + {isCustomLanguage && (
@@ -1497,7 +1503,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)} - +
) } diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 7cafbd6663..df08a03971 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -22,6 +22,7 @@ import { Button, } from "@/components/ui" +import { Tab, TabContent, TabHeader } from "../common/Tab" import { SetCachedStateField, SetExperimentEnabled } from "./types" import { SectionHeader } from "./SectionHeader" import ApiConfigManager from "./ApiConfigManager" @@ -263,53 +264,41 @@ const SettingsView = forwardRef(({ onDone }, const scrollToSection = (ref: React.RefObject) => ref.current?.scrollIntoView() return ( -
-
-
-
-
-

Settings

-
- {sections.map(({ id, icon: Icon, ref }) => ( - - ))} -
-
-
- - Save - - checkUnsaveChanges(onDone)}> - Done - -
+ + +
+

Settings

+
+ {sections.map(({ id, icon: Icon, ref }) => ( + + ))}
-
+
+ + Save + + checkUnsaveChanges(onDone)}> + Done + +
+ -
+
@@ -425,7 +414,7 @@ const SettingsView = forwardRef(({ onDone }, telemetrySetting={telemetrySetting} setTelemetrySetting={setTelemetrySetting} /> -
+ @@ -442,7 +431,7 @@ const SettingsView = forwardRef(({ onDone }, -
+ ) }) diff --git a/webview-ui/src/components/ui/dropdown-menu.tsx b/webview-ui/src/components/ui/dropdown-menu.tsx index fc5ad5b2b8..3193f497ca 100644 --- a/webview-ui/src/components/ui/dropdown-menu.tsx +++ b/webview-ui/src/components/ui/dropdown-menu.tsx @@ -53,23 +53,25 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam const DropdownMenuContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & { - container?: HTMLElement - } ->(({ className, sideOffset = 4, container, ...props }, ref) => ( - - - -)) + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => { + const container = React.useMemo(() => document.getElementById("roo-portal"), []) + + return ( + + + + ) +}) DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName const DropdownMenuItem = React.forwardRef< diff --git a/webview-ui/src/components/ui/popover.tsx b/webview-ui/src/components/ui/popover.tsx index 3ab3344689..b6235853ca 100644 --- a/webview-ui/src/components/ui/popover.tsx +++ b/webview-ui/src/components/ui/popover.tsx @@ -11,23 +11,25 @@ const PopoverAnchor = PopoverPrimitive.Anchor const PopoverContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & { - container?: HTMLElement - } ->(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => ( - - - -)) + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => { + const container = React.useMemo(() => document.getElementById("roo-portal"), []) + + return ( + + + + ) +}) PopoverContent.displayName = PopoverPrimitive.Content.displayName export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/webview-ui/src/components/ui/select-dropdown.tsx b/webview-ui/src/components/ui/select-dropdown.tsx index 775066732d..eef474cd02 100644 --- a/webview-ui/src/components/ui/select-dropdown.tsx +++ b/webview-ui/src/components/ui/select-dropdown.tsx @@ -7,7 +7,6 @@ import { DropdownMenuSeparator, } from "./dropdown-menu" import { cn } from "@/lib/utils" -import { useEffect, useState } from "react" // Constants for option types export enum DropdownOptionType { @@ -39,6 +38,8 @@ export interface SelectDropdownProps { shortcutText?: string } +// TODO: Get rid of this and use the native @shadcn/ui `Select` component. + export const SelectDropdown = React.forwardRef, SelectDropdownProps>( ( { @@ -60,16 +61,6 @@ export const SelectDropdown = React.forwardRef { // Track open state const [open, setOpen] = React.useState(false) - const [portalContainer, setPortalContainer] = useState() - - useEffect(() => { - // The dropdown menu uses a portal from @shadcn/ui which by default renders - // at the document root. This causes the menu to remain visible even when - // the parent ChatView component is hidden (during settings/history view). - // By moving the portal inside ChatView, the menu will properly hide when - // its parent is hidden. - setPortalContainer(document.getElementById("chat-view-portal") || undefined) - }, []) // Find the selected option label const selectedOption = options.find((option) => option.value === value) @@ -130,7 +121,6 @@ export const SelectDropdown = React.forwardRef setOpen(false)} onInteractOutside={() => setOpen(false)} - container={portalContainer} className={cn( "bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border z-50", contentClassName, diff --git a/webview-ui/src/components/ui/select.tsx b/webview-ui/src/components/ui/select.tsx index 50f89b3760..5e673257cf 100644 --- a/webview-ui/src/components/ui/select.tsx +++ b/webview-ui/src/components/ui/select.tsx @@ -39,8 +39,10 @@ function SelectContent({ position = "popper", ...props }: React.ComponentProps) { + const container = React.useMemo(() => document.getElementById("roo-portal"), []) + return ( - + { const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme } = useExtensionState() @@ -23,18 +26,16 @@ const WelcomeView = () => { }, [apiConfiguration, currentApiConfigName]) return ( -
-

Hi, I'm Roo!

-

- I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and access - to tools that let me create & edit files, explore complex projects, use the browser, and execute - terminal commands (with your permission, of course). I can even use MCP to create new tools and extend - my own capabilities. -

- - To get started, this extension needs an API provider. - -
+ + +

Hi, I'm Roo!

+
+ I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and + access to tools that let me create & edit files, explore complex projects, use the browser, and + execute terminal commands (with your permission, of course). I can even use MCP to create new tools + and extend my own capabilities. +
+ To get started, this extension needs an API provider. { errorMessage={errorMessage} setErrorMessage={setErrorMessage} /> -
- -
-
+ +
+
Let's go! - {errorMessage && {errorMessage}} + {errorMessage &&
{errorMessage}
}
-
+ ) } diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css index 6cd405b93f..2144557a98 100644 --- a/webview-ui/src/index.css +++ b/webview-ui/src/index.css @@ -111,6 +111,10 @@ --color-vscode-charts-green: var(--vscode-charts-green); --color-vscode-charts-yellow: var(--vscode-charts-yellow); + + --color-vscode-inputValidation-infoForeground: var(--vscode-inputValidation-infoForeground); + --color-vscode-inputValidation-infoBackground: var(--vscode-inputValidation-infoBackground); + --color-vscode-inputValidation-infoBorder: var(--vscode-inputValidation-infoBorder); } @layer base {