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 3b9ec25..a98a11c 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,75 @@ 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(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) => (
-
-
-
+
+