Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pin config save/reset/actions to top of dialog, resizable canvas drawers #217

Merged
merged 6 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions workbench-app/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
"devcontainers",
"devtunnel",
"dotenv",
"dppx",
"echosql",
"endregion",
"epivision",
Expand Down
64 changes: 60 additions & 4 deletions workbench-app/src/components/App/CommandButton.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,56 @@
// Copyright (c) Microsoft. All rights reserved.

import { Button, ButtonProps, ToolbarButton, Tooltip } from '@fluentui/react-components';
import {
Button,
ButtonProps,
makeStyles,
mergeClasses,
tokens,
ToolbarButton,
Tooltip,
} from '@fluentui/react-components';
import React from 'react';
import { DialogControl, DialogControlContent } from './DialogControl';

const useClasses = makeStyles({
menuItem: {
paddingLeft: tokens.spacingHorizontalXS,
paddingRight: tokens.spacingHorizontalXS,
justifyContent: 'flex-start',
fontWeight: 'normal',
},
});

type CommandButtonProps = ButtonProps & {
className?: string;
label?: string;
description?: string;
onClick?: () => void;
dialogContent?: DialogControlContent;
open?: boolean;
iconOnly?: boolean;
asToolbarButton?: boolean;
simulateMenuItem?: boolean;
};

