Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lemon-bulldogs-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"roo-cline": patch
---

App tab layout fixes
3 changes: 1 addition & 2 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
# PostHog API Keys for telemetry
POSTHOG_API_KEY=key-goes-here
POSTHOG_API_KEY=key-goes-here
50 changes: 18 additions & 32 deletions webview-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,34 @@ 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",
promptsButtonClicked: "prompts",
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))
Expand Down Expand Up @@ -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(() => {
Expand All @@ -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" })
}, [])
Expand All @@ -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 })}
/>
</>
)
Expand All @@ -147,6 +132,7 @@ const App = () => {
const AppWithProviders = () => (
<ExtensionStateContextProvider>
<App />
<div id="roo-portal" />
</ExtensionStateContextProvider>
)

Expand Down
4 changes: 2 additions & 2 deletions webview-ui/src/components/chat/Announcement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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{" "}
Expand Down
14 changes: 2 additions & 12 deletions webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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)

Expand Down Expand Up @@ -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 && (
Expand All @@ -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">
Expand Down
15 changes: 15 additions & 0 deletions webview-ui/src/components/common/Alert.tsx
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions webview-ui/src/components/common/Tab.tsx
Original file line number Diff line number Diff line change
@@ -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>
)
15 changes: 9 additions & 6 deletions webview-ui/src/components/history/HistoryView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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>
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -312,11 +314,12 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
</div>
)}
/>
</div>
</TabContent>

{deleteTaskId && (
<DeleteTaskDialog taskId={deleteTaskId} onOpenChange={(open) => !open && setDeleteTaskId(null)} open />
)}
</div>
</Tab>
)
}

Expand Down
26 changes: 15 additions & 11 deletions webview-ui/src/components/mcp/McpView.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from "react"
import {
VSCodeButton,
VSCodeCheckbox,
Expand All @@ -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
Expand All @@ -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)",
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 12 additions & 6 deletions webview-ui/src/components/prompts/PromptsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -934,6 +936,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>
</div>
</div>

<div
style={{
paddingBottom: "40px",
Expand Down Expand Up @@ -1172,7 +1175,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)}
</div>
</div>
</div>
</TabContent>

{isCreateModeDialogOpen && (
<div
style={{
Expand Down Expand Up @@ -1396,6 +1400,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>
</div>
)}

{isDialogOpen && (
<div
style={{
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -1497,7 +1503,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>
</div>
)}
</div>
</Tab>
)
}

Expand Down
Loading