From d531137f14accb82732804452bf324093a827cea Mon Sep 17 00:00:00 2001 From: Paul Jankowski <33367713+8bittitan@users.noreply.github.com> Date: Wed, 26 Nov 2025 10:35:36 -0500 Subject: [PATCH] feat(askai): Handle errors better for Ask AI (#2803) --- packages/docsearch-css/src/modal.css | 26 +++++++++++---- packages/docsearch-react/src/AskAiScreen.tsx | 32 +++++++++++-------- .../docsearch-react/src/DocSearchModal.tsx | 10 ++---- packages/docsearch-react/src/ScreenState.tsx | 6 ++-- .../src/__tests__/askai.test.tsx | 8 +---- packages/docsearch-react/src/askai.ts | 7 +++- 6 files changed, 50 insertions(+), 39 deletions(-) diff --git a/packages/docsearch-css/src/modal.css b/packages/docsearch-css/src/modal.css index 8b0bf53c..b30390cc 100644 --- a/packages/docsearch-css/src/modal.css +++ b/packages/docsearch-css/src/modal.css @@ -871,10 +871,14 @@ assistive tech users */ cursor: default; } +.DocSearch-AskAiScreen-MessageContent { + display: flex; + flex-direction: column; + row-gap: 1em; +} + .DocSearch-AskAiScreen-Error { padding: var(--docsearch-spacing); - display: flex; - align-items: baseline; padding: 1em; gap: 8px; color: var(--docsearch-error-color); @@ -882,12 +886,11 @@ assistive tech users */ border-radius: 4px; font-size: 1em; font-weight: 400; + flex-direction: row; } -.DocSearch-AskAiScreen-MessageContent { - display: flex; - flex-direction: column; - row-gap: 1em; +.DocSearch-AskAiScreen-Error svg { + margin-top: 0.25rem; } .DocSearch-AskAiScreen-Error svg, @@ -901,6 +904,17 @@ assistive tech users */ margin: 0; } +.DocSearch-AskAiScreen-Error-Content { + display: flex; + flex-direction: column; + flex: 1 1 0%; +} + +.DocSearch-AskAiScreen-Error-Title { + font-weight: 700; + margin-bottom: 4px; +} + .DocSearch-AskAiScreen-Error .DocSearch-Markdown-Content { color: var(--docsearch-error-color); } diff --git a/packages/docsearch-react/src/AskAiScreen.tsx b/packages/docsearch-react/src/AskAiScreen.tsx index 19d54299..a87368f5 100644 --- a/packages/docsearch-react/src/AskAiScreen.tsx +++ b/packages/docsearch-react/src/AskAiScreen.tsx @@ -48,13 +48,16 @@ export type AskAiScreenTranslations = Partial<{ * Message that's shown when user has stopped the streaming of a message. */ stoppedStreamingText: string; + /** + * Error title shown if there is an error while chatting. + */ + errorTitleText: string; }>; type AskAiScreenProps = Omit, 'translations'> & { messages: AIMessage[]; status: UseChatHelpers['status']; - askAiStreamError: Error | null; - askAiFetchError: Error | undefined; + askAiError?: Error; translations?: AskAiScreenTranslations; }; @@ -74,7 +77,7 @@ function AskAiScreenHeader({ disclaimerText }: AskAiScreenHeaderProps): JSX.Elem interface AskAiExchangeCardProps { exchange: Exchange; - askAiStreamError: Error | null; + askAiError?: Error; isLastExchange: boolean; loadingStatus: UseChatHelpers['status']; onSearchQueryClick: (query: string) => void; @@ -85,7 +88,7 @@ interface AskAiExchangeCardProps { function AskAiExchangeCard({ exchange, - askAiStreamError, + askAiError, isLastExchange, loadingStatus, onSearchQueryClick, @@ -95,7 +98,7 @@ function AskAiExchangeCard({ }: AskAiExchangeCardProps): JSX.Element { const { userMessage, assistantMessage } = exchange; - const { stoppedStreamingText = 'You stopped this response' } = translations; + const { stoppedStreamingText = 'You stopped this response', errorTitleText = 'Chat error' } = translations; const assistantContent = useMemo(() => getMessageContent(assistantMessage), [assistantMessage]); const userContent = useMemo(() => getMessageContent(userMessage), [userMessage]); @@ -124,15 +127,18 @@ function AskAiExchangeCard({
- {loadingStatus === 'error' && askAiStreamError && isLastExchange && ( + {loadingStatus === 'error' && askAiError && isLastExchange && (
- +
+

{errorTitleText}

+ +
)} {isThinking && ( @@ -406,7 +412,7 @@ export function AskAiScreen({ translations = {}, ...props }: AskAiScreenProps): (null); const [stoppedStream, setStoppedStream] = React.useState(false); const { @@ -405,7 +404,7 @@ export function DocSearchModal({ sendMessage, status, setMessages, - error: askAiFetchError, + error: askAiError, stop: stopAskAiStreaming, } = useChat({ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, @@ -426,7 +425,6 @@ export function DocSearchModal({ return { ...(token ? { authorization: `TOKEN ${token}` } : {}), - // 'Content-Type': 'application/json', 'X-Algolia-API-Key': askAiConfig?.apiKey || apiKey, 'X-Algolia-Application-Id': askAiConfig?.appId || appId, 'X-Algolia-Index-Name': askAiConfig?.indexName || defaultIndexName, @@ -436,9 +434,6 @@ export function DocSearchModal({ }, body: askAiSearchParameters ? { searchParameters: askAiSearchParameters } : {}, }), - onError(streamError) { - setAskAiStreamError(streamError); - }, }); const prevStatus = React.useRef(status); @@ -907,8 +902,7 @@ export function DocSearchModal({ isAskAiActive={isAskAiActive} canHandleAskAi={canHandleAskAi} messages={messages} - askAiStreamError={askAiStreamError} - askAiFetchError={askAiFetchError} + askAiError={askAiError} status={status} hasCollections={hasCollections} askAiState={askAiState} diff --git a/packages/docsearch-react/src/ScreenState.tsx b/packages/docsearch-react/src/ScreenState.tsx index 9d27e979..d138f430 100644 --- a/packages/docsearch-react/src/ScreenState.tsx +++ b/packages/docsearch-react/src/ScreenState.tsx @@ -44,8 +44,7 @@ export interface ScreenStateProps indexName: DocSearchProps['indexName']; messages: UseChatHelpers['messages']; status: UseChatHelpers['status']; - askAiStreamError: Error | null; - askAiFetchError: Error | undefined; + askAiError?: Error; disableUserPersonalization: boolean; resultsFooterComponent: DocSearchProps['resultsFooterComponent']; translations: ScreenStateTranslations; @@ -80,8 +79,7 @@ export const ScreenState = React.memo( {...props} messages={props.messages} status={props.status} - askAiStreamError={props.askAiStreamError} - askAiFetchError={props.askAiFetchError} + askAiError={props.askAiError} translations={translations?.askAiScreen} /> ); diff --git a/packages/docsearch-react/src/__tests__/askai.test.tsx b/packages/docsearch-react/src/__tests__/askai.test.tsx index 649141b4..9aca7ee0 100644 --- a/packages/docsearch-react/src/__tests__/askai.test.tsx +++ b/packages/docsearch-react/src/__tests__/askai.test.tsx @@ -37,13 +37,7 @@ describe('AskAiScreen', () => { ]; const { getByText } = render( - , + , ); expect(getByText('oh no')).toBeInTheDocument(); diff --git a/packages/docsearch-react/src/askai.ts b/packages/docsearch-react/src/askai.ts index 1ec30219..57b8410b 100644 --- a/packages/docsearch-react/src/askai.ts +++ b/packages/docsearch-react/src/askai.ts @@ -38,7 +38,12 @@ export const getValidToken = async ({ assistantId }: { assistantId: string }): P }, }) .then((r) => r.json()) - .then(({ token }) => { + .then(({ token, success, message }) => { + // If request was unsuccessful, throw an error to prevent calling `/chat` without a token + if (!success && message) { + throw new Error(message); + } + sessionStorage.setItem(TOKEN_KEY, token); return token; })