Compare commits

...

10 Commits

Author SHA1 Message Date
celestial-vault 5b1113ca93 separate fetch useEffect for more efficient rendering 2025-04-07 19:27:52 -07:00
Saoud Rizwan 3482aa3a5e Reduce padding in modal 2025-04-07 19:24:10 -07:00
celestial-vault 4081ac6445 merge conflicts 2025-04-07 14:19:47 -07:00
celestial-vault 44a5b1befd changest 2025-04-07 14:14:33 -07:00
celestial-vault 01b9aa4ef1 add servers modal 2025-04-07 14:14:07 -07:00
celestial-vault 0fff9b6a0b merge conflicts 2025-04-04 19:40:23 -07:00
celestial-vault 442ca8d3f3 changeset 2025-04-04 17:49:33 -07:00
celestial-vault e57dddb449 factor out servers toggle list 2025-04-04 17:49:14 -07:00
celestial-vault 8883890812 changeset 2025-04-04 17:15:39 -07:00
celestial-vault 766cbaea2d make ServerRow not optionally not expandable 2025-04-04 17:14:57 -07:00
7 changed files with 148 additions and 17 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Add modal for toggling MCP servers to the chat area
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
factor out servers list
@@ -25,6 +25,7 @@ import ApiOptions, { normalizeApiConfiguration } from "@/components/settings/Api
import { MAX_IMAGES_PER_MESSAGE } from "@/components/chat/ChatView"
import ContextMenu from "@/components/chat/ContextMenu"
import { ChatSettings } from "@shared/ChatSettings"
import ServersToggleModal from "./ServersToggleModal"
interface ChatTextAreaProps {
inputValue: string
@@ -1190,7 +1191,9 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
onClick={handleContextButtonClick}
style={{ padding: "0px 0px", height: "20px" }}>
<ButtonContainer>
<span style={{ fontSize: "13px", marginBottom: 1 }}>@</span>
<span className="flex items-center" style={{ fontSize: "13px", marginBottom: 1 }}>
@
</span>
{/* {showButtonText && <span style={{ fontSize: "10px" }}>Context</span>} */}
</ButtonContainer>
</VSCodeButton>
@@ -1207,10 +1210,14 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}}
style={{ padding: "0px 0px", height: "20px" }}>
<ButtonContainer>
<span className="codicon codicon-device-camera" style={{ fontSize: "14px", marginBottom: -3 }} />
<span
className="codicon codicon-device-camera flex items-center"
style={{ fontSize: "14px", marginBottom: -3 }}
/>
{/* {showButtonText && <span style={{ fontSize: "10px" }}>Images</span>} */}
</ButtonContainer>
</VSCodeButton>
<ServersToggleModal />
<ModelContainer ref={modelSelectorRef}>
<ModelButtonWrapper ref={buttonRef}>
@@ -0,0 +1,86 @@
import React, { useRef, useState, useEffect } from "react"
import { useClickAway, useWindowSize } from "react-use"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
import ServersToggleList from "@/components/mcp/configuration/tabs/installed/ServersToggleList"
import { vscode } from "@/utils/vscode"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
const ServersToggleModal: React.FC = () => {
const { mcpServers } = useExtensionState()
const [isVisible, setIsVisible] = useState(false)
const buttonRef = useRef<HTMLDivElement>(null)
const modalRef = useRef<HTMLDivElement>(null)
const { width: viewportWidth, height: viewportHeight } = useWindowSize()
const [arrowPosition, setArrowPosition] = useState(0)
const [menuPosition, setMenuPosition] = useState(0)
// Close modal when clicking outside
useClickAway(modalRef, () => {
setIsVisible(false)
})
// Calculate positions for modal and arrow
useEffect(() => {
if (isVisible && buttonRef.current) {
const buttonRect = buttonRef.current.getBoundingClientRect()
const buttonCenter = buttonRect.left + buttonRect.width / 2
const rightPosition = document.documentElement.clientWidth - buttonCenter - 5
setArrowPosition(rightPosition)
setMenuPosition(buttonRect.top + 1)
}
}, [isVisible, viewportWidth, viewportHeight])
useEffect(() => {
if (isVisible) {
vscode.postMessage({ type: "fetchLatestMcpServersFromHub" })
}
}, [isVisible])
return (
<div ref={modalRef}>
<div ref={buttonRef} className="inline-flex min-w-0 max-w-full">
<VSCodeButton
appearance="icon"
aria-label="MCP Servers"
onClick={() => setIsVisible(!isVisible)}
style={{ padding: "0px 0px", height: "20px" }}>
<div className="flex items-center gap-1 text-xs whitespace-nowrap min-w-0 w-full">
<span
className="codicon codicon-server flex items-center"
style={{ fontSize: "12.5px", marginBottom: 1 }}
/>
</div>
</VSCodeButton>
</div>
{isVisible && (
<div
className="fixed left-[15px] right-[15px] border border-[var(--vscode-editorGroup-border)] p-3 rounded z-[1000] overflow-y-auto"
style={{
bottom: `calc(100vh - ${menuPosition}px + 6px)`,
background: CODE_BLOCK_BG_COLOR,
maxHeight: "calc(100vh - 100px)",
overscrollBehavior: "contain",
}}>
<div
className="fixed w-[10px] h-[10px] z-[-1] rotate-45 border-r border-b border-[var(--vscode-editorGroup-border)]"
style={{
bottom: `calc(100vh - ${menuPosition}px)`,
right: arrowPosition,
background: CODE_BLOCK_BG_COLOR,
}}
/>
<div className="m-0 mb-2.5">MCP Servers</div>
<div style={{ marginBottom: "-10px" }}>
<ServersToggleList servers={mcpServers} isExpandable={false} hasTrashIcon={false} listGap="small" />
</div>
</div>
)}
</div>
)
}
export default ServersToggleModal
@@ -29,7 +29,7 @@ const InstalledServersView = () => {
</VSCodeLink>
</div>
<ServersToggleList servers={servers} />
<ServersToggleList servers={servers} isExpandable={true} hasTrashIcon={false} />
{/* Settings Section */}
<div style={{ marginBottom: "20px", marginTop: 10 }}>
@@ -1,11 +1,29 @@
import { McpServer } from "@shared/mcp"
import ServerRow from "./server-row/ServerRow"
const ServersToggleList = ({ servers }: { servers: McpServer[] }) => {
const ServersToggleList = ({
servers,
isExpandable,
hasTrashIcon,
listGap = "medium",
}: {
servers: McpServer[]
isExpandable: boolean
hasTrashIcon: boolean
listGap?: "small" | "medium" | "large"
}) => {
const gapClasses = {
small: "gap-0",
medium: "gap-2.5",
large: "gap-5",
}
const gapClass = gapClasses[listGap]
return servers.length > 0 ? (
<div className="flex flex-col gap-2.5">
<div className={`flex flex-col ${gapClass}`}>
{servers.map((server) => (
<ServerRow key={server.name} server={server} />
<ServerRow key={server.name} server={server} isExpandable={isExpandable} hasTrashIcon={hasTrashIcon} />
))}
</div>
) : (
@@ -17,7 +17,15 @@ import McpToolRow from "./McpToolRow"
import McpResourceRow from "./McpResourceRow"
import { useExtensionState } from "@/context/ExtensionStateContext"
const ServerRow = ({ server, isExpandable = true }: { server: McpServer; isExpandable?: boolean }) => {
const ServerRow = ({
server,
isExpandable = true,
hasTrashIcon = true,
}: {
server: McpServer
isExpandable?: boolean
hasTrashIcon?: boolean
}) => {
const { mcpMarketplaceCatalog, autoApprovalSettings } = useExtensionState()
const [isExpanded, setIsExpanded] = useState(false)
@@ -138,16 +146,18 @@ const ServerRow = ({ server, isExpandable = true }: { server: McpServer; isExpan
disabled={server.status === "connecting"}>
<span className="codicon codicon-sync"></span>
</VSCodeButton>
<VSCodeButton
appearance="icon"
title="Delete Server"
onClick={(e) => {
e.stopPropagation()
handleDelete()
}}
disabled={isDeleting}>
<span className="codicon codicon-trash"></span>
</VSCodeButton>
{hasTrashIcon && (
<VSCodeButton
appearance="icon"
title="Delete Server"
onClick={(e) => {
e.stopPropagation()
handleDelete()
}}
disabled={isDeleting}>
<span className="codicon codicon-trash"></span>
</VSCodeButton>
)}
</div>
)}
{/* Toggle Switch */}