@yext/chat-ui-react 0.8.7 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/THIRD-PARTY-NOTICES +52 -81
  2. package/lib/bundle.css +1 -1
  3. package/lib/commonjs/package.json.js +1 -1
  4. package/lib/commonjs/src/components/ChatInput.d.ts +6 -1
  5. package/lib/commonjs/src/components/ChatInput.d.ts.map +1 -1
  6. package/lib/commonjs/src/components/ChatInput.js +8 -8
  7. package/lib/commonjs/src/components/ChatInput.js.map +1 -1
  8. package/lib/commonjs/src/components/ChatPanel.d.ts +5 -0
  9. package/lib/commonjs/src/components/ChatPanel.d.ts.map +1 -1
  10. package/lib/commonjs/src/components/ChatPanel.js +15 -4
  11. package/lib/commonjs/src/components/ChatPanel.js.map +1 -1
  12. package/lib/commonjs/src/components/Markdown.d.ts.map +1 -1
  13. package/lib/commonjs/src/components/Markdown.js +7 -1
  14. package/lib/commonjs/src/components/Markdown.js.map +1 -1
  15. package/lib/commonjs/src/components/MessageSuggestions.d.ts +10 -0
  16. package/lib/commonjs/src/components/MessageSuggestions.d.ts.map +1 -1
  17. package/lib/commonjs/src/components/MessageSuggestions.js +9 -4
  18. package/lib/commonjs/src/components/MessageSuggestions.js.map +1 -1
  19. package/lib/commonjs/src/hooks/useSendMessageWithRetries.d.ts +17 -0
  20. package/lib/commonjs/src/hooks/useSendMessageWithRetries.d.ts.map +1 -0
  21. package/lib/commonjs/src/hooks/useSendMessageWithRetries.js +52 -0
  22. package/lib/commonjs/src/hooks/useSendMessageWithRetries.js.map +1 -0
  23. package/lib/esm/index.d.ts +21 -1
  24. package/lib/esm/package.json.mjs +1 -1
  25. package/lib/esm/src/components/ChatInput.d.ts +6 -1
  26. package/lib/esm/src/components/ChatInput.d.ts.map +1 -1
  27. package/lib/esm/src/components/ChatInput.mjs +9 -9
  28. package/lib/esm/src/components/ChatInput.mjs.map +1 -1
  29. package/lib/esm/src/components/ChatPanel.d.ts +5 -0
  30. package/lib/esm/src/components/ChatPanel.d.ts.map +1 -1
  31. package/lib/esm/src/components/ChatPanel.mjs +16 -5
  32. package/lib/esm/src/components/ChatPanel.mjs.map +1 -1
  33. package/lib/esm/src/components/Markdown.d.ts.map +1 -1
  34. package/lib/esm/src/components/Markdown.mjs +7 -1
  35. package/lib/esm/src/components/Markdown.mjs.map +1 -1
  36. package/lib/esm/src/components/MessageSuggestions.d.ts +10 -0
  37. package/lib/esm/src/components/MessageSuggestions.d.ts.map +1 -1
  38. package/lib/esm/src/components/MessageSuggestions.mjs +9 -4
  39. package/lib/esm/src/components/MessageSuggestions.mjs.map +1 -1
  40. package/lib/esm/src/hooks/useSendMessageWithRetries.d.ts +17 -0
  41. package/lib/esm/src/hooks/useSendMessageWithRetries.d.ts.map +1 -0
  42. package/lib/esm/src/hooks/useSendMessageWithRetries.mjs +50 -0
  43. package/lib/esm/src/hooks/useSendMessageWithRetries.mjs.map +1 -0
  44. package/package.json +2 -2
  45. package/src/components/ChatInput.tsx +15 -10
  46. package/src/components/ChatPanel.tsx +34 -2
  47. package/src/components/Markdown.tsx +8 -2
  48. package/src/components/MessageSuggestions.tsx +22 -3
  49. package/src/hooks/useSendMessageWithRetries.ts +49 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Returns a function that sends a message to the chat API with retries
3
+ * if the API returns a 5xx status code.
4
+ *
5
+ * @remarks
6
+ * The function will throw the error if the maximum number of retries is reached
7
+ * or if the error is not a 5xx status code.
8
+ *
9
+ * @internal
10
+ *
11
+ * @param stream - If true, use streaming API
12
+ * @param maxRetries - Maximum number of retries
13
+ * @param onRetry - Callback to handle error on each retry
14
+ *
15
+ */
16
+ export declare function useSendMessageWithRetries(stream?: boolean, maxRetries?: number, onRetry?: (e: unknown) => void): (input: string) => Promise<void>;
17
+ //# sourceMappingURL=useSendMessageWithRetries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSendMessageWithRetries.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useSendMessageWithRetries.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,UAAQ,EACd,UAAU,SAAI,EACd,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,GAC7B,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CA0BlC"}
@@ -0,0 +1,50 @@
1
+ import { useChatActions, ApiError } from '@yext/chat-headless-react';
2
+ import { useCallback } from 'react';
3
+
4
+ /**
5
+ * Returns a function that sends a message to the chat API with retries
6
+ * if the API returns a 5xx status code.
7
+ *
8
+ * @remarks
9
+ * The function will throw the error if the maximum number of retries is reached
10
+ * or if the error is not a 5xx status code.
11
+ *
12
+ * @internal
13
+ *
14
+ * @param stream - If true, use streaming API
15
+ * @param maxRetries - Maximum number of retries
16
+ * @param onRetry - Callback to handle error on each retry
17
+ *
18
+ */
19
+ function useSendMessageWithRetries(stream = false, maxRetries = 0, onRetry) {
20
+ const chat = useChatActions();
21
+ return useCallback(async (input) => {
22
+ let err;
23
+ let text = input;
24
+ for (let numRetries = 0; numRetries <= maxRetries; numRetries++) {
25
+ if (numRetries > 0 && !!err) {
26
+ if (err instanceof ApiError && !!err.statusCode && err.statusCode >= 500) {
27
+ onRetry?.(err);
28
+ // avoid re-adding user message to conversation history on retry
29
+ text = "";
30
+ }
31
+ else {
32
+ throw err;
33
+ }
34
+ }
35
+ try {
36
+ await (stream
37
+ ? chat.streamNextMessage(text)
38
+ : chat.getNextMessage(text));
39
+ return;
40
+ }
41
+ catch (e) {
42
+ err = e;
43
+ }
44
+ }
45
+ throw err;
46
+ }, [chat, maxRetries, onRetry, stream]);
47
+ }
48
+
49
+ export { useSendMessageWithRetries };
50
+ //# sourceMappingURL=useSendMessageWithRetries.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSendMessageWithRetries.mjs","sources":["../../../../src/hooks/useSendMessageWithRetries.ts"],"sourcesContent":["import { ApiError, useChatActions } from \"@yext/chat-headless-react\";\nimport { useCallback } from \"react\";\n\n/**\n * Returns a function that sends a message to the chat API with retries\n * if the API returns a 5xx status code.\n * \n * @remarks\n * The function will throw the error if the maximum number of retries is reached\n * or if the error is not a 5xx status code.\n * \n * @internal\n * \n * @param stream - If true, use streaming API\n * @param maxRetries - Maximum number of retries\n * @param onRetry - Callback to handle error on each retry\n * \n */\nexport function useSendMessageWithRetries(\n stream = false,\n maxRetries = 0,\n onRetry?: (e: unknown) => void\n): (input: string) => Promise<void> {\n const chat = useChatActions()\n return useCallback(async (input: string) => {\n let err: unknown;\n let text = input;\n for (let numRetries = 0; numRetries <= maxRetries; numRetries++) {\n if (numRetries > 0 && !!err) {\n if (err instanceof ApiError && !!err.statusCode && err.statusCode >= 500) {\n onRetry?.(err)\n // avoid re-adding user message to conversation history on retry\n text = \"\";\n } else {\n throw err;\n }\n }\n try {\n await (stream\n ? chat.streamNextMessage(text)\n : chat.getNextMessage(text));\n return;\n } catch (e) {\n err = e;\n }\n }\n throw err\n }, [chat, maxRetries, onRetry, stream])\n}\n"],"names":[],"mappings":";;;AAGA;;;;;;;;;;;;;;AAcG;AACG,SAAU,yBAAyB,CACvC,MAAM,GAAG,KAAK,EACd,UAAU,GAAG,CAAC,EACd,OAA8B,EAAA;AAE9B,IAAA,MAAM,IAAI,GAAG,cAAc,EAAE,CAAA;AAC7B,IAAA,OAAO,WAAW,CAAC,OAAO,KAAa,KAAI;AACzC,QAAA,IAAI,GAAY,CAAC;QACjB,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,KAAK,IAAI,UAAU,GAAG,CAAC,EAAE,UAAU,IAAI,UAAU,EAAE,UAAU,EAAE,EAAE;AAC/D,YAAA,IAAI,UAAU,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE;AAC3B,gBAAA,IAAI,GAAG,YAAY,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,EAAE;AACxE,oBAAA,OAAO,GAAG,GAAG,CAAC,CAAA;;oBAEd,IAAI,GAAG,EAAE,CAAC;AACX,iBAAA;AAAM,qBAAA;AACL,oBAAA,MAAM,GAAG,CAAC;AACX,iBAAA;AACF,aAAA;YACD,IAAI;AACF,gBAAA,OAAO,MAAM;AACX,sBAAE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC;sBAC5B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC/B,OAAO;AACR,aAAA;AAAC,YAAA,OAAO,CAAC,EAAE;gBACV,GAAG,GAAG,CAAC,CAAC;AACT,aAAA;AACF,SAAA;AACD,QAAA,MAAM,GAAG,CAAA;KACV,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;AACzC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yext/chat-ui-react",
3
- "version": "0.8.7",
3
+ "version": "0.9.0",
4
4
  "description": "A library of React Components for powering Yext Chat integrations.",
5
5
  "author": "clippy@yext.com",
6
6
  "main": "./lib/commonjs/src/index.js",
@@ -69,7 +69,7 @@
69
69
  "@testing-library/user-event": "^14.4.3",
70
70
  "@types/jest": "^29.5.1",
71
71
  "@types/react": "^18.2.7",
72
- "@yext/chat-headless-react": "^0.6.1",
72
+ "@yext/chat-headless-react": "^0.7.0",
73
73
  "@yext/eslint-config": "^1.0.0",
74
74
  "babel-jest": "^29.5.0",
75
75
  "eslint": "^8.39.0",
@@ -1,10 +1,11 @@
1
1
  import React, { useCallback, useState } from "react";
2
- import { useChatActions, useChatState } from "@yext/chat-headless-react";
2
+ import { useChatState } from "@yext/chat-headless-react";
3
3
  import { ArrowIcon } from "../icons/Arrow";
4
4
  import { useComposedCssClasses } from "../hooks";
5
5
  import TextareaAutosize from "react-textarea-autosize";
6
6
  import { useDefaultHandleApiError } from "../hooks/useDefaultHandleApiError";
7
7
  import { withStylelessCssClasses } from "../utils/withStylelessCssClasses";
8
+ import { useSendMessageWithRetries } from "../hooks/useSendMessageWithRetries";
8
9
 
9
10
  /**
10
11
  * The CSS class interface for the {@link ChatInput} component.
@@ -58,6 +59,11 @@ export interface ChatInputProps {
58
59
  customCssClasses?: ChatInputCssClasses;
59
60
  /** A callback which is called when user sends a message. */
60
61
  onSend?: (message: string) => void;
62
+ /**
63
+ * A function which is called when a retryable error occurs from
64
+ * Chat API while processing the user's message.
65
+ */
66
+ onRetry?: (e: unknown) => void
61
67
  }
62
68
 
63
69
  /**
@@ -79,25 +85,24 @@ export function ChatInput({
79
85
  sendButtonIcon = <ArrowIcon />,
80
86
  customCssClasses,
81
87
  onSend,
88
+ onRetry,
82
89
  }: ChatInputProps) {
83
- const chat = useChatActions();
84
90
  const [input, setInput] = useState("");
85
91
  const canSendMessage = useChatState(
86
92
  (state) => state.conversation.canSendMessage
87
93
  );
88
94
  const defaultHandleApiError = useDefaultHandleApiError();
89
-
95
+ const sendMessageWithRetries = useSendMessageWithRetries(stream, 1, onRetry)
90
96
  const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses);
91
97
 
92
98
  const sendMessage = useCallback(async () => {
93
- const res = stream
94
- ? chat.streamNextMessage(input)
95
- : chat.getNextMessage(input);
96
99
  setInput("");
97
- res.then(() => {
98
- onSend?.(input)
99
- }).catch((e) => (handleError ? handleError(e) : defaultHandleApiError(e)));
100
- }, [chat, input, handleError, defaultHandleApiError, stream, onSend]);
100
+ sendMessageWithRetries(input)
101
+ .catch(handleError ?? defaultHandleApiError)
102
+ .finally(() => {
103
+ onSend?.(input)
104
+ })
105
+ }, [sendMessageWithRetries, input, handleError, defaultHandleApiError, onSend]);
101
106
 
102
107
  const handleKeyDown = useCallback(
103
108
  (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
@@ -4,6 +4,7 @@ import React, {
4
4
  useEffect,
5
5
  useMemo,
6
6
  useRef,
7
+ useState,
7
8
  } from "react";
8
9
  import { useChatState } from "@yext/chat-headless-react";
9
10
  import {
@@ -76,6 +77,11 @@ export interface ChatPanelProps
76
77
  messageSuggestions?: string[];
77
78
  /** A callback which is called when user clicks a link. */
