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
19 changed files with 627 additions and 233 deletions
|
|
@ -20,8 +20,7 @@ const meta: Meta<typeof LogControls> = {
|
||||||
onSetLevel: fn(),
|
onSetLevel: fn(),
|
||||||
onFilterChange: fn(),
|
onFilterChange: fn(),
|
||||||
onToggleLevel: fn(),
|
onToggleLevel: fn(),
|
||||||
onClear: fn(),
|
onToggleAllLevels: fn(),
|
||||||
onExport: fn(),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
|
||||||
Group,
|
Group,
|
||||||
Select,
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title,
|
||||||
|
UnstyledButton,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
|
||||||
const LOG_LEVELS = [
|
const LOG_LEVELS = [
|
||||||
|
|
@ -20,22 +20,17 @@ const LOG_LEVELS = [
|
||||||
"emergency",
|
"emergency",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const LEVEL_COLORS: Record<string, { c: string; fw?: number }> = {
|
const LEVEL_COLORS: Record<string, { c: string }> = {
|
||||||
debug: { c: "dimmed" },
|
debug: { c: "dimmed" },
|
||||||
info: { c: "blue" },
|
info: { c: "blue" },
|
||||||
notice: { c: "teal" },
|
notice: { c: "teal" },
|
||||||
warning: { c: "yellow" },
|
warning: { c: "yellow" },
|
||||||
error: { c: "red" },
|
error: { c: "red" },
|
||||||
critical: { c: "red", fw: 700 },
|
critical: { c: "red" },
|
||||||
alert: { c: "red", fw: 700 },
|
alert: { c: "red" },
|
||||||
emergency: { c: "red", fw: 900 },
|
emergency: { c: "red" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const ToolbarButton = Button.withProps({
|
|
||||||
variant: "light",
|
|
||||||
size: "sm",
|
|
||||||
});
|
|
||||||
|
|
||||||
const SubtleButton = Button.withProps({
|
const SubtleButton = Button.withProps({
|
||||||
variant: "subtle",
|
variant: "subtle",
|
||||||
size: "xs",
|
size: "xs",
|
||||||
|
|
@ -49,8 +44,6 @@ export interface LogControlsProps {
|
||||||
onFilterChange: (text: string) => void;
|
onFilterChange: (text: string) => void;
|
||||||
onToggleLevel: (level: string, visible: boolean) => void;
|
onToggleLevel: (level: string, visible: boolean) => void;
|
||||||
onToggleAllLevels: () => void;
|
onToggleAllLevels: () => void;
|
||||||
onClear: () => void;
|
|
||||||
onExport: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LogControls({
|
export function LogControls({
|
||||||
|
|
@ -61,8 +54,6 @@ export function LogControls({
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
onToggleLevel,
|
onToggleLevel,
|
||||||
onToggleAllLevels,
|
onToggleAllLevels,
|
||||||
onClear,
|
|
||||||
onExport,
|
|
||||||
}: LogControlsProps) {
|
}: LogControlsProps) {
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
|
|
@ -71,6 +62,7 @@ export function LogControls({
|
||||||
<Title order={5}>Active Level</Title>
|
<Title order={5}>Active Level</Title>
|
||||||
<Group wrap="nowrap">
|
<Group wrap="nowrap">
|
||||||
<Select
|
<Select
|
||||||
|
flex={1}
|
||||||
data={LOG_LEVELS.map((level) => ({ value: level, label: level }))}
|
data={LOG_LEVELS.map((level) => ({ value: level, label: level }))}
|
||||||
value={currentLevel}
|
value={currentLevel}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
|
@ -97,26 +89,24 @@ export function LogControls({
|
||||||
: "Select All"}
|
: "Select All"}
|
||||||
</SubtleButton>
|
</SubtleButton>
|
||||||
</Group>
|
</Group>
|
||||||
{LOG_LEVELS.map((level) => {
|
<Stack gap="xs">
|
||||||
const style = LEVEL_COLORS[level];
|
{LOG_LEVELS.map((level) => {
|
||||||
return (
|
const style = LEVEL_COLORS[level];
|
||||||
<Checkbox
|
const active = !!visibleLevels[level];
|
||||||
key={level}
|
return (
|
||||||
checked={!!visibleLevels[level]}
|
<UnstyledButton
|
||||||
onChange={(e) => onToggleLevel(level, e.currentTarget.checked)}
|
key={level}
|
||||||
label={
|
w="100%"
|
||||||
<Text c={style.c} fw={style.fw}>
|
p="sm"
|
||||||
{level}
|
variant="listItem"
|
||||||
</Text>
|
bg={active ? "var(--mantine-primary-color-light)" : undefined}
|
||||||
}
|
onClick={() => onToggleLevel(level, !active)}
|
||||||
/>
|
>
|
||||||
);
|
<Text c={style.c} ta="center" fw={500}>{level}</Text>
|
||||||
})}
|
</UnstyledButton>
|
||||||
|
);
|
||||||
<Group>
|
})}
|
||||||
<ToolbarButton onClick={onClear}>Clear</ToolbarButton>
|
</Stack>
|
||||||
<ToolbarButton onClick={onExport}>Export</ToolbarButton>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ const meta: Meta<typeof LogStreamPanel> = {
|
||||||
autoScroll: true,
|
autoScroll: true,
|
||||||
onToggleAutoScroll: fn(),
|
onToggleAutoScroll: fn(),
|
||||||
onCopyAll: fn(),
|
onCopyAll: fn(),
|
||||||
|
onClear: fn(),
|
||||||
|
onExport: fn(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ export interface LogStreamPanelProps {
|
||||||
autoScroll: boolean;
|
autoScroll: boolean;
|
||||||
onToggleAutoScroll: () => void;
|
onToggleAutoScroll: () => void;
|
||||||
onCopyAll: () => void;
|
onCopyAll: () => void;
|
||||||
|
onClear: () => void;
|
||||||
|
onExport: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PanelContainer = Paper.withProps({
|
const PanelContainer = Paper.withProps({
|
||||||
|
|
@ -61,6 +63,8 @@ export function LogStreamPanel({
|
||||||
autoScroll,
|
autoScroll,
|
||||||
onToggleAutoScroll,
|
onToggleAutoScroll,
|
||||||
onCopyAll,
|
onCopyAll,
|
||||||
|
onClear,
|
||||||
|
onExport,
|
||||||
}: LogStreamPanelProps) {
|
}: LogStreamPanelProps) {
|
||||||
const filteredEntries = useMemo(
|
const filteredEntries = useMemo(
|
||||||
() => entries.filter((e) => matchesFilters(e, filterText, visibleLevels)),
|
() => entries.filter((e) => matchesFilters(e, filterText, visibleLevels)),
|
||||||
|
|
@ -77,6 +81,8 @@ export function LogStreamPanel({
|
||||||
checked={autoScroll}
|
checked={autoScroll}
|
||||||
onChange={onToggleAutoScroll}
|
onChange={onToggleAutoScroll}
|
||||||
/>
|
/>
|
||||||
|
<ToolbarButton onClick={onClear}>Clear</ToolbarButton>
|
||||||
|
<ToolbarButton onClick={onExport}>Export</ToolbarButton>
|
||||||
<ToolbarButton onClick={onCopyAll}>Copy All</ToolbarButton>
|
<ToolbarButton onClick={onCopyAll}>Copy All</ToolbarButton>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ const meta: Meta<typeof PromptArgumentsForm> = {
|
||||||
title: "Groups/PromptArgumentsForm",
|
title: "Groups/PromptArgumentsForm",
|
||||||
component: PromptArgumentsForm,
|
component: PromptArgumentsForm,
|
||||||
args: {
|
args: {
|
||||||
onSelectPrompt: fn(),
|
|
||||||
onArgumentChange: fn(),
|
onArgumentChange: fn(),
|
||||||
onGetPrompt: fn(),
|
onGetPrompt: fn(),
|
||||||
},
|
},
|
||||||
|
|
@ -15,31 +14,19 @@ const meta: Meta<typeof PromptArgumentsForm> = {
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof PromptArgumentsForm>;
|
type Story = StoryObj<typeof PromptArgumentsForm>;
|
||||||
|
|
||||||
const samplePrompts = [
|
export const NoArguments: Story = {
|
||||||
{
|
args: {
|
||||||
name: "summarize",
|
name: "summarize",
|
||||||
description: "Summarize the given text into key points",
|
description: "Summarize the given text into key points",
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "translate",
|
|
||||||
description: "Translate text from one language to another",
|
|
||||||
},
|
|
||||||
{ name: "analyze", description: "Analyze sentiment and tone of the text" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const NoSelection: Story = {
|
|
||||||
args: {
|
|
||||||
prompts: samplePrompts,
|
|
||||||
selectedPrompt: undefined,
|
|
||||||
arguments: [],
|
arguments: [],
|
||||||
argumentValues: {},
|
argumentValues: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Selected: Story = {
|
export const WithArguments: Story = {
|
||||||
args: {
|
args: {
|
||||||
prompts: samplePrompts,
|
name: "translate",
|
||||||
selectedPrompt: "translate",
|
description: "Translate text from one language to another",
|
||||||
arguments: [
|
arguments: [
|
||||||
{ name: "text", required: true, description: "The text to translate" },
|
{ name: "text", required: true, description: "The text to translate" },
|
||||||
{
|
{
|
||||||
|
|
@ -52,10 +39,10 @@ export const Selected: Story = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithRequiredArgs: Story = {
|
export const WithRequiredAndOptional: Story = {
|
||||||
args: {
|
args: {
|
||||||
prompts: samplePrompts,
|
name: "summarize",
|
||||||
selectedPrompt: "summarize",
|
description: "Summarize the given text into key points",
|
||||||
arguments: [
|
arguments: [
|
||||||
{ name: "text", required: true, description: "The text to summarize" },
|
{ name: "text", required: true, description: "The text to summarize" },
|
||||||
{
|
{
|
||||||
|
|
@ -75,8 +62,8 @@ export const WithRequiredArgs: Story = {
|
||||||
|
|
||||||
export const AllFilled: Story = {
|
export const AllFilled: Story = {
|
||||||
args: {
|
args: {
|
||||||
prompts: samplePrompts,
|
name: "translate",
|
||||||
selectedPrompt: "translate",
|
description: "Translate text from one language to another",
|
||||||
arguments: [
|
arguments: [
|
||||||
{ name: "text", required: true, description: "The text to translate" },
|
{ name: "text", required: true, description: "The text to translate" },
|
||||||
{
|
{
|
||||||
|
|
@ -91,3 +78,13 @@ export const AllFilled: Story = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NoDescription: Story = {
|
||||||
|
args: {
|
||||||
|
name: "code-review",
|
||||||
|
arguments: [
|
||||||
|
{ name: "code", required: true, description: "The code to review" },
|
||||||
|
],
|
||||||
|
argumentValues: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, Select, Stack, Text, TextInput, Title } from "@mantine/core";
|
import { Button, Group, Stack, Text, TextInput, Title } from "@mantine/core";
|
||||||
|
|
||||||
export interface PromptArgument {
|
export interface PromptArgument {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -6,17 +6,11 @@ export interface PromptArgument {
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PromptInfo {
|
export interface PromptArgumentsFormProps {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
|
||||||
|
|
||||||
export interface PromptArgumentsFormProps {
|
|
||||||
prompts: PromptInfo[];
|
|
||||||
selectedPrompt?: string;
|
|
||||||
arguments: PromptArgument[];
|
arguments: PromptArgument[];
|
||||||
argumentValues: Record<string, string>;
|
argumentValues: Record<string, string>;
|
||||||
onSelectPrompt: (name: string) => void;
|
|
||||||
onArgumentChange: (name: string, value: string) => void;
|
onArgumentChange: (name: string, value: string) => void;
|
||||||
onGetPrompt: () => void;
|
onGetPrompt: () => void;
|
||||||
}
|
}
|
||||||
|
|
@ -31,34 +25,19 @@ function formatPlaceholder(name: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PromptArgumentsForm({
|
export function PromptArgumentsForm({
|
||||||
prompts,
|
name,
|
||||||
selectedPrompt,
|
description,
|
||||||
arguments: promptArguments,
|
arguments: promptArguments,
|
||||||
argumentValues,
|
argumentValues,
|
||||||
onSelectPrompt,
|
|
||||||
onArgumentChange,
|
onArgumentChange,
|
||||||
onGetPrompt,
|
onGetPrompt,
|
||||||
}: PromptArgumentsFormProps) {
|
}: PromptArgumentsFormProps) {
|
||||||
const selectedPromptInfo = prompts.find((p) => p.name === selectedPrompt);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Title order={4}>Prompts</Title>
|
<Title order={4}>{name}</Title>
|
||||||
<Select
|
{description && <DescriptionText>{description}</DescriptionText>}
|
||||||
data={prompts.map((p) => ({ value: p.name, label: p.name }))}
|
|
||||||
value={selectedPrompt ?? null}
|
|
||||||
onChange={(value) => {
|
|
||||||
if (value) {
|
|
||||||
onSelectPrompt(value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
placeholder="Choose a prompt..."
|
|
||||||
/>
|
|
||||||
{selectedPrompt && selectedPromptInfo?.description && (
|
|
||||||
<DescriptionText>{selectedPromptInfo.description}</DescriptionText>
|
|
||||||
)}
|
|
||||||
{promptArguments.length > 0 && (
|
{promptArguments.length > 0 && (
|
||||||
<>
|
<Stack gap="sm">
|
||||||
{promptArguments.map((arg) => (
|
{promptArguments.map((arg) => (
|
||||||
<TextInput
|
<TextInput
|
||||||
key={arg.name}
|
key={arg.name}
|
||||||
|
|
@ -72,11 +51,13 @@ export function PromptArgumentsForm({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
<Button fullWidth onClick={onGetPrompt} disabled={!selectedPrompt}>
|
<Group justify="flex-end">
|
||||||
Get Prompt
|
<Button size="sm" onClick={onGetPrompt}>
|
||||||
</Button>
|
Get Prompt
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import { fn } from "storybook/test";
|
||||||
|
import { PromptControls } from "./PromptControls";
|
||||||
|
|
||||||
|
const meta: Meta<typeof PromptControls> = {
|
||||||
|
title: "Groups/PromptControls",
|
||||||
|
component: PromptControls,
|
||||||
|
args: {
|
||||||
|
searchText: "",
|
||||||
|
onSearchChange: fn(),
|
||||||
|
onRefreshList: fn(),
|
||||||
|
onSelectPrompt: fn(),
|
||||||
|
listChanged: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof PromptControls>;
|
||||||
|
|
||||||
|
const samplePrompts = [
|
||||||
|
{
|
||||||
|
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: "analyze",
|
||||||
|
description: "Analyze sentiment and tone of the text",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "code-review",
|
||||||
|
description: "Review code for issues",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
{ name: "refactor", selected: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
prompts: samplePrompts,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithSelection: Story = {
|
||||||
|
args: {
|
||||||
|
prompts: samplePrompts.map((p) =>
|
||||||
|
p.name === "translate" ? { ...p, selected: true } : p,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithSearch: Story = {
|
||||||
|
args: {
|
||||||
|
prompts: samplePrompts,
|
||||||
|
searchText: "sum",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ListChanged: Story = {
|
||||||
|
args: {
|
||||||
|
prompts: samplePrompts,
|
||||||
|
listChanged: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Empty: Story = {
|
||||||
|
args: {
|
||||||
|
prompts: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { ScrollArea, Stack, TextInput, Title } from "@mantine/core";
|
||||||
|
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
|
||||||
|
import { PromptListItem } from "../PromptListItem/PromptListItem";
|
||||||
|
import type { PromptItem } from "../../screens/PromptsScreen/PromptsScreen";
|
||||||
|
|
||||||
|
export interface PromptControlsProps {
|
||||||
|
prompts: PromptItem[];
|
||||||
|
listChanged: boolean;
|
||||||
|
searchText: string;
|
||||||
|
onSearchChange: (text: string) => void;
|
||||||
|
onRefreshList: () => void;
|
||||||
|
onSelectPrompt: (name: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function listMaxHeight(): string {
|
||||||
|
return "calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2 - 160px)";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PromptControls({
|
||||||
|
prompts,
|
||||||
|
listChanged,
|
||||||
|
searchText,
|
||||||
|
onSearchChange,
|
||||||
|
onRefreshList,
|
||||||
|
onSelectPrompt,
|
||||||
|
}: PromptControlsProps) {
|
||||||
|
const filteredPrompts = prompts.filter((p) =>
|
||||||
|
p.name.toLowerCase().includes(searchText.toLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Title order={4}>Prompts</Title>
|
||||||
|
<ListChangedIndicator visible={listChanged} onRefresh={onRefreshList} />
|
||||||
|
<TextInput
|
||||||
|
placeholder="Search prompts..."
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => onSearchChange(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ScrollArea.Autosize mah={listMaxHeight()}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
{filteredPrompts.map((prompt) => (
|
||||||
|
<PromptListItem
|
||||||
|
key={prompt.name}
|
||||||
|
name={prompt.name}
|
||||||
|
description={prompt.description}
|
||||||
|
selected={prompt.selected}
|
||||||
|
onClick={() => onSelectPrompt(prompt.name)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</ScrollArea.Autosize>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import { fn } from "storybook/test";
|
||||||
|
import { PromptListItem } from "./PromptListItem";
|
||||||
|
|
||||||
|
const meta: Meta<typeof PromptListItem> = {
|
||||||
|
title: "Groups/PromptListItem",
|
||||||
|
component: PromptListItem,
|
||||||
|
args: {
|
||||||
|
onClick: fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof PromptListItem>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
name: "summarize",
|
||||||
|
description: "Summarize the given text into key points",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Selected: Story = {
|
||||||
|
args: {
|
||||||
|
name: "translate",
|
||||||
|
description: "Translate text from one language to another",
|
||||||
|
selected: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NoDescription: Story = {
|
||||||
|
args: {
|
||||||
|
name: "code-review",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Stack, Text, UnstyledButton } from "@mantine/core";
|
||||||
|
|
||||||
|
export interface PromptListItemProps {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
selected: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NameText = Text.withProps({
|
||||||
|
fw: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
const DescriptionText = Text.withProps({
|
||||||
|
size: "xs",
|
||||||
|
c: "dimmed",
|
||||||
|
lineClamp: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function PromptListItem({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
selected,
|
||||||
|
onClick,
|
||||||
|
}: PromptListItemProps) {
|
||||||
|
return (
|
||||||
|
<UnstyledButton
|
||||||
|
w="100%"
|
||||||
|
p="sm"
|
||||||
|
variant="listItem"
|
||||||
|
bg={selected ? "var(--mantine-primary-color-light)" : undefined}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Stack gap={2}>
|
||||||
|
<NameText>{name}</NameText>
|
||||||
|
{description && <DescriptionText>{description}</DescriptionText>}
|
||||||
|
</Stack>
|
||||||
|
</UnstyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import { fn } from "storybook/test";
|
||||||
|
import { ToolControls } from "./ToolControls";
|
||||||
|
|
||||||
|
const meta: Meta<typeof ToolControls> = {
|
||||||
|
title: "Groups/ToolControls",
|
||||||
|
component: ToolControls,
|
||||||
|
args: {
|
||||||
|
searchText: "",
|
||||||
|
onSearchChange: fn(),
|
||||||
|
onRefreshList: fn(),
|
||||||
|
onSelectTool: fn(),
|
||||||
|
listChanged: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof ToolControls>;
|
||||||
|
|
||||||
|
const sampleTools = [
|
||||||
|
{
|
||||||
|
name: "send_message",
|
||||||
|
title: "Send Message",
|
||||||
|
selected: false,
|
||||||
|
onClick: fn(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create_record",
|
||||||
|
title: "Create Record",
|
||||||
|
selected: false,
|
||||||
|
onClick: fn(),
|
||||||
|
},
|
||||||
|
{ name: "delete_records", selected: false, onClick: fn() },
|
||||||
|
{ name: "list_users", selected: false, onClick: fn() },
|
||||||
|
{ name: "batch_process", selected: false, onClick: fn() },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
tools: sampleTools,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithSelection: Story = {
|
||||||
|
args: {
|
||||||
|
tools: sampleTools.map((t) =>
|
||||||
|
t.name === "create_record" ? { ...t, selected: true } : t,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithSearch: Story = {
|
||||||
|
args: {
|
||||||
|
tools: sampleTools,
|
||||||
|
searchText: "send",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ListChanged: Story = {
|
||||||
|
args: {
|
||||||
|
tools: sampleTools,
|
||||||
|
listChanged: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Empty: Story = {
|
||||||
|
args: {
|
||||||
|
tools: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { ScrollArea, Stack, TextInput, Title } from "@mantine/core";
|
||||||
|
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
|
||||||
|
import { ToolListItem } from "../ToolListItem/ToolListItem";
|
||||||
|
import type { ToolListItemProps } from "../ToolListItem/ToolListItem";
|
||||||
|
|
||||||
|
export interface ToolControlsProps {
|
||||||
|
tools: ToolListItemProps[];
|
||||||
|
listChanged: boolean;
|
||||||
|
searchText: string;
|
||||||
|
onSearchChange: (text: string) => void;
|
||||||
|
onRefreshList: () => void;
|
||||||
|
onSelectTool: (name: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function listMaxHeight(): string {
|
||||||
|
return "calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2 - 160px)";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ToolControls({
|
||||||
|
tools,
|
||||||
|
listChanged,
|
||||||
|
searchText,
|
||||||
|
onSearchChange,
|
||||||
|
onRefreshList,
|
||||||
|
onSelectTool,
|
||||||
|
}: ToolControlsProps) {
|
||||||
|
const filteredTools = searchText
|
||||||
|
? tools.filter((tool) =>
|
||||||
|
tool.name.toLowerCase().includes(searchText.toLowerCase()),
|
||||||
|
)
|
||||||
|
: tools;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Title order={4}>Tools</Title>
|
||||||
|
<ListChangedIndicator visible={listChanged} onRefresh={onRefreshList} />
|
||||||
|
<TextInput
|
||||||
|
placeholder="Search tools..."
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => onSearchChange(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ScrollArea.Autosize mah={listMaxHeight()}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
{filteredTools.map((tool) => (
|
||||||
|
<ToolListItem
|
||||||
|
key={tool.name}
|
||||||
|
{...tool}
|
||||||
|
onClick={() => onSelectTool(tool.name)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</ScrollArea.Autosize>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ const ScreenLayout = Flex.withProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const Sidebar = Stack.withProps({
|
const Sidebar = Stack.withProps({
|
||||||
w: 280,
|
w: 340,
|
||||||
flex: "0 0 auto",
|
flex: "0 0 auto",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const ScreenLayout = Flex.withProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const Sidebar = Stack.withProps({
|
const Sidebar = Stack.withProps({
|
||||||
w: 280,
|
w: 340,
|
||||||
flex: "0 0 auto",
|
flex: "0 0 auto",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -82,8 +82,6 @@ export function LoggingScreen({
|
||||||
onFilterChange={setFilterText}
|
onFilterChange={setFilterText}
|
||||||
onToggleLevel={handleToggleLevel}
|
onToggleLevel={handleToggleLevel}
|
||||||
onToggleAllLevels={handleToggleAllLevels}
|
onToggleAllLevels={handleToggleAllLevels}
|
||||||
onClear={onClear}
|
|
||||||
onExport={onExport}
|
|
||||||
/>
|
/>
|
||||||
</SidebarCard>
|
</SidebarCard>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
@ -94,6 +92,8 @@ export function LoggingScreen({
|
||||||
autoScroll={autoScroll}
|
autoScroll={autoScroll}
|
||||||
onToggleAutoScroll={onToggleAutoScroll}
|
onToggleAutoScroll={onToggleAutoScroll}
|
||||||
onCopyAll={onCopyAll}
|
onCopyAll={onCopyAll}
|
||||||
|
onClear={onClear}
|
||||||
|
onExport={onExport}
|
||||||
/>
|
/>
|
||||||
</ScreenLayout>
|
</ScreenLayout>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,90 @@
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
import { PromptsScreen } from "./PromptsScreen";
|
import { PromptsScreen } from "./PromptsScreen";
|
||||||
|
import type { PromptItem, SelectedPrompt } from "./PromptsScreen";
|
||||||
|
|
||||||
const meta: Meta<typeof PromptsScreen> = {
|
const meta: Meta<typeof PromptsScreen> = {
|
||||||
title: "Screens/PromptsScreen",
|
title: "Screens/PromptsScreen",
|
||||||
component: PromptsScreen,
|
component: PromptsScreen,
|
||||||
parameters: { layout: "fullscreen" },
|
parameters: { layout: "fullscreen" },
|
||||||
args: {
|
args: {
|
||||||
|
searchText: "",
|
||||||
|
onSearchChange: fn(),
|
||||||
onRefreshList: fn(),
|
onRefreshList: fn(),
|
||||||
|
onSelectPrompt: fn(),
|
||||||
|
onArgumentChange: fn(),
|
||||||
|
onGetPrompt: fn(),
|
||||||
|
listChanged: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof PromptsScreen>;
|
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 = {
|
export const NoSelection: Story = {
|
||||||
args: {
|
args: {
|
||||||
promptForm: {
|
prompts: samplePrompts,
|
||||||
prompts: [
|
},
|
||||||
{ name: "summarize", description: "Summarize a document" },
|
};
|
||||||
{
|
|
||||||
name: "translate",
|
export const PromptSelected: Story = {
|
||||||
description: "Translate text to another language",
|
args: {
|
||||||
},
|
prompts: samplePrompts.map((p) =>
|
||||||
{ name: "code-review", description: "Review code for issues" },
|
p.name === "translate" ? { ...p, selected: true } : p,
|
||||||
],
|
),
|
||||||
selectedPrompt: undefined,
|
selectedPrompt: selectedTranslate,
|
||||||
arguments: [],
|
|
||||||
argumentValues: {},
|
|
||||||
onSelectPrompt: fn(),
|
|
||||||
onArgumentChange: fn(),
|
|
||||||
onGetPrompt: fn(),
|
|
||||||
},
|
|
||||||
messages: undefined,
|
|
||||||
listChanged: false,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithResult: Story = {
|
export const WithResult: Story = {
|
||||||
args: {
|
args: {
|
||||||
promptForm: {
|
prompts: samplePrompts.map((p) =>
|
||||||
prompts: [
|
p.name === "translate" ? { ...p, selected: true } : p,
|
||||||
{ name: "summarize", description: "Summarize a document" },
|
),
|
||||||
{
|
selectedPrompt: {
|
||||||
name: "translate",
|
...selectedTranslate,
|
||||||
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",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
argumentValues: {
|
argumentValues: {
|
||||||
text: "Hello, how are you?",
|
text: "Hello, how are you?",
|
||||||
targetLanguage: "es",
|
targetLanguage: "es",
|
||||||
},
|
},
|
||||||
onSelectPrompt: fn(),
|
|
||||||
onArgumentChange: fn(),
|
|
||||||
onGetPrompt: fn(),
|
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
onCopyAll: fn(),
|
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 { PromptArgumentsForm } from "../../groups/PromptArgumentsForm/PromptArgumentsForm";
|
||||||
import { PromptMessagesDisplay } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay";
|
import { PromptMessagesDisplay } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay";
|
||||||
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
|
import type { PromptArgument } from "../../groups/PromptArgumentsForm/PromptArgumentsForm";
|
||||||
import type { PromptArgumentsFormProps } from "../../groups/PromptArgumentsForm/PromptArgumentsForm";
|
|
||||||
import type { PromptMessagesDisplayProps } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay";
|
import type { PromptMessagesDisplayProps } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay";
|
||||||
|
|
||||||
export interface PromptsScreenProps {
|
export interface PromptItem {
|
||||||
promptForm: PromptArgumentsFormProps;
|
name: string;
|
||||||
messages?: PromptMessagesDisplayProps;
|
description?: string;
|
||||||
listChanged: boolean;
|
selected: boolean;
|
||||||
onRefreshList: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageContainer = Container.withProps({
|
export interface SelectedPrompt {
|
||||||
size: "xl",
|
name: string;
|
||||||
py: "xl",
|
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,
|
withBorder: true,
|
||||||
padding: "lg",
|
padding: "lg",
|
||||||
h: "100%",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const EmptyState = Text.withProps({
|
const EmptyState = Text.withProps({
|
||||||
|
|
@ -30,37 +60,62 @@ const EmptyState = Text.withProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
export function PromptsScreen({
|
export function PromptsScreen({
|
||||||
promptForm,
|
prompts,
|
||||||
|
selectedPrompt,
|
||||||
messages,
|
messages,
|
||||||
listChanged,
|
listChanged,
|
||||||
|
searchText,
|
||||||
|
onSearchChange,
|
||||||
onRefreshList,
|
onRefreshList,
|
||||||
|
onSelectPrompt,
|
||||||
|
onArgumentChange,
|
||||||
|
onGetPrompt,
|
||||||
}: PromptsScreenProps) {
|
}: PromptsScreenProps) {
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<ScreenLayout>
|
||||||
<Grid align="stretch">
|
<Sidebar>
|
||||||
<Grid.Col span={5}>
|
<SidebarCard>
|
||||||
<FullHeightCard>
|
<PromptControls
|
||||||
<Stack gap="md">
|
prompts={prompts}
|
||||||
<ListChangedIndicator
|
listChanged={listChanged}
|
||||||
visible={listChanged}
|
searchText={searchText}
|
||||||
onRefresh={onRefreshList}
|
onSearchChange={onSearchChange}
|
||||||
/>
|
onRefreshList={onRefreshList}
|
||||||
<PromptArgumentsForm {...promptForm} />
|
onSelectPrompt={onSelectPrompt}
|
||||||
</Stack>
|
/>
|
||||||
</FullHeightCard>
|
</SidebarCard>
|
||||||
</Grid.Col>
|
</Sidebar>
|
||||||
<Grid.Col span={7}>
|
|
||||||
<FullHeightCard>
|
<ScrollArea.Autosize
|
||||||
{messages ? (
|
flex={1}
|
||||||
<PromptMessagesDisplay {...messages} />
|
mah="calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2)"
|
||||||
) : (
|
>
|
||||||
<EmptyState>
|
<Stack gap="md">
|
||||||
Select a prompt and click Get Prompt to see messages
|
{selectedPrompt ? (
|
||||||
</EmptyState>
|
<>
|
||||||
)}
|
<DetailCard>
|
||||||
</FullHeightCard>
|
<PromptArgumentsForm
|
||||||
</Grid.Col>
|
name={selectedPrompt.name}
|
||||||
</Grid>
|
description={selectedPrompt.description}
|
||||||
</PageContainer>
|
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({
|
const Sidebar = Stack.withProps({
|
||||||
w: 280,
|
w: 340,
|
||||||
flex: "0 0 auto",
|
flex: "0 0 auto",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,5 @@
|
||||||
import {
|
import { Card, Flex, Grid, Stack, Text } from "@mantine/core";
|
||||||
Card,
|
import { ToolControls } from "../../groups/ToolControls/ToolControls";
|
||||||
Container,
|
|
||||||
Grid,
|
|
||||||
Stack,
|
|
||||||
Text,
|
|
||||||
TextInput,
|
|
||||||
Title,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
|
|
||||||
import { ToolListItem } from "../../groups/ToolListItem/ToolListItem";
|
|
||||||
import { ToolDetailPanel } from "../../groups/ToolDetailPanel/ToolDetailPanel";
|
import { ToolDetailPanel } from "../../groups/ToolDetailPanel/ToolDetailPanel";
|
||||||
import { ResultPanel } from "../../groups/ResultPanel/ResultPanel";
|
import { ResultPanel } from "../../groups/ResultPanel/ResultPanel";
|
||||||
import type { ToolListItemProps } from "../../groups/ToolListItem/ToolListItem";
|
import type { ToolListItemProps } from "../../groups/ToolListItem/ToolListItem";
|
||||||
|
|
@ -26,9 +17,21 @@ export interface ToolsScreenProps {
|
||||||
onSelectTool: (name: string) => void;
|
onSelectTool: (name: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageContainer = Container.withProps({
|
const ScreenLayout = Flex.withProps({
|
||||||
size: "xl",
|
variant: "screen",
|
||||||
py: "xl",
|
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({
|
const FullHeightCard = Card.withProps({
|
||||||
|
|
@ -53,40 +56,23 @@ export function ToolsScreen({
|
||||||
onRefreshList,
|
onRefreshList,
|
||||||
onSelectTool,
|
onSelectTool,
|
||||||
}: ToolsScreenProps) {
|
}: ToolsScreenProps) {
|
||||||
const filteredTools = searchText
|
|
||||||
? tools.filter((tool) =>
|
|
||||||
tool.name.toLowerCase().includes(searchText.toLowerCase()),
|
|
||||||
)
|
|
||||||
: tools;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<ScreenLayout>
|
||||||
<Grid align="stretch">
|
<Sidebar>
|
||||||
<Grid.Col span={3}>
|
<SidebarCard>
|
||||||
<FullHeightCard>
|
<ToolControls
|
||||||
<Stack gap="sm">
|
tools={tools}
|
||||||
<Title order={4}>Tools</Title>
|
listChanged={listChanged}
|
||||||
<ListChangedIndicator
|
searchText={searchText}
|
||||||
visible={listChanged}
|
onSearchChange={onSearchChange}
|
||||||
onRefresh={onRefreshList}
|
onRefreshList={onRefreshList}
|
||||||
/>
|
onSelectTool={onSelectTool}
|
||||||
<TextInput
|
/>
|
||||||
placeholder="Search tools..."
|
</SidebarCard>
|
||||||
value={searchText}
|
</Sidebar>
|
||||||
onChange={(event) => onSearchChange(event.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
{filteredTools.map((tool) => (
|
|
||||||
<ToolListItem
|
|
||||||
key={tool.name}
|
|
||||||
{...tool}
|
|
||||||
onClick={() => onSelectTool(tool.name)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
</FullHeightCard>
|
|
||||||
</Grid.Col>
|
|
||||||
|
|
||||||
<Grid.Col span={5}>
|
<Grid align="stretch" flex={1}>
|
||||||
|
<Grid.Col span={7}>
|
||||||
<FullHeightCard>
|
<FullHeightCard>
|
||||||
{selectedTool ? (
|
{selectedTool ? (
|
||||||
<ToolDetailPanel {...selectedTool} />
|
<ToolDetailPanel {...selectedTool} />
|
||||||
|
|
@ -96,7 +82,7 @@ export function ToolsScreen({
|
||||||
</FullHeightCard>
|
</FullHeightCard>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={4}>
|
<Grid.Col span={5}>
|
||||||
<FullHeightCard>
|
<FullHeightCard>
|
||||||
{result ? (
|
{result ? (
|
||||||
<ResultPanel {...result} />
|
<ResultPanel {...result} />
|
||||||
|
|
@ -106,6 +92,6 @@ export function ToolsScreen({
|
||||||
</FullHeightCard>
|
</FullHeightCard>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</PageContainer>
|
</ScreenLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -465,16 +465,26 @@ export const PromptsActive: Story = {
|
||||||
activeTab: "Prompts",
|
activeTab: "Prompts",
|
||||||
children: (
|
children: (
|
||||||
<PromptsScreen
|
<PromptsScreen
|
||||||
promptForm={{
|
prompts={[
|
||||||
prompts: [
|
{
|
||||||
{ name: "summarize", description: "Summarize a document" },
|
name: "summarize",
|
||||||
{
|
description: "Summarize a document",
|
||||||
name: "translate",
|
selected: false,
|
||||||
description: "Translate text to another language",
|
},
|
||||||
},
|
{
|
||||||
{ name: "code-review", description: "Review code for issues" },
|
name: "translate",
|
||||||
],
|
description: "Translate text to another language",
|
||||||
selectedPrompt: "translate",
|
selected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "code-review",
|
||||||
|
description: "Review code for issues",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
selectedPrompt={{
|
||||||
|
name: "translate",
|
||||||
|
description: "Translate text to another language",
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
name: "text",
|
name: "text",
|
||||||
|
|
@ -491,9 +501,6 @@ export const PromptsActive: Story = {
|
||||||
text: "Hello, how are you?",
|
text: "Hello, how are you?",
|
||||||
targetLanguage: "es",
|
targetLanguage: "es",
|
||||||
},
|
},
|
||||||
onSelectPrompt: fn(),
|
|
||||||
onArgumentChange: fn(),
|
|
||||||
onGetPrompt: fn(),
|
|
||||||
}}
|
}}
|
||||||
messages={{
|
messages={{
|
||||||
onCopyAll: fn(),
|
onCopyAll: fn(),
|
||||||
|
|
@ -507,7 +514,12 @@ export const PromptsActive: Story = {
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
listChanged={false}
|
listChanged={false}
|
||||||
|
searchText=""
|
||||||
|
onSearchChange={fn()}
|
||||||
onRefreshList={fn()}
|
onRefreshList={fn()}
|
||||||
|
onSelectPrompt={fn()}
|
||||||
|
onArgumentChange={fn()}
|
||||||
|
onGetPrompt={fn()}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue