Merge pull request #1516 from RooVetGit/cte/layout-fixes

App tab layout fixes
This commit is contained in:
Chris Estreich 2025-03-09 21:53:51 -07:00 • committed by GitHub
commit 0b2abee923
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 200 additions and 178 deletions

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
App tab layout fixes

View file

@ -1,2 +1 @@
# PostHog API Keys for telemetry
POSTHOG_API_KEY=key-goes-here
POSTHOG_API_KEY=key-goes-here

View file

@ -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<Record<NonNullable<ExtensionMessage["action"]>, Tab>> = {
chatButtonClicked: "chat",
settingsButtonClicked: "settings",
@ -24,24 +30,21 @@ const tabsByMessageAction: Partial<Record<NonNullable<ExtensionMessage["action"]
mcpButtonClicked: "mcp",
historyButtonClicked: "history",
}
const App = () => {
const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, telemetryKey, machineId } =
useExtensionState()
const [showAnnouncement, setShowAnnouncement] = useState(false)
const [tab, setTab] = useState<Tab>("chat")
const settingsRef = useRef<SettingsViewRef>(null)
// Human Relay Dialog Status
const [humanRelayDialogState, setHumanRelayDialogState] = useState<{
isOpen: boolean
requestId: string
promptText: string
}>({
const [humanRelayDialogState, setHumanRelayDialogState] = useState<HumanRelayDialogState>({
isOpen: false,
requestId: "",
promptText: "",
})
const settingsRef = useRef<SettingsViewRef>(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 = () => {
<WelcomeView />
) : (
<>
{tab === "settings" && <SettingsView ref={settingsRef} onDone={() => setTab("chat")} />}
{tab === "history" && <HistoryView onDone={() => switchTab("chat")} />}
{tab === "mcp" && <McpView onDone={() => switchTab("chat")} />}
{tab === "prompts" && <PromptsView onDone={() => switchTab("chat")} />}
{tab === "mcp" && <McpView onDone={() => switchTab("chat")} />}
{tab === "history" && <HistoryView onDone={() => switchTab("chat")} />}
{tab === "settings" && <SettingsView ref={settingsRef} onDone={() => setTab("chat")} />}
<ChatView
isHidden={tab !== "chat"}
showAnnouncement={showAnnouncement}
hideAnnouncement={() => setShowAnnouncement(false)}
showHistoryView={() => switchTab("history")}
/>
{/* Human Relay Dialog */}
<HumanRelayDialog
isOpen={humanRelayDialogState.isOpen}
requestId={humanRelayDialogState.requestId}
promptText={humanRelayDialogState.promptText}
onClose={() => 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 })}
/>
</>
)

View file

@ -33,7 +33,7 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
</p>
<h3 style={{ margin: "12px 0 8px" }}>What's New</h3>
<p style={{ margin: "5px 0px" }}>
<div style={{ margin: "5px 0px" }}>
<ul style={{ margin: "4px 0 6px 20px", padding: 0 }}>
<li>• Faster asynchronous checkpoints</li>
<li>• Support for .rooignore files</li>
@ -44,7 +44,7 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
<li>• Updated DeepSeek provider</li>
<li>• New "Human Relay" provider</li>
</ul>
</p>
</div>
<p style={{ margin: "10px 0px 0px" }}>
Get more details and discuss in{" "}

View file

@ -1,22 +1,25 @@
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
import DynamicTextArea from "react-textarea-autosize"
import { mentionRegex, mentionRegexGlobal } from "../../../../src/shared/context-mentions"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
import { Mode, getAllModes } from "../../../../src/shared/modes"
import { vscode } from "@/utils/vscode"
import {
ContextMenuOptionType,
getContextMenuOptions,
insertMention,
removeMention,
shouldShowContextMenu,
} from "../../utils/context-mentions"
} from "@/utils/context-mentions"
import { SelectDropdown, DropdownOptionType } from "@/components/ui"
import { useExtensionState } from "../../context/ExtensionStateContext"
import Thumbnails from "../common/Thumbnails"
import { convertToMentionPath } from "../../utils/path-mentions"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu"
import Thumbnails from "../common/Thumbnails"
import { vscode } from "../../utils/vscode"
import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
import { Mode, getAllModes } from "../../../../src/shared/modes"
import { convertToMentionPath } from "../../utils/path-mentions"
import { SelectDropdown, DropdownOptionType } from "../ui"
interface ChatTextAreaProps {
inputValue: string

View file

@ -1275,7 +1275,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
modeShortcutText={modeShortcutText}
/>
<div id="chat-view-portal" />
<div id="roo-portal" />
</div>
)
}

View file

@ -1,7 +1,8 @@
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"
import { useRooPortal } from "@/components/ui/hooks"
import { vscode } from "../../../utils/vscode"
import { Checkpoint } from "./schema"
@ -14,9 +15,9 @@ type CheckpointMenuProps = {
}
export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: CheckpointMenuProps) => {
const [portalContainer, setPortalContainer] = useState<HTMLElement>()
const [isOpen, setIsOpen] = useState(false)
const [isConfirming, setIsConfirming] = useState(false)
const portalContainer = useRooPortal("roo-portal")
const isCurrent = currentHash === commitHash
const isFirst = checkpoint.isFirst
@ -42,15 +43,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 (
<div className="flex flex-row gap-1">
{isDiffAvailable && (

View file

@ -0,0 +1,15 @@
import { cn } from "@/lib/utils"
import { HTMLAttributes } from "react"
type AlertProps = HTMLAttributes<HTMLDivElement>
export const Alert = ({ className, children, ...props }: AlertProps) => (
<div
className={cn(
"text-vscode-inputValidation-infoForeground bg-vscode-inputValidation-infoBackground border border-vscode-inputValidation-infoBorder rounded-xs p-2",
className,
)}
{...props}>
{children}
</div>
)

View file

@ -0,0 +1,23 @@
import { HTMLAttributes } from "react"
import { cn } from "@/lib/utils"
type TabProps = HTMLAttributes<HTMLDivElement>
export const Tab = ({ className, children, ...props }: TabProps) => (
<div className={cn("fixed inset-0 flex flex-col overflow-hidden", className)} {...props}>
{children}
</div>
)
export const TabHeader = ({ className, children, ...props }: TabProps) => (
<div className={cn("px-5 py-2.5 border-b border-vscode-panel-border", className)} {...props}>
{children}
</div>
)
export const TabContent = ({ className, children, ...props }: TabProps) => (
<div className={cn("flex-1 overflow-auto p-5", className)} {...props}>
{children}
</div>
)

View file

@ -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<string | null>(null)
return (
<div className="fixed inset-0 flex flex-col">
<div className="flex flex-col gap-2 px-5 py-2.5 border-b border-vscode-panel-border">
<Tab>
<TabHeader className="flex flex-col gap-2">
<div className="flex justify-between items-center">
<h3 className="text-vscode-foreground m-0">History</h3>
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
@ -81,8 +82,9 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
</VSCodeRadio>
</VSCodeRadioGroup>
</div>
</div>
<div style={{ flexGrow: 1, overflowY: "auto", margin: 0 }}>
</TabHeader>
<TabContent className="p-0">
<Virtuoso
style={{
flexGrow: 1,
@ -312,11 +314,12 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
</div>
)}
/>
</div>
</TabContent>
{deleteTaskId && (
<DeleteTaskDialog taskId={deleteTaskId} onOpenChange={(open) => !open && setDeleteTaskId(null)} open />
)}
</div>
</Tab>
)
}

View file

@ -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 (
<div className="fixed inset-0 flex flex-col">
<div className="flex justify-between items-center px-5 py-2.5 border-b border-vscode-panel-border">
<Tab>
<TabHeader className="flex justify-between items-center">
<h3 className="text-vscode-foreground m-0">MCP Servers</h3>
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
</div>
<div className="flex-1 overflow-auto p-5">
</TabHeader>
<TabContent>
<div
style={{
color: "var(--vscode-foreground)",
@ -103,12 +108,11 @@ const McpView = ({ onDone }: McpViewProps) => {
</div>
</>
)}
</div>
</div>
</TabContent>
</Tab>
)
}
// Server Row Component
const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowMcp?: boolean }) => {
const [isExpanded, setIsExpanded] = useState(false)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)

View file

@ -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)
@ -419,12 +420,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
}
return (
<div className="fixed inset-0 flex flex-col">
<div className="flex justify-between items-center px-5 py-2.5 border-b border-vscode-panel-border">
<Tab>
<TabHeader className="flex justify-between items-center">
<h3 className="text-vscode-foreground m-0">Prompts</h3>
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
</div>
<div className="flex-1 overflow-auto p-5">
</TabHeader>
<TabContent>
<div className="pb-5 border-b border-vscode-input-border">
<div className="mb-5">
<div className="font-bold mb-1">Preferred Language</div>
@ -946,6 +948,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>
</div>
</div>
<div
style={{
paddingBottom: "40px",
@ -1213,7 +1216,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)}
</div>
</div>
</div>
</TabContent>
{isCreateModeDialogOpen && (
<div
style={{
@ -1437,6 +1441,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>
</div>
)}
{isDialogOpen && (
<div
style={{
@ -1504,6 +1509,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>
</div>
)}
{isCustomLanguage && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-[var(--vscode-editor-background)] p-6 rounded-lg w-96">
@ -1538,7 +1544,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>
</div>
)}
</div>
</Tab>
)
}

View file

@ -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<SettingsViewRef, SettingsViewProps>(({ onDone },
const scrollToSection = (ref: React.RefObject<HTMLDivElement>) => ref.current?.scrollIntoView()
return (
<div className="fixed inset-0 flex flex-col overflow-hidden">
<div className="px-5 py-2.5 border-b border-vscode-panel-border">
<div className="flex flex-col">
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<h3 className="text-vscode-foreground m-0">Settings</h3>
<div className="hidden [@media(min-width:400px)]:flex items-center">
{sections.map(({ id, icon: Icon, ref }) => (
<Button
key={id}
variant="ghost"
onClick={() => scrollToSection(ref)}
className={cn("w-6 h-6", activeSection === id ? "opacity-100" : "opacity-40")}>
<Icon />
</Button>
))}
</div>
</div>
<div className="flex gap-2">
<VSCodeButton
appearance={isSettingValid ? "primary" : "secondary"}
className={!isSettingValid ? "!border-vscode-errorForeground" : ""}
title={
!isSettingValid
? errorMessage
: isChangeDetected
? "Save changes"
: "Nothing changed"
}
onClick={handleSubmit}
disabled={!isChangeDetected || !isSettingValid}>
Save
</VSCodeButton>
<VSCodeButton
appearance="secondary"
title="Discard unsaved changes and close settings panel"
onClick={() => checkUnsaveChanges(onDone)}>
Done
</VSCodeButton>
</div>
<Tab>
<TabHeader className="flex justify-between items-center gap-2">
<div className="flex items-center gap-2">
<h3 className="text-vscode-foreground m-0">Settings</h3>
<div className="hidden [@media(min-width:400px)]:flex items-center">
{sections.map(({ id, icon: Icon, ref }) => (
<Button
key={id}
variant="ghost"
onClick={() => scrollToSection(ref)}
className={cn("w-6 h-6", activeSection === id ? "opacity-100" : "opacity-40")}>
<Icon />
</Button>
))}
</div>
</div>
</div>
<div className="flex gap-2">
<VSCodeButton
appearance={isSettingValid ? "primary" : "secondary"}
className={!isSettingValid ? "!border-vscode-errorForeground" : ""}
title={!isSettingValid ? errorMessage : isChangeDetected ? "Save changes" : "Nothing changed"}
onClick={handleSubmit}
disabled={!isChangeDetected || !isSettingValid}>
Save
</VSCodeButton>
<VSCodeButton
appearance="secondary"
title="Discard unsaved changes and close settings panel"
onClick={() => checkUnsaveChanges(onDone)}>
Done
</VSCodeButton>
</div>
</TabHeader>
<div
className="flex flex-col flex-1 overflow-auto divide-y divide-vscode-sideBar-background"
onScroll={handleScroll}>
<TabContent className="p-0 divide-y divide-vscode-sideBar-background" onScroll={handleScroll}>
<div ref={providersRef}>
<SectionHeader>
<div className="flex items-center gap-2">
@ -425,7 +414,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
telemetrySetting={telemetrySetting}
setTelemetrySetting={setTelemetrySetting}
/>
</div>
</TabContent>
<AlertDialog open={isDiscardDialogShow} onOpenChange={setDiscardDialogShow}>
<AlertDialogContent>
@ -442,7 +431,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</Tab>
)
})

View file

@ -1,5 +1,6 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { PortalProps } from "@radix-ui/react-portal"
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
@ -53,9 +54,7 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> & {
container?: HTMLElement
}
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> & Pick<PortalProps, "container">
>(({ className, sideOffset = 4, container, ...props }, ref) => (
<DropdownMenuPrimitive.Portal container={container}>
<DropdownMenuPrimitive.Content

View file

@ -1 +1,2 @@
export * from "./useClipboard"
export * from "./useRooPortal"

View file

@ -0,0 +1,10 @@
import { useState } from "react"
import { useMount } from "react-use"
export const useRooPortal = (id: string) => {
const [container, setContainer] = useState<HTMLElement>()
useMount(() => setContainer(document.getElementById(id) ?? undefined))
return container
}

View file

@ -1,4 +1,5 @@
import * as React from "react"
import { PortalProps } from "@radix-ui/react-portal"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
@ -11,9 +12,7 @@ const PopoverAnchor = PopoverPrimitive.Anchor
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
container?: HTMLElement
}
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & Pick<PortalProps, "container">
>(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => (
<PopoverPrimitive.Portal container={container}>
<PopoverPrimitive.Content

View file

@ -1,4 +1,8 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { useRooPortal } from "./hooks/useRooPortal"
import {
DropdownMenu,
DropdownMenuContent,
@ -6,10 +10,7 @@ import {
DropdownMenuTrigger,
DropdownMenuSeparator,
} from "./dropdown-menu"
import { cn } from "@/lib/utils"
import { useEffect, useState } from "react"
// Constants for option types
export enum DropdownOptionType {
ITEM = "item",
SEPARATOR = "separator",
@ -20,7 +21,7 @@ export interface DropdownOption {
value: string
label: string
disabled?: boolean
type?: DropdownOptionType // Optional type to specify special behaviors
type?: DropdownOptionType
}
export interface SelectDropdownProps {
@ -58,34 +59,19 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
},
ref,
) => {
// Track open state
const [open, setOpen] = React.useState(false)
const [portalContainer, setPortalContainer] = useState<HTMLElement>()
const portalContainer = useRooPortal("roo-portal")
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)
const displayText = selectedOption?.label || placeholder || ""
// Handle menu item click
const handleSelect = (option: DropdownOption) => {
// Check if this is an action option by its explicit type
if (option.type === DropdownOptionType.ACTION) {
window.postMessage({
type: "action",
action: option.value,
})
window.postMessage({ type: "action", action: option.value })
setOpen(false)
return
}
onChange(option.value)
setOpen(false)
}
@ -103,7 +89,7 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
triggerClassName,
)}
style={{
width: "100%", // Take full width of parent
width: "100%", // Take full width of parent.
minWidth: "0",
maxWidth: "100%",
}}>
@ -136,12 +122,10 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
contentClassName,
)}>
{options.map((option, index) => {
// Handle separator type
if (option.type === DropdownOptionType.SEPARATOR) {
return <DropdownMenuSeparator key={`sep-${index}`} />
}
// Handle shortcut text type (disabled label for keyboard shortcuts)
if (
option.type === DropdownOptionType.SHORTCUT ||
(option.disabled && shortcutText && option.label.includes(shortcutText))
@ -153,7 +137,6 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
)
}
// Regular menu items
return (
<DropdownMenuItem
key={`item-${option.value}`}

View file

@ -1,4 +1,5 @@
import * as React from "react"
import { PortalProps } from "@radix-ui/react-portal"
import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
@ -37,10 +38,11 @@ function SelectContent({
className,
children,
position = "popper",
container,
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
}: React.ComponentProps<typeof SelectPrimitive.Content> & Pick<PortalProps, "container">) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Portal container={container}>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(

View file

@ -1,9 +1,12 @@
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { useCallback, useState } from "react"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { validateApiConfiguration } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import ApiOptions from "../settings/ApiOptions"
import { Tab, TabContent } from "../common/Tab"
import { Alert } from "../common/Alert"
const WelcomeView = () => {
const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme } = useExtensionState()
@ -23,18 +26,16 @@ const WelcomeView = () => {
}, [apiConfiguration, currentApiConfigName])
return (
<div className="flex flex-col min-h-screen px-0 pb-5">
<h2>Hi, I'm Roo!</h2>
<p>
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.
</p>
<b>To get started, this extension needs an API provider.</b>
<div className="mt-3">
<Tab>
<TabContent className="flex flex-col gap-5">
<h2 className="m-0 p-0">Hi, I'm Roo!</h2>
<div>
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.
</div>
<Alert className="font-bold text-sm">To get started, this extension needs an API provider.</Alert>
<ApiOptions
fromWelcomeView
apiConfiguration={apiConfiguration || {}}
@ -43,15 +44,14 @@ const WelcomeView = () => {
errorMessage={errorMessage}
setErrorMessage={setErrorMessage}
/>
</div>
<div className="sticky bottom-0 bg-[var(--vscode-sideBar-background)] py-3">
<div className="flex flex-col gap-1.5">
</TabContent>
<div className="sticky bottom-0 bg-vscode-sideBar-background p-5">
<div className="flex flex-col gap-1">
<VSCodeButton onClick={handleSubmit}>Let's go!</VSCodeButton>
{errorMessage && <span className="text-destructive">{errorMessage}</span>}
{errorMessage && <div className="text-vscode-errorForeground">{errorMessage}</div>}
</div>
</div>
</div>
</Tab>
)
}

View file

@ -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 {