瀏覽代碼

Add mock data and gui

Saoud Rizwan 1 年之前
父節點
當前提交
bee3df5319
共有 1 個文件被更改,包括 356 次插入4 次删除
  1. 356 4
      webview-ui/src/components/mcp/McpView.tsx

+ 356 - 4
webview-ui/src/components/mcp/McpView.tsx

@@ -1,10 +1,120 @@
-import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
+import {
+	VSCodeButton,
+	VSCodeDivider,
+	VSCodeTextArea,
+	VSCodeTextField,
+	VSCodeTag,
+	VSCodePanelTab,
+	VSCodePanelView,
+	VSCodeDataGrid,
+	VSCodeDataGridRow,
+	VSCodeDataGridCell,
+	VSCodePanels,
+} from "@vscode/webview-ui-toolkit/react"
+import { useState } from "react"
+
+type McpServer = {
+	name: string
+	config: string // JSON config
+	status: "connected" | "connecting" | "disconnected"
+	error?: string
+	tools?: any[] // We'll type this properly later
+	resources?: any[] // We'll type this properly later
+}
 
 type McpViewProps = {
 	onDone: () => void
 }
 
 const McpView = ({ onDone }: McpViewProps) => {
+	const [isAdding, setIsAdding] = useState(false)
+	const [servers, setServers] = useState<McpServer[]>([
+		// Add some mock servers for testing
+		{
+			name: "local-tools",
+			config: JSON.stringify({
+				mcpServers: {
+					"local-tools": {
+						command: "npx",
+						args: ["-y", "@modelcontextprotocol/server-tools"],
+					},
+				},
+			}),
+			status: "connected",
+			tools: [
+				{
+					name: "execute_command",
+					description: "Run a shell command on the local system",
+				},
+				{
+					name: "read_file",
+					description: "Read contents of a file from the filesystem",
+				},
+			],
+		},
+		{
+			name: "postgres-db",
+			config: JSON.stringify({
+				mcpServers: {
+					"postgres-db": {
+						command: "npx",
+						args: ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
+					},
+				},
+			}),
+			status: "disconnected",
+			error: "Failed to connect to database: Connection refused",
+		},
+		{
+			name: "github-tools",
+			config: JSON.stringify({
+				mcpServers: {
+					"github-tools": {
+						command: "npx",
+						args: ["-y", "@modelcontextprotocol/server-github"],
+					},
+				},
+			}),
+			status: "connecting",
+			resources: [
+				{
+					uri: "github://repo/issues",
+					name: "Repository Issues",
+				},
+				{
+					uri: "github://repo/pulls",
+					name: "Pull Requests",
+				},
+			],
+		},
+	])
+	const [configInput, setConfigInput] = useState("")
+
+	const handleAddServer = () => {
+		try {
+			const config = JSON.parse(configInput)
+			const serverName = Object.keys(config.mcpServers)[0]
+
+			setServers((prev) => [
+				...prev,
+				{
+					name: serverName,
+					config: configInput,
+					status: "connecting",
+				},
+			])
+
+			setIsAdding(false)
+			setConfigInput("")
+
+			// Here you would trigger the actual server connection
+			// and update its status/tools/resources accordingly
+		} catch (e) {
+			// Handle invalid JSON
+			console.error("Invalid server configuration:", e)
+		}
+	}
+
 	return (
 		<div
 			style={{
@@ -24,12 +134,254 @@ const McpView = ({ onDone }: McpViewProps) => {
 					alignItems: "center",
 					padding: "10px 17px 10px 20px",
 				}}>
-				<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>MCP</h3>
+				<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>MCP Servers</h3>
 				<VSCodeButton onClick={onDone}>Done</VSCodeButton>
 			</div>
-			<div style={{ padding: "20px", display: "flex", justifyContent: "center" }}>
-				<VSCodeButton>Add Server</VSCodeButton>
+			<p style={{ padding: "0 20px", color: "var(--vscode-foreground)", fontSize: "13px" }}>
+				MCP (Model Context Protocol) enables AI models to access external tools and data through standardized
+				interfaces. Add MCP servers to extend Claude's capabilities with custom functionality and real-time data
+				access.
+			</p>
+
+			{/* Server List */}
+			<div style={{ flex: 1, overflow: "auto", padding: "0 20px" }}>
+				{servers.map((server) => (
+					<ServerRow key={server.name} server={server} />
+				))}
+			</div>
+
+			{/* Add Server UI */}
+			<div style={{ padding: "20px" }}>
+				{isAdding ? (
+					<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
+						<VSCodeTextArea
+							rows={4}
+							placeholder='{"mcpServers": {"server-name": {"command": "...", "args": [...]}}}'
+							value={configInput}
+							onChange={(e) => setConfigInput((e.target as HTMLTextAreaElement).value)}
+						/>
+						<div style={{ display: "flex", gap: "10px" }}>
+							<VSCodeButton style={{ flex: 1 }} onClick={handleAddServer}>
+								Add Server
+							</VSCodeButton>
+							<VSCodeButton style={{ flex: 1 }} appearance="secondary" onClick={() => setIsAdding(false)}>
+								Cancel
+							</VSCodeButton>
+						</div>
+					</div>
+				) : (
+					<VSCodeButton style={{ width: "100%" }} onClick={() => setIsAdding(true)}>
+						<span className="codicon codicon-add" style={{ marginRight: "6px" }}></span>
+						Add MCP Server
+					</VSCodeButton>
+				)}
+			</div>
+		</div>
+	)
+}
+
+// Server Row Component
+const ServerRow = ({ server }: { server: McpServer }) => {
+	const [isExpanded, setIsExpanded] = useState(false)
+	const [isEditing, setIsEditing] = useState(false)
+	const [editConfig, setEditConfig] = useState(server.config)
+
+	const getStatusColor = () => {
+		switch (server.status) {
+			case "connected":
+				return "var(--vscode-testing-iconPassed)"
+			case "connecting":
+				return "var(--vscode-charts-yellow)"
+			case "disconnected":
+				return "var(--vscode-testing-iconFailed)"
+		}
+	}
+
+	const handleSaveConfig = () => {
+		try {
+			JSON.parse(editConfig) // Validate JSON
+			// Here you would update the server config
+			setIsEditing(false)
+		} catch (e) {
+			console.error("Invalid JSON config:", e)
+		}
+	}
+
+	// Don't allow expansion if server has error
+	const handleRowClick = () => {
+		if (!server.error) {
+			setIsExpanded(!isExpanded)
+		}
+	}
+
+	return (
+		<div style={{ marginBottom: "10px" }}>
+			<div
+				style={{
+					display: "flex",
+					alignItems: "center",
+					padding: "8px",
+					background: "var(--vscode-list-hoverBackground)",
+					cursor: server.error ? "default" : "pointer",
+					borderRadius: isExpanded || server.error ? "4px 4px 0 0" : "4px",
+				}}
+				onClick={handleRowClick}>
+				{!server.error && (
+					<span
+						className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
+						style={{ marginRight: "8px" }}
+					/>
+				)}
+				<span style={{ flex: 1 }}>{server.name}</span>
+				<div
+					style={{
+						width: "8px",
+						height: "8px",
+						borderRadius: "50%",
+						background: getStatusColor(),
+						marginLeft: "8px",
+					}}
+				/>
 			</div>
+
+			{server.error ? (
+				<div
+					style={{
+						padding: "8px",
+						fontSize: "13px",
+						color: "var(--vscode-testing-iconFailed)",
+						background: "var(--vscode-list-hoverBackground)",
+						borderRadius: "0 0 4px 4px",
+					}}>
+					{server.error}
+				</div>
+			) : (
+				isExpanded && (
+					<div
+						style={{
+							background: "var(--vscode-list-hoverBackground)",
+							padding: "0 12px 12px 12px",
+							fontSize: "13px",
+							borderRadius: "0 0 4px 4px",
+						}}>
+						<VSCodePanels>
+							<VSCodePanelTab id="tools">Tools ({server.tools?.length || 0})</VSCodePanelTab>
+							<VSCodePanelTab id="resources">Resources ({server.resources?.length || 0})</VSCodePanelTab>
+
+							<VSCodePanelView id="tools-view">
+								{server.tools && server.tools.length > 0 ? (
+									<div style={{ display: "flex", flexDirection: "column", gap: "3px" }}>
+										{server.tools.map((tool) => (
+											<div
+												key={tool.name}
+												style={{
+													padding: "8px 0",
+												}}>
+												<div style={{ display: "flex" }}>
+													<span
+														className="codicon codicon-symbol-method"
+														style={{ marginRight: "6px" }}></span>
+													<span style={{ fontWeight: 500 }}>{tool.name}</span>
+												</div>
+												<div
+													style={{
+														marginLeft: "0px",
+														marginTop: "4px",
+														opacity: 0.8,
+														fontSize: "12px",
+													}}>
+													{tool.description}
+												</div>
+											</div>
+										))}
+									</div>
+								) : (
+									<div style={{ padding: "10px 0", color: "var(--vscode-descriptionForeground)" }}>
+										No tools found
+									</div>
+								)}
+							</VSCodePanelView>
+
+							{/* Resources Panel View */}
+							<VSCodePanelView id="resources-view">
+								{server.resources && server.resources.length > 0 ? (
+									<div style={{ display: "flex", flexDirection: "column", gap: "3px" }}>
+										{server.resources.map((resource) => (
+											<div
+												key={resource.uri}
+												style={{
+													padding: "8px 0",
+												}}>
+												<div style={{ display: "flex" }}>
+													<span
+														className="codicon codicon-symbol-file"
+														style={{ marginRight: "6px" }}></span>
+													<span style={{ fontWeight: 500 }}>{resource.name}</span>
+												</div>
+												<div style={{ marginTop: "6px", fontSize: "12px" }}>
+													<code
+														style={{
+															color: "var(--vscode-textPreformat-foreground)",
+															background: "var(--vscode-textPreformat-background)",
+															padding: "2px 4px",
+															borderRadius: "3px",
+														}}>
+														{resource.uri}
+													</code>
+												</div>
+											</div>
+										))}
+									</div>
+								) : (
+									<div style={{ padding: "10px 0", color: "var(--vscode-descriptionForeground)" }}>
+										No resources found
+									</div>
+								)}
+							</VSCodePanelView>
+						</VSCodePanels>
+
+						{/* Edit/Remove Buttons */}
+						<div style={{ display: "flex", flexDirection: "column", gap: "8px", marginTop: "0px" }}>
+							{isEditing ? (
+								<>
+									<VSCodeTextArea
+										value={editConfig}
+										onChange={(e) => setEditConfig((e.target as HTMLTextAreaElement).value)}
+										style={{ width: "100%" }}
+									/>
+									<div style={{ display: "flex", gap: "8px" }}>
+										<VSCodeButton onClick={handleSaveConfig} style={{ flex: 1 }}>
+											Save
+										</VSCodeButton>
+										<VSCodeButton
+											appearance="secondary"
+											onClick={() => setIsEditing(false)}
+											style={{ flex: 1 }}>
+											Cancel
+										</VSCodeButton>
+									</div>
+								</>
+							) : (
+								<div style={{ display: "flex", gap: "8px" }}>
+									<VSCodeButton
+										appearance="secondary"
+										onClick={() => setIsEditing(true)}
+										style={{ flex: 1 }}>
+										Edit
+									</VSCodeButton>
+									<VSCodeButton
+										appearance="secondary"
+										style={{
+											flex: 1,
+										}}>
+										Remove
+									</VSCodeButton>
+								</div>
+							)}
+						</div>
+					</div>
+				)
+			)}
 		</div>
 	)
 }