Skip to content
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
13 changes: 13 additions & 0 deletions docs/chat-ui-react.chatpanelprops.linktarget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/chat-ui-react](./chat-ui-react.md) &gt; [ChatPanelProps](./chat-ui-react.chatpanelprops.md) &gt; [linkTarget](./chat-ui-react.chatpanelprops.linktarget.md)

## ChatPanelProps.linkTarget property

Link target open behavior on click. Defaults to "\_blank".

**Signature:**

```typescript
linkTarget?: string;
```
1 change: 1 addition & 0 deletions docs/chat-ui-react.chatpanelprops.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface ChatPanelProps extends Omit<MessageBubbleProps, "customCssClass
| [customCssClasses?](./chat-ui-react.chatpanelprops.customcssclasses.md) | | [ChatPanelCssClasses](./chat-ui-react.chatpanelcssclasses.md) | _(Optional)_ CSS classes for customizing the component styling. |
| [footer?](./chat-ui-react.chatpanelprops.footer.md) | | string | _(Optional)_ A footer markdown string to render at the bottom of the panel. |
| [header?](./chat-ui-react.chatpanelprops.header.md) | | ReactNode | _(Optional)_ A header to render at the top of the panel. |
| [linkTarget?](./chat-ui-react.chatpanelprops.linktarget.md) | | string | _(Optional)_ Link target open behavior on click. Defaults to "\_blank". |
| [messageSuggestions?](./chat-ui-react.chatpanelprops.messagesuggestions.md) | | string\[\] | _(Optional)_ A set of pre-written initial messages that the user can click on instead of typing their own. |
| [onLinkClick?](./chat-ui-react.chatpanelprops.onlinkclick.md) | | (href?: string) =&gt; void | _(Optional)_ A callback which is called when user clicks a link. |
| [retryText?](./chat-ui-react.chatpanelprops.retrytext.md) | | string | _(Optional)_ Text to display when retrying. Defaults to "Error occurred. Retrying". |
Expand Down
2 changes: 1 addition & 1 deletion docs/chat-ui-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
| [ChatInput({ placeholder, stream, inputAutoFocus, handleError, sendButtonIcon, customCssClasses, onSend, onRetry, })](./chat-ui-react.chatinput.md) | A component that allows user to input message and send to Chat API. |
| [ChatPanel(props)](./chat-ui-react.chatpanel.md) | A component that renders a full panel for chat bot interactions. This includes the message bubbles for the conversation, input box with send button, and header (if provided). |
| [ChatPopUp(props)](./chat-ui-react.chatpopup.md) | A component that renders a popup button that displays and hides a panel for chat bot interactions. |
| [MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, onLinkClick, })](./chat-ui-react.messagebubble.md) | A component that displays the provided message. |
| [MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, })](./chat-ui-react.messagebubble.md) | A component that displays the provided message. |
| [useComposedCssClasses(builtInClasses, customClasses)](./chat-ui-react.usecomposedcssclasses.md) | useComposedCssClasses merges a component's built-in tailwind classes with custom tailwind classes. |
| [useReportAnalyticsEvent()](./chat-ui-react.usereportanalyticsevent.md) | Returns a function to send requests to Yext Analytics API. |

Expand Down
4 changes: 2 additions & 2 deletions docs/chat-ui-react.messagebubble.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ A component that displays the provided message.
**Signature:**

