refactor molecule components to use .withProps() subcomponents and extract inline code
- Add UnstyledButton listItem theme variant to replace inline style for border-radius - Extract subcomponent constants with .withProps() across all molecules - Replace raw <span /> with Mantine <Flex /> in ResourcePreviewPanel - Extract inline template literals and expressions to named functions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
04c23094a4
commit
27186c2ff7
27 changed files with 666 additions and 356 deletions
|
|
@ -12,6 +12,19 @@ export interface ElicitationFormPanelProps {
|
|||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const QuotedMessage = Text.withProps({
|
||||
size: "md",
|
||||
fs: "italic",
|
||||
});
|
||||
|
||||
function formatQuoted(text: string): string {
|
||||
return `\u201C${text}\u201D`;
|
||||
}
|
||||
|
||||
function formatWarning(serverName: string): string {
|
||||
return `Only provide information you trust this server with. The server \u201C${serverName}\u201D is requesting this data.`;
|
||||
}
|
||||
|
||||
export function ElicitationFormPanel({
|
||||
message,
|
||||
schema,
|
||||
|
|
@ -23,14 +36,11 @@ export function ElicitationFormPanel({
|
|||
}: ElicitationFormPanelProps) {
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Text size="md" fs="italic">
|
||||
“{message}”
|
||||
</Text>
|
||||
<QuotedMessage>{formatQuoted(message)}</QuotedMessage>
|
||||
<Divider />
|
||||
<SchemaForm schema={schema} values={values} onChange={onChange} />
|
||||
<Alert color="yellow" title="Warning">
|
||||
Only provide information you trust this server with. The server “
|
||||
{serverName}” is requesting this data.
|
||||
{formatWarning(serverName)}
|
||||
</Alert>
|
||||
<Group justify="flex-end">
|
||||
<Button variant="light" onClick={onCancel}>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,30 @@ export interface ElicitationUrlPanelProps {
|
|||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const ItalicMessage = Text.withProps({
|
||||
size: "md",
|
||||
fs: "italic",
|
||||
});
|
||||
|
||||
const WrappingCode = Code.withProps({
|
||||
block: true,
|
||||
variant: "wrapping",
|
||||
});
|
||||
|
||||
const HintText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const MetaText = Text.withProps({
|
||||
size: "xs",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
function formatElicitationId(id: string): string {
|
||||
return `Elicitation ID: ${id}`;
|
||||
}
|
||||
|
||||
export function ElicitationUrlPanel({
|
||||
message,
|
||||
url,
|
||||
|
|
@ -30,14 +54,10 @@ export function ElicitationUrlPanel({
|
|||
}: ElicitationUrlPanelProps) {
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Text size="md" fs="italic">
|
||||
{message}
|
||||
</Text>
|
||||
<ItalicMessage>{message}</ItalicMessage>
|
||||
<Divider />
|
||||
<Text size="sm">The server is requesting you visit:</Text>
|
||||
<Code block variant="wrapping">
|
||||
{url}
|
||||
</Code>
|
||||
<WrappingCode>{url}</WrappingCode>
|
||||
<Group>
|
||||
<Button variant="light" onClick={onCopyUrl}>
|
||||
Copy URL
|
||||
|
|
@ -50,14 +70,10 @@ export function ElicitationUrlPanel({
|
|||
{isWaiting && (
|
||||
<Group>
|
||||
<Loader size="sm" />
|
||||
<Text size="sm" c="dimmed">
|
||||
Waiting for completion...
|
||||
</Text>
|
||||
<HintText>Waiting for completion...</HintText>
|
||||
</Group>
|
||||
)}
|
||||
<Text size="xs" c="dimmed">
|
||||
Elicitation ID: {elicitationId}
|
||||
</Text>
|
||||
<MetaText>{formatElicitationId(elicitationId)}</MetaText>
|
||||
<Alert color="yellow" title="Warning">
|
||||
This will open an external URL. Verify the domain before proceeding.
|
||||
</Alert>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,39 @@ export interface ExperimentalFeaturesPanelProps {
|
|||
onTestCapability: (name: string) => void;
|
||||
}
|
||||
|
||||
const HintText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const MetaText = Text.withProps({
|
||||
size: "xs",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const CapCard = Card.withProps({
|
||||
withBorder: true,
|
||||
p: "sm",
|
||||
});
|
||||
|
||||
const CompactButton = Button.withProps({
|
||||
size: "xs",
|
||||
variant: "light",
|
||||
});
|
||||
|
||||
const RemoveIcon = ActionIcon.withProps({
|
||||
variant: "light",
|
||||
color: "red",
|
||||
});
|
||||
|
||||
function formatMethods(methods: string[]): string {
|
||||
return `Methods: ${methods.join(", ")}`;
|
||||
}
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
return `${ms}ms`;
|
||||
}
|
||||
|
||||
export function ExperimentalFeaturesPanel({
|
||||
serverCapabilities,
|
||||
clientCapabilities,
|
||||
|
|
@ -82,30 +115,20 @@ export function ExperimentalFeaturesPanel({
|
|||
<Text c="dimmed">No experimental capabilities</Text>
|
||||
) : (
|
||||
serverCapabilities.map((cap) => (
|
||||
<Card key={cap.name} withBorder p="sm">
|
||||
<CapCard key={cap.name}>
|
||||
<Stack gap="xs">
|
||||
<Text fw={600}>{cap.name}</Text>
|
||||
{cap.description && (
|
||||
<Text size="sm" c="dimmed">
|
||||
{cap.description}
|
||||
</Text>
|
||||
)}
|
||||
{cap.description && <HintText>{cap.description}</HintText>}
|
||||
{cap.methods && cap.methods.length > 0 && (
|
||||
<Text size="xs" c="dimmed">
|
||||
Methods: {cap.methods.join(", ")}
|
||||
</Text>
|
||||
<MetaText>{formatMethods(cap.methods)}</MetaText>
|
||||
)}
|
||||
<Group>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={() => onTestCapability(cap.name)}
|
||||
>
|
||||
<CompactButton onClick={() => onTestCapability(cap.name)}>
|
||||
Test →
|
||||
</Button>
|
||||
</CompactButton>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
</CapCard>
|
||||
))
|
||||
)}
|
||||
|
||||
|
|
@ -128,9 +151,7 @@ export function ExperimentalFeaturesPanel({
|
|||
|
||||
<Title order={5}>Advanced JSON-RPC Tester</Title>
|
||||
|
||||
<Text size="sm" c="dimmed">
|
||||
Send raw JSON-RPC requests to test ANY method
|
||||
</Text>
|
||||
<HintText>Send raw JSON-RPC requests to test ANY method</HintText>
|
||||
|
||||
{customHeaders.map((header, index) => (
|
||||
<Group key={index}>
|
||||
|
|
@ -148,20 +169,14 @@ export function ExperimentalFeaturesPanel({
|
|||
onHeaderChange(index, header.key, e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
color="red"
|
||||
onClick={() => onRemoveHeader(index)}
|
||||
>
|
||||
<RemoveIcon onClick={() => onRemoveHeader(index)}>
|
||||
<Text size="xs">✕</Text>
|
||||
</ActionIcon>
|
||||
</RemoveIcon>
|
||||
</Group>
|
||||
))}
|
||||
|
||||
<Group>
|
||||
<Button size="xs" variant="light" onClick={onAddHeader}>
|
||||
+ Add Header
|
||||
</Button>
|
||||
<CompactButton onClick={onAddHeader}>+ Add Header</CompactButton>
|
||||
</Group>
|
||||
|
||||
<Textarea
|
||||
|
|
@ -186,9 +201,7 @@ export function ExperimentalFeaturesPanel({
|
|||
minRows={4}
|
||||
/>
|
||||
<Group>
|
||||
<Button variant="light" size="xs" onClick={onCopyResponse}>
|
||||
Copy
|
||||
</Button>
|
||||
<CompactButton onClick={onCopyResponse}>Copy</CompactButton>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -211,7 +224,7 @@ export function ExperimentalFeaturesPanel({
|
|||
<Table.Td>{item.timestamp}</Table.Td>
|
||||
<Table.Td>{item.method}</Table.Td>
|
||||
<Table.Td>{item.status}</Table.Td>
|
||||
<Table.Td>{item.durationMs}ms</Table.Td>
|
||||
<Table.Td>{formatDuration(item.durationMs)}</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,63 @@ export interface HistoryEntryProps {
|
|||
onTogglePin: () => void;
|
||||
}
|
||||
|
||||
const EntryContainer = Card.withProps({
|
||||
withBorder: true,
|
||||
padding: "md",
|
||||
});
|
||||
|
||||
const HeaderRow = Group.withProps({
|
||||
justify: "space-between",
|
||||
wrap: "nowrap",
|
||||
});
|
||||
|
||||
const TimestampText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
ff: "monospace",
|
||||
});
|
||||
|
||||
const TargetText = Text.withProps({
|
||||
size: "sm",
|
||||
fw: 500,
|
||||
});
|
||||
|
||||
const DurationText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const SubtleButton = Button.withProps({
|
||||
variant: "subtle",
|
||||
size: "xs",
|
||||
});
|
||||
|
||||
const ChildMethodBadge = Badge.withProps({
|
||||
color: "dark",
|
||||
size: "sm",
|
||||
});
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
return `${ms}ms`;
|
||||
}
|
||||
|
||||
function formatStatusLabel(status: "success" | "error"): string {
|
||||
return status === "success" ? "OK" : "Error";
|
||||
}
|
||||
|
||||
function statusColor(status: "success" | "error"): string {
|
||||
return status === "success" ? "green" : "red";
|
||||
}
|
||||
|
||||
function formatPinLabel(isPinned: boolean, pinLabel?: string): string {
|
||||
const base = isPinned ? "Unpin" : "Pin";
|
||||
return isPinned && pinLabel ? `${base} (${pinLabel})` : base;
|
||||
}
|
||||
|
||||
function serializeJson(value: Record<string, unknown>): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
export function HistoryEntry({
|
||||
timestamp,
|
||||
method,
|
||||
|
|
@ -51,41 +108,30 @@ export function HistoryEntry({
|
|||
onTogglePin,
|
||||
}: HistoryEntryProps) {
|
||||
return (
|
||||
<Card withBorder padding="md">
|
||||
<EntryContainer>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<HeaderRow>
|
||||
<Group gap="sm">
|
||||
<Text size="sm" c="dimmed" ff="monospace">
|
||||
{timestamp}
|
||||
</Text>
|
||||
<TimestampText>{timestamp}</TimestampText>
|
||||
<Badge color="dark">{method}</Badge>
|
||||
{target && (
|
||||
<Text size="sm" fw={500}>
|
||||
{target}
|
||||
</Text>
|
||||
)}
|
||||
{target && <TargetText>{target}</TargetText>}
|
||||
</Group>
|
||||
<Group gap="sm">
|
||||
<Text size="sm" c="dimmed">
|
||||
{durationMs}ms
|
||||
</Text>
|
||||
<Badge color={status === "success" ? "green" : "red"}>
|
||||
{status === "success" ? "OK" : "Error"}
|
||||
<DurationText>{formatDuration(durationMs)}</DurationText>
|
||||
<Badge color={statusColor(status)}>
|
||||
{formatStatusLabel(status)}
|
||||
</Badge>
|
||||
</Group>
|
||||
</Group>
|
||||
</HeaderRow>
|
||||
|
||||
<Group gap="xs">
|
||||
<Button variant="subtle" size="xs" onClick={onReplay}>
|
||||
Replay
|
||||
</Button>
|
||||
<Button variant="subtle" size="xs" onClick={onTogglePin}>
|
||||
{isPinned ? "Unpin" : "Pin"}
|
||||
{isPinned && pinLabel ? ` (${pinLabel})` : ""}
|
||||
</Button>
|
||||
<Button variant="subtle" size="xs" onClick={onToggleExpand} ml="auto">
|
||||
<SubtleButton onClick={onReplay}>Replay</SubtleButton>
|
||||
<SubtleButton onClick={onTogglePin}>
|
||||
{formatPinLabel(isPinned, pinLabel)}
|
||||
</SubtleButton>
|
||||
<SubtleButton onClick={onToggleExpand} ml="auto">
|
||||
{isExpanded ? "Collapse" : "Expand"}
|
||||
</Button>
|
||||
</SubtleButton>
|
||||
</Group>
|
||||
|
||||
<Collapse in={isExpanded}>
|
||||
|
|
@ -95,7 +141,7 @@ export function HistoryEntry({
|
|||
<Text size="sm">Parameters:</Text>
|
||||
<ContentViewer
|
||||
type="json"
|
||||
content={JSON.stringify(parameters)}
|
||||
content={serializeJson(parameters)}
|
||||
copyable
|
||||
/>
|
||||
</Stack>
|
||||
|
|
@ -105,7 +151,7 @@ export function HistoryEntry({
|
|||
<Text size="sm">Response:</Text>
|
||||
<ContentViewer
|
||||
type="json"
|
||||
content={JSON.stringify(response)}
|
||||
content={serializeJson(response)}
|
||||
copyable
|
||||
/>
|
||||
</Stack>
|
||||
|
|
@ -114,25 +160,16 @@ export function HistoryEntry({
|
|||
<Stack gap="xs">
|
||||
{childEntries.map((child, index) => (
|
||||
<Group key={index} pl="lg" gap="sm">
|
||||
<Text size="sm" c="dimmed" ff="monospace">
|
||||
+--{" "}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" ff="monospace">
|
||||
{child.timestamp}
|
||||
</Text>
|
||||
<Badge color="dark" size="sm">
|
||||
{child.method}
|
||||
</Badge>
|
||||
<TimestampText>+-- </TimestampText>
|
||||
<TimestampText>{child.timestamp}</TimestampText>
|
||||
<ChildMethodBadge>{child.method}</ChildMethodBadge>
|
||||
{child.target && <Text size="sm">{child.target}</Text>}
|
||||
<Badge
|
||||
color={child.status === "success" ? "green" : "red"}
|
||||
size="sm"
|
||||
>
|
||||
{child.status === "success" ? "OK" : "Error"}
|
||||
<Badge color={statusColor(child.status)} size="sm">
|
||||
{formatStatusLabel(child.status)}
|
||||
</Badge>
|
||||
<Text size="sm" c="dimmed">
|
||||
{child.durationMs}ms
|
||||
</Text>
|
||||
<DurationText>
|
||||
{formatDuration(child.durationMs)}
|
||||
</DurationText>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
|
|
@ -140,6 +177,6 @@ export function HistoryEntry({
|
|||
</Stack>
|
||||
</Collapse>
|
||||
</Stack>
|
||||
</Card>
|
||||
</EntryContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,15 @@ const validationIcons: Record<
|
|||
error: { icon: "\u2717", color: "red" },
|
||||
};
|
||||
|
||||
const HintText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
function formatPackageLabel(pkg: PackageInfo): string {
|
||||
return `${pkg.registryType}: ${pkg.identifier} (${pkg.runtimeHint})`;
|
||||
}
|
||||
|
||||
export function ImportServerJsonPanel({
|
||||
jsonContent,
|
||||
validationResults,
|
||||
|
|
@ -73,9 +82,7 @@ export function ImportServerJsonPanel({
|
|||
<Stack gap="md">
|
||||
<Title order={4}>Import MCP Registry server.json</Title>
|
||||
|
||||
<Text size="sm" c="dimmed">
|
||||
Paste server.json content or drag and drop a file:
|
||||
</Text>
|
||||
<HintText>Paste server.json content or drag and drop a file:</HintText>
|
||||
|
||||
<Textarea
|
||||
value={jsonContent}
|
||||
|
|
@ -113,7 +120,7 @@ export function ImportServerJsonPanel({
|
|||
<Radio
|
||||
key={index}
|
||||
value={String(index)}
|
||||
label={`${pkg.registryType}: ${pkg.identifier} (${pkg.runtimeHint})`}
|
||||
label={formatPackageLabel(pkg)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,37 @@ export interface InlineElicitationRequestProps {
|
|||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const RequestContainer = Paper.withProps({
|
||||
p: "md",
|
||||
withBorder: true,
|
||||
});
|
||||
|
||||
const QueueLabel = Text.withProps({
|
||||
size: "xs",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const ItalicMessage = Text.withProps({
|
||||
size: "sm",
|
||||
fs: "italic",
|
||||
});
|
||||
|
||||
const ActionsRow = Group.withProps({
|
||||
justify: "flex-end",
|
||||
gap: "xs",
|
||||
});
|
||||
|
||||
const CompactButton = Button.withProps({
|
||||
size: "xs",
|
||||
variant: "light",
|
||||
});
|
||||
|
||||
function getBadgeLabel(mode: "form" | "url"): string {
|
||||
return mode === "form"
|
||||
? "elicitation/create (form)"
|
||||
: "elicitation/create (url)";
|
||||
}
|
||||
|
||||
export function InlineElicitationRequest({
|
||||
mode,
|
||||
message,
|
||||
|
|
@ -36,22 +67,15 @@ export function InlineElicitationRequest({
|
|||
onSubmit,
|
||||
onCancel,
|
||||
}: InlineElicitationRequestProps) {
|
||||
const badgeLabel =
|
||||
mode === "form" ? "elicitation/create (form)" : "elicitation/create (url)";
|
||||
|
||||
return (
|
||||
<Paper p="md" withBorder>
|
||||
<RequestContainer>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between">
|
||||
<Badge color="violet">{badgeLabel}</Badge>
|
||||
<Text size="xs" c="dimmed">
|
||||
{queuePosition}
|
||||
</Text>
|
||||
<Badge color="violet">{getBadgeLabel(mode)}</Badge>
|
||||
<QueueLabel>{queuePosition}</QueueLabel>
|
||||
</Group>
|
||||
|
||||
<Text size="sm" fs="italic">
|
||||
{message}
|
||||
</Text>
|
||||
<ItalicMessage>{message}</ItalicMessage>
|
||||
|
||||
{mode === "form" && schema && (
|
||||
<SchemaForm
|
||||
|
|
@ -73,17 +97,15 @@ export function InlineElicitationRequest({
|
|||
</>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end" gap="xs">
|
||||
<Button size="xs" variant="light" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<ActionsRow>
|
||||
<CompactButton onClick={onCancel}>Cancel</CompactButton>
|
||||
{mode === "form" && (
|
||||
<Button size="xs" onClick={onSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</ActionsRow>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</RequestContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,47 @@ export interface InlineSamplingRequestProps {
|
|||
onViewDetails: () => void;
|
||||
}
|
||||
|
||||
const RequestContainer = Paper.withProps({
|
||||
p: "md",
|
||||
withBorder: true,
|
||||
});
|
||||
|
||||
const QueueLabel = Text.withProps({
|
||||
size: "xs",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const PreviewText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
lineClamp: 2,
|
||||
});
|
||||
|
||||
const DetailButton = Button.withProps({
|
||||
variant: "subtle",
|
||||
size: "xs",
|
||||
});
|
||||
|
||||
const ActionsRow = Group.withProps({
|
||||
justify: "flex-end",
|
||||
gap: "xs",
|
||||
});
|
||||
|
||||
const CompactButton = Button.withProps({
|
||||
size: "xs",
|
||||
variant: "light",
|
||||
});
|
||||
|
||||
const RejectButton = Button.withProps({
|
||||
size: "xs",
|
||||
variant: "light",
|
||||
color: "red",
|
||||
});
|
||||
|
||||
function formatModelHints(hints: string[]): string {
|
||||
return `Model hints: ${hints.join(", ")}`;
|
||||
}
|
||||
|
||||
export function InlineSamplingRequest({
|
||||
queuePosition,
|
||||
modelHints,
|
||||
|
|
@ -30,26 +71,18 @@ export function InlineSamplingRequest({
|
|||
onViewDetails,
|
||||
}: InlineSamplingRequestProps) {
|
||||
return (
|
||||
<Paper p="md" withBorder>
|
||||
<RequestContainer>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between">
|
||||
<Badge color="blue">sampling/createMessage</Badge>
|
||||
<Text size="xs" c="dimmed">
|
||||
{queuePosition}
|
||||
</Text>
|
||||
<QueueLabel>{queuePosition}</QueueLabel>
|
||||
</Group>
|
||||
|
||||
{modelHints && (
|
||||
<Text size="sm">Model hints: {modelHints.join(", ")}</Text>
|
||||
)}
|
||||
{modelHints && <Text size="sm">{formatModelHints(modelHints)}</Text>}
|
||||
|
||||
<Text size="sm" c="dimmed" lineClamp={2}>
|
||||
{messagePreview}
|
||||
</Text>
|
||||
<PreviewText>{messagePreview}</PreviewText>
|
||||
|
||||
<Button variant="subtle" size="xs" onClick={onViewDetails}>
|
||||
View Details
|
||||
</Button>
|
||||
<DetailButton onClick={onViewDetails}>View Details</DetailButton>
|
||||
|
||||
<Textarea
|
||||
size="sm"
|
||||
|
|
@ -60,18 +93,12 @@ export function InlineSamplingRequest({
|
|||
readOnly
|
||||
/>
|
||||
|
||||
<Group justify="flex-end" gap="xs">
|
||||
<Button size="xs" variant="light" onClick={onAutoRespond}>
|
||||
Auto-respond
|
||||
</Button>
|
||||
<Button size="xs" variant="light" onClick={onEditAndSend}>
|
||||
Edit & Send
|
||||
</Button>
|
||||
<Button size="xs" variant="light" color="red" onClick={onReject}>
|
||||
Reject
|
||||
</Button>
|
||||
</Group>
|
||||
<ActionsRow>
|
||||
<CompactButton onClick={onAutoRespond}>Auto-respond</CompactButton>
|
||||
<CompactButton onClick={onEditAndSend}>Edit & Send</CompactButton>
|
||||
<RejectButton onClick={onReject}>Reject</RejectButton>
|
||||
</ActionsRow>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</RequestContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ const LEVEL_COLORS: Record<string, { c: string; fw?: number }> = {
|
|||
emergency: { c: "red", fw: 900 },
|
||||
};
|
||||
|
||||
const ToolbarButton = Button.withProps({
|
||||
variant: "light",
|
||||
size: "sm",
|
||||
});
|
||||
|
||||
export interface LogControlsProps {
|
||||
currentLevel: string;
|
||||
filterText: string;
|
||||
|
|
@ -93,12 +98,8 @@ export function LogControls({
|
|||
})}
|
||||
|
||||
<Group>
|
||||
<Button variant="light" size="sm" onClick={onClear}>
|
||||
Clear
|
||||
</Button>
|
||||
<Button variant="light" size="sm" onClick={onExport}>
|
||||
Export
|
||||
</Button>
|
||||
<ToolbarButton onClick={onClear}>Clear</ToolbarButton>
|
||||
<ToolbarButton onClick={onExport}>Export</ToolbarButton>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ export interface PendingClientRequestsProps {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
function formatTitle(count: number): string {
|
||||
return `Pending Client Requests (${count})`;
|
||||
}
|
||||
|
||||
export function PendingClientRequests({
|
||||
count,
|
||||
children,
|
||||
}: PendingClientRequestsProps) {
|
||||
return (
|
||||
<Alert
|
||||
color="blue"
|
||||
variant="light"
|
||||
title={`Pending Client Requests (${count})`}
|
||||
>
|
||||
<Alert color="blue" variant="light" title={formatTitle(count)}>
|
||||
<Stack gap="md">{children}</Stack>
|
||||
</Alert>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,15 @@ export interface PromptArgumentsFormProps {
|
|||
onGetPrompt: () => void;
|
||||
}
|
||||
|
||||
const DescriptionText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
function formatPlaceholder(name: string): string {
|
||||
return `Enter ${name}...`;
|
||||
}
|
||||
|
||||
export function PromptArgumentsForm({
|
||||
prompts,
|
||||
selectedPrompt,
|
||||
|
|
@ -46,9 +55,7 @@ export function PromptArgumentsForm({
|
|||
placeholder="Choose a prompt..."
|
||||
/>
|
||||
{selectedPrompt && selectedPromptInfo?.description && (
|
||||
<Text size="sm" c="dimmed">
|
||||
{selectedPromptInfo.description}
|
||||
</Text>
|
||||
<DescriptionText>{selectedPromptInfo.description}</DescriptionText>
|
||||
)}
|
||||
{promptArguments.length > 0 && (
|
||||
<>
|
||||
|
|
@ -58,7 +65,7 @@ export function PromptArgumentsForm({
|
|||
label={arg.name}
|
||||
withAsterisk={arg.required}
|
||||
description={arg.description}
|
||||
placeholder={`Enter ${arg.name}...`}
|
||||
placeholder={formatPlaceholder(arg.name)}
|
||||
value={argumentValues[arg.name] || ""}
|
||||
onChange={(event) =>
|
||||
onArgumentChange(arg.name, event.currentTarget.value)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ export interface PromptMessage {
|
|||
audioContent?: { data: string; mimeType: string };
|
||||
}
|
||||
|
||||
const CopyAllButton = Button.withProps({
|
||||
variant: "light",
|
||||
size: "sm",
|
||||
});
|
||||
|
||||
export interface PromptMessagesDisplayProps {
|
||||
messages: PromptMessage[];
|
||||
onCopyAll?: () => void;
|
||||
|
|
@ -22,9 +27,7 @@ export function PromptMessagesDisplay({
|
|||
<Group justify="space-between">
|
||||
<Title order={4}>Messages</Title>
|
||||
{onCopyAll && messages.length > 0 && (
|
||||
<Button variant="light" size="sm" onClick={onCopyAll}>
|
||||
Copy All
|
||||
</Button>
|
||||
<CopyAllButton onClick={onCopyAll}>Copy All</CopyAllButton>
|
||||
)}
|
||||
</Group>
|
||||
{messages.length === 0 ? (
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ export function ResourceListItem({
|
|||
<UnstyledButton
|
||||
w="100%"
|
||||
p="sm"
|
||||
variant="listItem"
|
||||
bg={selected ? "var(--mantine-primary-color-light)" : undefined}
|
||||
style={{ borderRadius: "var(--mantine-radius-md)" }}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Text fw={500}>{name}</Text>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { Flex, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { AnnotationBadge } from "../../atoms/AnnotationBadge/AnnotationBadge";
|
||||
import { ContentViewer } from "../../atoms/ContentViewer/ContentViewer";
|
||||
import { CopyButton } from "../../atoms/CopyButton/CopyButton";
|
||||
|
|
@ -27,6 +27,41 @@ function resolveContentType(mimeType: string): "json" | "image" | "text" {
|
|||
return "text";
|
||||
}
|
||||
|
||||
function formatLastUpdated(lastUpdated: string): string {
|
||||
return `Last updated: ${lastUpdated}`;
|
||||
}
|
||||
|
||||
const HeaderRow = Group.withProps({
|
||||
justify: "space-between",
|
||||
wrap: "nowrap",
|
||||
});
|
||||
|
||||
const UriGroup = Group.withProps({
|
||||
gap: "xs",
|
||||
wrap: "nowrap",
|
||||
});
|
||||
|
||||
const UriText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "blue",
|
||||
truncate: "end",
|
||||
});
|
||||
|
||||
const MetaRow = Group.withProps({
|
||||
justify: "space-between",
|
||||
wrap: "nowrap",
|
||||
});
|
||||
|
||||
const TimestampText = Text.withProps({
|
||||
size: "xs",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const MimeText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
export function ResourcePreviewPanel({
|
||||
uri,
|
||||
mimeType,
|
||||
|
|
@ -39,33 +74,27 @@ export function ResourcePreviewPanel({
|
|||
}: ResourcePreviewPanelProps) {
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<HeaderRow>
|
||||
<Title order={4}>Resource</Title>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Text size="sm" c="blue" truncate="end">
|
||||
{uri}
|
||||
</Text>
|
||||
<UriGroup>
|
||||
<UriText>{uri}</UriText>
|
||||
<CopyButton value={uri} />
|
||||
</Group>
|
||||
</Group>
|
||||
</UriGroup>
|
||||
</HeaderRow>
|
||||
<ContentViewer
|
||||
type={resolveContentType(mimeType)}
|
||||
content={content}
|
||||
mimeType={mimeType}
|
||||
copyable
|
||||
/>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<MetaRow>
|
||||
{lastUpdated ? (
|
||||
<Text size="xs" c="dimmed">
|
||||
Last updated: {lastUpdated}
|
||||
</Text>
|
||||
<TimestampText>{formatLastUpdated(lastUpdated)}</TimestampText>
|
||||
) : (
|
||||
<span />
|
||||
<Flex />
|
||||
)}
|
||||
<Text size="sm" c="dimmed">
|
||||
{mimeType}
|
||||
</Text>
|
||||
</Group>
|
||||
<MimeText>{mimeType}</MimeText>
|
||||
</MetaRow>
|
||||
<Group justify="space-between">
|
||||
<Group gap="xs">
|
||||
{annotations?.audience && (
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ function parseTemplate(template: string): TemplatePart[] {
|
|||
return parts;
|
||||
}
|
||||
|
||||
const StaticPart = Text.withProps({
|
||||
size: "sm",
|
||||
pb: 6,
|
||||
});
|
||||
|
||||
export function ResourceTemplateInput({
|
||||
template,
|
||||
variables,
|
||||
|
|
@ -48,9 +53,7 @@ export function ResourceTemplateInput({
|
|||
<Group gap="xs" align="flex-end">
|
||||
{parts.map((part, index) =>
|
||||
part.type === "static" ? (
|
||||
<Text key={index} size="sm" pb={6}>
|
||||
{part.value}
|
||||
</Text>
|
||||
<StaticPart key={index}>{part.value}</StaticPart>
|
||||
) : (
|
||||
<TextInput
|
||||
key={part.value}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,19 @@ export interface ResultPanelProps {
|
|||
onClear: () => void;
|
||||
}
|
||||
|
||||
const ClearButton = Button.withProps({
|
||||
variant: "light",
|
||||
size: "sm",
|
||||
});
|
||||
|
||||
function resolveTextType(type: string): "text" | "json" {
|
||||
return type === "text" ? "text" : "json";
|
||||
}
|
||||
|
||||
function resolveMediaType(mimeType?: string): "image" | "audio" {
|
||||
return mimeType?.startsWith("image") ? "image" : "audio";
|
||||
}
|
||||
|
||||
export function ResultPanel({ content, onClear }: ResultPanelProps) {
|
||||
return (
|
||||
<Stack>
|
||||
|
|
@ -26,7 +39,7 @@ export function ResultPanel({ content, onClear }: ResultPanelProps) {
|
|||
return (
|
||||
<ContentViewer
|
||||
key={index}
|
||||
type={item.type === "text" ? "text" : "json"}
|
||||
type={resolveTextType(item.type)}
|
||||
content={item.text}
|
||||
copyable
|
||||
/>
|
||||
|
|
@ -36,7 +49,7 @@ export function ResultPanel({ content, onClear }: ResultPanelProps) {
|
|||
return (
|
||||
<ContentViewer
|
||||
key={index}
|
||||
type={item.mimeType?.startsWith("image") ? "image" : "audio"}
|
||||
type={resolveMediaType(item.mimeType)}
|
||||
content={item.data}
|
||||
mimeType={item.mimeType}
|
||||
/>
|
||||
|
|
@ -47,9 +60,7 @@ export function ResultPanel({ content, onClear }: ResultPanelProps) {
|
|||
</>
|
||||
)}
|
||||
<Group>
|
||||
<Button variant="light" size="sm" onClick={onClear}>
|
||||
Clear
|
||||
</Button>
|
||||
<ClearButton onClick={onClear}>Clear</ClearButton>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,21 @@ export interface RootsTableProps {
|
|||
onBrowse: () => void;
|
||||
}
|
||||
|
||||
const HintText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const RemoveIcon = ActionIcon.withProps({
|
||||
color: "red",
|
||||
variant: "subtle",
|
||||
});
|
||||
|
||||
const AddRootButton = Button.withProps({
|
||||
variant: "light",
|
||||
fullWidth: true,
|
||||
});
|
||||
|
||||
export function RootsTable({
|
||||
roots,
|
||||
newRootName,
|
||||
|
|
@ -40,9 +55,7 @@ export function RootsTable({
|
|||
return (
|
||||
<Stack gap="md">
|
||||
<Title order={4}>Roots Configuration</Title>
|
||||
<Text size="sm" c="dimmed">
|
||||
Filesystem roots exposed to the connected server:
|
||||
</Text>
|
||||
<HintText>Filesystem roots exposed to the connected server:</HintText>
|
||||
|
||||
{roots.length > 0 && (
|
||||
<Table>
|
||||
|
|
@ -59,13 +72,9 @@ export function RootsTable({
|
|||
<Table.Td>{root.name}</Table.Td>
|
||||
<Table.Td>{root.uri}</Table.Td>
|
||||
<Table.Td>
|
||||
<ActionIcon
|
||||
color="red"
|
||||
variant="subtle"
|
||||
onClick={() => onRemoveRoot(root.uri)}
|
||||
>
|
||||
<RemoveIcon onClick={() => onRemoveRoot(root.uri)}>
|
||||
X
|
||||
</ActionIcon>
|
||||
</RemoveIcon>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
|
|
@ -73,9 +82,7 @@ export function RootsTable({
|
|||
</Table>
|
||||
)}
|
||||
|
||||
<Button variant="light" fullWidth onClick={onAddRoot}>
|
||||
+ Add Root
|
||||
</Button>
|
||||
<AddRootButton onClick={onAddRoot}>+ Add Root</AddRootButton>
|
||||
|
||||
<Divider />
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,39 @@ function formatPriority(value: number): string {
|
|||
return `high (${value})`;
|
||||
}
|
||||
|
||||
function formatOptional(value: unknown, fallback: string): string {
|
||||
return value !== undefined ? String(value) : fallback;
|
||||
}
|
||||
|
||||
function serializeJson(value: unknown): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
const HintText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const PreferencesContainer = Paper.withProps({
|
||||
p: "sm",
|
||||
withBorder: true,
|
||||
});
|
||||
|
||||
const ToolCard = Paper.withProps({
|
||||
p: "xs",
|
||||
withBorder: true,
|
||||
});
|
||||
|
||||
const ToolName = Text.withProps({
|
||||
size: "sm",
|
||||
fw: 600,
|
||||
});
|
||||
|
||||
const RejectButton = Button.withProps({
|
||||
variant: "light",
|
||||
color: "red",
|
||||
});
|
||||
|
||||
export function SamplingRequestPanel({
|
||||
messages,
|
||||
modelHints,
|
||||
|
|
@ -78,9 +111,7 @@ export function SamplingRequestPanel({
|
|||
}: SamplingRequestPanelProps) {
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Text size="sm" c="dimmed">
|
||||
The server is requesting an LLM completion.
|
||||
</Text>
|
||||
<HintText>The server is requesting an LLM completion.</HintText>
|
||||
|
||||
<Title order={5}>Messages:</Title>
|
||||
{messages.map((message, index) => (
|
||||
|
|
@ -94,7 +125,7 @@ export function SamplingRequestPanel({
|
|||
))}
|
||||
|
||||
{modelHints && modelHints.length > 0 && (
|
||||
<Paper p="sm" withBorder>
|
||||
<PreferencesContainer>
|
||||
<Stack gap="xs">
|
||||
<Title order={5}>Model Preferences:</Title>
|
||||
<Group gap="xs">
|
||||
|
|
@ -119,17 +150,19 @@ export function SamplingRequestPanel({
|
|||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</PreferencesContainer>
|
||||
)}
|
||||
|
||||
<Title order={5}>Parameters:</Title>
|
||||
<Text size="sm">Max Tokens: {maxTokens ?? "not specified"}</Text>
|
||||
<Text size="sm">
|
||||
Stop Sequences:{" "}
|
||||
{stopSequences ? JSON.stringify(stopSequences) : "not specified"}
|
||||
Max Tokens: {formatOptional(maxTokens, "not specified")}
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
Temperature: {temperature !== undefined ? temperature : "not specified"}
|
||||
Stop Sequences:{" "}
|
||||
{stopSequences ? serializeJson(stopSequences) : "not specified"}
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
Temperature: {formatOptional(temperature, "not specified")}
|
||||
</Text>
|
||||
|
||||
{includeContext && (
|
||||
|
|
@ -143,12 +176,10 @@ export function SamplingRequestPanel({
|
|||
<>
|
||||
<Title order={5}>Available Tools:</Title>
|
||||
{tools.map((tool) => (
|
||||
<Paper key={tool.name} p="xs" withBorder>
|
||||
<Text size="sm" fw={600}>
|
||||
{tool.name}
|
||||
</Text>
|
||||
<ToolCard key={tool.name}>
|
||||
<ToolName>{tool.name}</ToolName>
|
||||
{tool.description && <Text size="sm">{tool.description}</Text>}
|
||||
</Paper>
|
||||
</ToolCard>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
|
@ -181,9 +212,7 @@ export function SamplingRequestPanel({
|
|||
<Button variant="light" onClick={onAutoRespond}>
|
||||
Auto-respond
|
||||
</Button>
|
||||
<Button variant="light" color="red" onClick={onReject}>
|
||||
Reject
|
||||
</Button>
|
||||
<RejectButton onClick={onReject}>Reject</RejectButton>
|
||||
<Button onClick={onSend}>Send Response</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,20 @@ export interface JsonSchema {
|
|||
format?: string;
|
||||
}
|
||||
|
||||
const FieldLabel = Text.withProps({
|
||||
fw: 500,
|
||||
size: "sm",
|
||||
});
|
||||
|
||||
const FieldDescription = Text.withProps({
|
||||
size: "xs",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
function serializeJson(value: unknown): string {
|
||||
return JSON.stringify(value, null, 2);
|
||||
}
|
||||
|
||||
export interface SchemaFormProps {
|
||||
schema: JsonSchema;
|
||||
values: Record<string, unknown>;
|
||||
|
|
@ -182,14 +196,8 @@ export function SchemaForm({
|
|||
if (fieldSchema.type === "object" && fieldSchema.properties) {
|
||||
return (
|
||||
<Stack key={fieldName} gap="sm">
|
||||
<Text fw={500} size="sm">
|
||||
{label}
|
||||
</Text>
|
||||
{description && (
|
||||
<Text size="xs" c="dimmed">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
<FieldLabel>{label}</FieldLabel>
|
||||
{description && <FieldDescription>{description}</FieldDescription>}
|
||||
<Stack gap="sm" pl="md">
|
||||
<SchemaForm
|
||||
schema={fieldSchema}
|
||||
|
|
@ -214,7 +222,7 @@ export function SchemaForm({
|
|||
disabled={disabled}
|
||||
formatOnBlur
|
||||
autosize
|
||||
value={rawValue !== undefined ? JSON.stringify(rawValue, null, 2) : ""}
|
||||
value={rawValue !== undefined ? serializeJson(rawValue) : ""}
|
||||
onChange={(val) => {
|
||||
try {
|
||||
handleFieldChange(fieldName, JSON.parse(val));
|
||||
|
|
|
|||
|
|
@ -39,6 +39,27 @@ const ActionsRow = Group.withProps({
|
|||
gap: "xs",
|
||||
});
|
||||
|
||||
const ServerName = Text.withProps({
|
||||
fw: 600,
|
||||
size: "lg",
|
||||
});
|
||||
|
||||
const ModeText = Text.withProps({
|
||||
size: "sm",
|
||||
c: "dimmed",
|
||||
});
|
||||
|
||||
const SubtleButton = Button.withProps({
|
||||
variant: "subtle",
|
||||
size: "xs",
|
||||
});
|
||||
|
||||
const RemoveButton = Button.withProps({
|
||||
variant: "subtle",
|
||||
size: "xs",
|
||||
color: "red.6",
|
||||
});
|
||||
|
||||
export function ServerCard({
|
||||
name,
|
||||
version,
|
||||
|
|
@ -68,9 +89,7 @@ export function ServerCard({
|
|||
<Stack gap="sm">
|
||||
<Group justify="space-between" wrap="wrap">
|
||||
<HeaderLeft>
|
||||
<Text fw={600} size="lg">
|
||||
{name}
|
||||
</Text>
|
||||
<ServerName>{name}</ServerName>
|
||||
{version && <Badge variant="outline">{version}</Badge>}
|
||||
</HeaderLeft>
|
||||
<HeaderRight>
|
||||
|
|
@ -86,9 +105,7 @@ export function ServerCard({
|
|||
|
||||
<Group gap="sm">
|
||||
<TransportBadge transport={transport} />
|
||||
<Text size="sm" c="dimmed">
|
||||
{connectionMode}
|
||||
</Text>
|
||||
<ModeText>{connectionMode}</ModeText>
|
||||
</Group>
|
||||
|
||||
<ContentViewer type="text" content={command} copyable />
|
||||
|
|
@ -103,29 +120,17 @@ export function ServerCard({
|
|||
|
||||
<Group justify="space-between">
|
||||
<ActionsRow>
|
||||
<Button variant="subtle" size="xs" onClick={onClone}>
|
||||
Clone
|
||||
</Button>
|
||||
<Button variant="subtle" size="xs" onClick={onEdit}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button variant="subtle" size="xs" color="red.6" onClick={onRemove}>
|
||||
Remove
|
||||
</Button>
|
||||
<SubtleButton onClick={onClone}>Clone</SubtleButton>
|
||||
<SubtleButton onClick={onEdit}>Edit</SubtleButton>
|
||||
<RemoveButton onClick={onRemove}>Remove</RemoveButton>
|
||||
</ActionsRow>
|
||||
<ActionsRow>
|
||||
<Button variant="subtle" size="xs" onClick={onServerInfo}>
|
||||
Server Info
|
||||
</Button>
|
||||
<Button variant="subtle" size="xs" onClick={onSettings}>
|
||||
Settings
|
||||
</Button>
|
||||
<SubtleButton onClick={onServerInfo}>Server Info</SubtleButton>
|
||||
<SubtleButton onClick={onSettings}>Settings</SubtleButton>
|
||||
{canTestClientFeatures && (
|
||||
<Menu>
|
||||
<Menu.Target>
|
||||
<Button variant="subtle" size="xs">
|
||||
Test Client Features ▾
|
||||
</Button>
|
||||
<SubtleButton>Test Client Features ▾</SubtleButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item onClick={onTestSampling}>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@ export interface ServerInfoContentProps {
|
|||
oauthDetails?: { authUrl?: string; scopes?: string[]; accessToken?: string };
|
||||
}
|
||||
|
||||
const ValueText = Text.withProps({
|
||||
size: "sm",
|
||||
fw: 600,
|
||||
});
|
||||
|
||||
function formatScopes(scopes: string[]): string {
|
||||
return scopes.join(", ");
|
||||
}
|
||||
|
||||
export function ServerInfoContent({
|
||||
name,
|
||||
version,
|
||||
|
|
@ -42,19 +51,13 @@ export function ServerInfoContent({
|
|||
|
||||
<SimpleGrid cols={2}>
|
||||
<Text size="sm">Name</Text>
|
||||
<Text size="sm" fw={600}>
|
||||
{name}
|
||||
</Text>
|
||||
<ValueText>{name}</ValueText>
|
||||
|
||||
<Text size="sm">Version</Text>
|
||||
<Text size="sm" fw={600}>
|
||||
{version}
|
||||
</Text>
|
||||
<ValueText>{version}</ValueText>
|
||||
|
||||
<Text size="sm">Protocol</Text>
|
||||
<Text size="sm" fw={600}>
|
||||
{protocolVersion}
|
||||
</Text>
|
||||
<ValueText>{protocolVersion}</ValueText>
|
||||
|
||||
<Text size="sm">Transport</Text>
|
||||
<Badge variant="outline">{transport}</Badge>
|
||||
|
|
@ -105,9 +108,7 @@ export function ServerInfoContent({
|
|||
{oauthDetails.scopes && oauthDetails.scopes.length > 0 && (
|
||||
<SimpleGrid cols={2}>
|
||||
<Text size="sm">Scopes</Text>
|
||||
<Text size="sm" fw={600}>
|
||||
{oauthDetails.scopes.join(", ")}
|
||||
</Text>
|
||||
<ValueText>{formatScopes(oauthDetails.scopes)}</ValueText>
|
||||
</SimpleGrid>
|
||||
)}
|
||||
{oauthDetails.accessToken && (
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue