Merge branch 'main' into fix-reconnect

This commit is contained in:
Cliff Hall
2025-04-09 14:07:11 -04:00
committed by GitHub
3 changed files with 80 additions and 17 deletions

View File

@@ -12,6 +12,7 @@ interface JsonViewProps {
initialExpandDepth?: number; initialExpandDepth?: number;
className?: string; className?: string;
withCopyButton?: boolean; withCopyButton?: boolean;
isError?: boolean;
} }
const JsonView = memo( const JsonView = memo(
@@ -21,6 +22,7 @@ const JsonView = memo(
initialExpandDepth = 3, initialExpandDepth = 3,
className, className,
withCopyButton = true, withCopyButton = true,
isError = false,
}: JsonViewProps) => { }: JsonViewProps) => {
const { toast } = useToast(); const { toast } = useToast();
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
@@ -86,6 +88,7 @@ const JsonView = memo(
name={name} name={name}
depth={0} depth={0}
initialExpandDepth={initialExpandDepth} initialExpandDepth={initialExpandDepth}
isError={isError}
/> />
</div> </div>
</div> </div>
@@ -100,17 +103,25 @@ interface JsonNodeProps {
name?: string; name?: string;
depth: number; depth: number;
initialExpandDepth: number; initialExpandDepth: number;
isError?: boolean;
} }
const JsonNode = memo( const JsonNode = memo(
({ data, name, depth = 0, initialExpandDepth }: JsonNodeProps) => { ({
data,
name,
depth = 0,
initialExpandDepth,
isError = false,
}: JsonNodeProps) => {
const [isExpanded, setIsExpanded] = useState(depth < initialExpandDepth); const [isExpanded, setIsExpanded] = useState(depth < initialExpandDepth);
const [typeStyleMap] = useState<Record<string, string>>({ const [typeStyleMap] = useState<Record<string, string>>({
number: "text-blue-600", number: "text-blue-600",
boolean: "text-amber-600", boolean: "text-amber-600",
null: "text-purple-600", null: "text-purple-600",
undefined: "text-gray-600", undefined: "text-gray-600",
string: "text-green-600 break-all whitespace-pre-wrap", string: "text-green-600 group-hover:text-green-500",
error: "text-red-600 group-hover:text-red-500",
default: "text-gray-700", default: "text-gray-700",
}); });
const dataType = getDataType(data); const dataType = getDataType(data);
@@ -214,7 +225,14 @@ const JsonNode = memo(
{name}: {name}:
</span> </span>
)} )}
<pre className={typeStyleMap.string}>"{value}"</pre> <pre
className={clsx(
typeStyleMap.string,
"break-all whitespace-pre-wrap",
)}
>
"{value}"
</pre>
</div> </div>
); );
} }
@@ -228,8 +246,8 @@ const JsonNode = memo(
)} )}
<pre <pre
className={clsx( className={clsx(
typeStyleMap.string, isError ? typeStyleMap.error : typeStyleMap.string,
"cursor-pointer group-hover:text-green-500", "cursor-pointer break-all whitespace-pre-wrap",
)} )}
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
title={isExpanded ? "Click to collapse" : "Click to expand"} title={isExpanded ? "Click to collapse" : "Click to expand"}

View File

