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

📝 RLFH UX Skeleton #31

Merged
merged 1 commit into from
Jul 25, 2023
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
17 changes: 15 additions & 2 deletions webapp/src/components/chat/chat-history/ChatHistoryItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

import { Persona, Text, makeStyles, mergeClasses, shorthands, tokens } from '@fluentui/react-components';
import React from 'react';
import { AuthorRoles, ChatMessageType, IChatMessage } from '../../../libs/models/ChatMessage';
import { AuthorRoles, ChatMessageType, IChatMessage, UserFeedback } from '../../../libs/models/ChatMessage';
import { GetResponseOptions, useChat } from '../../../libs/useChat';
import { useAppSelector } from '../../../redux/app/hooks';
import { RootState } from '../../../redux/app/store';
import { Breakpoints } from '../../../styles';
import { Breakpoints, customTokens } from '../../../styles';
import { timestampToDateString } from '../../utils/TextUtils';
import { PlanViewer } from '../plan-viewer/PlanViewer';
import { PromptDetails } from '../prompt-details/PromptDetails';
import { ChatHistoryDocumentContent } from './ChatHistoryDocumentContent';
import { ChatHistoryTextContent } from './ChatHistoryTextContent';
import * as utils from './../../utils/TextUtils';
import { ThumbLike16Filled, ThumbDislike24Filled } from '@fluentui/react-icons';
import { UserFeedbackActions } from './UserFeedbackActions';

