Skip to content

Commit

Permalink
pin config save/reset/actions to top of dialog, resizable canvas draw…
Browse files Browse the repository at this point in the history
…ers (#217)
  • Loading branch information
bkrabach authored Nov 6, 2024
1 parent 71f362c commit 451fe7e
Show file tree
Hide file tree
Showing 21 changed files with 718 additions and 310 deletions.
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

0 comments on commit 451fe7e

Please sign in to comment.