78
79
  onLinkClick?: (href?: string) => void;
80
+ /**
81
+ * Text to display when retrying.
82
+ * Defaults to "Error occurred. Retrying".
83
+ */
84
+ retryText?: string;
79
85
  }
80
86
 
81
87
  /**
@@ -96,6 +102,9 @@ export function ChatPanel(props: ChatPanelProps) {
96
102
  handleError,
97
103
  messageSuggestions,
98
104
  onLinkClick,
105
+ onSend:onSendProp,
106
+ onRetry:onRetryProp,
107
+ retryText = "Error occurred. Retrying",
99
108
  } = props;
100
109
  const messages = useChatState((state) => state.conversation.messages);
101
110
  const loading = useChatState((state) => state.conversation.isLoading);
@@ -106,6 +115,17 @@ export function ChatPanel(props: ChatPanelProps) {
106
115
  const reportAnalyticsEvent = useReportAnalyticsEvent();
107
116
  useFetchInitialMessage(handleError, stream);
108
117
 
118
+ const [retry, setRetry] = useState(false);
119
+ const onSend = useCallback((message: string) => {
120
+ onSendProp?.(message);
121
+ setRetry(false)
122
+ }, [onSendProp])
123
+
124
+ const onRetry = useCallback((e: unknown) => {
125
+ onRetryProp?.(e);
126
+ setRetry(true)
127
+ }, [onRetryProp])
128
+
109
129
  useEffect(() => {
110
130
  reportAnalyticsEvent({
111
131
  action: "CHAT_IMPRESSION",
@@ -174,17 +194,29 @@ export function ChatPanel(props: ChatPanelProps) {
174
194
  />
175
195
  </div>
176
196
  ))}
177
- {loading && <LoadingDots />}
197
+ {loading && <div className="flex">
198
+ <LoadingDots />
199
+ {retry && <p className="text-slate-500 text-[13px] font-bold">{retryText}</p>}
200
+ </div>}
178
201
  </div>
179
202
  </div>
180
203
  <div className={cssClasses.inputContainer}>
181
204
  {suggestions && (
182
205
  <MessageSuggestions
206
+ stream={stream}
207
+ onSend={onSend}
208
+ onRetry={onRetry}
209
+ handleError={handleError}
183
210
  suggestions={suggestions}
184
211
  customCssClasses={cssClasses.messageSuggestionClasses}
185
212
  />
186
213
  )}
187
- <ChatInput {...props} customCssClasses={cssClasses.inputCssClasses} />
214
+ <ChatInput
215
+ {...props}
216
+ onSend={onSend}
217
+ onRetry={onRetry}
218
+ customCssClasses={cssClasses.inputCssClasses}
219
+ />
188
220
  </div>
189
221
  {footer && (
190
222
  <Markdown
@@ -78,7 +78,7 @@ export function Markdown({
78
78
  responseId,
79
79
  },
80
80
  });
81
- onLinkClick?.(href)
81
+ onLinkClick?.(href);
82
82
  };
83
83
  return {
84
84
  a: ({ node: _, children, ...props }) => {
@@ -95,7 +95,13 @@ export function Markdown({
95
95
  );
96
96
  },
97
97
  };
98
- }, [reportAnalyticsEvent, linkClickEvent, responseId, cssClasses, onLinkClick]);
98
+ }, [
99
+ reportAnalyticsEvent,
100
+ linkClickEvent,
101
+ responseId,
102
+ cssClasses,
103
+ onLinkClick,
104
+ ]);
99
105
 
100
106
  return (
101
107
  <ReactMarkdown
@@ -7,6 +7,7 @@ import {
7
7
  import { useDefaultHandleApiError } from "../hooks/useDefaultHandleApiError";
8
8
  import { withStylelessCssClasses } from "../utils/withStylelessCssClasses";
9
9
  import { useComposedCssClasses } from "../hooks";
10
+ import { useSendMessageWithRetries } from "../hooks/useSendMessageWithRetries";
10
11
 
11
12
  /**
12
13
  * The CSS class interface for the MessageSuggestion component.
@@ -24,8 +25,18 @@ export interface MessageSuggestionCssClasses {
24
25
  * @public
25
26
  */
26
27
  export interface MessageSuggestionsProps {
28
+ /** List of clickable message suggestions to render. */
27
29
  suggestions: string[];
30
+ /** {@inheritdoc ChatInputProps.handleError} */
31
+ handleError?: (e: unknown) => void;
32
+ /** CSS classes for customizing the component styling. */
28
33
  customCssClasses?: MessageSuggestionCssClasses;
34
+ /** {@inheritdoc ChatInputProps.stream} */
35
+ stream?: boolean;
36
+ /** {@inheritdoc ChatInputProps.onSend} */
37
+ onSend?: (message: string) => void;
38
+ /** {@inheritdoc ChatInputProps.onRetry} */
39
+ onRetry?: (e: unknown) => void
29
40
  }
30
41
 
31
42
  const defaultClassnames: MessageSuggestionCssClasses = withStylelessCssClasses(
@@ -44,12 +55,17 @@ const defaultClassnames: MessageSuggestionCssClasses = withStylelessCssClasses(
44
55
  * @internal
45
56
  */
46
57
  export const MessageSuggestions: React.FC<MessageSuggestionsProps> = ({
58
+ handleError,
47
59
  suggestions,
48
60
  customCssClasses,
61
+ stream = false,
62
+ onSend,
63
+ onRetry,
49
64
  }) => {
50
65
  const actions = useChatActions();
51
66
  const notes = useChatState((state) => state.conversation.notes);
52
67
  const defaultHandleApiError = useDefaultHandleApiError();
68
+ const sendMessageWithRetries = useSendMessageWithRetries(stream, 1, onRetry)
53
69
  const sendMsg = useCallback(
54
70
  (msg: string) => {
55
71
  const newNotes = {
@@ -57,10 +73,13 @@ export const MessageSuggestions: React.FC<MessageSuggestionsProps> = ({
57
73
  suggestedReplies: undefined,
58
74
  } satisfies MessageNotes;
59
75
  actions.setMessageNotes(newNotes);
60
- const res = actions.getNextMessage(msg);
61
- res.catch(defaultHandleApiError);
76
+ sendMessageWithRetries(msg)
77
+ .catch(handleError ?? defaultHandleApiError)
78
+ .finally(() => {
79
+ onSend?.(msg)
80
+ })
62
81
  },
63
- [actions, notes, defaultHandleApiError]
82
+ [notes, actions, sendMessageWithRetries, handleError, defaultHandleApiError, onSend]
64
83
  );
65
84
 
66
85
  const classes = useComposedCssClasses(defaultClassnames, customCssClasses);
@@ -0,0 +1,49 @@
1
+ import { ApiError, useChatActions } from "@yext/chat-headless-react";
2
+ import { useCallback } from "react";
3
+
4
+ /**
5
+ * Returns a function that sends a message to the chat API with retries
6
+ * if the API returns a 5xx status code.
7
+ *
8
+ * @remarks
9
+ * The function will throw the error if the maximum number of retries is reached
10
+ * or if the error is not a 5xx status code.
11
+ *
12
+ * @internal
13
+ *
14
+ * @param stream - If true, use streaming API
15
+ * @param maxRetries - Maximum number of retries
16
+ * @param onRetry - Callback to handle error on each retry
17
+ *
18
+ */
19
+ export function useSendMessageWithRetries(
20
+ stream = false,
21
+ maxRetries = 0,
22
+ onRetry?: (e: unknown) => void
23
+ ): (input: string) => Promise<void> {
24
+ const chat = useChatActions()
25
+ return useCallback(async (input: string) => {
26
+ let err: unknown;
27
+ let text = input;
28
+ for (let numRetries = 0; numRetries <= maxRetries; numRetries++) {
29
+ if (numRetries > 0 && !!err) {
30
+ if (err instanceof ApiError && !!err.statusCode && err.statusCode >= 500) {
31
+ onRetry?.(err)
32
+ // avoid re-adding user message to conversation history on retry
33
+ text = "";
34
+ } else {
35
+ throw err;
36
+ }
37
+ }
38
+ try {
39
+ await (stream
40
+ ? chat.streamNextMessage(text)
41
+ : chat.getNextMessage(text));
42
+ return;
43
+ } catch (e) {
44
+ err = e;
45
+ }
46
+ }
47
+ throw err
48
+ }, [chat, maxRetries, onRetry, stream])
49
+ }