@@ -103,14 +103,19 @@ const Sidebar = ({
<div className="p-4 flex-1 overflow-auto"> <div className="p-4 flex-1 overflow-auto">
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Transport Type</label> <label
className="text-sm font-medium"
htmlFor="transport-type-select"
>
Transport Type
</label>
<Select <Select
value={transportType} value={transportType}
onValueChange={(value: "stdio" | "sse") => onValueChange={(value: "stdio" | "sse") =>
setTransportType(value) setTransportType(value)
} }
> >
<SelectTrigger> <SelectTrigger id="transport-type-select">
<SelectValue placeholder="Select transport type" /> <SelectValue placeholder="Select transport type" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -123,8 +128,11 @@ const Sidebar = ({
{transportType === "stdio" ? ( {transportType === "stdio" ? (
<> <>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Command</label> <label className="text-sm font-medium" htmlFor="command-input">
Command
</label>
<Input <Input
id="command-input"
placeholder="Command" placeholder="Command"
value={command} value={command}
onChange={(e) => setCommand(e.target.value)} onChange={(e) => setCommand(e.target.value)}
@@ -132,8 +140,14 @@ const Sidebar = ({
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Arguments</label> <label
className="text-sm font-medium"
htmlFor="arguments-input"
>
Arguments
</label>
<Input <Input
id="arguments-input"
placeholder="Arguments (space-separated)" placeholder="Arguments (space-separated)"
value={args} value={args}
onChange={(e) => setArgs(e.target.value)} onChange={(e) => setArgs(e.target.value)}
@@ -144,8 +158,11 @@ const Sidebar = ({
) : ( ) : (
<> <>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">URL</label> <label className="text-sm font-medium" htmlFor="sse-url-input">
URL
</label>
<Input <Input
id="sse-url-input"
placeholder="URL" placeholder="URL"
value={sseUrl} value={sseUrl}
onChange={(e) => setSseUrl(e.target.value)} onChange={(e) => setSseUrl(e.target.value)}
@@ -157,6 +174,7 @@ const Sidebar = ({
variant="outline" variant="outline"
onClick={() => setShowBearerToken(!showBearerToken)} onClick={() => setShowBearerToken(!showBearerToken)}
className="flex items-center w-full" className="flex items-center w-full"
aria-expanded={showBearerToken}
> >
{showBearerToken ? ( {showBearerToken ? (
<ChevronDown className="w-4 h-4 mr-2" /> <ChevronDown className="w-4 h-4 mr-2" />
@@ -167,8 +185,14 @@ const Sidebar = ({
</Button> </Button>
{showBearerToken && ( {showBearerToken && (
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Bearer Token</label> <label
className="text-sm font-medium"
htmlFor="bearer-token-input"
>
Bearer Token
</label>
<Input <Input
id="bearer-token-input"
placeholder="Bearer Token" placeholder="Bearer Token"
value={bearerToken} value={bearerToken}
onChange={(e) => setBearerToken(e.target.value)} onChange={(e) => setBearerToken(e.target.value)}
@@ -187,6 +211,7 @@ const Sidebar = ({
onClick={() => setShowEnvVars(!showEnvVars)} onClick={() => setShowEnvVars(!showEnvVars)}
className="flex items-center w-full" className="flex items-center w-full"
data-testid="env-vars-button" data-testid="env-vars-button"
aria-expanded={showEnvVars}
> >
{showEnvVars ? ( {showEnvVars ? (
<ChevronDown className="w-4 h-4 mr-2" /> <ChevronDown className="w-4 h-4 mr-2" />
@@ -201,6 +226,7 @@ const Sidebar = ({
<div key={idx} className="space-y-2 pb-4"> <div key={idx} className="space-y-2 pb-4">
<div className="flex gap-2"> <div className="flex gap-2">
<Input <Input
aria-label={`Environment variable key ${idx + 1}`}
placeholder="Key" placeholder="Key"
value={key} value={key}
onChange={(e) => { onChange={(e) => {
@@ -243,6 +269,7 @@ const Sidebar = ({
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Input <Input
aria-label={`Environment variable value ${idx + 1}`}
type={shownEnvVars.has(key) ? "text" : "password"} type={shownEnvVars.has(key) ? "text" : "password"}
placeholder="Value" placeholder="Value"
value={value} value={value}
@@ -309,6 +336,7 @@ const Sidebar = ({
onClick={() => setShowConfig(!showConfig)} onClick={() => setShowConfig(!showConfig)}
className="flex items-center w-full" className="flex items-center w-full"
data-testid="config-button" data-testid="config-button"
aria-expanded={showConfig}
> >
{showConfig ? ( {showConfig ? (
<ChevronDown className="w-4 h-4 mr-2" /> <ChevronDown className="w-4 h-4 mr-2" />
@@ -325,7 +353,10 @@ const Sidebar = ({
return ( return (
<div key={key} className="space-y-2"> <div key={key} className="space-y-2">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<label className="text-sm font-medium text-green-600 break-all"> <label
className="text-sm font-medium text-green-600 break-all"
htmlFor={`${configKey}-input`}
>
{configItem.label} {configItem.label}
</label> </label>
<Tooltip> <Tooltip>
@@ -339,6 +370,7 @@ const Sidebar = ({
</div> </div>
{typeof configItem.value === "number" ? ( {typeof configItem.value === "number" ? (
<Input <Input
id={`${configKey}-input`}
type="number" type="number"
data-testid={`${configKey}-input`} data-testid={`${configKey}-input`}
value={configItem.value} value={configItem.value}
@@ -365,7 +397,7 @@ const Sidebar = ({
setConfig(newConfig); setConfig(newConfig);
}} }}
> >
<SelectTrigger> <SelectTrigger id={`${configKey}-input`}>
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -375,6 +407,7 @@ const Sidebar = ({
</Select> </Select>
) : ( ) : (
<Input <Input
id={`${configKey}-input`}
data-testid={`${configKey}-input`} data-testid={`${configKey}-input`}
value={configItem.value} value={configItem.value}
onChange={(e) => { onChange={(e) => {
@@ -454,14 +487,19 @@ const Sidebar = ({
{loggingSupported && connectionStatus === "connected" && ( {loggingSupported && connectionStatus === "connected" && (
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Logging Level</label> <label
className="text-sm font-medium"
htmlFor="logging-level-select"
>
Logging Level
</label>
<Select <Select
value={logLevel} value={logLevel}
onValueChange={(value: LoggingLevel) => onValueChange={(value: LoggingLevel) =>
sendLogLevelRequest(value) sendLogLevelRequest(value)
} }
> >
<SelectTrigger> <SelectTrigger id="logging-level-select">
<SelectValue placeholder="Select logging level" /> <SelectValue placeholder="Select logging level" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>

View File

@@ -69,11 +69,18 @@ const ToolsTab = ({
return ( return (
<> <>
<h4 className="font-semibold mb-2"> <h4 className="font-semibold mb-2">
Tool Result: {isError ? "Error" : "Success"} Tool Result:{" "}
{isError ? (
<span className="text-red-600 font-semibold">Error</span>
) : (
<span className="text-green-600 font-semibold">Success</span>
)}
</h4> </h4>
{structuredResult.content.map((item, index) => ( {structuredResult.content.map((item, index) => (
<div key={index} className="mb-2"> <div key={index} className="mb-2">
{item.type === "text" && <JsonView data={item.text} />} {item.type === "text" && (
<JsonView data={item.text} isError={isError} />
)}
{item.type === "image" && ( {item.type === "image" && (
<img <img
src={`data:${item.mimeType};base64,${item.data}`} src={`data:${item.mimeType};base64,${item.data}`}