import { Badge, Button, Divider, Group, Paper, ScrollArea, Select, Stack, Text, TextInput, Textarea, Title, } from "@mantine/core"; import { ClearButton } from "../../elements/ClearButton/ClearButton"; import type { CreateMessageRequestParams, CreateMessageResult, } from "@modelcontextprotocol/client"; import type { PendingRequestOrigin } from "@inspector/core/mcp/types.js"; import { MessageBubble } from "../../elements/MessageBubble/MessageBubble"; import { MrtrOriginNote } from "../../elements/MrtrOriginNote/MrtrOriginNote"; export interface SamplingRequestPanelProps { request: CreateMessageRequestParams; draftResult: CreateMessageResult; onResultChange: (result: CreateMessageResult) => void; onSend: () => void; onReject: () => void; /** * How the request reached the Inspector — a legacy server→client request or a * modern MRTR `input_required` round. Drives the era-accurate note; defaults * to `"server-request"` (legacy, note hidden). (#1704) */ origin?: PendingRequestOrigin; /** * A response has been dispatched; lock the actions so a second click can't * resolve the request twice (the underlying handler throws if called again). */ busy?: boolean; } function formatPriority(value: number): string { if (value < 0.4) return `low (${value})`; if (value <= 0.7) return `medium (${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); } // Section headings. `order: 3` (not 5) keeps them one level below the host // modal's `h2` title so the outline doesn't skip a level (axe `heading-order`); // `size: "h5"` preserves the original small visual size. const SectionTitle = Title.withProps({ order: 3, size: "h5" }); const HintText = Text.withProps({ size: "sm", c: "dimmed", }); // Only the request/response content scrolls; the Reject / Send Response actions // stay pinned below (in normal flow), always in view — the modal has no other // close affordance, so the actions must never scroll out of reach. Capping the // content at the modal's max height (≈90dvh) minus the ~10rem of non-content // chrome (modal header/padding + the pinned actions + gaps) keeps the whole // modal within the viewport; `ScrollArea.Autosize` sizes to content when the // request is short (nothing scrolls) and scrolls once it hits the cap. (Mirrors // ElicitationFormPanel.) const ContentScroll = ScrollArea.Autosize.withProps({ mah: "calc(90dvh - 10rem)", scrollbars: "y", offsetScrollbars: true, }); // Inner column for the scrolled content, so `ContentScroll` wraps a single // child and the sections keep their `md` spacing. const ContentStack = Stack.withProps({ gap: "md" }); const PreferencesContainer = Paper.withProps({ p: "sm", withBorder: true, }); const RejectButton = Button.withProps({ variant: "light", color: "red", }); export function SamplingRequestPanel({ request, draftResult, onResultChange, onSend, onReject, origin = "server-request", busy = false, }: SamplingRequestPanelProps) { const { messages, modelPreferences, maxTokens, stopSequences, temperature, includeContext, } = request; const hints = (modelPreferences?.hints?.map((h) => h.name).filter(Boolean) as string[]) ?? []; return ( The server is requesting an LLM completion. Messages: {messages.map((message, index) => ( ))} {modelPreferences && ( Model Preferences: {hints.length > 0 && ( Hints: {hints.map((hint) => ( {hint} ))} )} {modelPreferences?.costPriority !== undefined && ( Cost Priority:{" "} {formatPriority(modelPreferences.costPriority)} )} {modelPreferences?.speedPriority !== undefined && ( Speed Priority:{" "} {formatPriority(modelPreferences.speedPriority)} )} {modelPreferences?.intelligencePriority !== undefined && ( Intelligence Priority:{" "} {formatPriority(modelPreferences.intelligencePriority)} )} )} Parameters: Max Tokens: {formatOptional(maxTokens, "not specified")} Stop Sequences:{" "} {stopSequences ? serializeJson(stopSequences) : "not specified"} Temperature: {formatOptional(temperature, "not specified")} {includeContext && ( Include Context: {includeContext} )} Response: