1
0
Fork 0

feat(askai): Handle errors better for Ask AI (#2803)

This commit is contained in:
Paul Jankowski 2025-11-26 10:35:36 -05:00 committed by GitHub
parent 5ef01ecd4d
commit d531137f14
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 50 additions and 39 deletions

View file

@ -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);
}

View file

@ -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<ScreenStateProps<InternalDocSearchHit>, 'translations'> & {
messages: AIMessage[];
status: UseChatHelpers<AIMessage>['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<AIMessage>['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({
</div>
<div className="DocSearch-AskAiScreen-Message DocSearch-AskAiScreen-Message--assistant">
<div className="DocSearch-AskAiScreen-MessageContent">
{loadingStatus === 'error' && askAiStreamError && isLastExchange && (
{loadingStatus === 'error' && askAiError && isLastExchange && (
<div className="DocSearch-AskAiScreen-MessageContent DocSearch-AskAiScreen-Error">
<AlertIcon />
<MemoizedMarkdown
content={askAiStreamError.message}
copyButtonText=""
copyButtonCopiedText=""
isStreaming={false}
/>
<div className="DocSearch-AskAiScreen-Error-Content">
<h4 className="DocSearch-AskAiScreen-Error-Title">{errorTitleText}</h4>
<MemoizedMarkdown
content={askAiError.message}
copyButtonText=""
copyButtonCopiedText=""
isStreaming={false}
/>
</div>
</div>
)}
{isThinking && (
@ -406,7 +412,7 @@ export function AskAiScreen({ translations = {}, ...props }: AskAiScreenProps):
<AskAiExchangeCard
key={exchange.id}
exchange={exchange}
askAiStreamError={props.askAiStreamError}
askAiError={props.askAiError}
isLastExchange={index === 0}
loadingStatus={props.status}
translations={translations}

View file

@ -397,7 +397,6 @@ export function DocSearchModal({
}),
).current;
const [askAiStreamError, setAskAiStreamError] = React.useState<Error | null>(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<AIMessage>({
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}

View file

@ -44,8 +44,7 @@ export interface ScreenStateProps<TItem extends BaseItem>
indexName: DocSearchProps['indexName'];
messages: UseChatHelpers<AIMessage>['messages'];
status: UseChatHelpers<AIMessage>['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}
/>
);

View file

@ -37,13 +37,7 @@ describe('AskAiScreen', () => {
];
const { getByText } = render(
<AskAiScreen
{...baseProps}
messages={messages}
status="error"
askAiStreamError={new Error('oh no')}
askAiFetchError={undefined}
/>,
<AskAiScreen {...baseProps} messages={messages} status="error" askAiError={new Error('oh no')} />,
);
expect(getByText('oh no')).toBeInTheDocument();

View file

@ -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;
})