```typescript
export declare function MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, onLinkClick, }: MessageBubbleProps): React.JSX.Element;
export declare function MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, }: MessageBubbleProps): React.JSX.Element;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| { message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, onLinkClick, } | [MessageBubbleProps](./chat-ui-react.messagebubbleprops.md) | |
| { message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, } | [MessageBubbleProps](./chat-ui-react.messagebubbleprops.md) | |

**Returns:**

Expand Down
13 changes: 13 additions & 0 deletions docs/chat-ui-react.messagebubbleprops.linktarget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/chat-ui-react](./chat-ui-react.md) &gt; [MessageBubbleProps](./chat-ui-react.messagebubbleprops.md) &gt; [linkTarget](./chat-ui-react.messagebubbleprops.linktarget.md)

## MessageBubbleProps.linkTarget property

Link target open behavior on click.

**Signature:**

```typescript
linkTarget?: string;
```
1 change: 1 addition & 0 deletions docs/chat-ui-react.messagebubbleprops.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface MessageBubbleProps
| --- | --- | --- | --- |
| [customCssClasses?](./chat-ui-react.messagebubbleprops.customcssclasses.md) | | [MessageBubbleCssClasses](./chat-ui-react.messagebubblecssclasses.md) | _(Optional)_ CSS classes for customizing the component styling. |
| [formatTimestamp?](./chat-ui-react.messagebubbleprops.formattimestamp.md) | | (timestamp: string) =&gt; string | _(Optional)_ A function which is called to format the message's timestamp given in ISO format (e.g. "2023-05-18T19:33:34.553Z"). Defaults to "HH:MM A" (e.g. "7:33 PM"). |
| [linkTarget?](./chat-ui-react.messagebubbleprops.linktarget.md) | | string | _(Optional)_ Link target open behavior on click. |
| [message](./chat-ui-react.messagebubbleprops.message.md) | | Message | The message to display. |
| [onLinkClick?](./chat-ui-react.messagebubbleprops.onlinkclick.md) | | (href?: string) =&gt; void | _(Optional)_ A callback which is called when user clicks a link. |
| [showFeedbackButtons?](./chat-ui-react.messagebubbleprops.showfeedbackbuttons.md) | | boolean | _(Optional)_ Whether to show the feedback buttons on the message bubble. Defaults to true. |
Expand Down
4 changes: 3 additions & 1 deletion etc/chat-ui-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface ChatPanelProps extends Omit<MessageBubbleProps, "customCssClass
customCssClasses?: ChatPanelCssClasses;
footer?: string;
header?: ReactNode;
linkTarget?: string;
messageSuggestions?: string[];
onLinkClick?: (href?: string) => void;
retryText?: string;
Expand Down Expand Up @@ -176,7 +177,7 @@ export interface InitialMessagePopUpCssClasses {
}

// @public
export function MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, onLinkClick, }: MessageBubbleProps): React_2.JSX.Element;
export function MessageBubble({ message, showFeedbackButtons, showTimestamp, customCssClasses, formatTimestamp, linkTarget, onLinkClick, }: MessageBubbleProps): React_2.JSX.Element;

// @public
export interface MessageBubbleCssClasses {
Expand Down Expand Up @@ -214,6 +215,7 @@ export interface MessageBubbleCssClasses {
export interface MessageBubbleProps {
customCssClasses?: MessageBubbleCssClasses;
formatTimestamp?: (timestamp: string) => string;
linkTarget?: string;
message: Message;
onLinkClick?: (href?: string) => void;
showFeedbackButtons?: boolean;
Expand Down
23 changes: 16 additions & 7 deletions src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export interface ChatInputProps {
* A function which is called when a retryable error occurs from
* Chat API while processing the user's message.
*/
onRetry?: (e: unknown) => void
onRetry?: (e: unknown) => void;
}

/**
Expand Down Expand Up @@ -92,26 +92,35 @@ export function ChatInput({
(state) => state.conversation.canSendMessage
);
const defaultHandleApiError = useDefaultHandleApiError();
const sendMessageWithRetries = useSendMessageWithRetries(stream, 1, onRetry)
const sendMessageWithRetries = useSendMessageWithRetries(stream, 1, onRetry);
const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses);

const sendMessage = useCallback(async () => {
setInput("");
sendMessageWithRetries(input)
.catch(handleError ?? defaultHandleApiError)
.finally(() => {
onSend?.(input)
})
}, [sendMessageWithRetries, input, handleError, defaultHandleApiError, onSend]);
onSend?.(input);
});
}, [
sendMessageWithRetries,
input,
handleError,
defaultHandleApiError,
onSend,
]);

const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (!e.shiftKey && e.key === "Enter" &&
if (
!e.shiftKey &&
e.key === "Enter" &&
// The Japanese Keyboard uses "Enter" key to convert from Hiragana to Kanji.
// "isComposing" is a flag that indicates whether the event is part of an ongoing composition session.
// Safari does not support `isComposing` with the Japanese IME event,
// so we have to additionally check for the keyCode to handle that edge case.
!(e.nativeEvent.isComposing || e.keyCode === 229)) {
!(e.nativeEvent.isComposing || e.keyCode === 229)
) {
e.preventDefault();
if (canSendMessage && input.trim().length !== 0) {
sendMessage();
Expand Down
47 changes: 33 additions & 14 deletions src/components/ChatPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ export interface ChatPanelProps
* can click on instead of typing their own.
*/
messageSuggestions?: string[];
/** Link target open behavior on click.
* Defaults to "_blank".
*/
linkTarget?: string;
/** A callback which is called when user clicks a link. */
onLinkClick?: (href?: string) => void;
/**
Expand All @@ -101,9 +105,10 @@ export function ChatPanel(props: ChatPanelProps) {
stream,
handleError,
messageSuggestions,
linkTarget = "_blank",
onLinkClick,
onSend:onSendProp,
onRetry:onRetryProp,
onSend: onSendProp,
onRetry: onRetryProp,
retryText = "Error occurred. Retrying",
} = props;
const messages = useChatState((state) => state.conversation.messages);
Expand All @@ -116,15 +121,21 @@ export function ChatPanel(props: ChatPanelProps) {
useFetchInitialMessage(handleError, stream);

const [retry, setRetry] = useState(false);
const onSend = useCallback((message: string) => {
onSendProp?.(message);
setRetry(false)
}, [onSendProp])
const onSend = useCallback(
(message: string) => {
onSendProp?.(message);
setRetry(false);
},
[onSendProp]
);

const onRetry = useCallback((e: unknown) => {
onRetryProp?.(e);
setRetry(true)
}, [onRetryProp])
const onRetry = useCallback(
(e: unknown) => {
onRetryProp?.(e);
setRetry(true);
},
[onRetryProp]
);

useEffect(() => {
reportAnalyticsEvent({
Expand Down Expand Up @@ -190,14 +201,21 @@ export function ChatPanel(props: ChatPanelProps) {
{...props}
customCssClasses={cssClasses.messageBubbleCssClasses}
message={message}
linkTarget={linkTarget}
onLinkClick={onLinkClick}
/>
</div>
))}
{loading && <div className="flex">
<LoadingDots />
{retry && <p className="text-slate-500 text-[13px] font-bold">{retryText}</p>}
</div>}
{loading && (
<div className="flex">
<LoadingDots />
{retry && (
<p className="text-slate-500 text-[13px] font-bold">
{retryText}
</p>
)}
</div>
)}
</div>
</div>
<div className={cssClasses.inputContainer}>
Expand All @@ -222,6 +240,7 @@ export function ChatPanel(props: ChatPanelProps) {
<Markdown
content={footer}
linkClickEvent="WEBSITE"
linkTarget={linkTarget}
onLinkClick={onLinkClick}
customCssClasses={footerCssClasses}
/>
Expand Down
7 changes: 4 additions & 3 deletions src/components/ChatPopUp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,13 @@ export function ChatPopUp(props: ChatPopUpProps) {
// to avoid message requests immediately on load while the popup is still "hidden"
const [renderChat, setRenderChat] = useState(false);


// only fetch initial message when ChatPanel is closed on load (otherwise, it will be fetched in ChatPanel)
useFetchInitialMessage(
showInitialMessagePopUp ? console.error : handleError,
false,
(showUnreadNotification || showInitialMessagePopUp) && !renderChat && !openOnLoad,
(showUnreadNotification || showInitialMessagePopUp) &&
!renderChat &&
!openOnLoad
);

useEffect(() => {
Expand All @@ -180,7 +181,7 @@ export function ChatPopUp(props: ChatPopUpProps) {
}, [messages.length, openOnLoad, renderChat]);

const onClick = useCallback(() => {
setShowChat(prev => !prev);
setShowChat((prev) => !prev);
setRenderChat(true);
setshowInitialMessage(false);
}, []);
Expand Down
6 changes: 5 additions & 1 deletion src/components/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ interface MarkdownProps {
* Defaults to 'CHAT_LINK_CLICK'.
*/
linkClickEvent?: "WEBSITE" | "CHAT_LINK_CLICK";
/** Link target open behavior on click. */
linkTarget?: string;
/** A callback which is called when a link is clicked. */
onLinkClick?: (href?: string) => void;
}
Expand All @@ -64,6 +66,7 @@ export function Markdown({
responseId,
customCssClasses,
linkClickEvent = "CHAT_LINK_CLICK",
linkTarget,
onLinkClick,
}: MarkdownProps) {
const reportAnalyticsEvent = useReportAnalyticsEvent();
Expand All @@ -86,7 +89,7 @@ export function Markdown({
<a
{...props}
onClick={createClickHandlerFn(props.href)}
target="_blank"
target={linkTarget}
rel="noopener noreferrer"
className={cssClasses.link}
>
Expand All @@ -100,6 +103,7 @@ export function Markdown({
linkClickEvent,
responseId,
cssClasses,
linkTarget,
onLinkClick,
]);

Expand Down
4 changes: 4 additions & 0 deletions src/components/MessageBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export interface MessageBubbleProps {
formatTimestamp?: (timestamp: string) => string;
/** CSS classes for customizing the component styling. */
customCssClasses?: MessageBubbleCssClasses;
/** Link target open behavior on click. */
linkTarget?: string;
/** A callback which is called when user clicks a link. */
onLinkClick?: (href?: string) => void;
}
Expand All @@ -92,6 +94,7 @@ export function MessageBubble({
showTimestamp = true,
customCssClasses,
formatTimestamp = defaultFormatTimestamp,
linkTarget,
onLinkClick,
}: MessageBubbleProps) {
const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses);
Expand Down Expand Up @@ -141,6 +144,7 @@ export function MessageBubble({
content={message.text}
responseId={message.responseId}
customCssClasses={markdownCssClasses}
linkTarget={linkTarget}
onLinkClick={onLinkClick}
/>
</div>
Expand Down
17 changes: 12 additions & 5 deletions src/components/MessageSuggestions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface MessageSuggestionsProps {
/** {@inheritdoc ChatInputProps.onSend} */
onSend?: (message: string) => void;
/** {@inheritdoc ChatInputProps.onRetry} */
onRetry?: (e: unknown) => void
onRetry?: (e: unknown) => void;
}

const defaultClassnames: MessageSuggestionCssClasses = withStylelessCssClasses(
Expand Down Expand Up @@ -65,7 +65,7 @@ export const MessageSuggestions: React.FC<MessageSuggestionsProps> = ({
const actions = useChatActions();
const notes = useChatState((state) => state.conversation.notes);
const defaultHandleApiError = useDefaultHandleApiError();
const sendMessageWithRetries = useSendMessageWithRetries(stream, 1, onRetry)
const sendMessageWithRetries = useSendMessageWithRetries(stream, 1, onRetry);
const sendMsg = useCallback(
(msg: string) => {
const newNotes = {
Expand All @@ -76,10 +76,17 @@ export const MessageSuggestions: React.FC<MessageSuggestionsProps> = ({
sendMessageWithRetries(msg)
.catch(handleError ?? defaultHandleApiError)
.finally(() => {
onSend?.(msg)
})
onSend?.(msg);
});
},
[notes, actions, sendMessageWithRetries, handleError, defaultHandleApiError, onSend]
[
notes,
actions,
sendMessageWithRetries,
handleError,
defaultHandleApiError,
onSend,
]
);

const classes = useComposedCssClasses(defaultClassnames, customCssClasses);
Expand Down
14 changes: 11 additions & 3 deletions src/hooks/useFetchInitialMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,28 @@ import { useDefaultHandleApiError } from "../hooks/useDefaultHandleApiError";
export function useFetchInitialMessage(
handleError?: (e: unknown) => void,
stream = false,
customCondition = true,
customCondition = true
) {
const chat = useChatActions();
const defaultHandleApiError = useDefaultHandleApiError();
const messages = useChatState((state) => state.conversation.messages);
const canSendMessage = useChatState(
(state) => state.conversation.canSendMessage
);

useEffect(() => {
if (messages.length !== 0 || !canSendMessage || !customCondition) {
return;
}
const res = stream ? chat.streamNextMessage() : chat.getNextMessage();
res.catch((e) => (handleError ? handleError(e) : defaultHandleApiError(e)));
}, [chat, stream, handleError, defaultHandleApiError, canSendMessage, customCondition, messages.length]);
}, [
chat,
stream,
handleError,
defaultHandleApiError,
canSendMessage,
customCondition,
messages.length,
]);
}
Loading