refine UI layout, theming, and component consistency across screens
- Add MCP logo (light/dark variants) to ConnectedLayout header - Replace Tabs with SegmentedControl in header, split into 1/3 sections - Wrap all screens in Container for consistent margins - Use Card instead of Paper for panel backgrounds across all screens - Add SubscribeButton atom, CopyButton for resource URI - Move annotations from ResourceListItem to ResourcePreviewPanel - Improve AnnotationBadge colors for dark mode visibility - Add Tools, Resources, and Prompts headers to list panels - Add ConnectedLayout stories for Tools, Resources, and Prompts tabs - Remove unused onCopy prop and copy button from PromptMessagesDisplay Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
08c42a2e62
commit
f674ce0c22
22 changed files with 357 additions and 157 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { Badge } from "@mantine/core";
|
import { Badge, useComputedColorScheme } from "@mantine/core";
|
||||||
|
|
||||||
export type AnnotationVariant =
|
export type AnnotationVariant =
|
||||||
| "audience"
|
| "audience"
|
||||||
|
|
@ -17,11 +17,11 @@ const variantMap: Record<
|
||||||
AnnotationVariant,
|
AnnotationVariant,
|
||||||
{ color: string; variant: string }
|
{ color: string; variant: string }
|
||||||
> = {
|
> = {
|
||||||
audience: { color: "gray", variant: "outline" },
|
audience: { color: "blue", variant: "outline" },
|
||||||
readOnly: { color: "dark", variant: "filled" },
|
readOnly: { color: "green", variant: "filled" },
|
||||||
destructive: { color: "red", variant: "outline" },
|
destructive: { color: "red", variant: "filled" },
|
||||||
longRun: { color: "yellow", variant: "outline" },
|
longRun: { color: "yellow", variant: "filled" },
|
||||||
priority: { color: "orange", variant: "light" },
|
priority: { color: "orange", variant: "filled" },
|
||||||
default: { color: "gray", variant: "light" },
|
default: { color: "gray", variant: "light" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -29,10 +29,13 @@ export function AnnotationBadge({
|
||||||
label,
|
label,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
}: AnnotationBadgeProps) {
|
}: AnnotationBadgeProps) {
|
||||||
|
const colorScheme = useComputedColorScheme();
|
||||||
const { color, variant: badgeVariant } = variantMap[variant];
|
const { color, variant: badgeVariant } = variantMap[variant];
|
||||||
|
const textColor =
|
||||||
|
(variant === "priority" || variant === "destructive") && colorScheme === "dark" ? "black" : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge color={color} variant={badgeVariant}>
|
<Badge color={color} variant={badgeVariant} c={textColor}>
|
||||||
{label}
|
{label}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import { fn } from "storybook/test";
|
||||||
|
import { SubscribeButton } from "./SubscribeButton";
|
||||||
|
|
||||||
|
const meta: Meta<typeof SubscribeButton> = {
|
||||||
|
title: "Atoms/SubscribeButton",
|
||||||
|
component: SubscribeButton,
|
||||||
|
args: {
|
||||||
|
onToggle: fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof SubscribeButton>;
|
||||||
|
|
||||||
|
export const Subscribed: Story = {
|
||||||
|
args: {
|
||||||
|
subscribed: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Unsubscribed: Story = {
|
||||||
|
args: {
|
||||||
|
subscribed: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Button } from "@mantine/core";
|
||||||
|
|
||||||
|
export interface SubscribeButtonProps {
|
||||||
|
subscribed: boolean;
|
||||||
|
onToggle: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SubscribeButton({
|
||||||
|
subscribed,
|
||||||
|
onToggle,
|
||||||
|
}: SubscribeButtonProps) {
|
||||||
|
return (
|
||||||
|
<Button variant="outline" size="sm" onClick={onToggle}>
|
||||||
|
{subscribed ? "Unsubscribe" : "Subscribe"}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,9 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
import { Text } from "@mantine/core";
|
import { Text } from "@mantine/core";
|
||||||
import { ConnectedLayout } from "./ConnectedLayout";
|
import { ConnectedLayout } from "./ConnectedLayout";
|
||||||
|
import { ToolsScreen } from "../../organisms/ToolsScreen/ToolsScreen";
|
||||||
|
import { ResourcesScreen } from "../../organisms/ResourcesScreen/ResourcesScreen";
|
||||||
|
import { PromptsScreen } from "../../organisms/PromptsScreen/PromptsScreen";
|
||||||
|
|
||||||
const allTabs = ["Tools", "Resources", "Prompts", "Logs", "Tasks", "History"];
|
const allTabs = ["Tools", "Resources", "Prompts", "Logs", "Tasks", "History"];
|
||||||
|
|
||||||
|
|
@ -27,14 +30,153 @@ type Story = StoryObj<typeof ConnectedLayout>;
|
||||||
export const ToolsActive: Story = {
|
export const ToolsActive: Story = {
|
||||||
args: {
|
args: {
|
||||||
activeTab: "Tools",
|
activeTab: "Tools",
|
||||||
children: <Text>Tools screen content</Text>,
|
children: (
|
||||||
|
<ToolsScreen
|
||||||
|
tools={[
|
||||||
|
{ name: "send_message", title: "Send Message", selected: false, onClick: fn() },
|
||||||
|
{ name: "create_record", title: "Create Record", selected: true, onClick: fn() },
|
||||||
|
{ name: "delete_records", selected: false, onClick: fn() },
|
||||||
|
{ name: "list_users", selected: false, onClick: fn() },
|
||||||
|
{ name: "batch_process", selected: false, onClick: fn() },
|
||||||
|
]}
|
||||||
|
selectedTool={{
|
||||||
|
name: "create_record",
|
||||||
|
title: "Create Record",
|
||||||
|
description: "Creates a new record with the given parameters",
|
||||||
|
schema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
title: { type: "string", description: "Record title" },
|
||||||
|
count: { type: "number", description: "Number of items" },
|
||||||
|
enabled: { type: "boolean", description: "Whether the record is active" },
|
||||||
|
},
|
||||||
|
required: ["title"],
|
||||||
|
},
|
||||||
|
formValues: {},
|
||||||
|
isExecuting: false,
|
||||||
|
onFormChange: fn(),
|
||||||
|
onExecute: fn(),
|
||||||
|
onCancel: fn(),
|
||||||
|
}}
|
||||||
|
result={{
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify(
|
||||||
|
{ id: 42, title: "New Record", count: 5, enabled: true, createdAt: "2026-03-17T12:00:00Z" },
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onClear: fn(),
|
||||||
|
}}
|
||||||
|
searchText=""
|
||||||
|
listChanged={false}
|
||||||
|
onSearchChange={fn()}
|
||||||
|
onRefreshList={fn()}
|
||||||
|
onSelectTool={fn()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ResourcesActive: Story = {
|
export const ResourcesActive: Story = {
|
||||||
args: {
|
args: {
|
||||||
activeTab: "Resources",
|
activeTab: "Resources",
|
||||||
children: <Text>Resources screen content</Text>,
|
children: (
|
||||||
|
<ResourcesScreen
|
||||||
|
resources={[
|
||||||
|
{
|
||||||
|
name: "config.json",
|
||||||
|
uri: "file:///config.json",
|
||||||
|
annotations: { audience: "developer", priority: 0.8 },
|
||||||
|
selected: true,
|
||||||
|
onClick: fn(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "README.md",
|
||||||
|
uri: "file:///README.md",
|
||||||
|
selected: false,
|
||||||
|
onClick: fn(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "schema.sql",
|
||||||
|
uri: "file:///schema.sql",
|
||||||
|
annotations: { priority: 0.5 },
|
||||||
|
selected: false,
|
||||||
|
onClick: fn(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
templates={[
|
||||||
|
{
|
||||||
|
template: "file:///users/{userId}/profile",
|
||||||
|
variables: { userId: "" },
|
||||||
|
onVariableChange: fn(),
|
||||||
|
onSubmit: fn(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
subscriptions={[
|
||||||
|
{ name: "config.json", lastUpdated: "2026-03-17T10:30:00Z" },
|
||||||
|
]}
|
||||||
|
selectedResource={{
|
||||||
|
uri: "file:///config.json",
|
||||||
|
mimeType: "application/json",
|
||||||
|
annotations: { audience: "developer", priority: 0.8 },
|
||||||
|
content: JSON.stringify(
|
||||||
|
{ name: "my-project", version: "1.0.0", settings: { debug: true, logLevel: "info" } },
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
lastUpdated: "2026-03-17T10:30:00Z",
|
||||||
|
isSubscribed: true,
|
||||||
|
onSubscribe: fn(),
|
||||||
|
onUnsubscribe: fn(),
|
||||||
|
}}
|
||||||
|
listChanged={false}
|
||||||
|
searchText=""
|
||||||
|
onSearchChange={fn()}
|
||||||
|
onRefreshList={fn()}
|
||||||
|
onSelectResource={fn()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PromptsActive: Story = {
|
||||||
|
args: {
|
||||||
|
activeTab: "Prompts",
|
||||||
|
children: (
|
||||||
|
<PromptsScreen
|
||||||
|
promptForm={{
|
||||||
|
prompts: [
|
||||||
|
{ name: "summarize", description: "Summarize a document" },
|
||||||
|
{ name: "translate", description: "Translate text to another language" },
|
||||||
|
{ name: "code-review", description: "Review code for issues" },
|
||||||
|
],
|
||||||
|
selectedPrompt: "translate",
|
||||||
|
arguments: [
|
||||||
|
{ name: "text", required: true, description: "The text to translate" },
|
||||||
|
{ name: "targetLanguage", required: true, description: "Target language code" },
|
||||||
|
],
|
||||||
|
argumentValues: {
|
||||||
|
text: "Hello, how are you?",
|
||||||
|
targetLanguage: "es",
|
||||||
|
},
|
||||||
|
onSelectPrompt: fn(),
|
||||||
|
onArgumentChange: fn(),
|
||||||
|
onGetPrompt: fn(),
|
||||||
|
}}
|
||||||
|
messages={{
|
||||||
|
messages: [
|
||||||
|
{ role: "user", content: 'Translate the following text to Spanish: "Hello, how are you?"' },
|
||||||
|
{ role: "assistant", content: "Hola, como estas?" },
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
listChanged={false}
|
||||||
|
onRefreshList={fn()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -47,7 +189,7 @@ export const LimitedTabs: Story = {
|
||||||
|
|
||||||
export const LongServerName: Story = {
|
export const LongServerName: Story = {
|
||||||
args: {
|
args: {
|
||||||
serverName: "my-very-long-server-name-that-might-overflow",
|
serverName: "my-very-long-server-name-that-might-overflow-is",
|
||||||
children: <Text>Tools screen content</Text>,
|
children: <Text>Tools screen content</Text>,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { ActionIcon, AppShell, Button, Group, Tabs, Text } from "@mantine/core";
|
import { ActionIcon, AppShell, Button, Group, Image, SegmentedControl, Text } from "@mantine/core";
|
||||||
import { useComputedColorScheme } from "@mantine/core";
|
import { useComputedColorScheme } from "@mantine/core";
|
||||||
import { StatusIndicator } from "../../atoms/StatusIndicator/StatusIndicator";
|
import { StatusIndicator } from "../../atoms/StatusIndicator/StatusIndicator";
|
||||||
|
import mcpLogo from "../../../theme/assets/MCP.svg";
|
||||||
|
import mcpLogoDark from "../../../theme/assets/MCP-dark.svg";
|
||||||
|
|
||||||
export interface ConnectedLayoutProps {
|
export interface ConnectedLayoutProps {
|
||||||
serverName: string;
|
serverName: string;
|
||||||
|
|
@ -18,6 +20,8 @@ export interface ConnectedLayoutProps {
|
||||||
const ServerName = Text.withProps({
|
const ServerName = Text.withProps({
|
||||||
fw: 600,
|
fw: 600,
|
||||||
size: "lg",
|
size: "lg",
|
||||||
|
truncate: "end",
|
||||||
|
maw: "calc(100% - 40px)",
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ConnectedLayout({
|
export function ConnectedLayout({
|
||||||
|
|
@ -36,35 +40,23 @@ export function ConnectedLayout({
|
||||||
return (
|
return (
|
||||||
<AppShell header={{ height: 60 }} padding="md">
|
<AppShell header={{ height: 60 }} padding="md">
|
||||||
<AppShell.Header>
|
<AppShell.Header>
|
||||||
<Group h="100%" px="md" justify="space-between" wrap="nowrap">
|
<Group h="100%" px="md" wrap="nowrap" gap={0}>
|
||||||
<Group gap="md" wrap="nowrap">
|
<Group gap="md" wrap="nowrap" w="33.33%" miw={0}>
|
||||||
|
<Image src={colorScheme === "dark" ? mcpLogoDark : mcpLogo} alt="MCP" w={28} h={28} fit="contain" />
|
||||||
<ServerName>{serverName}</ServerName>
|
<ServerName>{serverName}</ServerName>
|
||||||
<StatusIndicator status={status} latencyMs={latencyMs} />
|
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Tabs
|
<Group w="33.33%" justify="center">
|
||||||
value={activeTab}
|
<SegmentedControl
|
||||||
onChange={(value) => value && onTabChange(value)}
|
value={activeTab}
|
||||||
variant="default"
|
onChange={onTabChange}
|
||||||
>
|
data={availableTabs}
|
||||||
<Tabs.List>
|
size="sm"
|
||||||
{availableTabs.map((tab) => (
|
/>
|
||||||
<Tabs.Tab key={tab} value={tab}>
|
</Group>
|
||||||
{tab}
|
|
||||||
</Tabs.Tab>
|
|
||||||
))}
|
|
||||||
</Tabs.List>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<Group gap="sm">
|
<Group gap="sm" w="33.33%" justify="flex-end">
|
||||||
<ActionIcon
|
<StatusIndicator status={status} latencyMs={latencyMs} />
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
aria-label="Toggle color scheme"
|
|
||||||
onClick={onToggleTheme}
|
|
||||||
>
|
|
||||||
{colorScheme === "dark" ? "\u2600" : "\u263E"}
|
|
||||||
</ActionIcon>
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="red"
|
color="red"
|
||||||
|
|
@ -73,6 +65,14 @@ export function ConnectedLayout({
|
||||||
>
|
>
|
||||||
Disconnect
|
Disconnect
|
||||||
</Button>
|
</Button>
|
||||||
|
<ActionIcon
|
||||||
|
variant="default"
|
||||||
|
size="lg"
|
||||||
|
aria-label="Toggle color scheme"
|
||||||
|
onClick={onToggleTheme}
|
||||||
|
>
|
||||||
|
{colorScheme === "dark" ? "\u2600" : "\u263E"}
|
||||||
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</AppShell.Header>
|
</AppShell.Header>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export function PromptArgumentsForm({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Title order={4}>Select Prompt</Title>
|
<Title order={4}>Prompts</Title>
|
||||||
<Select
|
<Select
|
||||||
data={prompts.map((p) => ({ value: p.name, label: p.name }))}
|
data={prompts.map((p) => ({ value: p.name, label: p.name }))}
|
||||||
value={selectedPrompt ?? null}
|
value={selectedPrompt ?? null}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@ import { PromptMessagesDisplay } from "./PromptMessagesDisplay";
|
||||||
const meta: Meta<typeof PromptMessagesDisplay> = {
|
const meta: Meta<typeof PromptMessagesDisplay> = {
|
||||||
title: "Molecules/PromptMessagesDisplay",
|
title: "Molecules/PromptMessagesDisplay",
|
||||||
component: PromptMessagesDisplay,
|
component: PromptMessagesDisplay,
|
||||||
args: {
|
args: {},
|
||||||
onCopy: fn(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, Stack, Text, Title } from "@mantine/core";
|
import { Stack, Text, Title } from "@mantine/core";
|
||||||
import { MessageBubble } from "../../atoms/MessageBubble/MessageBubble";
|
import { MessageBubble } from "../../atoms/MessageBubble/MessageBubble";
|
||||||
|
|
||||||
export interface PromptMessage {
|
export interface PromptMessage {
|
||||||
|
|
@ -10,12 +10,10 @@ export interface PromptMessage {
|
||||||
|
|
||||||
export interface PromptMessagesDisplayProps {
|
export interface PromptMessagesDisplayProps {
|
||||||
messages: PromptMessage[];
|
messages: PromptMessage[];
|
||||||
onCopy: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PromptMessagesDisplay({
|
export function PromptMessagesDisplay({
|
||||||
messages,
|
messages,
|
||||||
onCopy,
|
|
||||||
}: PromptMessagesDisplayProps) {
|
}: PromptMessagesDisplayProps) {
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
|
|
@ -34,9 +32,6 @@ export function PromptMessagesDisplay({
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
<Button variant="light" size="sm" onClick={onCopy}>
|
|
||||||
Copy
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Group, Stack, Text, UnstyledButton } from "@mantine/core";
|
import { Text, UnstyledButton } from "@mantine/core";
|
||||||
import { AnnotationBadge } from "../../atoms/AnnotationBadge/AnnotationBadge";
|
|
||||||
|
|
||||||
export interface ResourceAnnotations {
|
export interface ResourceAnnotations {
|
||||||
audience?: string;
|
audience?: string;
|
||||||
|
|
@ -14,15 +13,8 @@ export interface ResourceListItemProps {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function priorityLabel(priority: number): string {
|
|
||||||
if (priority >= 0.7) return "priority: high";
|
|
||||||
if (priority >= 0.4) return "priority: medium";
|
|
||||||
return "priority: low";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ResourceListItem({
|
export function ResourceListItem({
|
||||||
name,
|
name,
|
||||||
annotations,
|
|
||||||
selected,
|
selected,
|
||||||
onClick,
|
onClick,
|
||||||
}: ResourceListItemProps) {
|
}: ResourceListItemProps) {
|
||||||
|
|
@ -34,25 +26,7 @@ export function ResourceListItem({
|
||||||
style={{ borderRadius: "var(--mantine-radius-md)" }}
|
style={{ borderRadius: "var(--mantine-radius-md)" }}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Stack gap="xs">
|
<Text fw={500}>{name}</Text>
|
||||||
<Text fw={500}>{name}</Text>
|
|
||||||
{annotations && (
|
|
||||||
<Group gap="xs">
|
|
||||||
{annotations.audience && (
|
|
||||||
<AnnotationBadge
|
|
||||||
label={annotations.audience}
|
|
||||||
variant="audience"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{annotations.priority !== undefined && (
|
|
||||||
<AnnotationBadge
|
|
||||||
label={priorityLabel(annotations.priority)}
|
|
||||||
variant="priority"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import { Button, Group, Stack, Text, Title } from "@mantine/core";
|
import { Group, Stack, Text, Title } from "@mantine/core";
|
||||||
|
import { AnnotationBadge } from "../../atoms/AnnotationBadge/AnnotationBadge";
|
||||||
import { ContentViewer } from "../../atoms/ContentViewer/ContentViewer";
|
import { ContentViewer } from "../../atoms/ContentViewer/ContentViewer";
|
||||||
|
import { CopyButton } from "../../atoms/CopyButton/CopyButton";
|
||||||
|
import { SubscribeButton } from "../../atoms/SubscribeButton/SubscribeButton";
|
||||||
|
|
||||||
export interface ResourcePreviewPanelProps {
|
export interface ResourcePreviewPanelProps {
|
||||||
uri: string;
|
uri: string;
|
||||||
|
|
@ -12,6 +15,12 @@ export interface ResourcePreviewPanelProps {
|
||||||
onUnsubscribe: () => void;
|
onUnsubscribe: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function priorityLabel(priority: number): string {
|
||||||
|
if (priority >= 0.7) return "priority: high";
|
||||||
|
if (priority >= 0.4) return "priority: medium";
|
||||||
|
return "priority: low";
|
||||||
|
}
|
||||||
|
|
||||||
function resolveContentType(mimeType: string): "json" | "image" | "text" {
|
function resolveContentType(mimeType: string): "json" | "image" | "text" {
|
||||||
if (mimeType === "application/json") return "json";
|
if (mimeType === "application/json") return "json";
|
||||||
if (mimeType.startsWith("image/")) return "image";
|
if (mimeType.startsWith("image/")) return "image";
|
||||||
|
|
@ -30,51 +39,53 @@ export function ResourcePreviewPanel({
|
||||||
}: ResourcePreviewPanelProps) {
|
}: ResourcePreviewPanelProps) {
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Title order={4}>Content Preview</Title>
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Text size="sm">
|
<Title order={4}>Resource</Title>
|
||||||
URI:{" "}
|
<Group gap="xs" wrap="nowrap">
|
||||||
<Text span c="blue">
|
<Text size="sm" c="blue" truncate="end">
|
||||||
{uri}
|
{uri}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
<CopyButton value={uri} />
|
||||||
<Text size="sm" c="dimmed">
|
</Group>
|
||||||
MIME: {mimeType}
|
</Group>
|
||||||
</Text>
|
|
||||||
{annotations && (
|
|
||||||
<Stack gap="xs">
|
|
||||||
<Text size="sm">Annotations:</Text>
|
|
||||||
{annotations.audience && (
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
Audience: {annotations.audience}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{annotations.priority !== undefined && (
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
Priority: {annotations.priority}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
<ContentViewer
|
<ContentViewer
|
||||||
type={resolveContentType(mimeType)}
|
type={resolveContentType(mimeType)}
|
||||||
content={content}
|
content={content}
|
||||||
mimeType={mimeType}
|
mimeType={mimeType}
|
||||||
copyable
|
copyable
|
||||||
/>
|
/>
|
||||||
<Group>
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Button
|
{lastUpdated ? (
|
||||||
variant="light"
|
<Text size="xs" c="dimmed">
|
||||||
size="sm"
|
Last updated: {lastUpdated}
|
||||||
onClick={isSubscribed ? onUnsubscribe : onSubscribe}
|
</Text>
|
||||||
>
|
) : (
|
||||||
{isSubscribed ? "Unsubscribe" : "Subscribe"}
|
<span />
|
||||||
</Button>
|
)}
|
||||||
</Group>
|
<Text size="sm" c="dimmed">
|
||||||
{lastUpdated && (
|
{mimeType}
|
||||||
<Text size="xs" c="dimmed" ta="right">
|
|
||||||
Last updated: {lastUpdated}
|
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Group gap="xs">
|
||||||
|
{annotations?.audience && (
|
||||||
|
<AnnotationBadge
|
||||||
|
label={annotations.audience}
|
||||||
|
variant="audience"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{annotations?.priority !== undefined && (
|
||||||
|
<AnnotationBadge
|
||||||
|
label={priorityLabel(annotations.priority)}
|
||||||
|
variant="priority"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
<SubscribeButton
|
||||||
|
subscribed={isSubscribed}
|
||||||
|
onToggle={isSubscribed ? onUnsubscribe : onSubscribe}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ export function ResourceTemplateInput({
|
||||||
<TextInput
|
<TextInput
|
||||||
key={part.value}
|
key={part.value}
|
||||||
size="sm"
|
size="sm"
|
||||||
label={part.value}
|
|
||||||
placeholder={part.value}
|
placeholder={part.value}
|
||||||
value={variables[part.value] ?? ""}
|
value={variables[part.value] ?? ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,6 @@ export function ToolDetailPanel({
|
||||||
<Text fw={700} size="lg" truncate="end">
|
<Text fw={700} size="lg" truncate="end">
|
||||||
{title ?? name}
|
{title ?? name}
|
||||||
</Text>
|
</Text>
|
||||||
{title && (
|
|
||||||
<Text size="sm" c="dimmed" truncate="end">
|
|
||||||
{name}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{hasAnnotations && (
|
{hasAnnotations && (
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
{annotations.audience && (
|
{annotations.audience && (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Container,
|
||||||
Grid,
|
Grid,
|
||||||
Group,
|
Group,
|
||||||
Paper,
|
Paper,
|
||||||
|
|
@ -30,6 +31,7 @@ export function LoggingScreen({
|
||||||
onCopyAll,
|
onCopyAll,
|
||||||
}: LoggingScreenProps) {
|
}: LoggingScreenProps) {
|
||||||
return (
|
return (
|
||||||
|
<Container size="xl" py="xl">
|
||||||
<Grid align="stretch">
|
<Grid align="stretch">
|
||||||
<Grid.Col span={3}>
|
<Grid.Col span={3}>
|
||||||
<Paper withBorder p="md" h="100%">
|
<Paper withBorder p="md" h="100%">
|
||||||
|
|
@ -69,5 +71,6 @@ export function LoggingScreen({
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@ export const WithResult: Story = {
|
||||||
content: "Hola, como estas?",
|
content: "Hola, como estas?",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onCopy: fn(),
|
|
||||||
},
|
},
|
||||||
listChanged: false,
|
listChanged: false,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Grid, Paper, Stack, Text } from "@mantine/core";
|
import { Card, Container, Grid, Stack, Text } from "@mantine/core";
|
||||||
import { PromptArgumentsForm } from "../../molecules/PromptArgumentsForm/PromptArgumentsForm";
|
import { PromptArgumentsForm } from "../../molecules/PromptArgumentsForm/PromptArgumentsForm";
|
||||||
import { PromptMessagesDisplay } from "../../molecules/PromptMessagesDisplay/PromptMessagesDisplay";
|
import { PromptMessagesDisplay } from "../../molecules/PromptMessagesDisplay/PromptMessagesDisplay";
|
||||||
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
|
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
|
||||||
|
|
@ -19,9 +19,10 @@ export function PromptsScreen({
|
||||||
onRefreshList,
|
onRefreshList,
|
||||||
}: PromptsScreenProps) {
|
}: PromptsScreenProps) {
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Container size="xl" py="xl">
|
||||||
|
<Grid align="stretch">
|
||||||
<Grid.Col span={5}>
|
<Grid.Col span={5}>
|
||||||
<Paper withBorder p="md">
|
<Card withBorder padding="lg" h="100%">
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<ListChangedIndicator
|
<ListChangedIndicator
|
||||||
visible={listChanged}
|
visible={listChanged}
|
||||||
|
|
@ -29,10 +30,10 @@ export function PromptsScreen({
|
||||||
/>
|
/>
|
||||||
<PromptArgumentsForm {...promptForm} />
|
<PromptArgumentsForm {...promptForm} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={7}>
|
<Grid.Col span={7}>
|
||||||
<Paper withBorder p="md">
|
<Card withBorder padding="lg" h="100%">
|
||||||
{messages ? (
|
{messages ? (
|
||||||
<PromptMessagesDisplay {...messages} />
|
<PromptMessagesDisplay {...messages} />
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -40,8 +41,9 @@ export function PromptsScreen({
|
||||||
Select a prompt and click Get Prompt to see messages
|
Select a prompt and click Get Prompt to see messages
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,6 @@ const selectedResourceProps: ResourcePreviewPanelProps = {
|
||||||
),
|
),
|
||||||
lastUpdated: "2026-03-17T10:30:00Z",
|
lastUpdated: "2026-03-17T10:30:00Z",
|
||||||
isSubscribed: true,
|
isSubscribed: true,
|
||||||
onCopy: fn(),
|
|
||||||
onSubscribe: fn(),
|
onSubscribe: fn(),
|
||||||
onUnsubscribe: fn(),
|
onUnsubscribe: fn(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Accordion, Grid, Paper, Stack, Text, TextInput } from "@mantine/core";
|
import { Accordion, Card, Container, Grid, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||||
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
|
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
|
||||||
import { ResourceListItem } from "../../molecules/ResourceListItem/ResourceListItem";
|
import { ResourceListItem } from "../../molecules/ResourceListItem/ResourceListItem";
|
||||||
import { ResourcePreviewPanel } from "../../molecules/ResourcePreviewPanel/ResourcePreviewPanel";
|
import { ResourcePreviewPanel } from "../../molecules/ResourcePreviewPanel/ResourcePreviewPanel";
|
||||||
|
|
@ -38,11 +38,19 @@ export function ResourcesScreen({
|
||||||
r.name.toLowerCase().includes(searchText.toLowerCase()),
|
r.name.toLowerCase().includes(searchText.toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const openSections = [
|
||||||
|
...(filteredResources.length > 0 ? ["resources"] : []),
|
||||||
|
...(templates.length > 0 ? ["templates"] : []),
|
||||||
|
...(subscriptions.length > 0 ? ["subscriptions"] : []),
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Container size="xl" py="xl">
|
||||||
|
<Grid align="stretch">
|
||||||
<Grid.Col span={4}>
|
<Grid.Col span={4}>
|
||||||
<Paper withBorder p="md">
|
<Card withBorder padding="lg" h="100%">
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
|
<Title order={4}>Resources</Title>
|
||||||
<ListChangedIndicator
|
<ListChangedIndicator
|
||||||
visible={listChanged}
|
visible={listChanged}
|
||||||
onRefresh={onRefreshList}
|
onRefresh={onRefreshList}
|
||||||
|
|
@ -54,11 +62,11 @@ export function ResourcesScreen({
|
||||||
/>
|
/>
|
||||||
<Accordion
|
<Accordion
|
||||||
multiple
|
multiple
|
||||||
defaultValue={["resources", "templates", "subscriptions"]}
|
defaultValue={openSections}
|
||||||
>
|
>
|
||||||
<Accordion.Item value="resources">
|
<Accordion.Item value="resources">
|
||||||
<Accordion.Control>
|
<Accordion.Control disabled={filteredResources.length === 0}>
|
||||||
Resources ({filteredResources.length})
|
URIs ({filteredResources.length})
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
|
|
@ -70,7 +78,7 @@ export function ResourcesScreen({
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
|
|
||||||
<Accordion.Item value="templates">
|
<Accordion.Item value="templates">
|
||||||
<Accordion.Control>
|
<Accordion.Control disabled={templates.length === 0}>
|
||||||
Templates ({templates.length})
|
Templates ({templates.length})
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
|
|
@ -86,7 +94,7 @@ export function ResourcesScreen({
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
|
|
||||||
<Accordion.Item value="subscriptions">
|
<Accordion.Item value="subscriptions">
|
||||||
<Accordion.Control>
|
<Accordion.Control disabled={subscriptions.length === 0}>
|
||||||
Subscriptions ({subscriptions.length})
|
Subscriptions ({subscriptions.length})
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
|
|
@ -107,11 +115,11 @@ export function ResourcesScreen({
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={8}>
|
<Grid.Col span={8}>
|
||||||
<Paper withBorder p="md">
|
<Card withBorder padding="lg">
|
||||||
{selectedResource ? (
|
{selectedResource ? (
|
||||||
<ResourcePreviewPanel {...selectedResource} />
|
<ResourcePreviewPanel {...selectedResource} />
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -119,8 +127,9 @@ export function ResourcesScreen({
|
||||||
Select a resource to preview
|
Select a resource to preview
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import {
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { ServerCard } from "../../molecules/ServerCard/ServerCard";
|
import { ServerCard } from "../../molecules/ServerCard/ServerCard";
|
||||||
import { AddServerMenu } from "../../molecules/AddServerMenu/AddServerMenu";
|
import { AddServerMenu } from "../../molecules/AddServerMenu/AddServerMenu";
|
||||||
|
|
@ -26,8 +25,7 @@ export function ServerListScreen({
|
||||||
return (
|
return (
|
||||||
<Container size="xl" py="xl">
|
<Container size="xl" py="xl">
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Group justify="space-between">
|
<Group justify="flex-end">
|
||||||
<Title order={1}>MCP Inspector</Title>
|
|
||||||
<AddServerMenu
|
<AddServerMenu
|
||||||
onAddManually={onAddManually}
|
onAddManually={onAddManually}
|
||||||
onImportConfig={onImportConfig}
|
onImportConfig={onImportConfig}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, Group, Stack, Text, Title } from "@mantine/core";
|
import { Button, Container, Group, Stack, Text, Title } from "@mantine/core";
|
||||||
import { TaskCard } from "../../molecules/TaskCard/TaskCard";
|
import { TaskCard } from "../../molecules/TaskCard/TaskCard";
|
||||||
import type { TaskCardProps } from "../../molecules/TaskCard/TaskCard";
|
import type { TaskCardProps } from "../../molecules/TaskCard/TaskCard";
|
||||||
|
|
||||||
|
|
@ -16,11 +16,11 @@ export function TasksScreen({
|
||||||
onClearHistory,
|
onClearHistory,
|
||||||
}: TasksScreenProps) {
|
}: TasksScreenProps) {
|
||||||
return (
|
return (
|
||||||
|
<Container size="xl" py="xl">
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Group justify="space-between">
|
<Group justify="flex-end">
|
||||||
<Title order={2}>Tasks</Title>
|
<Button variant="dark" onClick={onRefresh}>
|
||||||
<Button variant="light" onClick={onRefresh}>
|
Refresh Tasks
|
||||||
Refresh
|
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
|
@ -37,9 +37,11 @@ export function TasksScreen({
|
||||||
|
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Title order={4}>Completed Tasks ({completedTasks.length})</Title>
|
<Title order={4}>Completed Tasks ({completedTasks.length})</Title>
|
||||||
<Button variant="light" size="xs" onClick={onClearHistory}>
|
{completedTasks.length > 0 && (
|
||||||
Clear History
|
<Button variant="light" size="xs" onClick={onClearHistory}>
|
||||||
</Button>
|
Clear History
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
{completedTasks.length === 0 ? (
|
{completedTasks.length === 0 ? (
|
||||||
<Text c="dimmed">No completed tasks</Text>
|
<Text c="dimmed">No completed tasks</Text>
|
||||||
|
|
@ -51,5 +53,6 @@ export function TasksScreen({
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Grid, Paper, Stack, Text, TextInput } from "@mantine/core";
|
import { Card, Container, Grid, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||||
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
|
import { ListChangedIndicator } from "../../atoms/ListChangedIndicator/ListChangedIndicator";
|
||||||
import { ToolListItem } from "../../molecules/ToolListItem/ToolListItem";
|
import { ToolListItem } from "../../molecules/ToolListItem/ToolListItem";
|
||||||
import { ToolDetailPanel } from "../../molecules/ToolDetailPanel/ToolDetailPanel";
|
import { ToolDetailPanel } from "../../molecules/ToolDetailPanel/ToolDetailPanel";
|
||||||
|
|
@ -35,10 +35,12 @@ export function ToolsScreen({
|
||||||
: tools;
|
: tools;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Container size="xl" py="xl">
|
||||||
|
<Grid align="stretch">
|
||||||
<Grid.Col span={3}>
|
<Grid.Col span={3}>
|
||||||
<Paper withBorder p="md">
|
<Card withBorder padding="lg" h="100%">
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
|
<Title order={4}>Tools</Title>
|
||||||
<ListChangedIndicator
|
<ListChangedIndicator
|
||||||
visible={listChanged}
|
visible={listChanged}
|
||||||
onRefresh={onRefreshList}
|
onRefresh={onRefreshList}
|
||||||
|
|
@ -56,11 +58,11 @@ export function ToolsScreen({
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={5}>
|
<Grid.Col span={5}>
|
||||||
<Paper withBorder p="md" variant="contained">
|
<Card withBorder padding="lg" h="100%">
|
||||||
{selectedTool ? (
|
{selectedTool ? (
|
||||||
<ToolDetailPanel {...selectedTool} />
|
<ToolDetailPanel {...selectedTool} />
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -68,11 +70,11 @@ export function ToolsScreen({
|
||||||
Select a tool to view details
|
Select a tool to view details
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={4}>
|
<Grid.Col span={4}>
|
||||||
<Paper withBorder p="md">
|
<Card withBorder padding="lg" h="100%">
|
||||||
{result ? (
|
{result ? (
|
||||||
<ResultPanel {...result} />
|
<ResultPanel {...result} />
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -80,8 +82,9 @@ export function ToolsScreen({
|
||||||
Results will appear here
|
Results will appear here
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Card>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue