diff --git a/client/package.json b/client/package.json index 8dc5a90..a415b18 100644 --- a/client/package.json +++ b/client/package.json @@ -32,6 +32,7 @@ "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toast": "^1.2.6", "@types/prismjs": "^1.26.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -42,7 +43,6 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-simple-code-editor": "^0.14.1", - "react-toastify": "^10.0.6", "serve-handler": "^6.1.6", "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7", diff --git a/client/src/App.tsx b/client/src/App.tsx index e560d55..5c09e8e 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -33,7 +33,6 @@ import { MessageSquare, } from "lucide-react"; -import { toast } from "react-toastify"; import { z } from "zod"; import "./App.css"; import ConsoleTab from "./components/ConsoleTab"; @@ -47,13 +46,14 @@ import Sidebar from "./components/Sidebar"; import ToolsTab from "./components/ToolsTab"; import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants"; import { InspectorConfig } from "./lib/configurationTypes"; - +import { useToast } from "@/hooks/use-toast"; const params = new URLSearchParams(window.location.search); const PROXY_PORT = params.get("proxyPort") ?? "6277"; const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`; const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1"; const App = () => { + const { toast } = useToast(); // Handle OAuth callback route const [resources, setResources] = useState([]); const [resourceTemplates, setResourceTemplates] = useState< @@ -208,11 +208,14 @@ const App = () => { newUrl.searchParams.delete("serverUrl"); window.history.replaceState({}, "", newUrl.toString()); // Show success toast for OAuth - toast.success("Successfully authenticated with OAuth"); + toast({ + title: "Success", + description: "Successfully authenticated with OAuth", + }); // Connect to the server connectMcpServer(); } - }, [connectMcpServer]); + }, [connectMcpServer, toast]); useEffect(() => { fetch(`${PROXY_SERVER_URL}/config`) diff --git a/client/src/components/History.tsx b/client/src/components/History.tsx index b03d1f4..0b05b55 100644 --- a/client/src/components/History.tsx +++ b/client/src/components/History.tsx @@ -1,5 +1,4 @@ import { ServerNotification } from "@modelcontextprotocol/sdk/types.js"; -import { Copy } from "lucide-react"; import { useState } from "react"; import JsonView from "./JsonView"; @@ -25,10 +24,6 @@ const HistoryAndNotifications = ({ setExpandedNotifications((prev) => ({ ...prev, [index]: !prev[index] })); }; - const copyToClipboard = (text: string) => { - navigator.clipboard.writeText(text); - }; - return (
@@ -68,16 +63,12 @@ const HistoryAndNotifications = ({ Request: - -
-
-
+ +
{request.response && (
@@ -85,16 +76,11 @@ const HistoryAndNotifications = ({ Response: - -
-
-
+ )} @@ -134,20 +120,11 @@ const HistoryAndNotifications = ({ Details: - - -
-
+ )} diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index e2922f0..b59f3cc 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -1,11 +1,16 @@ -import { useState, memo } from "react"; +import { useState, memo, useMemo, useCallback, useEffect } from "react"; import { JsonValue } from "./DynamicJsonForm"; import clsx from "clsx"; +import { Copy, CheckCheck } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { useToast } from "@/hooks/use-toast"; interface JsonViewProps { data: unknown; name?: string; initialExpandDepth?: number; + className?: string; + withCopyButton?: boolean; } function tryParseJson(str: string): { success: boolean; data: JsonValue } { @@ -24,22 +29,79 @@ function tryParseJson(str: string): { success: boolean; data: JsonValue } { } const JsonView = memo( - ({ data, name, initialExpandDepth = 3 }: JsonViewProps) => { - const normalizedData = - typeof data === "string" + ({ + data, + name, + initialExpandDepth = 3, + className, + withCopyButton = true, + }: JsonViewProps) => { + const { toast } = useToast(); + const [copied, setCopied] = useState(false); + + useEffect(() => { + let timeoutId: NodeJS.Timeout; + if (copied) { + timeoutId = setTimeout(() => { + setCopied(false); + }, 500); + } + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, [copied]); + + const normalizedData = useMemo(() => { + return typeof data === "string" ? tryParseJson(data).success ? tryParseJson(data).data : data : data; + }, [data]); + + const handleCopy = useCallback(() => { + try { + navigator.clipboard.writeText( + typeof normalizedData === "string" + ? normalizedData + : JSON.stringify(normalizedData, null, 2), + ); + setCopied(true); + } catch (error) { + toast({ + title: "Error", + description: `There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`, + variant: "destructive", + }); + } + }, [toast, normalizedData]); return ( -
- +
+ {withCopyButton && ( + + )} +
+ +
); }, diff --git a/client/src/components/PromptsTab.tsx b/client/src/components/PromptsTab.tsx index b42cf77..48c847d 100644 --- a/client/src/components/PromptsTab.tsx +++ b/client/src/components/PromptsTab.tsx @@ -152,9 +152,7 @@ const PromptsTab = ({ Get Prompt {promptContent && ( -
- -
+ )}
) : ( diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index 443a902..2a10824 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -215,9 +215,10 @@ const ResourcesTab = ({ {error} ) : selectedResource ? ( -
- -
+ ) : selectedTemplate ? (

diff --git a/client/src/components/SamplingTab.tsx b/client/src/components/SamplingTab.tsx index 21fc7dd..a72ea7d 100644 --- a/client/src/components/SamplingTab.tsx +++ b/client/src/components/SamplingTab.tsx @@ -44,9 +44,11 @@ const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {

Recent Requests

{pendingRequests.map((request) => (
-
- -
+ +