diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ChatSkill.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ChatSkill.cs index 81c1dbe7c4d4..8cede79bb7e1 100644 --- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ChatSkill.cs +++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Skills/ChatSkills/ChatSkill.cs @@ -309,7 +309,7 @@ public async Task ChatAsync(string message, SKContext context) { ChatMessage botMessage = await this.SaveNewResponseAsync(response, prompt, chatId); context.Variables.Set("messageId", botMessage.Id); - context.Variables.Set("messageType", botMessage.Type.ToString()); + context.Variables.Set("messageType", ((int)botMessage.Type).ToString(CultureInfo.InvariantCulture)); } catch (Exception ex) when (!ex.IsCriticalException()) { diff --git a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatHistoryFileItem.tsx b/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatHistoryFileItem.tsx deleted file mode 100644 index 71f029214f4a..000000000000 --- a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatHistoryFileItem.tsx +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { useMsal } from '@azure/msal-react'; -import { - Caption1, - Card, - CardHeader, - Persona, - ProgressBar, - Text, - makeStyles, - mergeClasses, - shorthands, - tokens, -} from '@fluentui/react-components'; -import React from 'react'; -import { AuthorRoles, IChatMessage } from '../../libs/models/ChatMessage'; -import { timestampToDateString } from '../utils/TextUtils'; -import { getFileIconByFileExtension } from './ChatResourceList'; - -const useClasses = makeStyles({ - root: { - display: 'flex', - flexDirection: 'row', - alignSelf: 'flex-end', - width: '35%', - }, - persona: { - paddingTop: tokens.spacingVerticalS, - }, - item: { - display: 'flex', - flexDirection: 'column', - width: '100%', - }, - header: { - position: 'relative', - display: 'flex', - flexDirection: 'row', - ...shorthands.gap(tokens.spacingHorizontalL), - paddingLeft: tokens.spacingHorizontalL, - }, - card: { - height: 'fit-content', - backgroundColor: tokens.colorNeutralBackground3, - ...shorthands.gap(0), - ...shorthands.padding(tokens.spacingVerticalXS, 0), - }, - cardCaption: { - color: tokens.colorNeutralForeground2, - }, - cardHeader: { - ...shorthands.margin(0, tokens.spacingHorizontalS), - }, - cardHeaderText: { - fontSize: 'small', - fontWeight: '500', - }, - footer: { - alignSelf: 'flex-end', - fontSize: 'small', - fontWeight: '500', - color: tokens.colorNeutralForegroundDisabled, - ...shorthands.margin(tokens.spacingVerticalXS, 0), - }, - time: { - color: tokens.colorNeutralForeground3, - fontSize: '12px', - fontWeight: 400, - }, - icon: { - height: '32px', - width: '32px', - }, - button: { - height: '18px', - width: '18px', - }, - alignEnd: { - alignSelf: 'flex-start', - }, -}); - -interface ChatHistoryFileItemProps { - message: IChatMessage; -} - -interface DocumentMessageContent { - name: string; - size: string; -} - -export const ChatHistoryFileItem: React.FC = ({ message }) => { - const classes = useClasses(); - const { instance } = useMsal(); - const account = instance.getActiveAccount(); - const isMe = message.authorRole === AuthorRoles.User && message.userId === account?.homeAccountId!; - - let name = '', - size = ''; - try { - ({ name, size } = JSON.parse(message.content) as DocumentMessageContent); - } catch (e) { - console.error('Error parsing chat history file item: ' + message.content); - } - - return ( -
- {!isMe && - } -
-
- {!isMe && {message.userName}} - {timestampToDateString(message.timestamp, true)} -
- - {name}} - description={{size}} - /> - - - - Success: memory established - -
-
- ); -}; diff --git a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatRoom.tsx b/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatRoom.tsx index a0ac01f6fbff..3e3960ff4c4b 100644 --- a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatRoom.tsx +++ b/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatRoom.tsx @@ -11,8 +11,8 @@ import { useAppDispatch, useAppSelector } from '../../redux/app/hooks'; import { RootState } from '../../redux/app/store'; import { updateConversationFromUser } from '../../redux/features/conversations/conversationsSlice'; import { SharedStyles } from '../../styles'; -import { ChatHistory } from './ChatHistory'; import { ChatInput } from './ChatInput'; +import { ChatHistory } from './chat-history/ChatHistory'; const log = debug(Constants.debug.root).extend('chat-room'); @@ -122,11 +122,7 @@ export const ChatRoom: React.FC = () => {
- +
); diff --git a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatHistory.tsx b/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistory.tsx similarity index 56% rename from samples/apps/copilot-chat-app/webapp/src/components/chat/ChatHistory.tsx rename to samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistory.tsx index 53afa9755dd7..21fba767c470 100644 --- a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatHistory.tsx +++ b/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistory.tsx @@ -2,9 +2,8 @@ import { makeStyles, shorthands, tokens } from '@fluentui/react-components'; import React from 'react'; -import { ChatMessageType, IChatMessage } from '../../libs/models/ChatMessage'; -import { GetResponseOptions } from '../../libs/useChat'; -import { ChatHistoryFileItem } from './ChatHistoryFileItem'; +import { IChatMessage } from '../../../libs/models/ChatMessage'; +import { GetResponseOptions } from '../../../libs/useChat'; import { ChatHistoryItem } from './ChatHistoryItem'; const useClasses = makeStyles({ @@ -35,18 +34,14 @@ export const ChatHistory: React.FC = ({ messages, onGetRespons {messages .slice() .sort((a, b) => a.timestamp - b.timestamp) - .map((message, index) => - message.type === ChatMessageType.Document ? ( - - ) : ( - - ), - )} + .map((message, index) => ( + + ))} ); }; diff --git a/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistoryDocumentContent.tsx b/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistoryDocumentContent.tsx new file mode 100644 index 000000000000..18b85c796bc6 --- /dev/null +++ b/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistoryDocumentContent.tsx @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { + Caption1, + Card, + CardHeader, + ProgressBar, + Text, + makeStyles, + mergeClasses, + shorthands, + tokens, +} from '@fluentui/react-components'; +import React from 'react'; +import { IChatMessage } from '../../../libs/models/ChatMessage'; +import { getFileIconByFileExtension } from '../ChatResourceList'; + +const useClasses = makeStyles({ + card: { + height: 'fit-content', + width: '275px', + backgroundColor: tokens.colorNeutralBackground3, + ...shorthands.gap(0), + ...shorthands.margin(tokens.spacingVerticalS, 0), + ...shorthands.padding(tokens.spacingVerticalXS, 0), + }, + cardCaption: { + color: tokens.colorNeutralForeground2, + }, + cardHeader: { + ...shorthands.margin(0, tokens.spacingHorizontalS), + }, + cardHeaderText: { + fontSize: 'small', + fontWeight: '500', + }, + footer: { + float: 'right', + fontSize: 'small', + fontWeight: '500', + color: tokens.colorNeutralForegroundDisabled, + }, + icon: { + height: '32px', + width: '32px', + }, + floatLeft: { + float: 'left', + }, +}); + +interface ChatHistoryDocumentContentProps { + isMe: boolean; + message: IChatMessage; +} + +interface DocumentMessageContent { + name: string; + size: string; +} + +export const ChatHistoryDocumentContent: React.FC = ({ isMe, message }) => { + const classes = useClasses(); + + let name = '', + size = ''; + try { + ({ name, size } = JSON.parse(message.content) as DocumentMessageContent); + } catch (e) { + console.error('Error parsing chat history file item: ' + message.content); + } + + return ( + <> + + {name}} + description={ + + {size} + + } + /> + + + + Success: memory established + + + ); +}; diff --git a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatHistoryItem.tsx b/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistoryItem.tsx similarity index 59% rename from samples/apps/copilot-chat-app/webapp/src/components/chat/ChatHistoryItem.tsx rename to samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistoryItem.tsx index 2b7a1405f691..fa7d8fbdd7ce 100644 --- a/samples/apps/copilot-chat-app/webapp/src/components/chat/ChatHistoryItem.tsx +++ b/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistoryItem.tsx @@ -3,15 +3,16 @@ import { useMsal } from '@azure/msal-react'; import { Persona, Text, makeStyles, mergeClasses, shorthands, tokens } from '@fluentui/react-components'; import React from 'react'; -import { AuthorRoles, IChatMessage } from '../../libs/models/ChatMessage'; -import { GetResponseOptions, useChat } from '../../libs/useChat'; -import { isPlan } from '../../libs/utils/PlanUtils'; -import { useAppSelector } from '../../redux/app/hooks'; -import { RootState } from '../../redux/app/store'; -import { Breakpoints } from '../../styles'; -import { convertToAnchorTags, timestampToDateString } from '../utils/TextUtils'; -import { PlanViewer } from './plan-viewer/PlanViewer'; -import { PromptDetails } from './prompt-details/PromptDetails'; +import { AuthorRoles, ChatMessageType, IChatMessage } 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 { timestampToDateString } from '../../utils/TextUtils'; +import { PlanViewer } from '../plan-viewer/PlanViewer'; +import { PromptDetails } from '../prompt-details/PromptDetails'; +import { ChatHistoryDocumentContent } from './ChatHistoryDocumentContent'; +import { ChatHistoryTextContent } from './ChatHistoryTextContent'; const useClasses = makeStyles({ root: { @@ -53,9 +54,6 @@ const useClasses = makeStyles({ flexDirection: 'row', ...shorthands.gap(tokens.spacingHorizontalL), }, - content: { - wordBreak: 'break-word', - }, canvas: { width: '100%', textAlign: 'center', @@ -68,11 +66,6 @@ interface ChatHistoryItemProps { messageIndex: number; } -const createCommandLink = (command: string) => { - const escapedCommand = encodeURIComponent(command); - return `${command}`; -}; - export const ChatHistoryItem: React.FC = ({ message, getResponse, messageIndex }) => { const classes = useClasses(); @@ -82,20 +75,6 @@ export const ChatHistoryItem: React.FC = ({ message, getRe const chat = useChat(); const { conversations, selectedId } = useAppSelector((state: RootState) => state.conversations); - const renderPlan = isPlan(message.content); - - const content = !renderPlan - ? (message.content as string) - .trim() - .replace(/[\u00A0-\u9999<>&]/g, function (i: string) { - return `&#${i.charCodeAt(0)};`; - }) - .replace(/^sk:\/\/.*$/gm, (match: string) => createCommandLink(match)) - .replace(/^!sk:.*$/gm, (match: string) => createCommandLink(match)) - .replace(/\n/g, '
') - .replace(/ {2}/g, '  ') - : ''; - const isMe = message.authorRole === AuthorRoles.User && message.userId === account?.homeAccountId!; const isBot = message.authorRole === AuthorRoles.Bot; const user = chat.getChatUserById(message.userName, selectedId, conversations[selectedId].users); @@ -105,11 +84,20 @@ export const ChatHistoryItem: React.FC = ({ message, getRe ? { image: { src: conversations[selectedId].botProfilePicture } } : { name: fullName, color: 'colorful' as 'colorful' }; + let content: JSX.Element; + if (isBot && message.type === ChatMessageType.Plan) { + content = ; + } else if (message.type === ChatMessageType.Document) { + content = ; + } else { + content = ; + } + return (
{!isMe && }
@@ -118,14 +106,7 @@ export const ChatHistoryItem: React.FC = ({ message, getRe {timestampToDateString(message.timestamp, true)} {isBot && }
- {renderPlan ? ( - - ) : ( -
- )} + {content}
); diff --git a/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistoryTextContent.tsx b/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistoryTextContent.tsx new file mode 100644 index 000000000000..87bc6889fa1b --- /dev/null +++ b/samples/apps/copilot-chat-app/webapp/src/components/chat/chat-history/ChatHistoryTextContent.tsx @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { makeStyles } from '@fluentui/react-components'; +import React from 'react'; +import { IChatMessage } from '../../../libs/models/ChatMessage'; +import { convertToAnchorTags } from '../../utils/TextUtils'; + +const useClasses = makeStyles({ + content: { + wordBreak: 'break-word', + }, +}); + +interface ChatHistoryTextContentProps { + message: IChatMessage; +} + +export const ChatHistoryTextContent: React.FC = ({ message }) => { + const classes = useClasses(); + + const content = message.content + .trim() + .replace(/[\u00A0-\u9999<>&]/g, function (i: string) { + return `&#${i.charCodeAt(0)};`; + }) + .replace(/^sk:\/\/.*$/gm, (match: string) => createCommandLink(match)) + .replace(/^!sk:.*$/gm, (match: string) => createCommandLink(match)) + .replace(/\n/g, '
') + .replace(/ {2}/g, '  '); + + return
; +}; + +const createCommandLink = (command: string) => { + const escapedCommand = encodeURIComponent(command); + return `${command}`; +}; diff --git a/samples/apps/copilot-chat-app/webapp/src/redux/features/message-relay/signalRMiddleware.ts b/samples/apps/copilot-chat-app/webapp/src/redux/features/message-relay/signalRMiddleware.ts index e2c1cd169fa8..81f932b93f90 100644 --- a/samples/apps/copilot-chat-app/webapp/src/redux/features/message-relay/signalRMiddleware.ts +++ b/samples/apps/copilot-chat-app/webapp/src/redux/features/message-relay/signalRMiddleware.ts @@ -8,7 +8,6 @@ import { IAskResult } from '../../../libs/semantic-kernel/model/AskResult'; import { addAlert } from '../app/appSlice'; import { ChatState } from '../conversations/ChatState'; import { AuthorRoles, ChatMessageType, IChatMessage } from './../../../libs/models/ChatMessage'; -import { isPlan } from './../../../libs/utils/PlanUtils'; import { getSelectedChatID } from './../../app/store'; // These have to match the callback names used in the backend @@ -137,10 +136,11 @@ export const registerSignalREvents = async (store: any) => { const loggedInUserId = store.getState().conversations.loggedInUserId; const originalMessageUserId = askResult.variables.find((v) => v.key === 'userId')?.value; const isPlanForLoggedInUser = loggedInUserId === originalMessageUserId; + const messageType = + Number(askResult.variables.find((v) => v.key === 'messageType')?.value) ?? ChatMessageType.Message; const message = { - type: (askResult.variables.find((v) => v.key === 'messageType')?.value ?? - ChatMessageType.Message) as ChatMessageType, + type: messageType, timestamp: new Date().getTime(), userName: 'bot', userId: 'bot', @@ -149,7 +149,9 @@ export const registerSignalREvents = async (store: any) => { authorRole: AuthorRoles.Bot, id: askResult.variables.find((v) => v.key === 'messageId')?.value, state: - isPlan(askResult.value) && isPlanForLoggedInUser ? PlanState.PlanApprovalRequired : PlanState.Disabled, + messageType === ChatMessageType.Plan && isPlanForLoggedInUser + ? PlanState.PlanApprovalRequired + : PlanState.Disabled, } as IChatMessage; store.dispatch({ type: 'conversations/updateConversationFromServer', payload: { message, chatId } });