Skip to content

Commit

Permalink
Simplifying state + removing unused components (microsoft#371)
Browse files Browse the repository at this point in the history
### Motivation and Context
Removing unused parts of app, including chatSlice from redux and filter
icon in ChatList

### Description
Consolidated ChatState into ConversationsState, will manage state from
conversations array.
Commenting out Filter icon under "Conversations" list until we're ready
to implement
Changed login error to be more helpful to error cases we've observed
  • Loading branch information
teresaqhoang authored Apr 10, 2023
1 parent 98a38fa commit 8ac4e6f
Show file tree
Hide file tree
Showing 18 changed files with 128 additions and 176 deletions.
2 changes: 1 addition & 1 deletion samples/apps/copilot-chat-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ and these components are functional:
2. Copy `.env.example` into a new file with the name “`.env`” and make the
following configuration changes to match your instance:
3. Use the Application (client) ID from the Azure Portal steps above and
paste the GUID into the `.env` file next to `REACT_APP_CHAT_CLIENT_ID= `
paste the GUID into the `.env` file next to `REACT_APP_AAD_CLIENT_ID= `
4. Execute the command `yarn install`
5. Execute the command `yarn start`

Expand Down
4 changes: 2 additions & 2 deletions samples/apps/copilot-chat-app/WebApp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ The Copilot Chat sameple showcases how to build an enriched intelligent app, wit

1. Ensure the SKWebApi is running at `https://localhost:40443/`. See [SKWebApi README](../SKWebApi/README.md) for instructions.
2. Create an `.env` file to this folder root with the following variables and fill in with your information, where
`REACT_APP_CHAT_CLIENT_ID=` is the GUID copied from the **Application (client) ID** from your app registration in the Azure Portal and
`REACT_APP_AAD_CLIENT_ID=` is the GUID copied from the **Application (client) ID** from your app registration in the Azure Portal and
`REACT_APP_BACKEND_URI=` is the URI where your backend is running.

```
REACT_APP_BACKEND_URI=https://localhost:40443/
REACT_APP_CHAT_CLIENT_ID=
REACT_APP_AAD_CLIENT_ID=
```

3. **Run** the following command `yarn install` (if you have never run the app before) and/or `yarn start` from the command line.
Expand Down
2 changes: 1 addition & 1 deletion samples/apps/copilot-chat-app/WebApp/env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
REACT_APP_BACKEND_URI=http://localhost:40443/
REACT_APP_CHAT_CLIENT_ID=
REACT_APP_AAD_CLIENT_ID=
19 changes: 10 additions & 9 deletions samples/apps/copilot-chat-app/WebApp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ const useClasses = makeStyles({
justifyContent: 'space-between',
},
persona: {
marginRight: '20px'
}
marginRight: '20px',
},
});

enum AppState {
ProbeForBackend,
Chat
Chat,
}

const App: FC = () => {
Expand All @@ -50,9 +50,10 @@ const App: FC = () => {
const account = msalInstance.getActiveAccount();

useEffect(() => {
// TODO: Load Conversations from BE
// TODO: Load conversations from BE
const keys = Object.keys(conversations);
dispatch(setSelectedConversation(keys[0]));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
Expand All @@ -61,15 +62,15 @@ const App: FC = () => {
<Login />
</UnauthenticatedTemplate>
<AuthenticatedTemplate>
{appState === AppState.ProbeForBackend &&
{appState === AppState.ProbeForBackend && (
<BackendProbe
uri={process.env.REACT_APP_BACKEND_URI as string}
onBackendFound={() => setAppState(AppState.Chat)}
/>
}
{appState === AppState.Chat &&
)}
{appState === AppState.Chat && (
<div style={{ display: 'flex', width: '100%', flexDirection: 'column', height: '100vh' }}>
<div className={classes.header} >
<div className={classes.header}>
<Subtitle1 as="h1">Copilot Chat</Subtitle1>
<Avatar
className={classes.persona}
Expand All @@ -81,7 +82,7 @@ const App: FC = () => {
</div>
<ChatView />
</div>
}
)}
</AuthenticatedTemplate>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion samples/apps/copilot-chat-app/WebApp/src/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const Constants = {
msal: {
method: 'redirect', // 'redirect' | 'popup'
auth: {
clientId: process.env.REACT_APP_CHAT_CLIENT_ID as string,
clientId: process.env.REACT_APP_AAD_CLIENT_ID as string,
authority: `https://login.microsoftonline.com/common`,
},
cache: {
Expand Down
15 changes: 8 additions & 7 deletions samples/apps/copilot-chat-app/WebApp/src/components/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ export const Login: React.FC = () => {

const handleError = (error: any) => {
console.error(error);
setErrorMessage((error as Error).message);
}
setErrorMessage(
`Login failed. Check that you have a valid REACT_APP_AAD_CLIENT_ID set in your .env file. See ${
(error as Error).name
} in console for more details.`,
);
};

const handleSignIn = async (): Promise<void> => {
try {
Expand All @@ -38,11 +42,8 @@ export const Login: React.FC = () => {

useEffect(() => {
handleSignIn();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<div className={classes.root}>
{errorMessage && <Alert intent="error">{errorMessage}</Alert>}
</div>
);
return <div className={classes.root}>{errorMessage && <Alert intent="error">{errorMessage}</Alert>}</div>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,19 @@ export const ChatHistoryItem: React.FC<ChatHistoryItemProps> = (props) => {
}

const isMe = message.sender === account?.homeAccountId;
const member = chat.getAudienceMemberForId(message.sender);
const avatar = isMe ?
member?.photo ? { image: { src: member.photo } } : undefined
const member = chat.getAudienceMemberForId(message.sender, selectedId, conversations[selectedId].audience);
const avatar = isMe
? member?.photo
? { image: { src: member.photo } }
: undefined
: { image: { src: conversations[selectedId].botProfilePicture } };
const fullName = member?.fullName ?? message.sender;

return (
<>
<div className={isMe ? mergeClasses(classes.root, classes.alignEnd) : classes.root}>
{!isMe && <Persona className={classes.persona} avatar={avatar} />}
<div
className={isMe ? mergeClasses(classes.item, classes.me) : classes.item}
>
<div className={isMe ? mergeClasses(classes.item, classes.me) : classes.item}>
<div className={classes.header}>
{!isMe && <Label weight="semibold">{fullName}</Label>}
<Label className={mergeClasses(classes.time, classes.alignEnd)} size="small">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@ const useClasses = makeStyles({
},
});



export const ChatRoom: React.FC = () => {
const { audience } = useAppSelector((state: RootState) => state.chat);
const { conversations, selectedId } = useAppSelector((state: RootState) => state.conversations);
const { audience } = conversations[selectedId];
const messages = conversations[selectedId].messages;
const classes = useClasses();
const account = useAccount();
Expand Down Expand Up @@ -79,9 +77,9 @@ export const ChatRoom: React.FC = () => {
const handleSubmit = async (value: string) => {
log('submitting user chat message');
const chatInput = {
timestamp: new Date().getTime(),
sender: account?.homeAccountId,
content: value,
timestamp: new Date().getTime(),
sender: account?.homeAccountId,
content: value,
};
dispatch(updateConversation({ message: chatInput }));
await chat.getResponse(value, selectedId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ const useClasses = makeStyles({
export const ChatStatus: React.FC = () => {
const classes = useClasses();
const account = useAccount();
const { audience, botTypingTimestamp } = useAppSelector((state: RootState) => state.chat);
const { conversations, selectedId } = useAppSelector((state: RootState) => state.conversations);
const { audience, botTypingTimestamp } = conversations[selectedId];
const [typing, setTyping] = React.useState<SKBotAudienceMember[]>([]);

// if audience is changed, check back in 5 seconds to see if they are still typing
Expand All @@ -37,7 +38,7 @@ export const ChatStatus: React.FC = () => {
});
}
const typingAudience = audience.filter(
(chatUser) =>
(chatUser: ChatUser) =>
chatUser.id !== account?.homeAccountId &&
chatUser.lastTypingTimestamp > Date.now() - timeoutDuration,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import { Button, Label, makeStyles, shorthands, Text } from "@fluentui/react-components";
import {
Tree,
TreeItem
} from "@fluentui/react-components/unstable";
import { Button, Label, makeStyles, shorthands, Text } from '@fluentui/react-components';
import { Tree, TreeItem } from '@fluentui/react-components/unstable';

import { BotAdd20Regular, Filter20Regular } from "@fluentui/react-icons";
import { FC } from "react";
import { useChat } from "../../../libs/useChat";
import { useAppSelector } from "../../../redux/app/hooks";
import { RootState } from "../../../redux/app/store";
import { ChatListItem } from "./ChatListItem";
import { BotAdd20Regular } from '@fluentui/react-icons';
import { FC } from 'react';
import { useChat } from '../../../libs/useChat';
import { useAppSelector } from '../../../redux/app/hooks';
import { RootState } from '../../../redux/app/store';
import { ChatListItem } from './ChatListItem';
const useClasses = makeStyles({
root: {
width: '25%',
minHeight: '100%',
overflowX: 'hidden',
overflowY: 'auto',
scrollbarWidth: 'thin',
backgroundColor: '#F0F0F0'
backgroundColor: '#F0F0F0',
},
header: {
display: 'flex',
Expand All @@ -30,11 +27,11 @@ const useClasses = makeStyles({
'& controls': {
...shorthands.gap('10px'),
display: 'flex',
}
},
},
label: {
marginLeft: '1em'
}
marginLeft: '1em',
},
});

export const ChatList: FC = () => {
Expand All @@ -46,49 +43,54 @@ export const ChatList: FC = () => {
chat.createChat();
};

// TODO: hookup buttons onClick
return <>
<div className={classes.root}>
<div className={classes.header}>
<Text weight="bold" size={500}>Conversations</Text>
<div >
<Button
return (
<>
<div className={classes.root}>
<div className={classes.header}>
<Text weight="bold" size={500}>
Conversations
</Text>
<div>
{/* TODO: Allow users to filter conversations by name
<Button
icon={<Filter20Regular />}
appearance="transparent" />
<Button
icon={<BotAdd20Regular />}
appearance="transparent"
onClick={onAddChat}
/>
appearance="transparent" /> */}
<Button icon={<BotAdd20Regular />} appearance="transparent" onClick={onAddChat} />
</div>
</div>
</div>
<Label className={classes.label}>Your Bot</Label>
<Tree aria-label={'chat list'}>
{
Object.keys(conversations).map((id) => {
<Label className={classes.label}>Your Bot</Label>
<Tree aria-label={'chat list'}>
{Object.keys(conversations).map((id) => {
const convo = conversations[id];
const messages = convo.messages;
const lastMessage = convo.messages.length - 1;
return (
<TreeItem
key={id}
leaf
style={id === selectedId
? { background: 'var(--colorNeutralBackground1Selected)' }
: undefined}
style={
id === selectedId
? { background: 'var(--colorNeutralBackground1Selected)' }
: undefined
}
>
<ChatListItem
id={id}
header={id}
timestamp={new Date(messages[lastMessage].timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
preview={messages.length > 0 ? messages[lastMessage].content : 'Click to start the chat'}
timestamp={new Date(messages[lastMessage].timestamp).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})}
preview={
messages.length > 0 ? messages[lastMessage].content : 'Click to start the chat'
}
botProfilePicture={convo.botProfilePicture}
/>
</TreeItem>
);
})
}
</Tree>
</div>
</>;
}
})}
</Tree>
</div>
</>
);
};
34 changes: 18 additions & 16 deletions samples/apps/copilot-chat-app/WebApp/src/libs/useChat.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
import { useAccount } from "@azure/msal-react";
import { Constants } from "../Constants";
import { useAppDispatch, useAppSelector } from "../redux/app/hooks";
import { RootState } from "../redux/app/store";
import { ChatState, initialBotMessage } from "../redux/features/chat/ChatState";
import { addConversation, setSelectedConversation, updateConversation } from "../redux/features/conversations/conversationsSlice";
import { ChatUser } from "./models/ChatUser";
import { useSemanticKernel } from "./semantic-kernel/useSemanticKernel";
import { useAccount } from '@azure/msal-react';
import { Constants } from '../Constants';
import { useAppDispatch, useAppSelector } from '../redux/app/hooks';
import { RootState } from '../redux/app/store';
import { ChatState, initialBotMessage } from '../redux/features/conversations/ChatState';
import {
addConversation,
setSelectedConversation,
updateConversation,
} from '../redux/features/conversations/conversationsSlice';
import { ChatUser } from './models/ChatUser';
import { useSemanticKernel } from './semantic-kernel/useSemanticKernel';

export const useChat = () => {
const { audience } = useAppSelector((state: RootState) => state.chat);
const dispatch = useAppDispatch();
const account = useAccount();
const sk = useSemanticKernel(process.env.REACT_APP_BACKEND_URI as string);
const { conversations } = useAppSelector((state: RootState) => state.conversations);

const botProfilePictures : string[] = [
const botProfilePictures: string[] = [
'/assets/bot-icon-1.png',
'/assets/bot-icon-2.png',
'/assets/bot-icon-3.png',
'/assets/bot-icon-4.png',
'/assets/bot-icon-5.png',
];

const getAudienceMemberForId = (id: string) =>
{
if (id === 'bot') return Constants.bot.profile;
const getAudienceMemberForId = (id: string, chatId: string, audience: ChatUser[]) => {
if (id === `${chatId}-bot` || id.toLocaleLowerCase() === 'bot') return Constants.bot.profile;
return audience.find((member) => member.id === id);
};

Expand All @@ -47,7 +49,7 @@ export const useChat = () => {
audience: [user],
botTypingTimestamp: 0,
botProfilePicture: botProfilePictures.at(botProfilePictureIndex) ?? '/assets/bot-icon-1.png',
}
};
dispatch(addConversation(newChat));
dispatch(setSelectedConversation(name));
return name;
Expand All @@ -71,6 +73,6 @@ export const useChat = () => {
return {
getAudienceMemberForId,
createChat,
getResponse
getResponse,
};
}
};
Loading

0 comments on commit 8ac4e6f

Please sign in to comment.