const useClasses = makeStyles({
root: {
Expand All @@ -23,6 +25,7 @@ const useClasses = makeStyles({
...Breakpoints.small({
maxWidth: '100%',
}),
...shorthands.gap(customTokens.spacingHorizontalXS),
},
debug: {
position: 'absolute',
Expand Down Expand Up @@ -91,6 +94,13 @@ export const ChatHistoryItem: React.FC<ChatHistoryItemProps> = ({ message, getRe
content = <ChatHistoryTextContent message={message} />;
}

// TODO: hookup to backend
teresaqhoang marked this conversation as resolved.
Show resolved Hide resolved
// Currently for demonstration purposes only, no feedback is actually sent to kernel / model
const showShowRLHFMessage =
message.userFeedback === UserFeedback.Requested &&
messageIndex === conversations[selectedId].messages.length - 1 &&
message.userId === 'bot';

return (
<div
className={isMe ? mergeClasses(classes.root, classes.alignEnd) : classes.root}
Expand All @@ -107,7 +117,10 @@ export const ChatHistoryItem: React.FC<ChatHistoryItemProps> = ({ message, getRe
{isBot && <PromptDetails message={message} />}
</div>
{content}
{showShowRLHFMessage && <UserFeedbackActions messageIndex={messageIndex} />}
</div>
{message.userFeedback === UserFeedback.Positive && <ThumbLike16Filled color="gray" />}
{message.userFeedback === UserFeedback.Negative && <ThumbDislike24Filled color="gray" />}
</div>
);
};
63 changes: 63 additions & 0 deletions webapp/src/components/chat/chat-history/UserFeedbackActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Button, Text, Tooltip, makeStyles } from '@fluentui/react-components';
import { useCallback } from 'react';
import { UserFeedback } from '../../../libs/models/ChatMessage';
import { useAppDispatch, useAppSelector } from '../../../redux/app/hooks';
import { RootState } from '../../../redux/app/store';
import { setUserFeedback } from '../../../redux/features/conversations/conversationsSlice';
import { ThumbDislike16, ThumbLike16 } from '../../shared/BundledIcons';

const useClasses = makeStyles({
root: {
display: 'flex',
'place-content': 'flex-end',
alignItems: 'center',
},
});

interface IUserFeedbackProps {
messageIndex: number;
}

export const UserFeedbackActions: React.FC<IUserFeedbackProps> = ({ messageIndex }) => {
const classes = useClasses();

const dispatch = useAppDispatch();
const { selectedId } = useAppSelector((state: RootState) => state.conversations);

const onUserFeedbackProvided = useCallback(
(positive: boolean) => {
dispatch(
setUserFeedback({
userFeedback: positive ? UserFeedback.Positive : UserFeedback.Negative,
messageIndex,
chatId: selectedId,
}),
);
},
[dispatch, messageIndex, selectedId],
);

return (
<div className={classes.root}>
<Text color="gray" size={200}>
AI-generated content may be incorrect
</Text>
<Tooltip content={'Like bot message'} relationship="label">
<Button
icon={<ThumbLike16 />}
appearance="transparent"
aria-label="Edit"
onClick={() => onUserFeedbackProvided(true)}
/>
</Tooltip>
<Tooltip content={'Dislike bot message'} relationship="label">
<Button
icon={<ThumbDislike16 />}
appearance="transparent"
aria-label="Edit"
onClick={() => onUserFeedbackProvided(false)}
/>
</Tooltip>
</div>
);
};
46 changes: 46 additions & 0 deletions webapp/src/components/shared/BundledIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
Add20Filled,
Add20Regular,
AppsAddIn24Filled,
AppsAddIn24Regular,
ArrowDownload16Filled,
ArrowDownload16Regular,
BotAdd20Filled,
BotAdd20Regular,
Checkmark20Filled,
Checkmark20Regular,
Delete16Filled,
Delete16Regular,
Dismiss16Filled,
Dismiss16Regular,
Dismiss20Filled,
Dismiss20Regular,
EditFilled,
EditRegular,
Filter20Filled,
Filter20Regular,
Info16Filled,
Info16Regular,
Share20Filled,
Share20Regular,
ThumbDislike16Filled,
ThumbDislike16Regular,
ThumbLike16Filled,
ThumbLike16Regular,
bundleIcon,
} from '@fluentui/react-icons';
teresaqhoang marked this conversation as resolved.
Show resolved Hide resolved

export const Add20 = bundleIcon(Add20Filled, Add20Regular);
export const AppsAddIn24 = bundleIcon(AppsAddIn24Filled, AppsAddIn24Regular);
export const BotAdd20 = bundleIcon(BotAdd20Filled, BotAdd20Regular);
export const Checkmark20 = bundleIcon(Checkmark20Filled, Checkmark20Regular);
export const Delete16 = bundleIcon(Delete16Filled, Delete16Regular);
export const Dismiss16 = bundleIcon(Dismiss16Filled, Dismiss16Regular);
export const Dismiss20 = bundleIcon(Dismiss20Filled, Dismiss20Regular);
export const Filter20 = bundleIcon(Filter20Filled, Filter20Regular);
export const Edit = bundleIcon(EditFilled, EditRegular);
export const ArrowDownload16 = bundleIcon(ArrowDownload16Filled, ArrowDownload16Regular);
export const Share20 = bundleIcon(Share20Filled, Share20Regular);
export const Info16 = bundleIcon(Info16Filled, Info16Regular);
export const ThumbLike16 = bundleIcon(ThumbLike16Filled, ThumbLike16Regular);
export const ThumbDislike16 = bundleIcon(ThumbDislike16Filled, ThumbDislike16Regular);
12 changes: 12 additions & 0 deletions webapp/src/libs/models/ChatMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ export enum ChatMessageType {
Document,
}

/**
* States for RLHF
*/
export enum UserFeedback {
Unknown,
Requested,
Positive,
Negative,
}

export interface IChatMessage {
type: ChatMessageType;
timestamp: number;
Expand All @@ -41,4 +51,6 @@ export interface IChatMessage {
authorRole: AuthorRoles;
debug?: string;
state?: PlanState;
// TODO: Persistent RLHF, view only right now
userFeedback?: UserFeedback;
}
2 changes: 1 addition & 1 deletion webapp/src/redux/features/app/appSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const initialState: AppState = {
alerts: [
{
message:
'Copilot chat is designed for internal use only. By using this chat bot, you agree to not to share confidential or customer information or store sensitive information in chat history. Further, you agree that Copilot chat can collect and retain your chat history for service improvement.',
'By using Chat Copilot, you agree to protect sensitive data, not store it in chat, and allow chat history collection for service improvements. This tool is for internal use only.',
type: AlertType.Info,
},
],
Expand Down
18 changes: 16 additions & 2 deletions webapp/src/redux/features/conversations/conversationsSlice.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

import { createSlice, PayloadAction, Slice } from '@reduxjs/toolkit';
import { IChatMessage } from '../../../libs/models/ChatMessage';
import { ChatMessageType, IChatMessage, UserFeedback } from '../../../libs/models/ChatMessage';
import { IChatUser } from '../../../libs/models/ChatUser';
import { PlanState } from '../../../libs/models/Plan';
import { ChatState } from './ChatState';
Expand Down Expand Up @@ -112,6 +112,15 @@ export const conversationsSlice: Slice<ConversationsState> = createSlice({
const conversation = state.conversations[chatId];
conversation.botResponseStatus = status;
},
setUserFeedback: (
state: ConversationsState,
action: PayloadAction<{ userFeedback: UserFeedback; messageIndex: number; chatId?: string }>,
) => {
const { userFeedback, messageIndex, chatId } = action.payload;
const id = chatId ?? state.selectedId;
state.conversations[id].messages[messageIndex].userFeedback = userFeedback;
frontLoadChat(state, id);
},
},
});

Expand All @@ -128,6 +137,7 @@ export const {
updateUserIsTypingFromServer,
updateBotResponseStatusFromServer,
setUsersLoaded,
setUserFeedback,
} = conversationsSlice.actions;

export default conversationsSlice.reducer;
Expand All @@ -139,7 +149,11 @@ const frontLoadChat = (state: ConversationsState, id: string) => {
};

const updateConversation = (state: ConversationsState, chatId: string, message: IChatMessage) => {
state.conversations[chatId].messages.push(message);
const requestUserFeedback = message.userId === 'bot' && message.type === ChatMessageType.Message;
state.conversations[chatId].messages.push({
...message,
userFeedback: requestUserFeedback ? UserFeedback.Requested : undefined,
});
frontLoadChat(state, chatId);
};

Expand Down
4 changes: 3 additions & 1 deletion webapp/src/styles.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BrandVariants, GriffelStyle, createLightTheme, tokens } from '@fluentui/react-components';
import { BrandVariants, GriffelStyle, createLightTheme, themeToTokensObject, tokens } from '@fluentui/react-components';

const semanticKernelBrandRamp: BrandVariants = {
10: '#060103',
Expand All @@ -21,6 +21,8 @@ const semanticKernelBrandRamp: BrandVariants = {

export const semanticKernelLightTheme = createLightTheme(semanticKernelBrandRamp);

export const customTokens = themeToTokensObject(semanticKernelLightTheme);

export const Breakpoints = {
small: (style: GriffelStyle): Record<string, GriffelStyle> => {
return { '@media (max-width: 744px)': style };
Expand Down