Phase 2: Refactor Prompts cluster to MCP SDK types
Replace ad hoc local types with MCP SDK schema types across the Prompts group components and PromptsScreen, per the v2 UX interfaces plan. - PromptListItem: accept `prompt: Prompt` instead of flat `name`/`description`; render `title ?? name` as primary label - PromptControls: accept `Prompt[]` + `selectedName` instead of `PromptItem[]`; no-op on clicking already-selected prompt; search includes title field - PromptArgumentsForm: accept `prompt: Prompt` instead of flat props; read `prompt.arguments` directly; display `title ?? name` as heading - PromptMessagesDisplay: narrow type from `(PromptMessage | SamplingMessage)[]` to `PromptMessage[]` per spec - PromptsScreen: accept `Prompt[]` + `selectedPromptName` + `GetPromptState`; derive selected prompt via `prompts.find()`; own `argumentValues` state locally; reset arguments on tool switch - Delete local `PromptItem`, `SelectedPrompt`, `PromptArgument` types - Update all stories with real SDK `Prompt` fixtures - Update ConnectedView PromptsActive story Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
12c41cbed7
commit
de40b3720e
11 changed files with 230 additions and 226 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import type { Prompt } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
import { PromptArgumentsForm } from "./PromptArgumentsForm";
|
import { PromptArgumentsForm } from "./PromptArgumentsForm";
|
||||||
|
|
||||||
|
|
@ -14,64 +15,73 @@ const meta: Meta<typeof PromptArgumentsForm> = {
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof PromptArgumentsForm>;
|
type Story = StoryObj<typeof PromptArgumentsForm>;
|
||||||
|
|
||||||
|
const summarizePrompt: Prompt = {
|
||||||
|
name: "summarize",
|
||||||
|
description: "Summarize the given text into key points",
|
||||||
|
};
|
||||||
|
|
||||||
|
const translatePrompt: Prompt = {
|
||||||
|
name: "translate",
|
||||||
|
description: "Translate text from one language to another",
|
||||||
|
arguments: [
|
||||||
|
{ name: "text", required: true, description: "The text to translate" },
|
||||||
|
{
|
||||||
|
name: "targetLanguage",
|
||||||
|
required: true,
|
||||||
|
description: "The language to translate into",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const summarizeWithArgsPrompt: Prompt = {
|
||||||
|
name: "summarize",
|
||||||
|
description: "Summarize the given text into key points",
|
||||||
|
arguments: [
|
||||||
|
{ name: "text", required: true, description: "The text to summarize" },
|
||||||
|
{
|
||||||
|
name: "maxLength",
|
||||||
|
required: true,
|
||||||
|
description: "Maximum length of summary in words",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format",
|
||||||
|
required: false,
|
||||||
|
description: "Output format (bullets or paragraph)",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const codeReviewPrompt: Prompt = {
|
||||||
|
name: "code-review",
|
||||||
|
arguments: [
|
||||||
|
{ name: "code", required: true, description: "The code to review" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export const NoArguments: Story = {
|
export const NoArguments: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "summarize",
|
prompt: summarizePrompt,
|
||||||
description: "Summarize the given text into key points",
|
|
||||||
arguments: [],
|
|
||||||
argumentValues: {},
|
argumentValues: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithArguments: Story = {
|
export const WithArguments: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "translate",
|
prompt: translatePrompt,
|
||||||
description: "Translate text from one language to another",
|
|
||||||
arguments: [
|
|
||||||
{ name: "text", required: true, description: "The text to translate" },
|
|
||||||
{
|
|
||||||
name: "targetLanguage",
|
|
||||||
required: true,
|
|
||||||
description: "The language to translate into",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
argumentValues: {},
|
argumentValues: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithRequiredAndOptional: Story = {
|
export const WithRequiredAndOptional: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "summarize",
|
prompt: summarizeWithArgsPrompt,
|
||||||
description: "Summarize the given text into key points",
|
|
||||||
arguments: [
|
|
||||||
{ name: "text", required: true, description: "The text to summarize" },
|
|
||||||
{
|
|
||||||
name: "maxLength",
|
|
||||||
required: true,
|
|
||||||
description: "Maximum length of summary in words",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "format",
|
|
||||||
required: false,
|
|
||||||
description: "Output format (bullets or paragraph)",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
argumentValues: {},
|
argumentValues: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AllFilled: Story = {
|
export const AllFilled: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "translate",
|
prompt: translatePrompt,
|
||||||
description: "Translate text from one language to another",
|
|
||||||
arguments: [
|
|
||||||
{ name: "text", required: true, description: "The text to translate" },
|
|
||||||
{
|
|
||||||
name: "targetLanguage",
|
|
||||||
required: true,
|
|
||||||
description: "The language to translate into",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
argumentValues: {
|
argumentValues: {
|
||||||
text: "Hello, how are you?",
|
text: "Hello, how are you?",
|
||||||
targetLanguage: "Spanish",
|
targetLanguage: "Spanish",
|
||||||
|
|
@ -81,10 +91,7 @@ export const AllFilled: Story = {
|
||||||
|
|
||||||
export const NoDescription: Story = {
|
export const NoDescription: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "code-review",
|
prompt: codeReviewPrompt,
|
||||||
arguments: [
|
|
||||||
{ name: "code", required: true, description: "The code to review" },
|
|
||||||
],
|
|
||||||
argumentValues: {},
|
argumentValues: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
import { Button, Group, Stack, Text, TextInput, Title } from "@mantine/core";
|
import { Button, Group, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||||
|
import type { Prompt } from "@modelcontextprotocol/sdk/types.js";
|
||||||
export interface PromptArgument {
|
|
||||||
name: string;
|
|
||||||
required: boolean;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PromptArgumentsFormProps {
|
export interface PromptArgumentsFormProps {
|
||||||
name: string;
|
prompt: Prompt;
|
||||||
description?: string;
|
|
||||||
arguments: PromptArgument[];
|
|
||||||
argumentValues: Record<string, string>;
|
argumentValues: Record<string, string>;
|
||||||
onArgumentChange: (name: string, value: string) => void;
|
onArgumentChange: (name: string, value: string) => void;
|
||||||
onGetPrompt: () => void;
|
onGetPrompt: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PromptTitle = Text.withProps({
|
||||||
|
fw: 700,
|
||||||
|
size: "lg",
|
||||||
|
truncate: "end",
|
||||||
|
});
|
||||||
|
|
||||||
const DescriptionText = Text.withProps({
|
const DescriptionText = Text.withProps({
|
||||||
size: "sm",
|
size: "sm",
|
||||||
c: "dimmed",
|
c: "dimmed",
|
||||||
|
|
@ -25,23 +24,25 @@ function formatPlaceholder(name: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PromptArgumentsForm({
|
export function PromptArgumentsForm({
|
||||||
description,
|
prompt,
|
||||||
arguments: promptArguments,
|
|
||||||
argumentValues,
|
argumentValues,
|
||||||
onArgumentChange,
|
onArgumentChange,
|
||||||
onGetPrompt,
|
onGetPrompt,
|
||||||
}: PromptArgumentsFormProps) {
|
}: PromptArgumentsFormProps) {
|
||||||
|
const { name, title, description, arguments: promptArguments } = prompt;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Title order={4}>Prompt Arguments</Title>
|
<PromptTitle>{title ?? name}</PromptTitle>
|
||||||
{description && <DescriptionText>{description}</DescriptionText>}
|
{description && <DescriptionText>{description}</DescriptionText>}
|
||||||
{promptArguments.length > 0 && (
|
<Title order={4}>Arguments</Title>
|
||||||
|
{promptArguments && promptArguments.length > 0 && (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{promptArguments.map((arg) => (
|
{promptArguments.map((arg) => (
|
||||||
<TextInput
|
<TextInput
|
||||||
key={arg.name}
|
key={arg.name}
|
||||||
label={arg.name}
|
label={arg.name}
|
||||||
withAsterisk={arg.required}
|
withAsterisk={arg.required === true}
|
||||||
description={arg.description}
|
description={arg.description}
|
||||||
placeholder={formatPlaceholder(arg.name)}
|
placeholder={formatPlaceholder(arg.name)}
|
||||||
value={argumentValues[arg.name] || ""}
|
value={argumentValues[arg.name] || ""}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import type { Prompt } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
import { PromptControls } from "./PromptControls";
|
import { PromptControls } from "./PromptControls";
|
||||||
|
|
||||||
|
|
@ -15,28 +16,24 @@ const meta: Meta<typeof PromptControls> = {
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof PromptControls>;
|
type Story = StoryObj<typeof PromptControls>;
|
||||||
|
|
||||||
const samplePrompts = [
|
const samplePrompts: Prompt[] = [
|
||||||
{
|
{
|
||||||
name: "summarize",
|
name: "summarize",
|
||||||
description: "Summarize the given text into key points",
|
description: "Summarize the given text into key points",
|
||||||
selected: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "translate",
|
name: "translate",
|
||||||
description: "Translate text from one language to another",
|
description: "Translate text from one language to another",
|
||||||
selected: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "analyze",
|
name: "analyze",
|
||||||
description: "Analyze sentiment and tone of the text",
|
description: "Analyze sentiment and tone of the text",
|
||||||
selected: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "code-review",
|
name: "code-review",
|
||||||
description: "Review code for issues",
|
description: "Review code for issues",
|
||||||
selected: false,
|
|
||||||
},
|
},
|
||||||
{ name: "refactor", selected: false },
|
{ name: "refactor" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
|
|
@ -47,9 +44,8 @@ export const Default: Story = {
|
||||||
|
|
||||||
export const WithSelection: Story = {
|
export const WithSelection: Story = {
|
||||||
args: {
|
args: {
|
||||||
prompts: samplePrompts.map((p) =>
|
prompts: samplePrompts,
|
||||||
p.name === "translate" ? { ...p, selected: true } : p,
|
selectedName: "translate",
|
||||||
),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Group, ScrollArea, Stack, TextInput, Title } from "@mantine/core";
|
import { Group, ScrollArea, Stack, TextInput, Title } from "@mantine/core";
|
||||||
|
import type { Prompt } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
|
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
|
||||||
import { PromptListItem } from "../PromptListItem/PromptListItem";
|
import { PromptListItem } from "../PromptListItem/PromptListItem";
|
||||||
import type { PromptItem } from "../../screens/PromptsScreen/PromptsScreen";
|
|
||||||
|
|
||||||
export interface PromptControlsProps {
|
export interface PromptControlsProps {
|
||||||
prompts: PromptItem[];
|
prompts: Prompt[];
|
||||||
|
selectedName?: string;
|
||||||
listChanged: boolean;
|
listChanged: boolean;
|
||||||
onRefreshList: () => void;
|
onRefreshList: () => void;
|
||||||
onSelectPrompt: (name: string) => void;
|
onSelectPrompt: (name: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function listMaxHeight(): string {
|
const LIST_MAX_HEIGHT =
|
||||||
return "calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2 - 160px)";
|
"calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2 - 160px)";
|
||||||
}
|
|
||||||
|
|
||||||
export function PromptControls({
|
export function PromptControls({
|
||||||
prompts,
|
prompts,
|
||||||
|
selectedName,
|
||||||
listChanged,
|
listChanged,
|
||||||
onRefreshList,
|
onRefreshList,
|
||||||
onSelectPrompt,
|
onSelectPrompt,
|
||||||
|
|
@ -26,6 +27,7 @@ export function PromptControls({
|
||||||
const filteredPrompts = prompts.filter(
|
const filteredPrompts = prompts.filter(
|
||||||
(p) =>
|
(p) =>
|
||||||
p.name.toLowerCase().includes(query) ||
|
p.name.toLowerCase().includes(query) ||
|
||||||
|
(p.title?.toLowerCase().includes(query) ?? false) ||
|
||||||
(p.description?.toLowerCase().includes(query) ?? false),
|
(p.description?.toLowerCase().includes(query) ?? false),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -40,15 +42,16 @@ export function PromptControls({
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.currentTarget.value)}
|
onChange={(e) => setSearchText(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ScrollArea.Autosize mah={listMaxHeight()}>
|
<ScrollArea.Autosize mah={LIST_MAX_HEIGHT}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{filteredPrompts.map((prompt) => (
|
{filteredPrompts.map((prompt) => (
|
||||||
<PromptListItem
|
<PromptListItem
|
||||||
key={prompt.name}
|
key={prompt.name}
|
||||||
name={prompt.name}
|
prompt={prompt}
|
||||||
description={prompt.description}
|
selected={prompt.name === selectedName}
|
||||||
selected={prompt.selected}
|
onClick={() => {
|
||||||
onClick={() => onSelectPrompt(prompt.name)}
|
if (prompt.name !== selectedName) onSelectPrompt(prompt.name);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
||||||
|
|
@ -15,23 +15,40 @@ type Story = StoryObj<typeof PromptListItem>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "summarize",
|
prompt: {
|
||||||
description: "Summarize the given text into key points",
|
name: "summarize",
|
||||||
|
description: "Summarize the given text into key points",
|
||||||
|
},
|
||||||
selected: false,
|
selected: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Selected: Story = {
|
export const Selected: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "translate",
|
prompt: {
|
||||||
description: "Translate text from one language to another",
|
name: "translate",
|
||||||
|
description: "Translate text from one language to another",
|
||||||
|
},
|
||||||
selected: true,
|
selected: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WithTitle: Story = {
|
||||||
|
args: {
|
||||||
|
prompt: {
|
||||||
|
name: "code-review",
|
||||||
|
title: "Code Review",
|
||||||
|
description: "Review code for issues and best practices",
|
||||||
|
},
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const NoDescription: Story = {
|
export const NoDescription: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "code-review",
|
prompt: {
|
||||||
|
name: "code-review",
|
||||||
|
},
|
||||||
selected: false,
|
selected: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Stack, Text, UnstyledButton } from "@mantine/core";
|
import { Stack, Text, UnstyledButton } from "@mantine/core";
|
||||||
|
import type { Prompt } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
export interface PromptListItemProps {
|
export interface PromptListItemProps {
|
||||||
name: string;
|
prompt: Prompt;
|
||||||
description?: string;
|
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
@ -18,11 +18,11 @@ const DescriptionText = Text.withProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
export function PromptListItem({
|
export function PromptListItem({
|
||||||
name,
|
prompt,
|
||||||
description,
|
|
||||||
selected,
|
selected,
|
||||||
onClick,
|
onClick,
|
||||||
}: PromptListItemProps) {
|
}: PromptListItemProps) {
|
||||||
|
const { name, title, description } = prompt;
|
||||||
return (
|
return (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
w="100%"
|
w="100%"
|
||||||
|
|
@ -32,7 +32,7 @@ export function PromptListItem({
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Stack gap={2}>
|
<Stack gap={2}>
|
||||||
<NameText>{name}</NameText>
|
<NameText>{title ?? name}</NameText>
|
||||||
{description && <DescriptionText>{description}</DescriptionText>}
|
{description && <DescriptionText>{description}</DescriptionText>}
|
||||||
</Stack>
|
</Stack>
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
|
|
|
||||||
|
|
@ -52,17 +52,11 @@ export const WithImage: Story = {
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: [
|
content: {
|
||||||
{
|
type: "image",
|
||||||
type: "text",
|
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||||
text: "Here is a photo for you to analyze.",
|
mimeType: "image/png",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: "image",
|
|
||||||
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
|
||||||
mimeType: "image/png",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,17 @@
|
||||||
import { Button, Group, Stack, Text, Title } from "@mantine/core";
|
import { Button, Group, Stack, Text, Title } from "@mantine/core";
|
||||||
import type {
|
import type { PromptMessage } from "@modelcontextprotocol/sdk/types.js";
|
||||||
PromptMessage,
|
|
||||||
SamplingMessage,
|
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
|
||||||
import { MessageBubble } from "../../elements/MessageBubble/MessageBubble";
|
import { MessageBubble } from "../../elements/MessageBubble/MessageBubble";
|
||||||
|
|
||||||
|
export interface PromptMessagesDisplayProps {
|
||||||
|
messages: PromptMessage[];
|
||||||
|
onCopyAll?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
const CopyAllButton = Button.withProps({
|
const CopyAllButton = Button.withProps({
|
||||||
variant: "subtle",
|
variant: "subtle",
|
||||||
size: "sm",
|
size: "sm",
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface PromptMessagesDisplayProps {
|
|
||||||
messages: (PromptMessage | SamplingMessage)[];
|
|
||||||
onCopyAll?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PromptMessagesDisplay({
|
export function PromptMessagesDisplay({
|
||||||
messages,
|
messages,
|
||||||
onCopyAll,
|
onCopyAll,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import type { Prompt } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
import { PromptsScreen } from "./PromptsScreen";
|
import { PromptsScreen } from "./PromptsScreen";
|
||||||
import type { PromptItem, SelectedPrompt } from "./PromptsScreen";
|
import type { GetPromptState } from "./PromptsScreen";
|
||||||
|
|
||||||
const meta: Meta<typeof PromptsScreen> = {
|
const meta: Meta<typeof PromptsScreen> = {
|
||||||
title: "Screens/PromptsScreen",
|
title: "Screens/PromptsScreen",
|
||||||
|
|
@ -10,8 +11,8 @@ const meta: Meta<typeof PromptsScreen> = {
|
||||||
args: {
|
args: {
|
||||||
onRefreshList: fn(),
|
onRefreshList: fn(),
|
||||||
onSelectPrompt: fn(),
|
onSelectPrompt: fn(),
|
||||||
onArgumentChange: fn(),
|
|
||||||
onGetPrompt: fn(),
|
onGetPrompt: fn(),
|
||||||
|
onCopyMessages: fn(),
|
||||||
listChanged: false,
|
listChanged: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -19,42 +20,54 @@ const meta: Meta<typeof PromptsScreen> = {
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof PromptsScreen>;
|
type Story = StoryObj<typeof PromptsScreen>;
|
||||||
|
|
||||||
const samplePrompts: PromptItem[] = [
|
const samplePrompts: Prompt[] = [
|
||||||
{
|
{
|
||||||
name: "summarize",
|
name: "summarize",
|
||||||
description: "Summarize the given text into key points",
|
description: "Summarize the given text into key points",
|
||||||
selected: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "translate",
|
name: "translate",
|
||||||
description: "Translate text from one language to another",
|
description: "Translate text from one language to another",
|
||||||
selected: false,
|
arguments: [
|
||||||
|
{ name: "text", required: true, description: "The text to translate" },
|
||||||
|
{
|
||||||
|
name: "targetLanguage",
|
||||||
|
required: true,
|
||||||
|
description: "Target language code",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "code-review",
|
name: "code-review",
|
||||||
description: "Review code for issues",
|
description: "Review code for issues",
|
||||||
selected: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "analyze",
|
name: "analyze",
|
||||||
description: "Analyze sentiment and tone of the text",
|
description: "Analyze sentiment and tone of the text",
|
||||||
selected: false,
|
|
||||||
},
|
},
|
||||||
{ name: "refactor", selected: false },
|
{ name: "refactor" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const selectedTranslate: SelectedPrompt = {
|
const translateResult: GetPromptState = {
|
||||||
name: "translate",
|
status: "ok",
|
||||||
description: "Translate text from one language to another",
|
result: {
|
||||||
arguments: [
|
messages: [
|
||||||
{ name: "text", required: true, description: "The text to translate" },
|
{
|
||||||
{
|
role: "user",
|
||||||
name: "targetLanguage",
|
content: {
|
||||||
required: true,
|
type: "text",
|
||||||
description: "Target language code",
|
text: 'Translate the following text to Spanish: "Hello, how are you?"',
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
argumentValues: {},
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: {
|
||||||
|
type: "text",
|
||||||
|
text: "Hola, como estas?",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoSelection: Story = {
|
export const NoSelection: Story = {
|
||||||
|
|
@ -65,44 +78,16 @@ export const NoSelection: Story = {
|
||||||
|
|
||||||
export const PromptSelected: Story = {
|
export const PromptSelected: Story = {
|
||||||
args: {
|
args: {
|
||||||
prompts: samplePrompts.map((p) =>
|
prompts: samplePrompts,
|
||||||
p.name === "translate" ? { ...p, selected: true } : p,
|
selectedPromptName: "translate",
|
||||||
),
|
|
||||||
selectedPrompt: selectedTranslate,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithResult: Story = {
|
export const WithResult: Story = {
|
||||||
args: {
|
args: {
|
||||||
prompts: samplePrompts.map((p) =>
|
prompts: samplePrompts,
|
||||||
p.name === "translate" ? { ...p, selected: true } : p,
|
selectedPromptName: "translate",
|
||||||
),
|
getPromptState: translateResult,
|
||||||
selectedPrompt: {
|
|
||||||
...selectedTranslate,
|
|
||||||
argumentValues: {
|
|
||||||
text: "Hello, how are you?",
|
|
||||||
targetLanguage: "es",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
messages: {
|
|
||||||
onCopyAll: fn(),
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
content: {
|
|
||||||
type: "text",
|
|
||||||
text: 'Translate the following text to Spanish: "Hello, how are you?"',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: "assistant",
|
|
||||||
content: {
|
|
||||||
type: "text",
|
|
||||||
text: "Hola, como estas?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,28 @@
|
||||||
|
import { useState } from "react";
|
||||||
import { Card, Flex, Group, ScrollArea, Stack, Text } from "@mantine/core";
|
import { Card, Flex, Group, ScrollArea, Stack, Text } from "@mantine/core";
|
||||||
|
import type {
|
||||||
|
GetPromptResult,
|
||||||
|
Prompt,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { PromptControls } from "../../groups/PromptControls/PromptControls";
|
import { PromptControls } from "../../groups/PromptControls/PromptControls";
|
||||||
import { PromptArgumentsForm } from "../../groups/PromptArgumentsForm/PromptArgumentsForm";
|
import { PromptArgumentsForm } from "../../groups/PromptArgumentsForm/PromptArgumentsForm";
|
||||||
import { PromptMessagesDisplay } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay";
|
import { PromptMessagesDisplay } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay";
|
||||||
import type { PromptArgument } from "../../groups/PromptArgumentsForm/PromptArgumentsForm";
|
|
||||||
import type { PromptMessagesDisplayProps } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay";
|
|
||||||
|
|
||||||
export interface PromptItem {
|
export interface GetPromptState {
|
||||||
name: string;
|
status: "idle" | "pending" | "ok" | "error";
|
||||||
description?: string;
|
result?: GetPromptResult;
|
||||||
selected: boolean;
|
error?: string;
|
||||||
}
|
|
||||||
|
|
||||||
export interface SelectedPrompt {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
arguments: PromptArgument[];
|
|
||||||
argumentValues: Record<string, string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PromptsScreenProps {
|
export interface PromptsScreenProps {
|
||||||
prompts: PromptItem[];
|
prompts: Prompt[];
|
||||||
selectedPrompt?: SelectedPrompt;
|
selectedPromptName?: string;
|
||||||
messages?: PromptMessagesDisplayProps;
|
getPromptState?: GetPromptState;
|
||||||
listChanged: boolean;
|
listChanged: boolean;
|
||||||
onRefreshList: () => void;
|
onRefreshList: () => void;
|
||||||
onSelectPrompt: (name: string) => void;
|
onSelectPrompt: (name: string) => void;
|
||||||
onArgumentChange: (name: string, value: string) => void;
|
onGetPrompt: (name: string, args: Record<string, string>) => void;
|
||||||
onGetPrompt: () => void;
|
onCopyMessages?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScreenLayout = Flex.withProps({
|
const ScreenLayout = Flex.withProps({
|
||||||
|
|
@ -60,23 +56,34 @@ const EmptyState = Text.withProps({
|
||||||
|
|
||||||
export function PromptsScreen({
|
export function PromptsScreen({
|
||||||
prompts,
|
prompts,
|
||||||
selectedPrompt,
|
selectedPromptName,
|
||||||
messages,
|
getPromptState,
|
||||||
listChanged,
|
listChanged,
|
||||||
onRefreshList,
|
onRefreshList,
|
||||||
onSelectPrompt,
|
onSelectPrompt,
|
||||||
onArgumentChange,
|
|
||||||
onGetPrompt,
|
onGetPrompt,
|
||||||
|
onCopyMessages,
|
||||||
}: PromptsScreenProps) {
|
}: PromptsScreenProps) {
|
||||||
|
const [argumentValues, setArgumentValues] = useState<Record<string, string>>(
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const selectedPrompt = selectedPromptName
|
||||||
|
? prompts.find((p) => p.name === selectedPromptName)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScreenLayout>
|
<ScreenLayout>
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<SidebarCard>
|
<SidebarCard>
|
||||||
<PromptControls
|
<PromptControls
|
||||||
prompts={prompts}
|
prompts={prompts}
|
||||||
|
selectedName={selectedPromptName}
|
||||||
listChanged={listChanged}
|
listChanged={listChanged}
|
||||||
onRefreshList={onRefreshList}
|
onRefreshList={onRefreshList}
|
||||||
onSelectPrompt={onSelectPrompt}
|
onSelectPrompt={(name) => {
|
||||||
|
setArgumentValues({});
|
||||||
|
onSelectPrompt(name);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</SidebarCard>
|
</SidebarCard>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
@ -90,17 +97,22 @@ export function PromptsScreen({
|
||||||
<>
|
<>
|
||||||
<DetailCard>
|
<DetailCard>
|
||||||
<PromptArgumentsForm
|
<PromptArgumentsForm
|
||||||
name={selectedPrompt.name}
|
prompt={selectedPrompt}
|
||||||
description={selectedPrompt.description}
|
argumentValues={argumentValues}
|
||||||
arguments={selectedPrompt.arguments}
|
onArgumentChange={(name, value) =>
|
||||||
argumentValues={selectedPrompt.argumentValues}
|
setArgumentValues((prev) => ({ ...prev, [name]: value }))
|
||||||
onArgumentChange={onArgumentChange}
|
}
|
||||||
onGetPrompt={onGetPrompt}
|
onGetPrompt={() =>
|
||||||
|
onGetPrompt(selectedPrompt.name, argumentValues)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</DetailCard>
|
</DetailCard>
|
||||||
{messages && (
|
{getPromptState?.result && (
|
||||||
<DetailCard>
|
<DetailCard>
|
||||||
<PromptMessagesDisplay {...messages} />
|
<PromptMessagesDisplay
|
||||||
|
messages={getPromptState.result.messages}
|
||||||
|
onCopyAll={onCopyMessages}
|
||||||
|
/>
|
||||||
</DetailCard>
|
</DetailCard>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -594,60 +594,52 @@ export const PromptsActive: Story = {
|
||||||
{
|
{
|
||||||
name: "summarize",
|
name: "summarize",
|
||||||
description: "Summarize a document",
|
description: "Summarize a document",
|
||||||
selected: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "translate",
|
name: "translate",
|
||||||
description: "Translate text to another language",
|
description: "Translate text to another language",
|
||||||
selected: true,
|
arguments: [
|
||||||
|
{
|
||||||
|
name: "text",
|
||||||
|
required: true,
|
||||||
|
description: "The text to translate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "targetLanguage",
|
||||||
|
required: true,
|
||||||
|
description: "Target language code",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "code-review",
|
name: "code-review",
|
||||||
description: "Review code for issues",
|
description: "Review code for issues",
|
||||||
selected: false,
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
selectedPrompt={{
|
selectedPromptName="translate"
|
||||||
name: "translate",
|
getPromptState={{
|
||||||
description: "Translate text to another language",
|
status: "ok",
|
||||||
arguments: [
|
result: {
|
||||||
{
|
messages: [
|
||||||
name: "text",
|
{
|
||||||
required: true,
|
role: "user",
|
||||||
description: "The text to translate",
|
content: {
|
||||||
},
|
type: "text",
|
||||||
{
|
text: 'Translate the following text to Spanish: "Hello, how are you?"',
|
||||||
name: "targetLanguage",
|
},
|
||||||
required: true,
|
|
||||||
description: "Target language code",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
argumentValues: {
|
|
||||||
text: "Hello, how are you?",
|
|
||||||
targetLanguage: "es",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
messages={{
|
|
||||||
onCopyAll: fn(),
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
content: {
|
|
||||||
type: "text",
|
|
||||||
text: 'Translate the following text to Spanish: "Hello, how are you?"',
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
role: "assistant",
|
||||||
role: "assistant",
|
content: { type: "text", text: "Hola, como estas?" },
|
||||||
content: { type: "text", text: "Hola, como estas?" },
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
}}
|
}}
|
||||||
listChanged={true}
|
listChanged={true}
|
||||||
onRefreshList={fn()}
|
onRefreshList={fn()}
|
||||||
onSelectPrompt={fn()}
|
onSelectPrompt={fn()}
|
||||||
onArgumentChange={fn()}
|
|
||||||
onGetPrompt={fn()}
|
onGetPrompt={fn()}
|
||||||
|
onCopyMessages={fn()}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue