Skip to content

Commit f1aaf91

Browse files
authored
feat(askai): Allow Agent Studio specific search params (#2842)
* feat(askai): Allow Agent Studio specific search params * Simplify DocSearchProps types definitions * Revert "Simplify DocSearchProps types definitions" This reverts commit b53bc97. * fix: types
1 parent 55db6e9 commit f1aaf91

File tree

9 files changed

+178
-74
lines changed

9 files changed

+178
-74
lines changed

examples/demo-react/src/examples/agent-studio.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ export function AgentStudioExample(): JSX.Element {
2121
apiKey="a00716d83c64f6c61905c078b7d5ab66"
2222
askAi={{
2323
assistantId: 'ccdec697-e3fe-465b-a1c3-657e7bf18aef',
24-
indexName: 'docsearch-markdown',
24+
agentStudio: true,
2525
}}
26-
agentStudio={true}
2726
/>
2827
</DocSearch>
2928

packages/docsearch-react/src/DocSearch.tsx

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ export type AskAiSearchParameters = {
3131
filters?: string;
3232
attributesToRetrieve?: string[];
3333
restrictSearchableAttributes?: string[];
34-
distinct?: boolean;
34+
distinct?: boolean | number | string;
3535
};
3636

37+
export type AgentStudioSearchParameters = Record<string, Omit<AskAiSearchParameters, 'facetFilters'>>;
38+
3739
export type DocSearchAskAi = {
3840
/**
3941
* The index name to use for the ask AI feature. Your assistant will search this index for relevant documents.
@@ -54,10 +56,6 @@ export type DocSearchAskAi = {
5456
* The assistant ID to use for the ask AI feature.
5557
*/
5658
assistantId: string;
57-
/**
58-
* The search parameters to use for the ask AI feature.
59-
*/
60-
searchParameters?: AskAiSearchParameters;
6159
/**
6260
* Enables displaying suggested questions on Ask AI's new conversation screen.
6361
*
@@ -66,7 +64,43 @@ export type DocSearchAskAi = {
6664
suggestedQuestions?: boolean;
6765
// HACK: This is a hack for testing staging, remove before releasing
6866
useStagingEnv?: boolean;
69-
};
67+
} & (
68+
| {
69+
/**
70+
* **Experimental:** Whether to use Agent Studio as the chat backend.
71+
*
72+
* This is an experimental feature and its API may change without notice in future releases.
73+
* Use with caution in production environments.
74+
*
75+
* @default false
76+
*/
77+
agentStudio?: never;
78+
/**
79+
* The search parameters to use for the ask AI feature.
80+
*
81+
* **NOTE**: If using `agentStudio = true`, the `searchParameters` object is
82+
* keyed by the index name.
83+
*/
84+
searchParameters?: AskAiSearchParameters;
85+
}
86+
| {
87+
agentStudio: false;
88+
searchParameters?: AskAiSearchParameters;
89+
}
90+
| {
91+
agentStudio: true;
92+
/**
93+
* The search parameters to use for the ask AI feature.
94+
* Keyed by the index name.
95+
*
96+
* @example
97+
* {
98+
* "INDEX_NAME": { distinct: false }
99+
* }
100+
*/
101+
searchParameters?: AgentStudioSearchParameters;
102+
}
103+
);
70104

71105
export interface DocSearchIndex {
72106
name: string;
@@ -105,15 +139,6 @@ export interface DocSearchProps {
105139
* Useful to route Ask AI into a different UI (e.g. `@docsearch/sidepanel-js`) without flicker.
106140
*/
107141
interceptAskAiEvent?: (initialMessage: InitialAskAiMessage) => boolean | void;
108-
/**
109-
* **Experimental:** Whether to use Agent Studio as the chat backend.
110-
*
111-
* This is an experimental feature and its API may change without notice in future releases.
112-
* Use with caution in production environments.
113-
*
114-
* @default false
115-
*/
116-
agentStudio?: boolean;
117142
/**
118143
* Theme overrides applied to the modal and related components.
119144
*/

packages/docsearch-react/src/DocSearchModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ export function DocSearchModal({
310310
indexName,
311311
searchParameters,
312312
isHybridModeSupported = false,
313-
agentStudio = false,
314313
...props
315314
}: DocSearchModalProps): JSX.Element {
316315
const { footer: footerTranslations, searchBox: searchBoxTranslations, ...screenStateTranslations } = translations;
@@ -360,6 +359,7 @@ export function DocSearchModal({
360359
searchClient,
361360
suggestedQuestionsEnabled: askAiConfig?.suggestedQuestions,
362361
});
362+
const agentStudio = askAiConfig?.agentStudio ?? false;
363363

364364
// Format the `indexes` to be used until `indexName` and `searchParameters` props are fully removed.
365365
const indexes: DocSearchIndex[] = [];

packages/docsearch-react/src/Sidepanel.tsx

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,49 @@ import type { JSX } from 'react';
44
import React from 'react';
55
import { createPortal } from 'react-dom';
66

7-
import type { AskAiSearchParameters } from './DocSearch';
8-
import type { SidepanelButtonProps, SidepanelProps } from './Sidepanel/index';
7+
import type { AgentStudioSearchParameters, AskAiSearchParameters } from './DocSearch';
8+
import type { SidepanelButtonProps, SidepanelProps as SidepanelPanelProps } from './Sidepanel/index';
99
import { SidepanelButton, Sidepanel } from './Sidepanel/index';
1010

1111
export type { DocSearchRef, DocSearchCallbacks } from '@docsearch/core';
1212

13+
export type SidepanelSearchParameters =
14+
| {
15+
/**
16+
* **Experimental:** Whether to use Agent Studio as the chat backend.
17+
*
18+
* This is an experimental feature and its API may change without notice in future releases.
19+
* Use with caution in production environments.
20+
*
21+
* @default false
22+
*/
23+
agentStudio?: never;
24+
/**
25+
* The search parameters to use for the ask AI feature.
26+
*
27+
* **NOTE**: If using `agentStudio = true`, the `searchParameters` object is
28+
* keyed by the index name.
29+
*/
30+
searchParameters?: AskAiSearchParameters;
31+
}
32+
| {
33+
agentStudio: false;
34+
searchParameters?: AskAiSearchParameters;
35+
}
36+
| {
37+
agentStudio: true;
38+
/**
39+
* The search parameters to use for the ask AI feature.
40+
* Keyed by the index name.
41+
*
42+
* @example
43+
* {
44+
* "INDEX_NAME": { distinct: false }
45+
* }
46+
*/
47+
searchParameters?: AgentStudioSearchParameters;
48+
};
49+
1350
export type DocSearchSidepanelProps = DocSearchCallbacks & {
1451
/**
1552
* The assistant ID to use for the ask AI feature.
@@ -27,10 +64,6 @@ export type DocSearchSidepanelProps = DocSearchCallbacks & {
2764
* The index name to use for the ask AI feature. Your assistant will search this index for relevant documents.
2865
*/
2966
indexName: string;
30-
/**
31-
* The search parameters to use for the ask AI feature.
32-
*/
33-
searchParameters?: AskAiSearchParameters;
3467
/**
3568
* Configuration for keyboard shortcuts. Allows enabling/disabling specific shortcuts.
3669
*
@@ -50,29 +83,13 @@ export type DocSearchSidepanelProps = DocSearchCallbacks & {
5083
/**
5184
* Props specific to the Sidepanel panel.
5285
*/
53-
panel?: Omit<SidepanelProps, 'keyboardShortcuts'>;
54-
/**
55-
* **Experimental:** Whether to use Agent Studio as the chat backend.
56-
*
57-
* This is an experimental feature and its API may change without notice in future releases.
58-
* Use with caution in production environments.
59-
*
60-
* @default false
61-
*/
62-
agentStudio?: boolean;
86+
panel?: Omit<SidepanelPanelProps, 'keyboardShortcuts'>;
6387
};
6488

89+
type SidepanelProps = DocSearchSidepanelProps & SidepanelSearchParameters;
90+
6591
function DocSearchSidepanelComponent(
66-
{
67-
keyboardShortcuts,
68-
theme,
69-
onReady,
70-
onOpen,
71-
onClose,
72-
onSidepanelOpen,
73-
onSidepanelClose,
74-
...props
75-
}: DocSearchSidepanelProps,
92+
{ keyboardShortcuts, theme, onReady, onOpen, onClose, onSidepanelOpen, onSidepanelClose, ...props }: SidepanelProps,
7693
ref: React.ForwardedRef<DocSearchRef>,
7794
): JSX.Element {
7895
return (

packages/docsearch-react/src/Sidepanel/ConversationScreen.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export type ConversationScreenTranslations = Partial<
5757
* Message displayed after feedback action.
5858
**/
5959
thanksForFeedbackText: string;
60+
/**
61+
* Error title shown if there is an error while chatting.
62+
*/
63+
errorTitleText;
6064
}
6165
>;
6266

@@ -98,6 +102,7 @@ const ConversationExchange = React.forwardRef<HTMLDivElement, ConversationnExcha
98102
toolCallResultText = 'Searched for',
99103
copyButtonText = 'Copy',
100104
copyButtonCopiedText = 'Copied!',
105+
errorTitleText = 'Chat error',
101106
} = translations;
102107

103108
const assistantContent = useMemo(() => getMessageContent(assistantMessage), [assistantMessage]);
@@ -110,7 +115,8 @@ const ConversationExchange = React.forwardRef<HTMLDivElement, ConversationnExcha
110115
const urlsToDisplay = React.useMemo(() => extractLinksFromMessage(assistantMessage), [assistantMessage]);
111116

112117
const wasStopped = userMessage.metadata?.stopped || assistantMessage?.metadata?.stopped;
113-
const isThinking = !assistantParts.some((part) => part.type !== 'step-start');
118+
const isThinking =
119+
['submitted', 'streaming'].includes(status) && !assistantParts.some((part) => part.type !== 'step-start');
114120
const showActions =
115121
!wasStopped && (!isLastExchange || (isLastExchange && status === 'ready' && Boolean(assistantMessage)));
116122

@@ -125,12 +131,15 @@ const ConversationExchange = React.forwardRef<HTMLDivElement, ConversationnExcha
125131
{status === 'error' && streamError && isLastExchange && (
126132
<div className="DocSearch-AskAiScreen-MessageContent DocSearch-AskAiScreen-Error">
127133
<AlertIcon />
128-
<MemoizedMarkdown
129-
content={streamError.message}
130-
copyButtonText=""
131-
copyButtonCopiedText=""
132-
isStreaming={false}
133-
/>
134+
<div className="DocSearch-AskAiScreen-Error-Content">
135+
<h4 className="DocSearch-AskAiScreen-Error-Title">{errorTitleText}</h4>
136+
<MemoizedMarkdown
137+
content={streamError.message}
138+
copyButtonText=""
139+
copyButtonCopiedText=""
140+
isStreaming={false}
141+
/>
142+
</div>
134143
</div>
135144
)}
136145

packages/docsearch-react/src/Sidepanel/Sidepanel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React, { useCallback } from 'react';
33
import type { JSX } from 'react';
44

55
import { AlgoliaLogo, type AlgoliaLogoTranslations } from '../AlgoliaLogo';
6-
import type { DocSearchSidepanelProps } from '../Sidepanel';
6+
import type { DocSearchSidepanelProps, SidepanelSearchParameters } from '../Sidepanel';
77
import type { StoredAskAiState, SuggestedQuestionHit } from '../types';
88
import { useAskAi } from '../useAskAi';
99
import { useSearchClient } from '../useSearchClient';
@@ -122,7 +122,8 @@ export type SidepanelProps = {
122122
};
123123

124124
type Props = Omit<DocSearchSidepanelProps, 'button' | 'panel'> &
125-
SidepanelProps & {
125+
SidepanelProps &
126+
SidepanelSearchParameters & {
126127
isOpen?: boolean;
127128
onOpen: () => void;
128129
onClose: () => void;

packages/docsearch-react/src/askai.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,35 @@ export const postFeedback = async ({
102102
headers,
103103
});
104104
};
105+
106+
interface AgentStudioValidationError extends Error {
107+
name: 'ValidationError';
108+
detail?: Array<{ type: string; loc: string[]; msg: string }>;
109+
}
110+
111+
// Parse Agent Studio errors as they are returned as JSON rather than Markdown/text
112+
export const getAgentStudioErrorMessage = (error: Error): Error => {
113+
let errorMessage = error.message;
114+
115+
try {
116+
const parsedError = JSON.parse(error.message) as Error;
117+
118+
// Check for known errors that we know how to parse
119+
if (parsedError.name === 'ValidationError') {
120+
const validationError = parsedError as AgentStudioValidationError;
121+
122+
if (validationError.detail && validationError.detail.length > 0) {
123+
const { msg, loc } = validationError.detail[0];
124+
const field = loc.at(-1);
125+
126+
errorMessage = `${msg}: ${field}`;
127+
}
128+
} else {
129+
errorMessage = parsedError.message;
130+
}
131+
} catch {
132+
// We don't care about this catch, we default to the error.message above
133+
}
134+
135+
return new Error(errorMessage);
136+
};

0 commit comments

Comments
 (0)