diff --git a/src/client/theme-default/styles/docsearch.css b/src/client/theme-default/styles/docsearch.css
new file mode 100644
index 000000000..df9e8e64b
--- /dev/null
+++ b/src/client/theme-default/styles/docsearch.css
@@ -0,0 +1,120 @@
+@import '@docsearch/css/dist/style.css';
+@import '@docsearch/css/dist/sidepanel.css';
+
+#vp-docsearch,
+#vp-docsearch-sidepanel,
+.DocSearch-SidepanelButton {
+ display: none;
+}
+
+:root:root {
+ --docsearch-actions-height: auto;
+ --docsearch-actions-width: auto;
+ --docsearch-background-color: var(--vp-c-bg-soft);
+ --docsearch-container-background: var(--vp-backdrop-bg-color);
+ --docsearch-dropdown-menu-background: var(--vp-c-bg-elv);
+ --docsearch-dropdown-menu-item-hover-background: var(--vp-c-default-soft);
+ --docsearch-focus-color: var(--vp-c-brand-1);
+ --docsearch-footer-background: var(--vp-c-bg-alt);
+ --docsearch-highlight-color: var(--vp-c-brand-1);
+ --docsearch-hit-background: var(--vp-c-bg);
+ --docsearch-hit-color: var(--vp-c-text-1);
+ --docsearch-hit-highlight-color: var(--vp-c-brand-soft);
+ --docsearch-icon-color: var(--vp-c-text-2);
+ --docsearch-key-background: var(--vp-code-bg);
+ --docsearch-modal-background: var(--vp-c-bg-soft);
+ --docsearch-muted-color: var(--vp-c-text-2);
+ --docsearch-primary-color: var(--vp-c-brand-1);
+ --docsearch-searchbox-background: var(--vp-c-bg-alt);
+ --docsearch-searchbox-focus-background: transparent;
+ --docsearch-secondary-text-color: var(--vp-c-text-2);
+ --docsearch-sidepanel-accent-muted: var(--vp-c-text-3);
+ --docsearch-sidepanel-text-base: var(--vp-c-text-1);
+ --docsearch-soft-muted-color: var(--vp-c-default-soft);
+ --docsearch-soft-primary-color: var(--vp-c-brand-soft);
+ --docsearch-subtle-color: var(--vp-c-divider);
+ --docsearch-success-color: var(--vp-c-brand-soft);
+ --docsearch-text-color: var(--vp-c-text-1);
+}
+
+:root.dark {
+ --docsearch-modal-shadow: none;
+}
+
+.DocSearch-AskAiScreen-RelatedSources-Item-Link {
+ padding: 8px 12px 8px 10px;
+}
+
+.DocSearch-AskAiScreen-RelatedSources-Item-Link svg {
+ width: 16px;
+ height: 16px;
+}
+
+.DocSearch-AskAiScreen-RelatedSources-Title {
+ padding-bottom: 0;
+ font-size: 12px;
+}
+
+.DocSearch-Clear {
+ padding-right: 6px;
+}
+
+.DocSearch-Commands-Key {
+ padding: 4px;
+ border: 1px solid var(--docsearch-subtle-color);
+ border-radius: 4px;
+}
+
+.DocSearch-Hit a:focus-visible {
+ outline: 2px solid var(--docsearch-focus-color);
+}
+
+.DocSearch-Logo [class^='cls-'] {
+ fill: currentColor;
+}
+
+.DocSearch-Markdown-Content code {
+ padding: 0.2em 0.4em;
+}
+
+.DocSearch-Menu-content {
+ margin-top: -4px;
+ padding: 6px;
+ border: 1px solid var(--vp-c-divider);
+ border-radius: 6px;
+ box-shadow: var(--vp-shadow-2);
+}
+
+.DocSearch-Menu-item {
+ border-radius: 4px;
+}
+
+.DocSearch-SearchBar + .DocSearch-Footer {
+ border-top-color: transparent;
+}
+
+.DocSearch-Sidepanel-Prompt--form {
+ border-color: var(--docsearch-subtle-color);
+ transition: border-color 0.2s;
+}
+
+.DocSearch-Sidepanel-Prompt--submit {
+ background-color: var(--docsearch-soft-primary-color);
+ color: var(--docsearch-primary-color);
+}
+
+.DocSearch-Sidepanel-Prompt--submit:hover {
+ background-color: var(--vp-button-brand-hover-bg);
+ color: var(--vp-button-brand-text);
+}
+
+.DocSearch-Sidepanel-Prompt--submit:disabled,
+.DocSearch-Sidepanel-Prompt--submit[aria-disabled='true'] {
+ background-color: var(--docsearch-soft-muted-color);
+ color: var(--docsearch-muted-color);
+}
+
+.DocSearch-Title {
+ font-size: revert;
+ line-height: revert;
+}
diff --git a/src/client/theme-default/styles/icons.css b/src/client/theme-default/styles/icons.css
index 5297cab00..9bc867e53 100644
--- a/src/client/theme-default/styles/icons.css
+++ b/src/client/theme-default/styles/icons.css
@@ -74,7 +74,10 @@
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2c-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z'/%3E%3C/svg%3E");
}
.vpi-search {
- --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.6'%3E%3Cpath d='m21 21l-4.34-4.34'/%3E%3Ccircle cx='11' cy='11' r='8' stroke-width='1.4'/%3E%3C/g%3E%3C/svg%3E");
+ --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m21 21l-4.34-4.34'/%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3C/g%3E%3C/svg%3E");
+}
+.vpi-sparkles {
+ --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.8'%3E%3Cpath d='M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594zM20 2v4m2-2h-4'/%3E%3Ccircle cx='4' cy='20' r='2'/%3E%3C/g%3E%3C/svg%3E");
}
.vpi-layout-list {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='7' height='7' x='3' y='3' rx='1'/%3E%3Crect width='7' height='7' x='3' y='14' rx='1'/%3E%3Cpath d='M14 4h7m-7 5h7m-7 6h7m-7 5h7'/%3E%3C/g%3E%3C/svg%3E");
diff --git a/src/client/theme-default/support/docsearch.ts b/src/client/theme-default/support/docsearch.ts
new file mode 100644
index 000000000..1df909937
--- /dev/null
+++ b/src/client/theme-default/support/docsearch.ts
@@ -0,0 +1,253 @@
+import type { DefaultTheme } from 'vitepress/theme'
+import type { DocSearchAskAi } from '../../../../types/docsearch'
+import { isObject } from '../../shared'
+
+export type FacetFilter = string | string[] | FacetFilter[]
+
+export interface ValidatedCredentials {
+ valid: boolean
+ appId?: string
+ apiKey?: string
+ indexName?: string
+}
+
+export type DocSearchMode = 'auto' | 'sidePanel' | 'hybrid' | 'modal'
+
+export interface ResolvedMode {
+ mode: DocSearchMode
+ showKeywordSearch: boolean
+ useSidePanel: boolean
+}
+
+/**
+ * Resolves the effective mode based on config and available features.
+ *
+ * - 'auto': infer hybrid vs sidePanel-only from provided config
+ * - 'sidePanel': force sidePanel-only even if keyword search is configured
+ * - 'hybrid': force hybrid (error if keyword search is not configured)
+ * - 'modal': force modal even if sidePanel is configured
+ */
+export function resolveMode(
+ options: Pick<
+ DefaultTheme.AlgoliaSearchOptions,
+ 'appId' | 'apiKey' | 'indexName' | 'askAi' | 'mode'
+ >
+): ResolvedMode {
+ const mode = options.mode ?? 'auto'
+ const hasKeyword = hasKeywordSearch(options)
+ const askAi = options.askAi
+ const hasSidePanelConfig = Boolean(
+ askAi && typeof askAi === 'object' && askAi.sidePanel
+ )
+
+ switch (mode) {
+ case 'sidePanel':
+ // Force sidePanel-only - hide keyword search
+ return {
+ mode,
+ showKeywordSearch: false,
+ useSidePanel: true
+ }
+
+ case 'hybrid':
+ // Force hybrid - keyword search must be configured
+ if (!hasKeyword) {
+ console.error(
+ '[vitepress] mode: "hybrid" requires keyword search credentials (appId, apiKey, indexName).'
+ )
+ }
+ return {
+ mode,
+ showKeywordSearch: hasKeyword,
+ useSidePanel: true
+ }
+
+ case 'modal':
+ // Force modal - don't use sidepanel for askai, even if configured
+ return {
+ mode,
+ showKeywordSearch: hasKeyword,
+ useSidePanel: false
+ }
+
+ case 'auto':
+ default:
+ // Auto-detect based on config
+ return {
+ mode: 'auto',
+ showKeywordSearch: hasKeyword,
+ useSidePanel: hasSidePanelConfig
+ }
+ }
+}
+
+export function hasKeywordSearch(
+ options: Pick<
+ DefaultTheme.AlgoliaSearchOptions,
+ 'appId' | 'apiKey' | 'indexName'
+ >
+): boolean {
+ return Boolean(options.appId && options.apiKey && options.indexName)
+}
+
+export function hasAskAi(
+ askAi: DefaultTheme.AlgoliaSearchOptions['askAi']
+): boolean {
+ if (!askAi) return false
+ if (typeof askAi === 'string') return askAi.length > 0
+ return Boolean(askAi.assistantId)
+}
+
+/**
+ * Removes existing `lang:` filters and appends `lang:${lang}`.
+ * Handles both flat arrays and nested arrays (for OR conditions).
+ */
+export function mergeLangFacetFilters(
+ rawFacetFilters: FacetFilter | FacetFilter[] | undefined,
+ lang: string
+): FacetFilter[] {
+ const input = Array.isArray(rawFacetFilters)
+ ? rawFacetFilters
+ : rawFacetFilters
+ ? [rawFacetFilters]
+ : []
+
+ const filtered = input
+ .map((filter) => {
+ if (Array.isArray(filter)) {
+ // Handle nested arrays (OR conditions)
+ return filter.filter(
+ (f) => typeof f === 'string' && !f.startsWith('lang:')
+ )
+ }
+ return filter
+ })
+ .filter((filter) => {
+ if (typeof filter === 'string') {
+ return !filter.startsWith('lang:')
+ }
+ // Keep nested arrays with remaining filters
+ return Array.isArray(filter) && filter.length > 0
+ })
+
+ return [...filtered, `lang:${lang}`]
+}
+
+/**
+ * Validates that required Algolia credentials are present.
+ */
+export function validateCredentials(
+ options: Pick<
+ DefaultTheme.AlgoliaSearchOptions,
+ 'appId' | 'apiKey' | 'indexName'
+ >
+): ValidatedCredentials {
+ const appId = options.appId
+ const apiKey = options.apiKey
+ const indexName = options.indexName
+
+ return {
+ valid: Boolean(appId && apiKey && indexName),
+ appId,
+ apiKey,
+ indexName
+ }
+}
+
+/**
+ * Builds Ask AI configuration from various input formats.
+ */
+export function buildAskAiConfig(
+ askAiProp: NonNullable,
+ options: DefaultTheme.AlgoliaSearchOptions,
+ lang: string
+): DocSearchAskAi {
+ const isAskAiString = typeof askAiProp === 'string'
+
+ const askAiSearchParameters =
+ !isAskAiString && askAiProp.searchParameters
+ ? { ...askAiProp.searchParameters }
+ : undefined
+
+ // If Ask AI defines its own facetFilters, merge lang filtering into those.
+ // Otherwise, reuse the keyword search facetFilters so Ask AI follows the
+ // same language filtering behavior by default.
+ const askAiFacetFiltersSource =
+ askAiSearchParameters?.facetFilters ??
+ options.searchParameters?.facetFilters
+ const askAiFacetFilters = mergeLangFacetFilters(
+ askAiFacetFiltersSource as FacetFilter | FacetFilter[] | undefined,
+ lang
+ )
+
+ const mergedAskAiSearchParameters = {
+ ...askAiSearchParameters,
+ facetFilters: askAiFacetFilters.length ? askAiFacetFilters : undefined
+ }
+
+ const result: Record = {
+ ...(isAskAiString ? {} : askAiProp),
+ indexName: isAskAiString ? options.indexName : askAiProp.indexName,
+ apiKey: isAskAiString ? options.apiKey : askAiProp.apiKey,
+ appId: isAskAiString ? options.appId : askAiProp.appId,
+ assistantId: isAskAiString ? askAiProp : askAiProp.assistantId
+ }
+
+ // Keep `searchParameters` undefined unless it has at least one key.
+ if (Object.values(mergedAskAiSearchParameters).some((v) => v != null)) {
+ result.searchParameters = mergedAskAiSearchParameters
+ }
+
+ return result
+}
+
+/**
+ * Resolves Algolia search options for the given language,
+ * merging in locale-specific overrides and language facet filters.
+ */
+export function resolveOptionsForLanguage(
+ options: DefaultTheme.AlgoliaSearchOptions,
+ localeIndex: string,
+ lang: string
+): DefaultTheme.AlgoliaSearchOptions {
+ options = deepMerge(options, options.locales?.[localeIndex] || {})
+
+ const facetFilters = mergeLangFacetFilters(
+ options.searchParameters?.facetFilters,
+ lang
+ )
+ const askAi = options.askAi
+ ? buildAskAiConfig(options.askAi, options, lang)
+ : undefined
+
+ return {
+ ...options,
+ searchParameters: { ...options.searchParameters, facetFilters },
+ askAi
+ }
+}
+
+function deepMerge(target: T, source: Partial): T {
+ const result = { ...target } as any
+
+ for (const key in source) {
+ const value = source[key]
+ if (value === undefined) continue
+
+ // special case: replace entirely
+ if (key === 'searchParameters') {
+ result[key] = value
+ continue
+ }
+
+ // deep-merge only plain objects; arrays are replaced entirely
+ if (isObject(value) && isObject(result[key])) {
+ result[key] = deepMerge(result[key], value)
+ } else {
+ result[key] = value
+ }
+ }
+
+ delete result.locales
+ return result
+}
diff --git a/src/client/theme-default/support/reactivity.ts b/src/client/theme-default/support/reactivity.ts
new file mode 100644
index 000000000..4fbcd9ad0
--- /dev/null
+++ b/src/client/theme-default/support/reactivity.ts
@@ -0,0 +1,14 @@
+import { type ComputedRef, computed } from 'vue'
+
+export function smartComputed(
+ getter: () => T,
+ comparator = (oldValue: T, newValue: T) =>
+ JSON.stringify(oldValue) === JSON.stringify(newValue)
+): ComputedRef {
+ return computed((oldValue) => {
+ const newValue = getter()
+ return oldValue === undefined || !comparator(oldValue, newValue)
+ ? newValue
+ : oldValue
+ })
+}
diff --git a/src/node/plugin.ts b/src/node/plugin.ts
index 7faeb1828..3d9d64a5c 100644
--- a/src/node/plugin.ts
+++ b/src/node/plugin.ts
@@ -41,7 +41,7 @@ declare module 'vite' {
const themeRE = /(?:^|\/)\.vitepress\/theme\/index\.(m|c)?(j|t)s$/
const startsWithThemeRE = /^@theme(?:\/|$)/
-const docsearchRE = /\/@docsearch\/css\/dist\/style.css(?:$|\?)/
+const docsearchRE = /\/docsearch\.css(?:$|\?)/
const hashRE = /\.([-\w]+)\.js$/
const staticInjectMarkerRE = /\bcreateStaticVNode\((?:(".*")|('.*')), (\d+)\)/g
@@ -147,7 +147,7 @@ export async function createVitePressPlugin(
'vitepress > @vue/devtools-api',
'vitepress > @vueuse/core'
].filter((d) => d != null),
- exclude: ['@docsearch/js', 'vitepress']
+ exclude: ['@docsearch/js', '@docsearch/sidepanel-js', 'vitepress']
},
server: {
fs: {
diff --git a/src/node/siteConfig.ts b/src/node/siteConfig.ts
index e27bbce1b..210bc3d01 100644
--- a/src/node/siteConfig.ts
+++ b/src/node/siteConfig.ts
@@ -39,8 +39,9 @@ export interface TransformPageContext {
siteConfig: SiteConfig
}
-export interface UserConfig
- extends LocaleSpecificConfig {
+export interface UserConfig<
+ ThemeConfig = any
+> extends LocaleSpecificConfig {
extends?: RawConfigExports
base?: string
@@ -207,27 +208,26 @@ export interface UserConfig
| AdditionalConfigLoader
}
-export interface SiteConfig
- extends Pick<
- UserConfig,
- | 'markdown'
- | 'vue'
- | 'vite'
- | 'shouldPreload'
- | 'router'
- | 'mpa'
- | 'metaChunk'
- | 'lastUpdated'
- | 'ignoreDeadLinks'
- | 'cleanUrls'
- | 'useWebFonts'
- | 'postRender'
- | 'buildEnd'
- | 'transformHead'
- | 'transformHtml'
- | 'transformPageData'
- | 'sitemap'
- > {
+export interface SiteConfig extends Pick<
+ UserConfig,
+ | 'markdown'
+ | 'vue'
+ | 'vite'
+ | 'shouldPreload'
+ | 'router'
+ | 'mpa'
+ | 'metaChunk'
+ | 'lastUpdated'
+ | 'ignoreDeadLinks'
+ | 'cleanUrls'
+ | 'useWebFonts'
+ | 'postRender'
+ | 'buildEnd'
+ | 'transformHead'
+ | 'transformHtml'
+ | 'transformPageData'
+ | 'sitemap'
+> {
root: string
srcDir: string
site: SiteData
diff --git a/template/.vitepress/theme/style.css b/template/.vitepress/theme/style.css
index 9b73b4116..f0339617f 100644
--- a/template/.vitepress/theme/style.css
+++ b/template/.vitepress/theme/style.css
@@ -128,14 +128,6 @@
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
}
-
-/**
- * Component: Algolia
- * -------------------------------------------------------------------------- */
-
-.DocSearch {
- --docsearch-primary-color: var(--vp-c-brand-1) !important;
-}
<% } else { %>
html {
font-family: Arial, Helvetica;
diff --git a/types/default-theme.d.ts b/types/default-theme.d.ts
index 03eb82f23..919f9d1fa 100644
--- a/types/default-theme.d.ts
+++ b/types/default-theme.d.ts
@@ -130,11 +130,6 @@ export namespace DefaultTheme {
| { provider: 'local'; options?: LocalSearchOptions }
| { provider: 'algolia'; options: AlgoliaSearchOptions }
- /**
- * @deprecated Use `search` instead.
- */
- algolia?: AlgoliaSearchOptions
-
/**
* The carbon ads options. Leave it undefined to disable the ads feature.
*/
@@ -402,6 +397,11 @@ export namespace DefaultTheme {
* `@docsearch/react/dist/esm/DocSearch.d.ts`
*/
export interface AlgoliaSearchOptions extends DocSearchProps {
+ /**
+ * Locale-specific overrides for Algolia search options.
+ * These options will be deeply merged with the root options,
+ * except for `searchParameters`, which is fully replaced.
+ */
locales?: Record>
}
diff --git a/types/docsearch.d.ts b/types/docsearch.d.ts
index c4c91823f..9fbc4cab9 100644
--- a/types/docsearch.d.ts
+++ b/types/docsearch.d.ts
@@ -1,205 +1,63 @@
-export interface DocSearchProps {
- appId: string
- apiKey: string
- indexName: string
- placeholder?: string
- searchParameters?: SearchOptions
- disableUserPersonalization?: boolean
- initialQuery?: string
- insights?: boolean
- translations?: DocSearchTranslations
- askAi?: DocSearchAskAi | string
-}
-
-export interface SearchOptions {
- query?: string
- similarQuery?: string
- facetFilters?: string | string[]
- optionalFilters?: string | string[]
- numericFilters?: string | string[]
- tagFilters?: string | string[]
- sumOrFiltersScores?: boolean
- filters?: string
- page?: number
- hitsPerPage?: number
- offset?: number
- length?: number
- attributesToHighlight?: string[]
- attributesToSnippet?: string[]
- attributesToRetrieve?: string[]
- highlightPreTag?: string
- highlightPostTag?: string
- snippetEllipsisText?: string
- restrictHighlightAndSnippetArrays?: boolean
- facets?: string[]
- maxValuesPerFacet?: number
- facetingAfterDistinct?: boolean
- minWordSizefor1Typo?: number
- minWordSizefor2Typos?: number
- allowTyposOnNumericTokens?: boolean
- disableTypoToleranceOnAttributes?: string[]
- queryType?: 'prefixLast' | 'prefixAll' | 'prefixNone'
- removeWordsIfNoResults?: 'none' | 'lastWords' | 'firstWords' | 'allOptional'
- advancedSyntax?: boolean
- advancedSyntaxFeatures?: ('exactPhrase' | 'excludeWords')[]
- optionalWords?: string | string[]
- disableExactOnAttributes?: string[]
- exactOnSingleWordQuery?: 'attribute' | 'none' | 'word'
- alternativesAsExact?: (
- | 'ignorePlurals'
- | 'singleWordSynonym'
- | 'multiWordsSynonym'
- )[]
- enableRules?: boolean
- ruleContexts?: string[]
- distinct?: boolean | number
- analytics?: boolean
- analyticsTags?: string[]
- synonyms?: boolean
- replaceSynonymsInHighlight?: boolean
- minProximity?: number
- responseFields?: string[]
- maxFacetHits?: number
- percentileComputation?: boolean
- clickAnalytics?: boolean
- personalizationImpact?: number
- enablePersonalization?: boolean
- restrictSearchableAttributes?: string[]
- sortFacetValuesBy?: 'count' | 'alpha'
- typoTolerance?: boolean | 'min' | 'strict'
- aroundLatLng?: string
- aroundLatLngViaIP?: boolean
- aroundRadius?: number | 'all'
- aroundPrecision?: number | { from: number; value: number }[]
- minimumAroundRadius?: number
- insideBoundingBox?: number[][]
- insidePolygon?: number[][]
- ignorePlurals?: boolean | string[]
- removeStopWords?: boolean | string[]
- naturalLanguages?: string[]
- getRankingInfo?: boolean
- userToken?: string
- enableABTest?: boolean
- decompoundQuery?: boolean
- relevancyStrictness?: number
-}
-
-export interface DocSearchTranslations {
- button?: ButtonTranslations
- modal?: ModalTranslations
-}
-
-export interface ButtonTranslations {
- buttonText?: string
- buttonAriaLabel?: string
-}
-
-export interface ModalTranslations extends ScreenStateTranslations {
- searchBox?: SearchBoxTranslations
- footer?: FooterTranslations
-}
-
-export interface ScreenStateTranslations {
- errorScreen?: ErrorScreenTranslations
- startScreen?: StartScreenTranslations
- resultsScreen?: ResultsScreenTranslations
- noResultsScreen?: NoResultsScreenTranslations
- askAiScreen?: AskAiScreenTranslations
-}
-
-export interface SearchBoxTranslations {
- clearButtonTitle?: string
- clearButtonAriaLabel?: string
- closeButtonText?: string
- closeButtonAriaLabel?: string
- placeholderText?: string
- placeholderTextAskAi?: string
- searchInputLabel?: string
- placeholderTextAskAiStreaming?: string
- backToKeywordSearchButtonText?: string
- backToKeywordSearchButtonAriaLabel?: string
-}
-
-export interface FooterTranslations {
- selectText?: string
- submitQuestionText?: string
- selectKeyAriaLabel?: string
- navigateText?: string
- navigateUpKeyAriaLabel?: string
- backToSearchText?: string
- navigateDownKeyAriaLabel?: string
- closeText?: string
- closeKeyAriaLabel?: string
- poweredByText?: string
-}
-
-export interface ErrorScreenTranslations {
- titleText?: string
- helpText?: string
-}
-
-export interface StartScreenTranslations {
- recentSearchesTitle?: string
- noRecentSearchesText?: string
- saveRecentSearchButtonTitle?: string
- removeRecentSearchButtonTitle?: string
- favoriteSearchesTitle?: string
- removeFavoriteSearchButtonTitle?: string
- recentConversationsTitle?: string
- removeRecentConversationButtonTitle?: string
-}
-
-export interface ResultsScreenTranslations {
- askAiPlaceholder?: string
-}
-
-export interface NoResultsScreenTranslations {
- noResultsText?: string
- suggestedQueryText?: string
- reportMissingResultsText?: string
- reportMissingResultsLinkText?: string
-}
-
-export interface AskAiScreenTranslations {
- disclaimerText?: string
- relatedSourcesText?: string
- thinkingText?: string
- copyButtonText?: string
- copyButtonCopiedText?: string
- copyButtonTitle?: string
- likeButtonTitle?: string
- dislikeButtonTitle?: string
- thanksForFeedbackText?: string
- preToolCallText?: string
- duringToolCallText?: string
- afterToolCallText?: string
- aggregatedToolCallText?: string
-}
-
-export interface DocSearchAskAi {
+import { type DocSearchProps as DocSearchPropsJS } from '@docsearch/js'
+import { type SidepanelProps as SidepanelPropsBase } from '@docsearch/sidepanel-js'
+
+export type DocSearchProps = Partial<
+ Pick<
+ DocSearchPropsJS,
+ | 'appId'
+ | 'apiKey'
+ | 'placeholder'
+ | 'maxResultsPerGroup'
+ | 'disableUserPersonalization'
+ | 'initialQuery'
+ | 'translations'
+ | 'recentSearchesLimit'
+ | 'recentSearchesWithFavoritesLimit'
+ >
+> & {
/**
- * The index name to use for the ask AI feature. Your assistant will search this index for relevant documents.
- * If not provided, the index name will be used.
+ * Name of the algolia index to query.
*/
indexName?: string
/**
- * The API key to use for the ask AI feature. Your assistant will use this API key to search the index.
- * If not provided, the API key will be used.
+ * Additional algolia search parameters to merge into each query.
*/
- apiKey?: string
+ searchParameters?: DocSearchPropsJS['searchParameters']
/**
- * The app ID to use for the ask AI feature. Your assistant will use this app ID to search the index.
- * If not provided, the app ID will be used.
+ * Insights client integration options to send analytics events.
*/
- appId?: string
+ insights?: boolean
/**
- * The assistant ID to use for the ask AI feature.
+ * Configuration or assistant id to enable ask ai mode. Pass a string assistant id or a full config object.
*/
- assistantId: string | null
+ askAi?: DocSearchAskAi | string
+ /**
+ * Ask AI side panel integration mode.
+ *
+ * - 'auto': infer hybrid vs sidePanel-only from provided config
+ * - 'sidePanel': force sidePanel-only even if keyword search is configured
+ * - 'hybrid': force hybrid (error if keyword search is not configured)
+ * - 'modal': force modal even if sidePanel is configured (ask ai in modal stays in modal)
+ *
+ * @default 'auto'
+ */
+ mode?: 'auto' | 'sidePanel' | 'hybrid' | 'modal'
+}
+
+export type DocSearchAskAi = Partial<
+ Exclude
+> & {
+ /**
+ * Ask AI side panel configuration.
+ */
+ sidePanel?: boolean | SidepanelProps
+}
+
+export type SidepanelProps = Partial<
+ Pick
+> & {
/**
- * The search parameters to use for the ask AI feature.
+ * Props specific to the Sidepanel panel.
*/
- searchParameters?: {
- facetFilters?: SearchOptions['facetFilters']
- }
+ panel?: Omit, 'portalContainer'>
}