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

updates assistant actions in participant list to use same dialog approach as conversations, resets default token limit for Explorer #238

Merged
merged 5 commits into from
Nov 12, 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
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