export const CommandButton: React.FC<CommandButtonProps> = (props) => {
const {
as,
className,
disabled,
icon,
label,
description,
onClick,
dialogContent,
open,
iconOnly,
asToolbarButton,
appearance,
size,
simulateMenuItem,
} = props;
const classes = useClasses();

let commandButton = null;

Expand All @@ -40,12 +64,27 @@ export const CommandButton: React.FC<CommandButtonProps> = (props) => {
} else {
commandButton = dialogContent.trigger;
}
} else if (simulateMenuItem) {
commandButton = (
<Button
as={as}
className={mergeClasses(classes.menuItem, className)}
appearance={appearance ?? 'subtle'}
size={size}
disabled={disabled}
icon={icon}
onClick={onClick}
>
{label}
</Button>
);
} else if (iconOnly) {
if (description) {
commandButton = (
<Tooltip content={description} relationship="label">
<Button
as={as}
className={className}
appearance={appearance}
size={size}
disabled={disabled}
Expand All @@ -56,18 +95,34 @@ export const CommandButton: React.FC<CommandButtonProps> = (props) => {
);
} else {
commandButton = (
<Button as={as} appearance={appearance} size={size} disabled={disabled} icon={icon} onClick={onClick} />
<Button
as={as}
className={className}
appearance={appearance}
size={size}
disabled={disabled}
icon={icon}
onClick={onClick}
/>
);
}
} else if (asToolbarButton) {
commandButton = (
<ToolbarButton disabled={disabled} icon={icon} onClick={onClick}>
<ToolbarButton className={className} disabled={disabled} icon={icon} onClick={onClick}>
{label}
</ToolbarButton>
);
} else {
commandButton = (
<Button as={as} disabled={disabled} icon={icon} appearance={appearance} size={size} onClick={onClick}>
<Button
as={as}
className={className}
disabled={disabled}
icon={icon}
appearance={appearance}
size={size}
onClick={onClick}
>
{label}
</Button>
);
Expand All @@ -88,6 +143,7 @@ export const CommandButton: React.FC<CommandButtonProps> = (props) => {
<DialogControl
trigger={commandButton}
classNames={dialogContent.classNames}
open={open}
title={dialogContent.title}
content={dialogContent.content}
closeLabel={dialogContent.closeLabel}
Expand Down
43 changes: 43 additions & 0 deletions workbench-app/src/components/App/MenuItemControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft. All rights reserved.

import { ButtonProps, MenuItem, Tooltip } from '@fluentui/react-components';
import React from 'react';

type MenuItemControlProps = ButtonProps & {
label?: string;
description?: string;
onClick?: () => void;
iconOnly?: boolean;
};

export const MenuItemControl: React.FC<MenuItemControlProps> = (props) => {
const { disabled, icon, label, description, onClick, iconOnly } = props;

let menuItem = null;

if (iconOnly) {
if (description) {
menuItem = (
<Tooltip content={description} relationship="label">
<MenuItem disabled={disabled} icon={icon} onClick={onClick} />
</Tooltip>
);
} else {
menuItem = <MenuItem disabled={disabled} icon={icon} onClick={onClick} />;
}
} else {
menuItem = (
<MenuItem disabled={disabled} icon={icon} onClick={onClick}>
{label}
</MenuItem>
);
if (description) {
menuItem = (
<Tooltip content={description} relationship="label">
{menuItem}
</Tooltip>
);
}
}
return menuItem;
};
21 changes: 15 additions & 6 deletions workbench-app/src/components/Assistants/AssistantConfiguration.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

import { Button, Card, Divider, makeStyles, shorthands, Text, tokens } from '@fluentui/react-components';
import { Button, Divider, makeStyles, shorthands, Text, tokens } from '@fluentui/react-components';
import { Warning24Filled } from '@fluentui/react-icons';
import Form from '@rjsf/fluentui-rc';
import { RegistryWidgetsType, RJSFSchema } from '@rjsf/utils';
Expand All @@ -26,8 +26,10 @@ import { AssistantConfigImportButton } from './AssistantConfigImportButton';
const log = debug(Constants.debug.root).extend('AssistantEdit');

const useClasses = makeStyles({
card: {
backgroundImage: `linear-gradient(to right, ${tokens.colorNeutralBackground1}, ${tokens.colorBrandBackground2})`,
root: {
display: 'flex',
flexDirection: 'column',
gap: tokens.spacingVerticalM,
},
actions: {
position: 'sticky',
Expand All @@ -51,10 +53,11 @@ const useClasses = makeStyles({

interface AssistantConfigurationProps {
assistant: Assistant;
onIsDirtyChange?: (isDirty: boolean) => void;
}

export const AssistantConfiguration: React.FC<AssistantConfigurationProps> = (props) => {
const { assistant } = props;
const { assistant, onIsDirtyChange } = props;
const classes = useClasses();
const {
data: config,
Expand All @@ -71,6 +74,12 @@ export const AssistantConfiguration: React.FC<AssistantConfigurationProps> = (pr
setConfigErrorMessage(configError ? JSON.stringify(configError) : undefined);
}, [configError]);

React.useEffect(() => {
if (onIsDirtyChange) {
onIsDirtyChange(isDirty);
}
}, [isDirty, onIsDirtyChange]);

React.useEffect(() => {
if (isLoadingConfig) return;
setFormData(config?.config);
Expand Down Expand Up @@ -137,7 +146,7 @@ export const AssistantConfiguration: React.FC<AssistantConfigurationProps> = (pr
}

return (
<Card className={classes.card}>
<div className={classes.root}>
<Text size={400} weight="semibold">
Assistant Configuration
</Text>
Expand Down Expand Up @@ -216,7 +225,7 @@ export const AssistantConfiguration: React.FC<AssistantConfigurationProps> = (pr
/>
</>
)}
</Card>
</div>
);
};

Expand Down
53 changes: 30 additions & 23 deletions workbench-app/src/components/Assistants/AssistantConfigure.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.

import { makeStyles, tokens } from '@fluentui/react-components';
import { Button, makeStyles, tokens } from '@fluentui/react-components';
import { SettingsRegular } from '@fluentui/react-icons';
import React from 'react';
import { useGetAssistantQuery } from '../../services/workbench';
import { Assistant } from '../../models/Assistant';
import { CommandButton } from '../App/CommandButton';
import { AssistantConfiguration } from './AssistantConfiguration';
import { AssistantServiceMetadata } from './AssistantServiceMetadata';

const useClasses = makeStyles({
dialogSurface: {
Expand All @@ -19,6 +18,7 @@ const useClasses = makeStyles({
width: 'calc(min(1000px, 100vw) - 100px)',
paddingRight: '8px',
boxSizing: 'border-box',
overflowY: 'auto',
},
content: {
display: 'flex',
Expand All @@ -28,47 +28,54 @@ const useClasses = makeStyles({
});

interface AssistantConfigureProps {
assistantId: string;
assistant: Assistant;
iconOnly?: boolean;
disabled?: boolean;
simulateMenuItem?: boolean;
}

export const AssistantConfigure: React.FC<AssistantConfigureProps> = (props) => {
const { assistantId, disabled } = props;
const { assistant, iconOnly, disabled, simulateMenuItem } = props;
const classes = useClasses();
const { data: assistant, error: assistantError, isLoading: assistantLoading } = useGetAssistantQuery(assistantId);
const [open, setOpen] = React.useState(false);
const [isDirty, setIsDirty] = React.useState(false);

if (assistantError) {
const errorMessage = JSON.stringify(assistantError);
throw new Error(`Error loading assistant (${assistantId}): ${errorMessage}`);
}

if (assistantLoading) {
return null;
}

if (!assistant) {
throw new Error(`Assistant (${assistantId}) not found`);
}
const handleClose = React.useCallback(() => {
if (isDirty) {
const result = window.confirm('Are you sure you want to close without saving?');
if (!result) {
return;
}
}
setOpen(false);
}, [isDirty]);

return (
<CommandButton
open={open}
onClick={() => setOpen(true)}
icon={<SettingsRegular />}
iconOnly
simulateMenuItem={simulateMenuItem}
label="Configure"
iconOnly={iconOnly}
disabled={disabled}
description={disabled ? `Workflow assistants cannot be configured` : 'Edit assistant configuration'}
dialogContent={{
title: `Configure "${assistant.name}"`,
content: (
<div className={classes.content}>
<AssistantServiceMetadata assistantServiceId={assistant.assistantServiceId} />
<AssistantConfiguration assistant={assistant} />
<AssistantConfiguration assistant={assistant} onIsDirtyChange={setIsDirty} />
</div>
),
closeLabel: 'Close',
hideDismissButton: true,
classNames: {
dialogSurface: classes.dialogSurface,
dialogContent: classes.dialogContent,
},
additionalActions: [
<Button key="close" appearance="primary" onClick={handleClose}>
Close
</Button>,
],
}}
/>
);
Expand Down
9 changes: 6 additions & 3 deletions workbench-app/src/components/Assistants/AssistantRemove.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import { CommandButton } from '../App/CommandButton';
interface AssistantRemoveProps {
participant: ConversationParticipant;
conversation: Conversation;
iconOnly?: boolean;
disabled?: boolean;
simulateMenuItem?: boolean;
}

export const AssistantRemove: React.FC<AssistantRemoveProps> = (props) => {
const { participant, conversation, disabled } = props;
const { participant, conversation, iconOnly, disabled, simulateMenuItem } = props;
const [removeConversationParticipant] = useRemoveConversationParticipantMutation();
const [createConversationMessage] = useCreateConversationMessageMutation();

Expand All @@ -40,9 +42,10 @@ export const AssistantRemove: React.FC<AssistantRemoveProps> = (props) => {
return (
<CommandButton
icon={<PlugDisconnectedRegular />}
iconOnly
simulateMenuItem={simulateMenuItem}
label="Remove"
iconOnly={iconOnly}
disabled={disabled}
description={disabled ? `Workflow assistants cannot be removed` : 'Remove assistant from conversation'}
dialogContent={{
title: `Remove "${participant.name}"`,
content: (
Expand Down
Loading