Skip to content

Commit

Permalink
updates assistant actions in participant list to use same dialog appr…
Browse files Browse the repository at this point in the history
…oach as conversations, resets default token limit for Explorer (#238)
  • Loading branch information
bkrabach authored Nov 12, 2024
1 parent f3819aa commit 5c2bd3f
Show file tree
Hide file tree
Showing 8 changed files with 411 additions and 210 deletions.
2 changes: 1 addition & 1 deletion assistants/explorer-assistant/assistant/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class RequestConfig(BaseModel):
),
),
UISchema(enable_markdown_in_description=True),
] = 50_000
] = 128_000

response_tokens: Annotated[
int,
Expand Down
78 changes: 36 additions & 42 deletions workbench-app/src/components/Assistants/AssistantConfigure.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.

import { Button, makeStyles, tokens } from '@fluentui/react-components';
import { SettingsRegular } from '@fluentui/react-icons';
import { DialogOpenChangeData, DialogOpenChangeEvent, makeStyles, tokens } from '@fluentui/react-components';
import React from 'react';
import { Assistant } from '../../models/Assistant';
import { CommandButton } from '../App/CommandButton';
import { DialogControl } from '../App/DialogControl';
import { AssistantConfiguration } from './AssistantConfiguration';

const useClasses = makeStyles({
Expand All @@ -27,55 +26,50 @@ const useClasses = makeStyles({
},
});

interface AssistantConfigureProps {
assistant: Assistant;
iconOnly?: boolean;
disabled?: boolean;
simulateMenuItem?: boolean;
interface AssistantConfigureDialogProps {
assistant?: Assistant;
open: boolean;
onOpenChange: (event: DialogOpenChangeEvent, data: DialogOpenChangeData) => void;
}

export const AssistantConfigure: React.FC<AssistantConfigureProps> = (props) => {
const { assistant, iconOnly, disabled, simulateMenuItem } = props;
export const AssistantConfigureDialog: React.FC<AssistantConfigureDialogProps> = (props) => {
const { assistant, open, onOpenChange } = props;
const classes = useClasses();
const [open, setOpen] = React.useState(false);
const [isDirty, setIsDirty] = React.useState(false);

const handleClose = React.useCallback(() => {
if (isDirty) {
const result = window.confirm('Are you sure you want to close without saving?');
if (!result) {
const handleOpenChange = React.useCallback(
(event: DialogOpenChangeEvent, data: DialogOpenChangeData) => {
if (data.open) {
setIsDirty(false);
return;
}
}
setOpen(false);
}, [isDirty]);

if (isDirty) {
const result = window.confirm('Are you sure you want to close without saving?');
if (!result) {
return;
}
}

setIsDirty(false);
onOpenChange(event, data);
},
[isDirty, onOpenChange],
);

return (
<CommandButton
<DialogControl
open={open}
onClick={() => setOpen(true)}
icon={<SettingsRegular />}
simulateMenuItem={simulateMenuItem}
label="Configure"
iconOnly={iconOnly}
disabled={disabled}
dialogContent={{
title: `Configure "${assistant.name}"`,
content: (
<div className={classes.content}>
<AssistantConfiguration assistant={assistant} onIsDirtyChange={setIsDirty} />
</div>
),
hideDismissButton: true,
classNames: {
dialogSurface: classes.dialogSurface,
dialogContent: classes.dialogContent,
},
additionalActions: [
<Button key="close" appearance="primary" onClick={handleClose}>
Close
</Button>,
],
onOpenChange={handleOpenChange}
title={assistant && `Configure "${assistant.name}"`}
content={
<div className={classes.content}>
{assistant && <AssistantConfiguration assistant={assistant} onIsDirtyChange={setIsDirty} />}
</div>
}
classNames={{
dialogSurface: classes.dialogSurface,
dialogContent: classes.dialogContent,
}}
/>
);
Expand Down
196 changes: 137 additions & 59 deletions workbench-app/src/components/Assistants/AssistantRemove.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,165 @@
// Copyright (c) Microsoft. All rights reserved.

import { DialogTrigger } from '@fluentui/react-components';
import { Button, DialogOpenChangeData, DialogOpenChangeEvent } from '@fluentui/react-components';
import { PlugDisconnectedRegular } from '@fluentui/react-icons';
import React from 'react';
import { Conversation } from '../../models/Conversation';
import { ConversationParticipant } from '../../models/ConversationParticipant';
import { Utility } from '../../libs/Utility';
import { useNotify } from '../../libs/useNotify';
import { Assistant } from '../../models/Assistant';
import {
useCreateConversationMessageMutation,
useGetConversationParticipantsQuery,
useRemoveConversationParticipantMutation,
} from '../../services/workbench';
import { CommandButton } from '../App/CommandButton';
import { DialogControl } from '../App/DialogControl';

interface AssistantRemoveProps {
participant: ConversationParticipant;
conversation: Conversation;
iconOnly?: boolean;
disabled?: boolean;
simulateMenuItem?: boolean;
}

export const AssistantRemove: React.FC<AssistantRemoveProps> = (props) => {
const { participant, conversation, iconOnly, disabled, simulateMenuItem } = props;
const [removeConversationParticipant] = useRemoveConversationParticipantMutation();
const useAssistantRemoveControls = (assistant?: Assistant, conversationId?: string) => {
const [createConversationMessage] = useCreateConversationMessageMutation();
const [removeConversationParticipant] = useRemoveConversationParticipantMutation();
const { refetch: refetchParticipants } = useGetConversationParticipantsQuery(conversationId ?? '', {
skip: !conversationId,
});
const [submitted, setSubmitted] = React.useState(false);

const handleAssistantRemove = React.useCallback(async () => {
if (submitted) {
return;
}
setSubmitted(true);
const handleRemove = React.useCallback(
async (onRemove?: (assistant: Assistant) => Promise<void>, onError?: (error: Error) => void) => {
if (!assistant || !conversationId || submitted) {
return;
}

try {
await removeConversationParticipant({
conversationId: conversation.id,
participantId: participant.id,
});
try {
await Utility.withStatus(setSubmitted, async () => {
await removeConversationParticipant({
conversationId: conversationId,
participantId: assistant.id,
});
await onRemove?.(assistant);

const content = `${participant.name} removed from conversation`;
const content = `${assistant.name} removed from conversation`;
await createConversationMessage({
conversationId: conversationId,
content,
messageType: 'notice',
});

await createConversationMessage({
conversationId: conversation.id,
content,
messageType: 'notice',
// Refetch participants to update the assistant name in the list
if (conversationId) {
await refetchParticipants();
}
});
} catch (error) {
onError?.(error as Error);
}
},
[
assistant,
conversationId,
createConversationMessage,
refetchParticipants,
removeConversationParticipant,
submitted,
],
);

const removeAssistantForm = React.useCallback(
(onRemove?: (assistant: Assistant) => Promise<void>) => (
<form
onSubmit={(event) => {
event.preventDefault();
handleRemove(onRemove);
}}
>
<p>
Are you sure you want to remove assistant <strong>{assistant?.name}</strong> from this conversation?
</p>
</form>
),
[assistant?.name, handleRemove],
);

const removeAssistantButton = React.useCallback(
(onRemove?: (assistant: Assistant) => Promise<void>, onError?: (error: Error) => void) => (
<Button
key="remove"
disabled={submitted}
onClick={() => handleRemove(onRemove, onError)}
appearance="primary"
>
{submitted ? 'Removing...' : 'Remove'}
</Button>
),
[handleRemove, submitted],
);

return {
removeAssistantForm,
removeAssistantButton,
};
};

interface AssistantRemoveDialogProps {
assistant?: Assistant;
conversationId?: string;
onRemove?: (assistant: Assistant) => Promise<void>;
open: boolean;
onOpenChange: (event: DialogOpenChangeEvent, data: DialogOpenChangeData) => void;
}

export const AssistantRemoveDialog: React.FC<AssistantRemoveDialogProps> = (props) => {
const { assistant, conversationId, open, onOpenChange, onRemove } = props;
const { removeAssistantForm, removeAssistantButton } = useAssistantRemoveControls(assistant, conversationId);
const { notifyWarning } = useNotify();

const handleError = React.useCallback(
(error: Error) => {
notifyWarning({
id: 'assistant-remove-error',
title: 'Remove assistant failed',
message: error.message,
});
} finally {
setSubmitted(false);
}
}, [
conversation.id,
createConversationMessage,
participant.id,
participant.name,
removeConversationParticipant,
submitted,
]);
},
[notifyWarning],
);

return (
<DialogControl
open={open}
onOpenChange={onOpenChange}
title="Remove assistant"
content={removeAssistantForm(onRemove)}
closeLabel="Cancel"
additionalActions={[removeAssistantButton(onRemove, handleError)]}
/>
);
};

interface AssistantRemoveProps {
assistant: Assistant;
conversationId: string;
disabled?: boolean;
onRemove?: (assistant: Assistant) => Promise<void>;
iconOnly?: boolean;
asToolbarButton?: boolean;
}

export const AssistantRemove: React.FC<AssistantRemoveProps> = (props) => {
const { assistant, conversationId, disabled, onRemove, iconOnly, asToolbarButton } = props;
const { removeAssistantForm, removeAssistantButton } = useAssistantRemoveControls(assistant, conversationId);

return (
<CommandButton
iconOnly={iconOnly}
icon={<PlugDisconnectedRegular />}
simulateMenuItem={simulateMenuItem}
label="Remove"
iconOnly={iconOnly}
disabled={disabled}
description="Remove assistant"
asToolbarButton={asToolbarButton}
dialogContent={{
title: `Remove "${participant.name}"`,
content: (
<p>
Are you sure you want to remove assistant <strong>{participant.name}</strong> from this
conversation?
</p>
),
title: `Remove "${assistant.name}"`,
content: removeAssistantForm(onRemove),
closeLabel: 'Cancel',
additionalActions: [
<DialogTrigger key="remove" disableButtonEnhancement>
<CommandButton
icon={<PlugDisconnectedRegular />}
disabled={submitted}
label={submitted ? 'Removing...' : 'Remove'}
onClick={handleAssistantRemove}
/>
</DialogTrigger>,
],
additionalActions: [removeAssistantButton(onRemove)],
}}
/>
);
Expand Down
Loading

0 comments on commit 5c2bd3f

Please sign in to comment.