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:
parent
23f3e6b657
commit
629fce7902
15 changed files with 382 additions and 117 deletions
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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([]);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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' }`.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue