1
0
Fork 0

feat(askai): Move askai related props under root askai (#2919)

* feat(askai): Move askai related props under root askai

* fix: playwright test case

* fix(docusaurus): validate Ask AI options
This commit is contained in:
Paul Jankowski 2026-07-16 11:03:50 -04:00 committed by GitHub
parent 23f3e6b657
commit 629fce7902
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 382 additions and 117 deletions

View file

@ -287,6 +287,58 @@ describe('validateThemeConfig', () => {
});
});
it('accepts sidePanel Agent Studio indices', () => {
const docsearch: DocSearchInput = {
...minimalDocSearchConfig,
askAi: minimalAskAiConfig,
sidePanel: {
indices: [
{
index: 'sidepanel-markdown-index',
description: 'Documentation content for the side panel.',
},
],
},
};
expect(testValidateThemeConfig(docsearch)).toEqual({
docsearch: {
...DEFAULT_CONFIG,
...docsearch,
},
});
});
it('rejects empty sidePanel Agent Studio indices', () => {
const docsearch = {
...minimalDocSearchConfig,
askAi: minimalAskAiConfig,
sidePanel: {
indices: [],
},
} as unknown as DocSearchInput;
expectThrowMessage(
() => testValidateThemeConfig(docsearch),
'"docsearch.sidePanel.indices" must contain at least 1 items',
);
});
it('rejects incomplete sidePanel Agent Studio indices', () => {
const docsearch = {
...minimalDocSearchConfig,
askAi: minimalAskAiConfig,
sidePanel: {
indices: [{ index: 'sidepanel-markdown-index' }],
},
} as unknown as DocSearchInput;
expectThrowMessage(
() => testValidateThemeConfig(docsearch),
'"docsearch.sidePanel.indices[0].description" is required',
);
});
it('rejects sidePanel without askAi', () => {
const docsearch: DocSearchInput = {
...minimalDocSearchConfig,
@ -298,6 +350,98 @@ describe('validateThemeConfig', () => {
'`themeConfig.docsearch.sidePanel` requires `themeConfig.docsearch.askAi`.',
);
});
it('accepts askAi memory object', () => {
const docsearch: DocSearchInput = {
...minimalDocSearchConfig,
askAi: {
...minimalAskAiConfig,
memory: {
enabled: true,
userToken: 'b2916249-b172-4ca2-8d0f-663e0f37f85d',
},
},
sidePanel: {
variant: 'inline',
side: 'left',
width: 420,
expandedWidth: '60vw',
pushSelector: '#__docusaurus',
hideButton: true,
keyboardShortcuts: {
'Ctrl/Cmd+I': false,
},
},
};
expect(testValidateThemeConfig(docsearch)).toEqual({
docsearch: {
...DEFAULT_CONFIG,
...docsearch,
},
});
});
it('accepts askAi promptSuggestions object', () => {
const docsearch: DocSearchInput = {
...minimalDocSearchConfig,
askAi: {
...minimalAskAiConfig,
promptSuggestions: {
indexName: 'test-index',
hitsPerPage: 7,
},
},
sidePanel: {
variant: 'inline',
side: 'left',
width: 420,
expandedWidth: '60vw',
pushSelector: '#__docusaurus',
hideButton: true,
keyboardShortcuts: {
'Ctrl/Cmd+I': false,
},
},
};
expect(testValidateThemeConfig(docsearch)).toEqual({
docsearch: {
...DEFAULT_CONFIG,
...docsearch,
},
});
});
it('rejects askAi custom tools definitions', () => {
const docsearch = {
...minimalDocSearchConfig,
askAi: {
...minimalAskAiConfig,
tools: {},
},
} as unknown as DocSearchInput;
expectThrowMessage(
() => testValidateThemeConfig(docsearch),
'`themeConfig.docsearch.askAi.tools` is not supported because Docusaurus removes function values',
);
});
it('rejects sidePanel custom tools definitions', () => {
const docsearch = {
...minimalDocSearchConfig,
askAi: minimalAskAiConfig,
sidePanel: {
tools: {},
},
} as unknown as DocSearchInput;
expectThrowMessage(
() => testValidateThemeConfig(docsearch),
'`themeConfig.docsearch.sidePanel.tools` is not supported because Docusaurus removes function values',
);
});
});
describe('removed config migration errors', () => {

View file

@ -14,12 +14,14 @@ import { mergeFacetFilters } from './utils';
import type { AskAiConfig } from '@docsearch/docusaurus-adapter';
type AskAiOptions = AskAiConfig & Pick<DocSearchAskAi, 'tools'>;
// The minimal props the hook needs from DocSearch
interface DocSearchPropsLite {
apiKey: string;
appId: string;
indices: NonNullable<DocSearchProps['indices']>;
askAi?: AskAiConfig;
askAi?: AskAiOptions;
}
type UseAskAiResult = {
@ -56,9 +58,9 @@ function applyContextualSearchToAgentStudioIndex(
// This can't be done at config normalization time because contextual filters
// can only be determined at runtime
function applyAskAiContextualSearch(
askAi: AskAiConfig | undefined,
askAi: AskAiOptions | undefined,
contextualSearchFilters: FacetFilters | undefined,
): AskAiConfig | undefined {
): AskAiOptions | undefined {
if (!askAi) {
return undefined;
}

View file

@ -11,10 +11,17 @@ declare module '@docsearch/docusaurus-adapter' {
AgentStudioSearchParameters,
DocSearchAskAi,
DocSearchProps,
ToolCalls,
} from '@docsearch/react';
import type { SidepanelProps } from '@docsearch/react/sidepanel';
type DocusaurusSidePanelConfig = boolean | (SidepanelProps & { hideButton?: boolean });
type DocusaurusSidePanelConfig = boolean | (Omit<SidepanelProps, 'tools'> & { hideButton?: boolean });
type DocusaurusSearchBarSidePanelProps =
| boolean
| (Omit<SidepanelProps, 'tools'> & {
hideButton?: boolean;
tools?: ToolCalls;
});
type SearchPageFacetConfig = {
/** Algolia attribute to build a refinement list from (e.g. `hierarchy.lvl0`). */
@ -39,8 +46,12 @@ declare module '@docsearch/docusaurus-adapter' {
suggestedQuestions?: DocSearchAskAi['suggestedQuestions'];
searchParameters?: AgentStudioSearchParameters;
indices?: AgentStudioIndices[];
memory?: DocSearchAskAi['memory'];
promptSuggestions?: DocSearchAskAi['promptSuggestions'];
};
export type DocusaurusSearchBarAskAiProps = AskAiConfig & Pick<DocSearchAskAi, 'tools'>;
// DocSearch props that Docusaurus exposes directly through props forwarding
type DocusaurusDocSearchProps = Pick<
DocSearchProps,
@ -76,6 +87,13 @@ declare module '@docsearch/docusaurus-adapter' {
};
};
export type DocusaurusSearchBarProps = Partial<
Omit<ThemeConfigDocSearch, 'askAi' | 'sidePanel'> & {
askAi?: DocusaurusSearchBarAskAiProps;
sidePanel?: DocusaurusSearchBarSidePanelProps;
}
>;
type UserDocSearchConfig = Omit<Partial<ThemeConfigDocSearch>, 'apiKey' | 'appId' | 'askAi' | 'indices'> & {
appId: ThemeConfigDocSearch['appId'];
apiKey: ThemeConfigDocSearch['apiKey'];
@ -101,7 +119,9 @@ declare module '@theme/SearchPage' {
declare module '@theme/SearchBar' {
import type { ReactNode } from 'react';
export default function SearchBar(): ReactNode;
import type { DocusaurusSearchBarProps } from '@docsearch/docusaurus-adapter';
export default function SearchBar(props?: DocusaurusSearchBarProps): ReactNode;
}
declare module '@theme/SearchTranslations' {

View file

@ -20,6 +20,7 @@ import type {
DocSearchTranslations,
InternalDocSearchHit,
StoredDocSearchHit,
ToolCalls,
} from '@docsearch/react';
import { SidepanelButton } from '@docsearch/sidepanel/button';
import type { Sidepanel as SidepanelType } from '@docsearch/sidepanel/sidepanel';
@ -43,12 +44,13 @@ import {
useSearchResultUrlProcessor,
} from '../../client';
import type { ThemeConfigDocSearch } from '@docsearch/docusaurus-adapter';
import type { DocusaurusSearchBarAskAiProps, ThemeConfigDocSearch } from '@docsearch/docusaurus-adapter';
type NavigatorNavigateParams = Parameters<NonNullable<NonNullable<DocSearchModalProps['navigator']>['navigate']>>[0];
type SidePanelOptions = Exclude<NonNullable<ThemeConfigDocSearch['sidePanel']>, boolean>;
type SidePanelOptions = Exclude<NonNullable<ThemeConfigDocSearch['sidePanel']>, boolean> & { tools?: ToolCalls };
type SidePanelPanelOptions = Omit<SidePanelOptions, 'hideButton' | 'keyboardShortcuts'>;
type AskAiOptions = NonNullable<ThemeConfigDocSearch['askAi']> & Pick<DocusaurusSearchBarAskAiProps, 'tools'>;
type AdapterDocSearchProps = Omit<
DocSearchAskAiModalProps,
@ -61,12 +63,12 @@ type AdapterDocSearchProps = Omit<
| 'onClose'
| 'searchParameters'
> & {
askAi?: ThemeConfigDocSearch['askAi'];
askAi?: AskAiOptions;
contextualSearch?: boolean;
externalUrlRegex?: string;
indices: NonNullable<DocSearchProps['indices']>;
searchPage: ThemeConfigDocSearch['searchPage'];
sidePanel?: ThemeConfigDocSearch['sidePanel'];
sidePanel?: SidePanelOptions | boolean;
translations?: DocSearchTranslations;
};
@ -305,7 +307,7 @@ function DocSearch({
translations: props.translations?.modal ?? translations.modal,
indices,
};
const panelOptions = getSidePanelPanelOptions(sidePanelOptions);
const panelOptions = getSidePanelPanelOptions(typeof sidePanel === 'object' ? sidePanel : undefined);
return (
<>
@ -356,8 +358,10 @@ function DocSearch({
appId={sidePanelAskAi.appId}
indexName={sidePanelAskAi.indexName}
searchParameters={sidePanelAskAi.searchParameters}
indices={sidePanelAskAi.indices}
indices={panelOptions.indices ?? sidePanelAskAi.indices}
suggestedQuestions={panelOptions.suggestedQuestions ?? sidePanelAskAi.suggestedQuestions}
tools={panelOptions.tools ?? sidePanelAskAi.tools}
memory={panelOptions.memory ?? sidePanelAskAi.memory}
/>
)}
</>

View file

@ -95,6 +95,10 @@
display: none;
}
.DocSearch-Sidepanel .DocSearch-AskAiScreen-Query {
margin: 0;
}
.DocSearch-SidepanelButton.inline {
background-color: var(--ifm-background-color);
}
@ -117,3 +121,4 @@
.DocSearch-Container {
z-index: calc(var(--ifm-z-index-fixed) + 1);
}

View file

@ -31,37 +31,6 @@ const SearchParametersSchema = Joi.object({
distinct: Joi.alternatives().try(Joi.boolean(), Joi.number(), Joi.string()).optional(),
}).unknown();
const SidePanelKeyboardShortcutsSchema = Joi.object({
'Ctrl/Cmd+I': Joi.boolean().optional(),
}).unknown(false);
const SidePanelSchema = Joi.object({
keyboardShortcuts: SidePanelKeyboardShortcutsSchema.optional(),
variant: Joi.string().valid('floating', 'inline').optional(),
side: Joi.string().valid('left', 'right').optional(),
width: Joi.alternatives().try(Joi.number(), Joi.string()).optional(),
expandedWidth: Joi.alternatives().try(Joi.number(), Joi.string()).optional(),
pushSelector: Joi.string().optional(),
suggestedQuestions: Joi.boolean().optional(),
translations: Joi.object().optional().unknown(),
hideButton: Joi.boolean().optional(),
portalContainer: Joi.object().optional().unknown(),
}).unknown(false);
const KeyboardShortcutsSchema = Joi.object({
'Ctrl/Cmd+K': Joi.boolean().optional(),
'/': Joi.boolean().optional(),
'Ctrl/Cmd+I': Joi.boolean().optional(),
}).unknown(false);
const IndexSchema = Joi.alternatives().try(
Joi.string(),
Joi.object({
name: Joi.string().required(),
searchParameters: SearchParametersSchema.optional(),
}).unknown(false),
);
const SearchControlTextParamSchema = Joi.object({
exposed: Joi.boolean().required(),
default: Joi.string().optional(),
@ -112,11 +81,56 @@ const AskAiIndexSchema = Joi.object({
searchControls: AgentStudioSearchControlsSchema.optional(),
}).unknown(false);
const AskAiMemorySchema = Joi.object({
enabled: Joi.bool().optional().default(false),
userToken: Joi.string().optional(),
}).unknown(false);
const SidePanelKeyboardShortcutsSchema = Joi.object({
'Ctrl/Cmd+I': Joi.boolean().optional(),
}).unknown(false);
const SidePanelSchema = Joi.object({
keyboardShortcuts: SidePanelKeyboardShortcutsSchema.optional(),
variant: Joi.string().valid('floating', 'inline').optional(),
side: Joi.string().valid('left', 'right').optional(),
width: Joi.alternatives().try(Joi.number(), Joi.string()).optional(),
expandedWidth: Joi.alternatives().try(Joi.number(), Joi.string()).optional(),
pushSelector: Joi.string().optional(),
suggestedQuestions: Joi.boolean().optional(),
translations: Joi.object().optional().unknown(),
hideButton: Joi.boolean().optional(),
portalContainer: Joi.object().optional().unknown(),
indices: Joi.array().items(AskAiIndexSchema).min(1).optional(),
memory: AskAiMemorySchema.optional(),
}).unknown(false);
const KeyboardShortcutsSchema = Joi.object({
'Ctrl/Cmd+K': Joi.boolean().optional(),
'/': Joi.boolean().optional(),
'Ctrl/Cmd+I': Joi.boolean().optional(),
}).unknown(false);
const IndexSchema = Joi.alternatives().try(
Joi.string(),
Joi.object({
name: Joi.string().required(),
searchParameters: SearchParametersSchema.optional(),
}).unknown(false),
);
const AskAiPromptSuggestionsSchema = Joi.object({
indexName: Joi.string().min(1).required(),
hitsPerPage: Joi.number().positive().optional().default(3),
}).unknown(false);
const AskAiSchema = Joi.object({
assistantId: Joi.string().required(),
suggestedQuestions: Joi.boolean().optional(),
searchParameters: Joi.object().pattern(Joi.string(), SearchParametersSchema).optional(),
indices: Joi.array().items(AskAiIndexSchema).min(1).optional(),
memory: AskAiMemorySchema.optional(),
promptSuggestions: AskAiPromptSuggestionsSchema.optional(),
}).unknown(false);
const SearchPageFacetSchema = Joi.object({
@ -219,6 +233,13 @@ function assertNoRemovedKeys(themeConfig: ThemeConfig): void {
);
}
const sidePanel = docsearchRecord.sidePanel;
if (sidePanel && typeof sidePanel === 'object' && (sidePanel as Record<string, unknown>).tools !== undefined) {
throw new Error(
'`themeConfig.docsearch.sidePanel.tools` is not supported because Docusaurus removes function values when serializing theme config. Pass custom tools through a swizzled `@theme/SearchBar` component instead: use `askAi` for the modal and `sidePanel` for the side panel.',
);
}
const askAi = docsearchRecord.askAi;
if (typeof askAi === 'string') {
throw new Error('`themeConfig.docsearch.askAi` must be an object with `assistantId`.');
@ -247,6 +268,12 @@ function assertNoRemovedKeys(themeConfig: ThemeConfig): void {
'`themeConfig.docsearch.askAi.sidePanel` was removed. Use `themeConfig.docsearch.sidePanel` instead.',
);
}
if (askAiRecord.tools !== undefined) {
throw new Error(
'`themeConfig.docsearch.askAi.tools` is not supported because Docusaurus removes function values when serializing theme config. Pass custom tools through a swizzled `@theme/SearchBar` component instead: use `askAi` for the modal and `sidePanel` for the side panel.',
);
}
}
function ensureSidePanelHasAskAi(themeConfig: ThemeConfig): void {

View file

@ -1,5 +1,7 @@
import { test, expect } from './fixtures';
const MAX_KEYBOARD_NAVIGATION_STEPS = 10;
test.describe('Start', () => {
test.beforeEach(async ({ docSearch }) => {
await docSearch.goto();
@ -89,9 +91,22 @@ test.describe('Search', () => {
await docSearch.typeQueryMatching();
await expect(docSearch.hits).toBeVisible();
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowUp');
const firstHitOption = page.locator('#docsearch-hits_docsearch-list').getByRole('option').first();
const firstHitOptionId = await firstHitOption.getAttribute('id');
expect(firstHitOptionId).not.toBeNull();
// Prompt suggestions precede document hits, but the number is not stable.
for (let index = 0; index < MAX_KEYBOARD_NAVIGATION_STEPS; index++) {
if ((await docSearch.input.getAttribute('aria-activedescendant')) === firstHitOptionId) {
break;
}
await page.keyboard.press('ArrowDown');
}
await expect(docSearch.input).toHaveAttribute('aria-activedescendant', firstHitOptionId!);
await expect(firstHitOption).toHaveAttribute('aria-selected', 'true');
await page.keyboard.press('Enter');
await expect(page).not.toHaveURL(initialURL, { timeout: 10000 });

View file

@ -1,9 +1,27 @@
/* eslint-disable react/react-in-jsx-scope */
import { DocSearchAI } from '@docsearch/react';
import { DocSearchAI, type ToolCalls } from '@docsearch/react';
import type { JSX } from 'react';
import type { DemoTheme } from '../App';
const customTools: ToolCalls = {
printConsoleMessage: {
render({ message: { output } }) {
if (!output) return '';
return output as string;
},
async onToolCall({ input, addToolOutput }) {
// eslint-disable-next-line no-console
console.log((input as any).message);
await addToolOutput({
output: 'Check your console for a nice message :)',
});
},
},
};
export default function BasicAskAI({ theme }: { theme: DemoTheme }): JSX.Element {
return (
<DocSearchAI
@ -13,6 +31,10 @@ export default function BasicAskAI({ theme }: { theme: DemoTheme }): JSX.Element
askAi={{
assistantId: 'ccdec697-e3fe-465b-a1c3-657e7bf18aef',
suggestedQuestions: true,
tools: customTools,
promptSuggestions: {
indexName: 'docsearch-markdown_prompt_suggestions',
},
}}
facets={[
{ key: 'language', label: 'Language' },
@ -22,27 +44,7 @@ export default function BasicAskAI({ theme }: { theme: DemoTheme }): JSX.Element
insights={true}
translations={{ button: { buttonText: 'Search with Ask AI' } }}
theme={theme}
tools={{
printConsoleMessage: {
render({ message: { output } }) {
if (!output) return '';
return output as string;
},
async onToolCall({ input, addToolOutput }) {
// eslint-disable-next-line no-console
console.log((input as any).message);
await addToolOutput({
output: 'Check your console for a nice message :)',
});
},
},
}}
resultBadgeKey="type"
promptSuggestions={{
indexName: 'docsearch-markdown_prompt_suggestions',
}}
/>
);
}

View file

@ -127,6 +127,34 @@ export interface AgentStudioIndices {
searchControls?: AgentStudioSearchControls;
}
export interface Memory {
/**
* Determines whether or not to display the memory based tool calls.
*
* @default false
*/
enabled?: boolean;
/**
* The JWT used by the agent to know which user's memory to read.
*
* @see https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/user-authentication
*/
userToken?: string;
}
export interface PromptSuggestions {
/**
* The name of the index where the prompt suggestions are stored.
*/
indexName: string;
/**
* The number of prompt suggestions that are retrieved and displayed.
*
* @default 3
*/
hitsPerPage?: number;
}
export interface DocSearchAskAi {
/**
* The index name to use for the Ask AI feature. Your assistant will search for relevant documents.
@ -167,34 +195,32 @@ export interface DocSearchAskAi {
* List of dynamic indices for the Agent Studio search tool to use.
*/
indices?: AgentStudioIndices[];
}
export interface Memory {
/**
* Determines whether or not to display the memory based tool calls.
* Use custom tools driven by Agent Studio.
*
* @default false
*/
enabled?: boolean;
* For best performance, memoize this object with `useMemo` or define it
* outside the component. Inline object literals will be recreated every
* render but will not affect correctness.
**/
tools?: ToolCalls;
/**
* The JWT used by the agent to know which user's memory to read.
* Configuration for the Agent Studio memory feature.
*
* @see https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/user-authentication
*/
userToken?: string;
}
export interface PromptSuggestions {
/**
* The name of the index where the prompt suggestions are stored.
*/
indexName: string;
/**
* The number of prompt suggestions that are retrieved and displayed.
* @see https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/memory/overview
*
* @default 3
* @example
* { enabled: true, userToken: '{{SERVER_GENERATED_JWT_TOKEN}}' }
*/
hitsPerPage?: number;
memory?: Memory;
/**
* Enables and configures prompt suggestions that are displayed during keyword search.
*
* @see https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/integration#prompt-suggestions
*
* @example
* { indexName: 'docsearch-markdown_prompt_suggestions', hitsPerPage: 1 }
*/
promptSuggestions?: PromptSuggestions;
}
export interface DocSearchAIProps extends DocSearchProps {
@ -209,27 +235,6 @@ export interface DocSearchAIProps extends DocSearchProps {
* Useful to route Ask AI into a different UI (e.g. `@docsearch/sidepanel-js`) without flicker.
*/
interceptAskAiEvent?: (initialMessage: InitialAskAiMessage) => boolean | void;
/**
* Use custom tools driven by Agent Studio.
*
* For best performance, memoize this object with `useMemo` or define it
* outside the component. Inline object literals will be recreated every
* render but will not affect correctness.
**/
tools?: ToolCalls;
/**
* Configuration for the Agent Studio memory feature.
*/
memory?: Memory;
/**
* Enables and configures prompt suggestions that are displayed during keyword search.
*
* @see https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/integration#prompt-suggestions
*
* @example
* { indexName: 'docsearch-markdown_prompt_suggestions', hitsPerPage: 1 }
*/
promptSuggestions?: PromptSuggestions;
}
function DocSearchAIComponent(props: DocSearchAIProps, ref: React.ForwardedRef<DocSearchRef>): JSX.Element {
@ -280,3 +285,5 @@ export function DocSearchAIInner(props: DocSearchAIProps): JSX.Element {
</>
);
}
export type { ToolCalls } from './types/AskiAi';

View file

@ -86,8 +86,6 @@ export function DocSearchAskAiModal({
searchParameters,
facets,
isHybridModeSupported = false,
tools = EMPTY_TOOLS,
promptSuggestions,
...props
}: DocSearchAskAiModalProps): JSX.Element {
const {
@ -133,7 +131,8 @@ export function DocSearchAskAiModal({
searchClient,
suggestedQuestionsEnabled: askAiConfig?.suggestedQuestions,
});
const memoryEnabled = props.memory?.enabled ?? false;
const memoryEnabled = askAiConfig?.memory?.enabled ?? false;
const tools = askAiConfig?.tools ?? EMPTY_TOOLS;
const indexes = React.useMemo(
() =>
@ -193,7 +192,7 @@ export function DocSearchAskAiModal({
indexName: askAiConfig?.indexName || defaultIndexName,
searchParameters: askAiSearchParameters,
tools,
memory: props.memory,
memory: askAiConfig?.memory,
indices: askAiConfig?.indices,
});
@ -376,7 +375,7 @@ export function DocSearchAskAiModal({
? buildAskAiActionSources({
query,
handleSelectAskAiQuestion,
promptSuggestionsOptions: promptSuggestions,
promptSuggestionsOptions: askAiConfig?.promptSuggestions,
searchClient,
})
: Promise.resolve([]);

View file

@ -71,6 +71,11 @@ export type DocSearchSidepanelProps = DocSearchCallbacks & {
tools?: ToolCalls;
/**
* Configuration for the Agent Studio memory feature.
*
* @see https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/memory/overview
*
* @example
* { enabled: true, userToken: '{{SERVER_GENERATED_JWT_TOKEN}}' }
*/
memory?: Memory;
/**

View file

@ -62,7 +62,7 @@ export type SidepanelTranslations = Partial<{
logo: AlgoliaLogoTranslations;
}>;
export type SidepanelProps = {
export type SidepanelProps = Pick<DocSearchSidepanelProps, 'indices' | 'memory' | 'tools'> & {
/**
* Variant of the Sidepanel positioning.
*
@ -120,7 +120,7 @@ export type SidepanelProps = {
keyboardShortcuts?: SidepanelShortcuts;
};
type Props = Omit<DocSearchSidepanelProps, 'button' | 'panel'> &
type Props = Omit<DocSearchSidepanelProps, 'button' | 'indices' | 'memory' | 'panel' | 'tools'> &
SidepanelProps &
SidepanelSearchParameters & {
isOpen?: boolean;

View file

@ -348,7 +348,6 @@ describe('api', () => {
render(
<DocSearchAI
promptSuggestions={{ indexName: 'prompt-suggestions' }}
transformSearchClient={(searchClient) => ({
...searchClient,
search: promptSuggestionsSearch,
@ -361,6 +360,12 @@ describe('api', () => {
},
},
}}
askAi={{
assistantId: '123',
promptSuggestions: {
indexName: 'prompt-suggestions',
},
}}
/>,
);

View file

@ -53,6 +53,33 @@ Use `themeConfig.docsearch` as the only adapter configuration key.
The adapter doesn't read `themeConfig.algolia`, which avoids built-in Docusaurus search-theme validation conflicts when you want newer DocSearch options like Agent Studio and the sidepanel.
### Client Side Tools
Docusaurus serializes `themeConfig` for the browser and removes function values. Configure `askAi.tools` and `sidePanel.tools` in a swizzled `@theme/SearchBar` component instead of `themeConfig.docsearch`:
```tsx title="src/theme/SearchBar/index.tsx"
import type { ToolCalls } from '@docsearch/react';
import SearchBar from '@theme-original/SearchBar';
import type { ReactNode } from 'react';
const tools: ToolCalls = {
logMessage: {
render: () => 'Tool completed.',
},
};
export default function SearchBarWithTools(): ReactNode {
return (
<SearchBar
askAi={{ assistantId: 'YOUR_ASSISTANT_ID', tools }}
sidePanel={{ tools }}
/>
);
}
```
Pass `tools` through `askAi` to enable them in the modal, `sidePanel` to enable them in the side panel, or both for both interfaces. Include any other Ask AI or side-panel options when overriding the corresponding object.
## Search Page
The adapter ships a full search page (enabled by default at `/search`) with faceted filtering, an accessible "Load more" pagination, recent searches, and "Browse by section" shortcuts. Disable it with `searchPage: false`, or change its path with `searchPage: { path: 'search' }`.

View file

@ -73,6 +73,9 @@ export default {
indices: [{ name: 'docsearch' }],
askAi: {
assistantId: 'ccdec697-e3fe-465b-a1c3-657e7bf18aef',
promptSuggestions: {
indexName: 'docsearch-markdown_prompt_suggestions',
},
},
sidePanel: true,
contextualSearch: true,