Skip to content

Commit

Permalink
Odie Client Refactor (#95521)
Browse files Browse the repository at this point in the history
* Odie Client Refactor

* Fix message listener

* Fix lost conversationId when sending a message

* Fix build issue

* Fix placeholder after user message

* Fix cleanup call back function

* Fix textarea size after sending long message

* Fix Wapuu thinking

---------

Co-authored-by: heavyweight <kpapazov@gmail.com>
Co-authored-by: Kosta <heavyweight@users.noreply.github.com>
Co-authored-by: Renan <renansscarvalho@gmail.com>
Co-authored-by: escapemanuele <escapemanuele@gmail.com>
  • Loading branch information
5 people authored Oct 23, 2024
1 parent 063cfce commit d1962e1
Show file tree
Hide file tree
Showing 42 changed files with 759 additions and 868 deletions.
3 changes: 0 additions & 3 deletions packages/odie-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,14 @@ const defaultContextInterfaceValues = {
botNameSlug: 'wpcom-support-chat', // Identifier for the chat bot configuration.
chat: { context: { section_name: '', site_id: null }, messages: [] }, // Current chat state, including context and messages.
clearChat: noop, // Function to clear the current chat.
isLoadingChat: false, // Flag indicating if the chat is loading.
isLoading: false, // Flag for general loading state.
isMinimized: false, // Flag to check if the chat is minimized.
isNudging: false, // Flag to check if a nudge action is occurring.
isVisible: false, // Flag to check if the chat is visible.
lastNudge: null, // Information about the last nudge action.
sendNudge: noop, // Function to trigger a nudge action.
setChat: noop, // Function to set the current chat.
setIsLoadingChat: noop, // Function to set the chat loading state.
setMessageLikedStatus: noop, // Function to set the liked status of a message.
setContext: noop, // Function to set the chat context.
setIsNudging: noop, // Function to set the nudge state.
setIsVisible: noop, // Function to set the visibility of the chat.
setIsLoading: noop, // Function to set the general loading state.
Expand Down
6 changes: 4 additions & 2 deletions packages/odie-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,24 @@
"@automattic/components": "workspace:^",
"@automattic/zendesk-client": "workspace:^",
"@tanstack/react-query": "^5.15.5",
"@types/react": "^18.2.6",
"@wordpress/api-fetch": "7.8.0",
"@wordpress/data": "^10.8.0",
"@wordpress/element": "^6.8.0",
"@wordpress/i18n": "^5.8.0",
"@wordpress/icons": "^10.8.0",
"autosize": "^6.0.1",
"clsx": "^2.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-intersection-observer": "^9.4.3",
"react-markdown": "^9.0.1",
"react-redux": "^8.1.3",
"tslib": "^2.3.0",
"wpcom-proxy-request": "^7.0.5"
},
"devDependencies": {
"@automattic/calypso-typescript-config": "workspace:^",
"@types/autosize": "^4.0.3",
"@types/react": "^18.2.6",
"typescript": "^5.3.3"
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import { useI18n } from '@wordpress/react-i18n';
import { __ } from '@wordpress/i18n';
import Markdown from 'react-markdown';
import WapuuAvatarSquared from '../../assets/wapuu-squared-avatar.svg';
import { useOdieAssistantContext } from '../../context';
import CustomALink from './custom-a-link';
import { GetSupport } from './get-support';
import { uriTransformer } from './uri-transformer';

export const DislikeFeedbackMessage = () => {
const { shouldUseHelpCenterExperience, extraContactOptions } = useOdieAssistantContext();
const { _x } = useI18n();
const { shouldUseHelpCenterExperience, extraContactOptions, botName } = useOdieAssistantContext();

return (
<>
<Markdown
urlTransform={ uriTransformer }
components={ {
a: CustomALink,
} }
>
{
/* translators: Message displayed when the user dislikes a message from the bot */
_x(
<div className="message-header bot">
<img
src={ WapuuAvatarSquared }
alt={ __( 'AI profile picture', __i18n_text_domain__ ) }
className="odie-chatbox-message-avatar"
/>
<strong className="message-header-name">{ botName }</strong>
</div>
<div className="odie-chatbox-dislike-feedback-message">
<Markdown
urlTransform={ uriTransformer }
components={ {
a: CustomALink,
} }
>
{ __(
'I’m sorry my last response didn’t meet your expectations! Here’s some other ways to get more in-depth help:',
'Message displayed when the user dislikes a message from the bot',
__i18n_text_domain__
)
}
</Markdown>
{ shouldUseHelpCenterExperience ? <GetSupport /> : extraContactOptions }
) }
</Markdown>
{ shouldUseHelpCenterExperience ? <GetSupport /> : extraContactOptions }
</div>
</>
);
};
Expand Down
8 changes: 4 additions & 4 deletions packages/odie-client/src/components/message/get-support.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useZendeskConversations } from '../../utils/use-zendesk-conversations';
import { useCreateZendeskConversation } from '../../query/use-create-zendesk-conversation';

import './get-support.scss';

export const GetSupport = () => {
const { startNewConversation } = useZendeskConversations();
const newConversation = useCreateZendeskConversation();

const handleOnClick = ( event: React.MouseEvent< HTMLButtonElement > ) => {
const handleOnClick = async ( event: React.MouseEvent< HTMLButtonElement > ) => {
event.preventDefault();

startNewConversation();
await newConversation();
};

return (
Expand Down
77 changes: 9 additions & 68 deletions packages/odie-client/src/components/message/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { Gravatar } from '@automattic/components';
import { useMobileBreakpoint } from '@automattic/viewport-react';
import { useI18n } from '@wordpress/react-i18n';
import { __ } from '@wordpress/i18n';
import clsx from 'clsx';
import { useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import { HumanAvatar, WapuuAvatar } from '../../assets';
import MaximizeIcon from '../../assets/maximize-icon.svg';
import MinimizeIcon from '../../assets/minimize-icon.svg';
import WapuuAvatarSquared from '../../assets/wapuu-squared-avatar.svg';
import WapuuThinking from '../../assets/wapuu-thinking.svg';
import { useOdieAssistantContext } from '../../context';
import { uuid } from '../../query';
import Button from '../button';
import { MessageContent } from './message-content';
import type { CurrentUser, Message } from '../../types/';
Expand Down Expand Up @@ -44,40 +42,21 @@ const MessageAvatarHeader = ( {
isFullscreen: boolean;
handleFullscreenToggle: () => void;
} ) => {
const { _x } = useI18n();
const isMobile = useMobileBreakpoint();
const { botName, shouldUseHelpCenterExperience } = useOdieAssistantContext();

if ( shouldUseHelpCenterExperience ) {
return message.role === 'bot' ? (
<>
<WapuuAvatar className={ wapuuAvatarClasses } />
{ message.type === 'placeholder' ? (
<img
src={ WapuuThinking }
alt={
/* translators: %s is bot name, like Wapuu */
_x( 'Loading state, awaiting response from AI', 'html alt tag', __i18n_text_domain__ )
}
className="odie-chatbox-thinking-icon"
/>
) : (
<strong className="message-header-name"></strong>
) }
<strong className="message-header-name"></strong>

<div className="message-header-buttons">
{ message.content?.length > 600 && ! isMobile && (
<Button compact borderless onClick={ handleFullscreenToggle }>
<img
src={ isFullscreen ? MinimizeIcon : MaximizeIcon }
alt={
/* translators: %s is bot name, like Wapuu */
_x(
'Icon to expand or collapse AI messages',
'html alt tag',
__i18n_text_domain__
)
}
alt={ __( 'Icon to expand or collapse AI messages', __i18n_text_domain__ ) }
/>
</Button>
) }
Expand All @@ -93,44 +72,24 @@ const MessageAvatarHeader = ( {
<Gravatar
user={ currentUser }
size={ 32 }
alt={ _x( 'User profile display picture', 'html alt tag', __i18n_text_domain__ ) }
alt={ __( 'User profile display picture', __i18n_text_domain__ ) }
/>
<strong className="message-header-name">{ currentUser.display_name }</strong>
</>
) : (
<>
<img
src={ WapuuAvatarSquared }
alt={
/* translators: %s is bot name, like Wapuu */
_x( 'AI profile picture', 'html alt tag', __i18n_text_domain__ )
}
alt={ __( 'AI profile picture', __i18n_text_domain__ ) }
className={ wapuuAvatarClasses }
/>
{ message.type === 'placeholder' ? (
<img
src={ WapuuThinking }
alt={ _x(
'Loading state, awaiting response from AI',
'html alt tag',
__i18n_text_domain__
) }
className="odie-chatbox-thinking-icon"
/>
) : (
<strong className="message-header-name">{ botName }</strong>
) }

<strong className="message-header-name">{ botName }</strong>
<div className="message-header-buttons">
{ message.content?.length > 600 && ! isMobile && (
<Button compact borderless onClick={ handleFullscreenToggle }>
<img
src={ isFullscreen ? MinimizeIcon : MaximizeIcon }
alt={ _x(
'Icon to expand or collapse AI messages',
'html alt tag',
__i18n_text_domain__
) }
alt={ __( 'Icon to expand or collapse AI messages', __i18n_text_domain__ ) }
/>
</Button>
) }
Expand All @@ -145,11 +104,10 @@ const ChatMessage = ( {
...messageIndicators
}: ChatMessageProps & MessageIndicators ) => {
const isBot = message.role === 'bot';
const { botName, addMessage } = useOdieAssistantContext();
const { botName } = useOdieAssistantContext();
const [ isFullscreen, setIsFullscreen ] = useState( false );
const [ isDisliked, setIsDisliked ] = useState( false );
const [ isDisliked ] = useState( false );

const isRequestingHumanSupport = message.context?.flags?.forward_to_human_support;
const fullscreenRef = useRef< HTMLDivElement >( null );

const handleBackdropClick = () => {
Expand Down Expand Up @@ -184,28 +142,12 @@ const ChatMessage = ( {
</div>
);

const onDislike = () => {
setIsDisliked( true );
if ( isRequestingHumanSupport || isDisliked ) {
return;
}
setTimeout( () => {
addMessage( {
internal_message_id: uuid(),
content: '...',
role: 'bot',
type: 'dislike-feedback',
} );
}, 1200 );
};

const fullscreenContent = (
<div className="odie-fullscreen" onClick={ handleBackdropClick }>
<div className="odie-fullscreen-backdrop" onClick={ handleContentClick }>
<MessageContent
message={ message }
messageHeader={ messageHeader }
onDislike={ onDislike }
ref={ fullscreenRef }
isDisliked={ isDisliked }
{ ...messageIndicators }
Expand All @@ -219,7 +161,6 @@ const ChatMessage = ( {
<MessageContent
message={ message }
messageHeader={ messageHeader }
onDislike={ onDislike }
ref={ fullscreenRef }
isDisliked={ isDisliked }
{ ...messageIndicators }
Expand Down
17 changes: 5 additions & 12 deletions packages/odie-client/src/components/message/jump-to-recent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { __ } from '@wordpress/i18n';
import { Icon, chevronDown } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';
import clsx from 'clsx';
import { RefObject, useCallback } from 'react';
import { useOdieAssistantContext } from '../../context';
Expand All @@ -9,8 +9,8 @@ export const JumpToRecent = ( {
}: {
containerReference: RefObject< HTMLDivElement >;
} ) => {
const { trackEvent, isMinimized, lastMessageInView, chat } = useOdieAssistantContext();
const { _x } = useI18n();
const { trackEvent, isMinimized, lastMessageInView, chat, chatStatus } =
useOdieAssistantContext();

const jumpToRecent = useCallback( () => {
if ( containerReference.current && chat.messages.length > 0 ) {
Expand All @@ -26,7 +26,7 @@ export const JumpToRecent = ( {
trackEvent( 'chat_jump_to_recent_click' );
}, [ containerReference, trackEvent, chat.messages.length ] );

if ( isMinimized || chat.messages.length < 2 ) {
if ( isMinimized || chat.messages.length < 2 || chatStatus !== 'loaded' ) {
return null;
}

Expand All @@ -42,14 +42,7 @@ export const JumpToRecent = ( {
disabled={ lastMessageInView }
onClick={ jumpToRecent }
>
{
/* translators: A dynamic button that appears on a chatbox, when the last message is not vissible */
_x(
'Jump to recent',
'A dynamic button that appears on a chatbox, when the last message is not vissible',
__i18n_text_domain__
)
}
{ __( 'Jump to recent', __i18n_text_domain__ ) }
<Icon icon={ chevronDown } fill="white" />
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export const MessageContent = forwardRef<
{
message: Message;
messageHeader: React.ReactNode;
onDislike: () => void;
isDisliked?: boolean;
} & MessageIndicators
>(
Expand All @@ -24,7 +23,6 @@ export const MessageContent = forwardRef<
isDisliked = false,
message,
messageHeader,
onDislike,
isLastErrorMessage,
isLastFeedbackMessage,
isLastMessage,
Expand Down Expand Up @@ -53,7 +51,7 @@ export const MessageContent = forwardRef<
{ messageHeader }
{ message.type === 'error' && <ErrorMessage message={ message } /> }
{ ( message.type === 'message' || ! message.type ) && (
<UserMessage message={ message } onDislike={ onDislike } isDisliked={ isDisliked } />
<UserMessage message={ message } isDisliked={ isDisliked } />
) }
{ message.type === 'introduction' && (
<div className="odie-introduction-message-content">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { forwardRef } from 'react';
import { useOdieAssistantContext } from '../../context';
import { useZendeskMessageListener } from '../../utils';
import { DislikeFeedbackMessage } from './dislike-feedback-message';
import { ThinkingPlaceholder } from './thinking-placeholder';
import ChatMessage from '.';
import type { CurrentUser } from '../../types/';

Expand All @@ -9,7 +12,8 @@ interface ChatMessagesProps {

export const MessagesContainer = forwardRef< HTMLDivElement, ChatMessagesProps >(
( { currentUser }, ref ) => {
const { chat } = useOdieAssistantContext();
const { chat, chatStatus } = useOdieAssistantContext();
useZendeskMessageListener();

let lastUserMessageIndex = -1;
let lastFeedbackMessageIndex = -1;
Expand Down Expand Up @@ -42,6 +46,12 @@ export const MessagesContainer = forwardRef< HTMLDivElement, ChatMessagesProps >
isLastMessage={ lastMessageIndex === index }
/>
) ) }
<div className="odie-chatbox__action-message">
{ chatStatus === 'sending' && lastUserMessageIndex === lastMessageIndex && (
<ThinkingPlaceholder />
) }
{ chatStatus === 'dislike' && <DislikeFeedbackMessage /> }
</div>
</div>
);
}
Expand Down
Loading

0 comments on commit d1962e1

Please sign in to comment.