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