Normalize screen layouts: extract control groups, unify sidebar widths, and refactor PromptsScreen
- Extract ToolControls group from ToolsScreen (search, list, list-changed indicator) - Extract PromptControls and PromptListItem groups for PromptsScreen sidebar - Refactor PromptsScreen to sidebar + detail layout (matching ResourcesScreen pattern) - Refactor PromptArgumentsForm to accept a single prompt instead of a dropdown - Normalize all screen sidebar widths to 340px (Tools, Resources, Prompts, Logs, Tasks, History) - Convert ToolsScreen from Grid-only to sidebar + Grid layout - Replace LogControls checkboxes with full-width toggle buttons - Move Clear/Export buttons from LogControls to LogStreamPanel toolbar - Make PromptArgumentsForm "Get Prompt" button right-aligned and compact - Update all affected stories and ConnectedView Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eca6cf7665
commit
a6724626b7
6 changed files with 627 additions and 233 deletions
|
|
@ -19,7 +19,7 @@ const ScreenLayout = Flex.withProps({
|
|||
});
|
||||
|
||||
const Sidebar = Stack.withProps({
|
||||
w: 280,
|
||||
w: 340,
|
||||
flex: "0 0 auto",
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const ScreenLayout = Flex.withProps({
|
|||
});
|
||||
|
||||
const Sidebar = Stack.withProps({
|
||||
w: 280,
|
||||
w: 340,
|
||||
flex: "0 0 auto",
|
||||
});
|
||||
|
||||
|
|
@ -82,8 +82,6 @@ export function LoggingScreen({
|
|||
onFilterChange={setFilterText}
|
||||
onToggleLevel={handleToggleLevel}
|
||||
onToggleAllLevels={handleToggleAllLevels}
|
||||
onClear={onClear}
|
||||
onExport={onExport}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
|
|
@ -94,6 +92,8 @@ export function LoggingScreen({
|
|||
autoScroll={autoScroll}
|
||||
onToggleAutoScroll={onToggleAutoScroll}
|
||||
onCopyAll={onCopyAll}
|
||||
onClear={onClear}
|
||||
onExport={onExport}
|
||||
/>
|
||||
</ScreenLayout>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,69 +1,90 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { fn } from "storybook/test";
|
||||
import { PromptsScreen } from "./PromptsScreen";
|
||||
import type { PromptItem, SelectedPrompt } from "./PromptsScreen";
|
||||
|
||||
const meta: Meta<typeof PromptsScreen> = {
|
||||
title: "Screens/PromptsScreen",
|
||||
component: PromptsScreen,
|
||||
parameters: { layout: "fullscreen" },
|
||||
args: {
|
||||
searchText: "",
|
||||
onSearchChange: fn(),
|
||||
onRefreshList: fn(),
|
||||
onSelectPrompt: fn(),
|
||||
onArgumentChange: fn(),
|
||||
onGetPrompt: fn(),
|
||||
listChanged: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PromptsScreen>;
|
||||
|
||||
const samplePrompts: PromptItem[] = [
|
||||
{
|
||||
name: "summarize",
|
||||
description: "Summarize the given text into key points",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "translate",
|
||||
description: "Translate text from one language to another",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "code-review",
|
||||
description: "Review code for issues",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
name: "analyze",
|
||||
description: "Analyze sentiment and tone of the text",
|
||||
selected: false,
|
||||
},
|
||||
{ name: "refactor", selected: false },
|
||||
];
|
||||
|
||||
const selectedTranslate: SelectedPrompt = {
|
||||
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: "Target language code",
|
||||
},
|
||||
],
|
||||
argumentValues: {},
|
||||
};
|
||||
|
||||
export const NoSelection: Story = {
|
||||
args: {
|
||||
promptForm: {
|
||||
prompts: [
|
||||
{ name: "summarize", description: "Summarize a document" },
|
||||
{
|
||||
name: "translate",
|
||||
description: "Translate text to another language",
|
||||
},
|
||||
{ name: "code-review", description: "Review code for issues" },
|
||||
],
|
||||
selectedPrompt: undefined,
|
||||
arguments: [],
|
||||
argumentValues: {},
|
||||
onSelectPrompt: fn(),
|
||||
onArgumentChange: fn(),
|
||||
onGetPrompt: fn(),
|
||||
},
|
||||
messages: undefined,
|
||||
listChanged: false,
|
||||
prompts: samplePrompts,
|
||||
},
|
||||
};
|
||||
|
||||
export const PromptSelected: Story = {
|
||||
args: {
|
||||
prompts: samplePrompts.map((p) =>
|
||||
p.name === "translate" ? { ...p, selected: true } : p,
|
||||
),
|
||||
selectedPrompt: selectedTranslate,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithResult: Story = {
|
||||
args: {
|
||||
promptForm: {
|
||||
prompts: [
|
||||
{ name: "summarize", description: "Summarize a document" },
|
||||
{
|
||||
name: "translate",
|
||||
description: "Translate text to another language",
|
||||
},
|
||||
{ name: "code-review", description: "Review code for issues" },
|
||||
],
|
||||
selectedPrompt: "translate",
|
||||
arguments: [
|
||||
{ name: "text", required: true, description: "The text to translate" },
|
||||
{
|
||||
name: "targetLanguage",
|
||||
required: true,
|
||||
description: "Target language code",
|
||||
},
|
||||
],
|
||||
prompts: samplePrompts.map((p) =>
|
||||
p.name === "translate" ? { ...p, selected: true } : p,
|
||||
),
|
||||
selectedPrompt: {
|
||||
...selectedTranslate,
|
||||
argumentValues: {
|
||||
text: "Hello, how are you?",
|
||||
targetLanguage: "es",
|
||||
},
|
||||
onSelectPrompt: fn(),
|
||||
onArgumentChange: fn(),
|
||||
onGetPrompt: fn(),
|
||||
},
|
||||
messages: {
|
||||
onCopyAll: fn(),
|
||||
|
|
@ -79,6 +100,18 @@ export const WithResult: Story = {
|
|||
},
|
||||
],
|
||||
},
|
||||
listChanged: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const ListChanged: Story = {
|
||||
args: {
|
||||
prompts: samplePrompts,
|
||||
listChanged: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
prompts: [],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,26 +1,56 @@
|
|||
import { Card, Container, Grid, Stack, Text } from "@mantine/core";
|
||||
import { Card, Flex, ScrollArea, Stack, Text } from "@mantine/core";
|
||||
import { PromptControls } from "../../groups/PromptControls/PromptControls";
|
||||
import { PromptArgumentsForm } from "../../groups/PromptArgumentsForm/PromptArgumentsForm";
|
||||
import { PromptMessagesDisplay } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay";
|
||||
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
|
||||
import type { PromptArgumentsFormProps } from "../../groups/PromptArgumentsForm/PromptArgumentsForm";
|
||||
import type { PromptArgument } from "../../groups/PromptArgumentsForm/PromptArgumentsForm";
|
||||
import type { PromptMessagesDisplayProps } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay";
|
||||
|
||||
export interface PromptsScreenProps {
|
||||
promptForm: PromptArgumentsFormProps;
|
||||
messages?: PromptMessagesDisplayProps;
|
||||
listChanged: boolean;
|
||||
onRefreshList: () => void;
|
||||
export interface PromptItem {
|
||||
name: string;
|
||||
description?: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
const PageContainer = Container.withProps({
|
||||
size: "xl",
|
||||
py: "xl",
|
||||
export interface SelectedPrompt {
|
||||
name: string;
|
||||
description?: string;
|
||||
arguments: PromptArgument[];
|
||||
argumentValues: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface PromptsScreenProps {
|
||||
prompts: PromptItem[];
|
||||
selectedPrompt?: SelectedPrompt;
|
||||
messages?: PromptMessagesDisplayProps;
|
||||
listChanged: boolean;
|
||||
searchText: string;
|
||||
onSearchChange: (text: string) => void;
|
||||
onRefreshList: () => void;
|
||||
onSelectPrompt: (name: string) => void;
|
||||
onArgumentChange: (name: string, value: string) => void;
|
||||
onGetPrompt: () => void;
|
||||
}
|
||||
|
||||
const ScreenLayout = Flex.withProps({
|
||||
variant: "screen",
|
||||
h: "calc(100vh - var(--app-shell-header-height, 0px))",
|
||||
gap: "xl",
|
||||
p: "xl",
|
||||
});
|
||||
|
||||
const FullHeightCard = Card.withProps({
|
||||
const Sidebar = Stack.withProps({
|
||||
w: 340,
|
||||
flex: "0 0 auto",
|
||||
});
|
||||
|
||||
const SidebarCard = Card.withProps({
|
||||
withBorder: true,
|
||||
padding: "lg",
|
||||
});
|
||||
|
||||
const DetailCard = Card.withProps({
|
||||
withBorder: true,
|
||||
padding: "lg",
|
||||
h: "100%",
|
||||
});
|
||||
|
||||
const EmptyState = Text.withProps({
|
||||
|
|
@ -30,37 +60,62 @@ const EmptyState = Text.withProps({
|
|||
});
|
||||
|
||||
export function PromptsScreen({
|
||||
promptForm,
|
||||
prompts,
|
||||
selectedPrompt,
|
||||
messages,
|
||||
listChanged,
|
||||
searchText,
|
||||
onSearchChange,
|
||||
onRefreshList,
|
||||
onSelectPrompt,
|
||||
onArgumentChange,
|
||||
onGetPrompt,
|
||||
}: PromptsScreenProps) {
|
||||
return (
|
||||
<PageContainer>
|
||||
<Grid align="stretch">
|
||||
<Grid.Col span={5}>
|
||||
<FullHeightCard>
|
||||
<Stack gap="md">
|
||||
<ListChangedIndicator
|
||||
visible={listChanged}
|
||||
onRefresh={onRefreshList}
|
||||
/>
|
||||
<PromptArgumentsForm {...promptForm} />
|
||||
</Stack>
|
||||
</FullHeightCard>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={7}>
|
||||
<FullHeightCard>
|
||||
{messages ? (
|
||||
<PromptMessagesDisplay {...messages} />
|
||||
) : (
|
||||
<EmptyState>
|
||||
Select a prompt and click Get Prompt to see messages
|
||||
</EmptyState>
|
||||
)}
|
||||
</FullHeightCard>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</PageContainer>
|
||||
<ScreenLayout>
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<PromptControls
|
||||
prompts={prompts}
|
||||
listChanged={listChanged}
|
||||
searchText={searchText}
|
||||
onSearchChange={onSearchChange}
|
||||
onRefreshList={onRefreshList}
|
||||
onSelectPrompt={onSelectPrompt}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
|
||||
<ScrollArea.Autosize
|
||||
flex={1}
|
||||
mah="calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2)"
|
||||
>
|
||||
<Stack gap="md">
|
||||
{selectedPrompt ? (
|
||||
<>
|
||||
<DetailCard>
|
||||
<PromptArgumentsForm
|
||||
name={selectedPrompt.name}
|
||||
description={selectedPrompt.description}
|
||||
arguments={selectedPrompt.arguments}
|
||||
argumentValues={selectedPrompt.argumentValues}
|
||||
onArgumentChange={onArgumentChange}
|
||||
onGetPrompt={onGetPrompt}
|
||||
/>
|
||||
</DetailCard>
|
||||
{messages && (
|
||||
<DetailCard>
|
||||
<PromptMessagesDisplay {...messages} />
|
||||
</DetailCard>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<DetailCard>
|
||||
<EmptyState>Select a prompt to view details</EmptyState>
|
||||
</DetailCard>
|
||||
)}
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const ScreenLayout = Flex.withProps({
|
|||
});
|
||||
|
||||
const Sidebar = Stack.withProps({
|
||||
w: 280,
|
||||
w: 340,
|
||||
flex: "0 0 auto",
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,5 @@
|
|||
import {
|
||||
Card,
|
||||
Container,
|
||||
Grid,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
|
||||
import { ToolListItem } from "../../groups/ToolListItem/ToolListItem";
|
||||
import { Card, Flex, Grid, Stack, Text } from "@mantine/core";
|
||||
import { ToolControls } from "../../groups/ToolControls/ToolControls";
|
||||
import { ToolDetailPanel } from "../../groups/ToolDetailPanel/ToolDetailPanel";
|
||||
import { ResultPanel } from "../../groups/ResultPanel/ResultPanel";
|
||||
import type { ToolListItemProps } from "../../groups/ToolListItem/ToolListItem";
|
||||
|
|
@ -26,9 +17,21 @@ export interface ToolsScreenProps {
|
|||
onSelectTool: (name: string) => void;
|
||||
}
|
||||
|
||||
const PageContainer = Container.withProps({
|
||||
size: "xl",
|
||||
py: "xl",
|
||||
const ScreenLayout = Flex.withProps({
|
||||
variant: "screen",
|
||||
h: "calc(100vh - var(--app-shell-header-height, 0px))",
|
||||
gap: "xl",
|
||||
p: "xl",
|
||||
});
|
||||
|
||||
const Sidebar = Stack.withProps({
|
||||
w: 340,
|
||||
flex: "0 0 auto",
|
||||
});
|
||||
|
||||
const SidebarCard = Card.withProps({
|
||||
withBorder: true,
|
||||
padding: "lg",
|
||||
});
|
||||
|
||||
const FullHeightCard = Card.withProps({
|
||||
|
|
@ -53,40 +56,23 @@ export function ToolsScreen({
|
|||
onRefreshList,
|
||||
onSelectTool,
|
||||
}: ToolsScreenProps) {
|
||||
const filteredTools = searchText
|
||||
? tools.filter((tool) =>
|
||||
tool.name.toLowerCase().includes(searchText.toLowerCase()),
|
||||
)
|
||||
: tools;
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Grid align="stretch">
|
||||
<Grid.Col span={3}>
|
||||
<FullHeightCard>
|
||||
<Stack gap="sm">
|
||||
<Title order={4}>Tools</Title>
|
||||
<ListChangedIndicator
|
||||
visible={listChanged}
|
||||
onRefresh={onRefreshList}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Search tools..."
|
||||
value={searchText}
|
||||
onChange={(event) => onSearchChange(event.currentTarget.value)}
|
||||
/>
|
||||
{filteredTools.map((tool) => (
|
||||
<ToolListItem
|
||||
key={tool.name}
|
||||
{...tool}
|
||||
onClick={() => onSelectTool(tool.name)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</FullHeightCard>
|
||||
</Grid.Col>
|
||||
<ScreenLayout>
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<ToolControls
|
||||
tools={tools}
|
||||
listChanged={listChanged}
|
||||
searchText={searchText}
|
||||
onSearchChange={onSearchChange}
|
||||
onRefreshList={onRefreshList}
|
||||
onSelectTool={onSelectTool}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
|
||||
<Grid.Col span={5}>
|
||||
<Grid align="stretch" flex={1}>
|
||||
<Grid.Col span={7}>
|
||||
<FullHeightCard>
|
||||
{selectedTool ? (
|
||||
<ToolDetailPanel {...selectedTool} />
|
||||
|
|
@ -96,7 +82,7 @@ export function ToolsScreen({
|
|||
</FullHeightCard>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={4}>
|
||||
<Grid.Col span={5}>
|
||||
<FullHeightCard>
|
||||
{result ? (
|
||||
<ResultPanel {...result} />
|
||||
|
|
@ -106,6 +92,6 @@ export function ToolsScreen({
|
|||
</FullHeightCard>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</PageContainer>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue