mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
App tab layout fixes
This commit is contained in:
parent
85dd1a1977
commit
9f40c4d57a
16 changed files with 198 additions and 183 deletions
|
|
@ -1,2 +1 @@
|
|||
# PostHog API Keys for telemetry
|
||||
POSTHOG_API_KEY=key-goes-here
|
||||
POSTHOG_API_KEY=key-goes-here
|
||||
|
|
|
|||
|
|
@ -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 })}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
@ -147,6 +132,7 @@ const App = () => {
|
|||
const AppWithProviders = () => (
|
||||
<ExtensionStateContextProvider>
|
||||
<App />
|
||||
<div id="roo-portal" />
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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{" "}
|
||||
|
|
|
|||
|
|
@ -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<HTMLElement>()
|
||||
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 (
|
||||
<div className="flex flex-row gap-1">
|
||||
{isDiffAvailable && (
|
||||
|
|
@ -70,7 +60,7 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
|
|||
<span className="codicon codicon-history" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" container={portalContainer}>
|
||||
<PopoverContent align="end">
|
||||
<div className="flex flex-col gap-2">
|
||||
{!isCurrent && (
|
||||
<div className="flex flex-col gap-1 group hover:text-foreground">
|
||||
|
|
|
|||
15
webview-ui/src/components/common/Alert.tsx
Normal file
15
webview-ui/src/components/common/Alert.tsx
Normal 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>
|
||||
)
|
||||
23
webview-ui/src/components/common/Tab.tsx
Normal file
23
webview-ui/src/components/common/Tab.tsx
Normal 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>
|
||||
)
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<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>
|
||||
|
|
@ -934,6 +936,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: "40px",
|
||||
|
|
@ -1172,7 +1175,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabContent>
|
||||
|
||||
{isCreateModeDialogOpen && (
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -1396,6 +1400,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDialogOpen && (
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -1463,6 +1468,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">
|
||||
|
|
@ -1497,7 +1503,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tab>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -53,23 +53,25 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam
|
|||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> & {
|
||||
container?: HTMLElement
|
||||
}
|
||||
>(({ className, sideOffset = 4, container, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal container={container}>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => {
|
||||
const container = React.useMemo(() => document.getElementById("roo-portal"), [])
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal container={container}>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
)
|
||||
})
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
|
|
|
|||
|
|
@ -11,23 +11,25 @@ const PopoverAnchor = PopoverPrimitive.Anchor
|
|||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
|
||||
container?: HTMLElement
|
||||
}
|
||||
>(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal container={container}>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-xs border border-vscode-dropdown-border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => {
|
||||
const container = React.useMemo(() => document.getElementById("roo-portal"), [])
|
||||
|
||||
return (
|
||||
<PopoverPrimitive.Portal container={container}>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-xs border border-vscode-dropdown-border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
)
|
||||
})
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||
|
|
|
|||
|
|
@ -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<React.ElementRef<typeof DropdownMenuTrigger>, SelectDropdownProps>(
|
||||
(
|
||||
{
|
||||
|
|
@ -60,16 +61,6 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
|
|||
) => {
|
||||
// Track open state
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [portalContainer, setPortalContainer] = useState<HTMLElement>()
|
||||
|
||||
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<React.ElementRef<typeof DropdownM
|
|||
sideOffset={sideOffset}
|
||||
onEscapeKeyDown={() => 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,
|
||||
|
|
|
|||
|
|
@ -39,8 +39,10 @@ function SelectContent({
|
|||
position = "popper",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||
const container = React.useMemo(() => document.getElementById("roo-portal"), [])
|
||||
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Portal container={container}>
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue