pull/5402/merge
Paul Jankowski 4 days ago committed by GitHub
commit 5ba35d3fc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -3,6 +3,7 @@ import {
buildSidePanelProps, buildSidePanelProps,
hasAskAi, hasAskAi,
hasKeywordSearch, hasKeywordSearch,
mergeLangFilters,
mergeLangFacetFilters, mergeLangFacetFilters,
validateCredentials validateCredentials
} from 'client/theme-default/support/docsearch' } from 'client/theme-default/support/docsearch'
@ -41,13 +42,55 @@ describe('client/theme-default/support/docsearch', () => {
}) })
}) })
describe('mergeLangFilters', () => {
test('adds a lang filter when none is provided', () => {
expect(mergeLangFilters(undefined, 'en')).toBe('lang:en')
})
test('replaces a lang filter in an AND expression', () => {
expect(
mergeLangFilters('type:lvl AND lang:en-US AND content:*', 'en')
).toBe('(type:lvl AND lang:en AND content:*) AND lang:en')
})
test('replaces a lang filter in an OR group', () => {
expect(
mergeLangFilters('(type:lvl OR lang:en-US) AND content:*', 'en')
).toBe('((type:lvl OR lang:en) AND content:*) AND lang:en')
})
test('preserves top-level OR precedence', () => {
expect(mergeLangFilters('tag:a OR tag:b', 'en')).toBe(
'(tag:a OR tag:b) AND lang:en'
)
})
test('preserves nested groups containing only lang filters', () => {
expect(mergeLangFilters('type:a AND (lang:en OR lang:fr)', 'de')).toBe(
'(type:a AND (lang:de OR lang:de)) AND lang:de'
)
})
test('preserves negated lang filters', () => {
expect(mergeLangFilters('tag:a AND NOT lang:fr', 'en')).toBe(
'(tag:a AND NOT lang:fr) AND lang:en'
)
})
test('normalizes quoted lang filter values', () => {
expect(mergeLangFilters('lang:"en US" AND tag:a', 'en')).toBe(
'(lang:en AND tag:a) AND lang:en'
)
})
})
describe('hasKeywordSearch', () => { describe('hasKeywordSearch', () => {
test('returns true when all credentials are provided', () => { test('returns true when all credentials are provided', () => {
expect( expect(
hasKeywordSearch({ hasKeywordSearch({
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: 'index' indices: ['index']
}) })
).toBe(true) ).toBe(true)
}) })
@ -57,41 +100,48 @@ describe('client/theme-default/support/docsearch', () => {
hasKeywordSearch({ hasKeywordSearch({
appId: undefined, appId: undefined,
apiKey: 'key', apiKey: 'key',
indexName: 'index' indices: ['index']
}) })
).toBe(false) ).toBe(false)
expect( expect(
hasKeywordSearch({ hasKeywordSearch({
appId: 'app', appId: 'app',
apiKey: undefined, apiKey: undefined,
indexName: 'index' indices: ['index']
})
).toBe(false)
expect(
hasKeywordSearch({
appId: 'app',
apiKey: 'key',
indices: undefined
}) })
).toBe(false) ).toBe(false)
expect( expect(
hasKeywordSearch({ hasKeywordSearch({
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: undefined indices: []
}) })
).toBe(false) ).toBe(false)
}) })
}) })
describe('hasAskAi', () => { describe('hasAskAi', () => {
test('returns true for valid string assistantId', () => { test('returns true for valid string agentId', () => {
expect(hasAskAi('assistant123')).toBe(true) expect(hasAskAi('agent123')).toBe(true)
}) })
test('returns false for empty string assistantId', () => { test('returns false for empty string agentId', () => {
expect(hasAskAi('')).toBe(false) expect(hasAskAi('')).toBe(false)
}) })
test('returns true for object with assistantId', () => { test('returns true for object with agentId', () => {
expect(hasAskAi({ assistantId: 'assistant123' } as any)).toBe(true) expect(hasAskAi({ agentId: 'agent123' } as any)).toBe(true)
}) })
test('returns false for object without assistantId', () => { test('returns false for object without agentId', () => {
expect(hasAskAi({ assistantId: null } as any)).toBe(false) expect(hasAskAi({ agentId: null } as any)).toBe(false)
expect(hasAskAi({} as any)).toBe(false) expect(hasAskAi({} as any)).toBe(false)
}) })
@ -105,12 +155,12 @@ describe('client/theme-default/support/docsearch', () => {
const result = validateCredentials({ const result = validateCredentials({
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: 'index' indices: ['index']
}) })
expect(result.valid).toBe(true) expect(result.valid).toBe(true)
expect(result.appId).toBe('app') expect(result.appId).toBe('app')
expect(result.apiKey).toBe('key') expect(result.apiKey).toBe('key')
expect(result.indexName).toBe('index') expect(result.indices).toEqual(['index'])
}) })
test('invalidates incomplete credentials', () => { test('invalidates incomplete credentials', () => {
@ -118,87 +168,140 @@ describe('client/theme-default/support/docsearch', () => {
validateCredentials({ validateCredentials({
appId: undefined, appId: undefined,
apiKey: 'key', apiKey: 'key',
indexName: 'index' indices: ['index']
}).valid }).valid
).toBe(false) ).toBe(false)
}) })
}) })
describe('buildAskAiConfig', () => { describe('buildAskAiConfig', () => {
test('builds config from string assistantId', () => { test('builds config from string agentId', () => {
const result = buildAskAiConfig( const result = buildAskAiConfig(
'assistant123', 'agent123',
{ {
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: 'index' indices: ['index']
} as any, } as any,
'en' 'en'
) )
expect(result.assistantId).toBe('assistant123') expect(result.agentId).toBe('agent123')
expect(result.appId).toBe('app') expect(result.appId).toBe('app')
expect(result.apiKey).toBe('key') expect(result.apiKey).toBe('key')
expect(result.indexName).toBe('index') expect(result.indices).toBeUndefined()
}) })
test('builds config from object with overrides', () => { test('builds config from object with overrides', () => {
const result = buildAskAiConfig( const result = buildAskAiConfig(
{ {
assistantId: 'assistant123', agentId: 'agent123',
appId: 'custom-app', appId: 'custom-app',
apiKey: 'custom-key', apiKey: 'custom-key'
indexName: 'custom-index'
} as any, } as any,
{ {
appId: 'default-app', appId: 'default-app',
apiKey: 'default-key', apiKey: 'default-key',
indexName: 'default-index' indices: ['default-index']
} as any, } as any,
'en' 'en'
) )
expect(result.assistantId).toBe('assistant123') expect(result.agentId).toBe('agent123')
expect(result.appId).toBe('custom-app') expect(result.appId).toBe('custom-app')
expect(result.apiKey).toBe('custom-key') expect(result.apiKey).toBe('custom-key')
expect(result.indexName).toBe('custom-index') expect(result.indices).toBeUndefined()
}) })
test('merges facet filters with lang', () => { test('merges filters with lang by index', () => {
const result = buildAskAiConfig( const result = buildAskAiConfig(
{ {
assistantId: 'assistant123', agentId: 'agent123',
indices: ['ai_index'],
searchParameters: { searchParameters: {
facetFilters: ['tag:docs'] ai_index: {
filters: 'tag:docs'
}
} }
} as any, },
{ {
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: 'index' indices: ['index']
} as any, },
'en'
)
expect(result.searchParameters?.ai_index.filters).toBe(
'(tag:docs) AND lang:en'
)
})
test('adds lang filters for indices without search parameters', () => {
const result = buildAskAiConfig(
{
agentId: 'agent123',
indices: ['ai_index']
},
{
appId: 'app',
apiKey: 'key',
indices: ['index']
},
'en' 'en'
) )
expect(result.searchParameters?.facetFilters).toContain('tag:docs')
expect(result.searchParameters?.facetFilters).toContain('lang:en') expect(result.searchParameters).toEqual({
ai_index: {
filters: 'lang:en'
}
})
}) })
test('always adds lang facet filter to searchParameters', () => { test('merges configured indices with search parameter indices', () => {
const result = buildAskAiConfig( const result = buildAskAiConfig(
'assistant123', {
agentId: 'agent123',
indices: ['configured_index'],
searchParameters: {
parameter_index: {
distinct: false
}
}
},
{ {
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: 'index' indices: ['index']
},
'en'
)
expect(result.searchParameters).toEqual({
configured_index: {
filters: 'lang:en'
},
parameter_index: {
distinct: false,
filters: 'lang:en'
}
})
})
test('does not create index-specific search parameters for string config', () => {
const result = buildAskAiConfig(
'agent123',
{
appId: 'app',
apiKey: 'key',
indices: ['index']
} as any, } as any,
'en' 'en'
) )
expect(result.searchParameters?.facetFilters).toEqual(['lang:en']) expect(result.searchParameters).toBeUndefined()
}) })
test('preserves Agent Studio search parameters by index', () => { test('adds lang filters to search parameters by index', () => {
const result = buildAskAiConfig( const result = buildAskAiConfig(
{ {
assistantId: 'assistant123', agentId: 'agent123',
agentStudio: true,
searchParameters: { searchParameters: {
index: { index: {
distinct: false distinct: false
@ -208,7 +311,7 @@ describe('client/theme-default/support/docsearch', () => {
{ {
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: 'index', indices: ['index'],
searchParameters: { searchParameters: {
facetFilters: ['tag:docs'] facetFilters: ['tag:docs']
} }
@ -218,7 +321,8 @@ describe('client/theme-default/support/docsearch', () => {
expect(result.searchParameters).toEqual({ expect(result.searchParameters).toEqual({
index: { index: {
distinct: false distinct: false,
filters: 'lang:en'
} }
}) })
expect(result.searchParameters).not.toHaveProperty('facetFilters') expect(result.searchParameters).not.toHaveProperty('facetFilters')
@ -227,13 +331,12 @@ describe('client/theme-default/support/docsearch', () => {
test('does not add legacy facet filters to Agent Studio config', () => { test('does not add legacy facet filters to Agent Studio config', () => {
const result = buildAskAiConfig( const result = buildAskAiConfig(
{ {
assistantId: 'assistant123', agentId: 'agent123'
agentStudio: true
} as any, } as any,
{ {
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: 'index', indices: ['index'],
searchParameters: { searchParameters: {
facetFilters: ['tag:docs'] facetFilters: ['tag:docs']
} }
@ -249,15 +352,13 @@ describe('client/theme-default/support/docsearch', () => {
test('passes resolved Ask AI options to the side panel', () => { test('passes resolved Ask AI options to the side panel', () => {
const result = buildSidePanelProps( const result = buildSidePanelProps(
{ {
assistantId: 'assistant123', agentId: 'agent123',
agentStudio: true,
searchParameters: { searchParameters: {
index: { index: {
facetFilters: ['lang:en'] facetFilters: ['lang:en']
} }
}, },
suggestedQuestions: true, suggestedQuestions: true,
useStagingEnv: true,
sidePanel: { sidePanel: {
button: { button: {
variant: 'inline' variant: 'inline'
@ -271,7 +372,7 @@ describe('client/theme-default/support/docsearch', () => {
{ {
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: 'index' indices: ['index']
} as any } as any
) )
@ -279,16 +380,13 @@ describe('client/theme-default/support/docsearch', () => {
container: '#vp-docsearch-sidepanel', container: '#vp-docsearch-sidepanel',
appId: 'app', appId: 'app',
apiKey: 'key', apiKey: 'key',
indexName: 'index', agentId: 'agent123',
assistantId: 'assistant123',
agentStudio: true,
searchParameters: { searchParameters: {
index: { index: {
facetFilters: ['lang:en'] facetFilters: ['lang:en']
} }
}, },
suggestedQuestions: true, suggestedQuestions: true,
useStagingEnv: true,
button: { button: {
variant: 'inline' variant: 'inline'
}, },

@ -89,11 +89,13 @@ export default defineConfig({
options: { options: {
appId: '8J64VVRP8K', appId: '8J64VVRP8K',
apiKey: '52f578a92b88ad6abde815aae2b0ad7c', apiKey: '52f578a92b88ad6abde815aae2b0ad7c',
indexName: 'vitepress', indices: ['vitepress']
askAi: { // TODO: Update to use Agent Studio `agentId` once created.
assistantId: 'YaVSonfX5bS8', // For now the Ask AI functionality won't work as DocSearch v5 only support Agent Studio.
sidePanel: true // askAi: {
} // agentId: '',
// sidePanel: true
// }
} }
}, },

@ -208,7 +208,7 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...' indices: ['...']
} }
} }
} }
@ -226,10 +226,14 @@ You can use a config like this to use multilingual search:
</details> </details>
Refer [official Algolia docs](https://docsearch.algolia.com/docs/api#translations) to learn more about them. To quickly get started, you can also copy the translations used by this site from [our GitHub repo](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code). Refer [official Algolia docs](https://docsearch.algolia.com/docs/packages/react/api-reference#translations) to learn more about them. To quickly get started, you can also copy the translations used by this site from [our GitHub repo](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code).
### Algolia Ask AI Support {#ask-ai} ### Algolia Ask AI Support {#ask-ai}
::: note Note
As of `v5.0.0`, Ask AI has now moved to using Algolia's [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/quickstart) as it's backend. There is an Ask AI -> Agent Studio [migration guide](https://docsearch.algolia.com/docs/agent-studio/migrate-to-agent-studio) available to help with the transition.
:::
If you would like to include **Ask AI**, pass the `askAi` option (or any of the partial fields) inside `options`: If you would like to include **Ask AI**, pass the `askAi` option (or any of the partial fields) inside `options`:
```ts ```ts
@ -242,16 +246,15 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
// askAi: "YOUR-ASSISTANT-ID" // askAi: "YOUR-AGENT-ID"
// OR // OR
askAi: { askAi: {
// at minimum you must provide the assistantId you received from Algolia // at minimum you must provide the agentId you received from Algolia
assistantId: 'XXXYYY', agentId: 'XXXYYY',
// optional overrides if omitted, the top-level appId/apiKey/indexName values are reused // optional overrides if omitted, the top-level appId/apiKey values are reused
// apiKey: '...', // apiKey: '...',
// appId: '...', // appId: '...',
// indexName: '...'
} }
} }
} }
@ -265,7 +268,7 @@ If you want to default to keyword search and do not want to use Ask AI, omit the
### Ask AI Side Panel {#ask-ai-side-panel} ### Ask AI Side Panel {#ask-ai-side-panel}
DocSearch v4.5+ supports an optional **Ask AI side panel**. When enabled, it can be opened with **Ctrl/Cmd+I** by default. The [Sidepanel API Reference](https://docsearch.algolia.com/docs/sidepanel/api-reference) contains the full list of options. DocSearch v4.5+ supports an optional **Ask AI side panel**. When enabled, it can be opened with **Ctrl/Cmd+I** by default. The [Sidepanel API Reference](https://docsearch.algolia.com/docs/packages/sidepanel/api) contains the full list of options.
```ts ```ts
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
@ -277,9 +280,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
panel: { panel: {
variant: 'floating', // or 'inline' variant: 'floating', // or 'inline'
@ -314,9 +317,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
keyboardShortcuts: { keyboardShortcuts: {
'Ctrl/Cmd+I': false 'Ctrl/Cmd+I': false
@ -352,10 +355,9 @@ export default defineConfig({
options: { options: {
mode: 'sidePanel', mode: 'sidePanel',
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...',
sidePanel: true sidePanel: true
} }
} }

@ -198,7 +198,7 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...' indices: ['...']
} }
} }
} }
@ -216,10 +216,14 @@ Puedes utilizar una configuración como esta para utilizar la búsqueda multilin
</details> </details>
Consulta la [documentación oficial de Algolia](https://docsearch.algolia.com/docs/api#translations) para conocer más detalles. Para empezar rápidamente, también puedes copiar las traducciones usadas por este sitio desde [nuestro repositorio de GitHub](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code). Consulta la [documentación oficial de Algolia](https://docsearch.algolia.com/docs/packages/react/api-reference#translations) para conocer más detalles. Para empezar rápidamente, también puedes copiar las traducciones usadas por este sitio desde [nuestro repositorio de GitHub](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code).
### Algolia Ask AI Support {#ask-ai} ### Algolia Ask AI Support {#ask-ai}
::: note Nota
A partir de `v5.0.0`, Ask AI ahora utiliza [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/quickstart) de Algolia como backend. Hay disponible una [guía de migración](https://docsearch.algolia.com/docs/agent-studio/migrate-to-agent-studio) de Ask AI -> Agent Studio para facilitar la transición.
:::
Si deseas incluir **Ask AI**, pasa la opción `askAi` (o alguno de sus campos parciales) dentro de `options`: Si deseas incluir **Ask AI**, pasa la opción `askAi` (o alguno de sus campos parciales) dentro de `options`:
```ts ```ts
@ -232,16 +236,15 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
// askAi: "TU-ID-DE-ASISTENTE" // askAi: "TU-ID-DE-AGENTE"
// O // O
askAi: { askAi: {
// como mínimo debes proporcionar el assistantId que recibiste de Algolia // como mínimo debes proporcionar el agentId que recibiste de Algolia
assistantId: 'XXXYYY', agentId: 'XXXYYY',
// anulaciones opcionales — si se omiten, se reutilizan los valores appId/apiKey/indexName de nivel superior // anulaciones opcionales — si se omiten, se reutilizan los valores appId/apiKey de nivel superior
// apiKey: '...', // apiKey: '...',
// appId: '...', // appId: '...',
// indexName: '...'
} }
} }
} }
@ -255,7 +258,7 @@ Si prefieres solo la búsqueda por palabra clave y no la Ask AI, simplemente omi
### Panel lateral de Ask AI {#ask-ai-side-panel} ### Panel lateral de Ask AI {#ask-ai-side-panel}
DocSearch v4.5+ admite un **panel lateral de Ask AI** opcional. Cuando está habilitado, se puede abrir con **Ctrl/Cmd+I** por defecto. La [Referencia de API del Panel Lateral](https://docsearch.algolia.com/docs/sidepanel/api-reference) contiene la lista completa de opciones. DocSearch v4.5+ admite un **panel lateral de Ask AI** opcional. Cuando está habilitado, se puede abrir con **Ctrl/Cmd+I** por defecto. La [Referencia de API del Panel Lateral](https://docsearch.algolia.com/docs/packages/sidepanel/api) contiene la lista completa de opciones.
```ts ```ts
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
@ -267,9 +270,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
// Refleja la API de @docsearch/sidepanel-js SidepanelProps // Refleja la API de @docsearch/sidepanel-js SidepanelProps
panel: { panel: {
@ -299,9 +302,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
keyboardShortcuts: { keyboardShortcuts: {
'Ctrl/Cmd+I': false 'Ctrl/Cmd+I': false
@ -337,10 +340,9 @@ export default defineConfig({
options: { options: {
mode: 'sidePanel', mode: 'sidePanel',
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...',
sidePanel: true sidePanel: true
} }
} }

@ -198,7 +198,7 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...' indices: ['...']
} }
} }
} }
@ -216,10 +216,14 @@ export default defineConfig({
</details> </details>
برای اطلاعات بیشتر به [مستندات رسمی Algolia](https://docsearch.algolia.com/docs/api#translations) مراجعه کنید. برای شروع سریع‌تر، می‌توانید ترجمه‌های استفاده‌شده در این سایت را از [مخزن GitHub ما](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code) کپی کنید. برای اطلاعات بیشتر به [مستندات رسمی Algolia](https://docsearch.algolia.com/docs/packages/react/api-reference#translations) مراجعه کنید. برای شروع سریع‌تر، می‌توانید ترجمه‌های استفاده‌شده در این سایت را از [مخزن GitHub ما](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code) کپی کنید.
### پشتیبانی Algolia Ask AI {#ask-ai} ### پشتیبانی Algolia Ask AI {#ask-ai}
::: note نکته
از نسخه `v5.0.0`، Ask AI اکنون از [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/quickstart) شرکت Algolia به‌عنوان بک‌اند خود استفاده می‌کند. برای تسهیل این انتقال، [راهنمای مهاجرت Ask AI به Agent Studio](https://docsearch.algolia.com/docs/agent-studio/migrate-to-agent-studio) در دسترس است.
:::
برای فعال‌سازی **Ask AI** کافی است گزینه `askAi` را اضافه کنید: برای فعال‌سازی **Ask AI** کافی است گزینه `askAi` را اضافه کنید:
```ts ```ts
@ -232,16 +236,15 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
// askAi: "شناسه-دستیار-شما" // askAi: "شناسه-عامل-شما"
// یا // یا
askAi: { askAi: {
// حداقل باید assistantId دریافت شده از Algolia را ارائه کنید // حداقل باید agentId دریافت شده از Algolia را ارائه کنید
assistantId: 'XXXYYY', agentId: 'XXXYYY',
// بازنویسی های اختیاری — اگر حذف شوند، مقادیر appId/apiKey/indexName سطح بالا دوباره استفاده می شوند // بازنویسی های اختیاری — اگر حذف شوند، مقادیر appId/apiKey سطح بالا دوباره استفاده می شوند
// apiKey: '...', // apiKey: '...',
// appId: '...', // appId: '...',
// indexName: '...'
} }
} }
} }
@ -255,7 +258,7 @@ export default defineConfig({
### پنل کناری Ask AI {#ask-ai-side-panel} ### پنل کناری Ask AI {#ask-ai-side-panel}
DocSearch v4.5+ از **پنل کناری Ask AI** اختیاری پشتیبانی می‌کند. وقتی فعال باشد، به طور پیش‌فرض می‌توان آن را با **Ctrl/Cmd+I** باز کرد. [مرجع API پنل کناری](https://docsearch.algolia.com/docs/sidepanel/api-reference) شامل لیست کامل گزینه‌ها است. DocSearch v4.5+ از **پنل کناری Ask AI** اختیاری پشتیبانی می‌کند. وقتی فعال باشد، به طور پیش‌فرض می‌توان آن را با **Ctrl/Cmd+I** باز کرد. [مرجع API پنل کناری](https://docsearch.algolia.com/docs/packages/sidepanel/api) شامل لیست کامل گزینه‌ها است.
```ts ```ts
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
@ -267,9 +270,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
// آینه API @docsearch/sidepanel-js SidepanelProps // آینه API @docsearch/sidepanel-js SidepanelProps
panel: { panel: {
@ -299,9 +302,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
keyboardShortcuts: { keyboardShortcuts: {
'Ctrl/Cmd+I': false 'Ctrl/Cmd+I': false
@ -337,10 +340,9 @@ export default defineConfig({
options: { options: {
mode: 'sidePanel', mode: 'sidePanel',
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...',
sidePanel: true sidePanel: true
} }
} }

@ -204,7 +204,7 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...' indices: ['...']
} }
} }
} }
@ -222,10 +222,14 @@ export default defineConfig({
</details> </details>
詳しくは[公式 Algolia ドキュメント](https://docsearch.algolia.com/docs/api#translations)を参照してください。すぐに始めるには、このサイトで使っている翻訳を[GitHub リポジトリ](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code)からコピーすることもできます。 詳しくは[公式 Algolia ドキュメント](https://docsearch.algolia.com/docs/packages/react/api-reference#translations)を参照してください。すぐに始めるには、このサイトで使っている翻訳を[GitHub リポジトリ](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code)からコピーすることもできます。
### Algolia Ask AI のサポート {#ask-ai} ### Algolia Ask AI のサポート {#ask-ai}
::: note 注意
`v5.0.0` 以降、Ask AI のバックエンドは Algolia の [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/quickstart) に移行しました。移行に役立つ Ask AI から Agent Studio への[移行ガイド](https://docsearch.algolia.com/docs/agent-studio/migrate-to-agent-studio)も利用できます。
:::
**Ask AI** を有効にするには、`options` 内に `askAi` オプション(またはその一部)を指定します。 **Ask AI** を有効にするには、`options` 内に `askAi` オプション(またはその一部)を指定します。
```ts ```ts
@ -238,16 +242,15 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
// askAi: "あなたのアシスタントID" // askAi: "あなたのエージェントID"
// または // または
askAi: { askAi: {
// 最低限、Algolia から受け取った assistantId を指定する必要があります // 最低限、Algolia から受け取った agentId を指定する必要があります
assistantId: 'XXXYYY', agentId: 'XXXYYY',
// 任意の上書き — 省略した場合は上位の appId/apiKey/indexName を再利用 // 任意の上書き — 省略した場合は上位の appId/apiKey の値を再利用
// apiKey: '...', // apiKey: '...',
// appId: '...', // appId: '...',
// indexName: '...'
} }
} }
} }
@ -261,7 +264,7 @@ export default defineConfig({
### Ask AI サイドパネル {#ask-ai-side-panel} ### Ask AI サイドパネル {#ask-ai-side-panel}
DocSearch v4.5+ はオプションの **Ask AI サイドパネル**をサポートしています。有効にすると、デフォルトで **Ctrl/Cmd+I** で開くことができます。[サイドパネル API リファレンス](https://docsearch.algolia.com/docs/sidepanel/api-reference)にオプションの完全なリストがあります。 DocSearch v4.5+ はオプションの **Ask AI サイドパネル**をサポートしています。有効にすると、デフォルトで **Ctrl/Cmd+I** で開くことができます。[サイドパネル API リファレンス](https://docsearch.algolia.com/docs/packages/sidepanel/api)にオプションの完全なリストがあります。
```ts ```ts
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
@ -273,9 +276,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
// @docsearch/sidepanel-js SidepanelProps API をミラー // @docsearch/sidepanel-js SidepanelProps API をミラー
panel: { panel: {
@ -305,9 +308,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
keyboardShortcuts: { keyboardShortcuts: {
'Ctrl/Cmd+I': false 'Ctrl/Cmd+I': false
@ -343,10 +346,9 @@ export default defineConfig({
options: { options: {
mode: 'sidePanel', mode: 'sidePanel',
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...',
sidePanel: true sidePanel: true
} }
} }

@ -198,7 +198,7 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...' indices: ['...']
} }
} }
} }
@ -216,10 +216,14 @@ export default defineConfig({
</details> </details>
자세한 내용은 [공식 Algolia 문서](https://docsearch.algolia.com/docs/api#translations)를 참고하세요. 빠르게 시작하려면 이 사이트에서 사용하는 번역을 [GitHub 저장소](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code)에서 복사할 수도 있습니다. 자세한 내용은 [공식 Algolia 문서](https://docsearch.algolia.com/docs/packages/react/api-reference#translations)를 참고하세요. 빠르게 시작하려면 이 사이트에서 사용하는 번역을 [GitHub 저장소](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code)에서 복사할 수도 있습니다.
### Algolia Ask AI 지원 {#ask-ai} ### Algolia Ask AI 지원 {#ask-ai}
::: note 참고
`v5.0.0`부터 Ask AI는 백엔드로 Algolia의 [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/quickstart)를 사용합니다. 전환에 도움이 되는 Ask AI -> Agent Studio [마이그레이션 가이드](https://docsearch.algolia.com/docs/agent-studio/migrate-to-agent-studio)도 제공됩니다.
:::
**Ask AI** 기능을 사용하려면 `askAi` 옵션을 추가하세요: **Ask AI** 기능을 사용하려면 `askAi` 옵션을 추가하세요:
```ts ```ts
@ -232,16 +236,15 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
// askAi: "내-어시스턴트-ID" // askAi: "내-에이전트-ID"
// 또는 // 또는
askAi: { askAi: {
// 최소한 Algolia에서 받은 assistantId를 제공해야 합니다 // 최소한 Algolia에서 받은 agentId를 제공해야 합니다
assistantId: 'XXXYYY', agentId: 'XXXYYY',
// 선택적 재정의 — 생략하면 상위 appId/apiKey/indexName 값이 재사용됩니다 // 선택적 재정의 — 생략하면 상위 appId/apiKey 값이 재사용됩니다
// apiKey: '...', // apiKey: '...',
// appId: '...', // appId: '...',
// indexName: '...'
} }
} }
} }
@ -255,7 +258,7 @@ Ask AI를 사용하지 않으려면 `askAi` 옵션을 생략하면 됩니다.
### Ask AI 사이드 패널 {#ask-ai-side-panel} ### Ask AI 사이드 패널 {#ask-ai-side-panel}
DocSearch v4.5+는 선택적 **Ask AI 사이드 패널**을 지원합니다. 활성화되면 기본적으로 **Ctrl/Cmd+I**로 열 수 있습니다. [사이드 패널 API 참조](https://docsearch.algolia.com/docs/sidepanel/api-reference)에 전체 옵션 목록이 있습니다. DocSearch v4.5+는 선택적 **Ask AI 사이드 패널**을 지원합니다. 활성화되면 기본적으로 **Ctrl/Cmd+I**로 열 수 있습니다. [사이드 패널 API 참조](https://docsearch.algolia.com/docs/packages/sidepanel/api)에 전체 옵션 목록이 있습니다.
```ts ```ts
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
@ -267,9 +270,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
// @docsearch/sidepanel-js SidepanelProps API 반영 // @docsearch/sidepanel-js SidepanelProps API 반영
panel: { panel: {
@ -299,9 +302,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
keyboardShortcuts: { keyboardShortcuts: {
'Ctrl/Cmd+I': false 'Ctrl/Cmd+I': false
@ -337,10 +340,9 @@ export default defineConfig({
options: { options: {
mode: 'sidePanel', mode: 'sidePanel',
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...',
sidePanel: true sidePanel: true
} }
} }

@ -198,7 +198,7 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...' indices: ['...']
} }
} }
} }
@ -216,10 +216,14 @@ Você pode usar uma configuração como esta para usar a pesquisa multilínguas:
</details> </details>
Consulte a [documentação oficial da Algolia](https://docsearch.algolia.com/docs/api#translations) para saber mais. Para começar rapidamente, você também pode copiar as traduções usadas por este site do [nosso repositório no GitHub](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code). Consulte a [documentação oficial da Algolia](https://docsearch.algolia.com/docs/packages/react/api-reference#translations) para saber mais. Para começar rapidamente, você também pode copiar as traduções usadas por este site do [nosso repositório no GitHub](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code).
### Suporte ao Algolia Ask AI {#ask-ai} ### Suporte ao Algolia Ask AI {#ask-ai}
::: note Nota
A partir da versão `v5.0.0`, o Ask AI passou a usar o [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/quickstart) da Algolia como backend. Um [guia de migração](https://docsearch.algolia.com/docs/agent-studio/migrate-to-agent-studio) do Ask AI para o Agent Studio está disponível para ajudar na transição.
:::
Se quiser incluir o **Ask AI**, adicione `askAi` em `options`: Se quiser incluir o **Ask AI**, adicione `askAi` em `options`:
```ts ```ts
@ -232,16 +236,15 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
// askAi: "SEU-ID-DO-ASSISTENTE" // askAi: "SEU-ID-DO-AGENTE"
// OU // OU
askAi: { askAi: {
// no mínimo, você deve fornecer o assistantId recebido da Algolia // no mínimo, você deve fornecer o agentId recebido da Algolia
assistantId: 'XXXYYY', agentId: 'XXXYYY',
// substituições opcionais — se omitidas, os valores appId/apiKey/indexName de nível superior são reutilizados // substituições opcionais — se omitidas, os valores appId/apiKey de nível superior são reutilizados
// apiKey: '...', // apiKey: '...',
// appId: '...', // appId: '...',
// indexName: '...'
} }
} }
} }
@ -255,7 +258,7 @@ Caso queira apenas a pesquisa por palavra-chave, omita `askAi`.
### Painel Lateral do Ask AI {#ask-ai-side-panel} ### Painel Lateral do Ask AI {#ask-ai-side-panel}
O DocSearch v4.5+ suporta um **painel lateral do Ask AI** opcional. Quando habilitado, pode ser aberto com **Ctrl/Cmd+I** por padrão. A [Referência da API do Painel Lateral](https://docsearch.algolia.com/docs/sidepanel/api-reference) contém a lista completa de opções. O DocSearch v4.5+ suporta um **painel lateral do Ask AI** opcional. Quando habilitado, pode ser aberto com **Ctrl/Cmd+I** por padrão. A [Referência da API do Painel Lateral](https://docsearch.algolia.com/docs/packages/sidepanel/api) contém a lista completa de opções.
```ts ```ts
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
@ -267,9 +270,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
// Espelha a API do @docsearch/sidepanel-js SidepanelProps // Espelha a API do @docsearch/sidepanel-js SidepanelProps
panel: { panel: {
@ -299,9 +302,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
keyboardShortcuts: { keyboardShortcuts: {
'Ctrl/Cmd+I': false 'Ctrl/Cmd+I': false
@ -337,10 +340,9 @@ export default defineConfig({
options: { options: {
mode: 'sidePanel', mode: 'sidePanel',
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...',
sidePanel: true sidePanel: true
} }
} }

@ -208,7 +208,7 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...' indices: ['...']
} }
} }
} }
@ -226,10 +226,14 @@ export default defineConfig({
</details> </details>
Подробности см. в [официальной документации Algolia](https://docsearch.algolia.com/docs/api#translations). Чтобы быстрее начать, можно также скопировать переводы, используемые на этом сайте, из [нашего репозитория GitHub](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code). Подробности см. в [официальной документации Algolia](https://docsearch.algolia.com/docs/packages/react/api-reference#translations). Чтобы быстрее начать, можно также скопировать переводы, используемые на этом сайте, из [нашего репозитория GitHub](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code).
### Поддержка Ask AI в Algolia {#ask-ai} ### Поддержка Ask AI в Algolia {#ask-ai}
::: note Примечание
Начиная с `v5.0.0`, Ask AI использует [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/quickstart) от Algolia в качестве серверной части. Для упрощения перехода доступно [руководство по миграции](https://docsearch.algolia.com/docs/agent-studio/migrate-to-agent-studio) с Ask AI на Agent Studio.
:::
Если вы хотите добавить функцию **Ask AI**, передайте параметр `askAi` (или любые из его отдельных полей) внутри объекта `options`: Если вы хотите добавить функцию **Ask AI**, передайте параметр `askAi` (или любые из его отдельных полей) внутри объекта `options`:
```ts ```ts
@ -242,16 +246,15 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
// askAi: "ВАШ-ID-АССИСТЕНТА" // askAi: "ВАШ-ID-АГЕНТА"
// ИЛИ // ИЛИ
askAi: { askAi: {
// как минимум нужно указать assistantId, полученный от Algolia // как минимум нужно указать agentId, полученный от Algolia
assistantId: 'XXXYYY', agentId: 'XXXYYY',
// необязательные переопределения — если их нет, используются значения appId/apiKey/indexName верхнего уровня // необязательные переопределения — если их нет, используются значения appId/apiKey верхнего уровня
// apiKey: '...', // apiKey: '...',
// appId: '...', // appId: '...',
// indexName: '...'
} }
} }
} }
@ -265,7 +268,7 @@ export default defineConfig({
### Боковая панель Ask AI {#ask-ai-side-panel} ### Боковая панель Ask AI {#ask-ai-side-panel}
DocSearch v4.5+ поддерживает опциональную **боковую панель Ask AI**. Когда она включена, её можно открыть с помощью **Ctrl/Cmd+I** по умолчанию. [Справочник API боковой панели](https://docsearch.algolia.com/docs/sidepanel/api-reference) содержит полный список опций. DocSearch v4.5+ поддерживает опциональную **боковую панель Ask AI**. Когда она включена, её можно открыть с помощью **Ctrl/Cmd+I** по умолчанию. [Справочник API боковой панели](https://docsearch.algolia.com/docs/packages/sidepanel/api) содержит полный список опций.
```ts ```ts
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
@ -277,9 +280,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
panel: { panel: {
variant: 'floating', // или 'inline' variant: 'floating', // или 'inline'
@ -310,9 +313,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
keyboardShortcuts: { keyboardShortcuts: {
'Ctrl/Cmd+I': false 'Ctrl/Cmd+I': false
@ -348,10 +351,9 @@ export default defineConfig({
options: { options: {
mode: 'sidePanel', mode: 'sidePanel',
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...',
sidePanel: true sidePanel: true
} }
} }

@ -7,7 +7,7 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
locales: { locales: {
zh: { zh: {
translations: { translations: {
@ -33,6 +33,14 @@ export default defineConfig({
viewConversationHistoryText: '对话历史', viewConversationHistoryText: '对话历史',
threadDepthErrorPlaceholder: '对话已达上限' threadDepthErrorPlaceholder: '对话已达上限'
}, },
facets: {
defaultValueLabel: '全部',
facetMenuTriggerAriaLabel: '已选择',
clearAllLabel: '清除全部',
facetsAriaLabel: '搜索筛选条件',
selectedFacetsAriaLabel: '已选择的搜索筛选条件',
clearFacetAriaLabel: '清除筛选条件:'
},
newConversation: { newConversation: {
newConversationTitle: '我今天能帮你什么?', newConversationTitle: '我今天能帮你什么?',
newConversationDescription: newConversationDescription:
@ -72,7 +80,11 @@ export default defineConfig({
}, },
resultsScreen: { resultsScreen: {
askAiPlaceholder: '询问 AI', askAiPlaceholder: '询问 AI',
noResultsAskAiPlaceholder: '文档里没找到?让 Ask AI 帮忙:' noResultsAskAiPlaceholder: '文档里没找到?让 Ask AI 帮忙:',
resultsSectionTitle: '搜索结果',
askAiResultsTitle: '询问 AI 助手',
recentConversationTimestampFallback: '不久前',
resultBadgeLabelText: '类别'
}, },
askAiScreen: { askAiScreen: {
disclaimerText: '回答由 AI 生成,可能会出错。请核实。', disclaimerText: '回答由 AI 生成,可能会出错。请核实。',
@ -89,7 +101,24 @@ export default defineConfig({
afterToolCallText: '已搜索', afterToolCallText: '已搜索',
stoppedStreamingText: '你已停止此回复', stoppedStreamingText: '你已停止此回复',
errorTitleText: '聊天错误', errorTitleText: '聊天错误',
startNewConversationButtonText: '开始新的对话' startNewConversationButtonText: '开始新的对话',
relatedSourcesTextPlural: '相关来源',
savedMemoryToolResultText: '已保存到记忆',
memoryToolResultText: '已使用记忆增强结果',
feedbackPanelTitle: '哪里出了问题?(可选)',
feedbackDetailsPlaceholder: '请分享更多细节...',
feedbackDisclaimerText: '反馈中将包含此对话的副本。',
feedbackSubmitButtonText: '提交',
feedbackCloseButtonTitle: '关闭',
feedbackTagIncorrect: '不正确或不完整',
feedbackTagNotWhatIAsked: '不是我想问的',
feedbackTagSlowOrBuggy: '响应缓慢或存在故障',
feedbackTagStyleOrTone: '风格或语气',
feedbackTagSafetyOrLegal: '安全或法律问题',
feedbackTagOther: '其他',
threadDepthExceededMessage:
'为确保回答准确,此对话现已关闭。',
suggestedPromptsTitleText: '推荐问题'
} }
} }
}, },
@ -115,7 +144,10 @@ export default defineConfig({
promptAskAnotherQuestionText: '再问一个问题', promptAskAnotherQuestionText: '再问一个问题',
promptDisclaimerText: '回答由 AI 生成,可能会出错。', promptDisclaimerText: '回答由 AI 生成,可能会出错。',
promptLabelText: '按回车发送Shift+回车换行。', promptLabelText: '按回车发送Shift+回车换行。',
promptAriaLabelText: '问题输入' promptAriaLabelText: '问题输入',
startNewConversationButtonText: '开始新的对话',
blockingErrorContinueText: '以继续。',
blockingErrorFallbackText: '此对话无法继续。'
}, },
conversationScreen: { conversationScreen: {
preToolCallText: '搜索中...', preToolCallText: '搜索中...',
@ -132,7 +164,22 @@ export default defineConfig({
likeButtonTitle: '喜欢', likeButtonTitle: '喜欢',
dislikeButtonTitle: '不喜欢', dislikeButtonTitle: '不喜欢',
thanksForFeedbackText: '感谢你的反馈!', thanksForFeedbackText: '感谢你的反馈!',
errorTitleText: '聊天错误' errorTitleText: '聊天错误',
relatedSourcesTextPlural: '相关来源',
savedMemoryToolResultText: '已保存到记忆',
memoryToolResultText: '已使用记忆增强结果',
feedbackPanelTitle: '哪里出了问题?(可选)',
feedbackDetailsPlaceholder: '请分享更多细节...',
feedbackDisclaimerText: '反馈中将包含此对话的副本。',
feedbackSubmitButtonText: '提交',
feedbackCloseButtonTitle: '关闭',
feedbackTagIncorrect: '不正确或不完整',
feedbackTagNotWhatIAsked: '不是我想问的',
feedbackTagSlowOrBuggy: '响应缓慢或存在故障',
feedbackTagStyleOrTone: '风格或语气',
feedbackTagSafetyOrLegal: '安全或法律问题',
feedbackTagOther: '其他',
suggestedPromptsTitleText: '推荐问题'
}, },
newConversationScreen: { newConversationScreen: {
titleText: '我今天能帮你什么?', titleText: '我今天能帮你什么?',

@ -198,7 +198,7 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...' indices: ['...']
} }
} }
} }
@ -216,10 +216,14 @@ export default defineConfig({
</details> </details>
更多信息请参考[官方 Algolia 文档](https://docsearch.algolia.com/docs/api#translations)。想要快速开始,你也可以从[我们的 GitHub 仓库](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code)复制此站点使用的翻译。 更多信息请参考[官方 Algolia 文档](https://docsearch.algolia.com/docs/packages/react/api-reference#translations)。想要快速开始,你也可以从[我们的 GitHub 仓库](https://github.com/search?q=repo:vuejs/vitepress+%22function+searchOptions%22&type=code)复制此站点使用的翻译。
### Algolia Ask AI 支持 {#ask-ai} ### Algolia Ask AI 支持 {#ask-ai}
::: note 注意
`v5.0.0`Ask AI 已改用 Algolia 的 [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/quickstart) 作为后端。可参考 Ask AI -> Agent Studio [迁移指南](https://docsearch.algolia.com/docs/agent-studio/migrate-to-agent-studio),以帮助完成迁移。
:::
如果需要启用 **Ask AI**,只需在 `options` 中添加 `askAi` 如果需要启用 **Ask AI**,只需在 `options` 中添加 `askAi`
```ts ```ts
@ -232,16 +236,15 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
// askAi: "你的助手ID" // askAi: "你的智能体ID"
// 或 // 或
askAi: { askAi: {
// 至少需要提供从 Algolia 获取的 assistantId // 至少需要提供从 Algolia 获取的 agentId
assistantId: 'XXXYYY', agentId: 'XXXYYY',
// 可选覆盖 — 若省略,将复用顶层 appId/apiKey/indexName 的值 // 可选覆盖 — 若省略,将复用顶层 appId/apiKey 的值
// apiKey: '...', // apiKey: '...',
// appId: '...', // appId: '...'
// indexName: '...'
} }
} }
} }
@ -255,7 +258,7 @@ export default defineConfig({
### Ask AI 侧边栏 {#ask-ai-side-panel} ### Ask AI 侧边栏 {#ask-ai-side-panel}
DocSearch v4.5+ 支持可选的 **Ask AI 侧边栏**。启用后,默认可通过 **Ctrl/Cmd+I** 打开。完整的选项列表请参阅[侧边栏 API 参考](https://docsearch.algolia.com/docs/sidepanel/api-reference)。 DocSearch v4.5+ 支持可选的 **Ask AI 侧边栏**。启用后,默认可通过 **Ctrl/Cmd+I** 打开。完整的选项列表请参阅[侧边栏 API 参考](https://docsearch.algolia.com/docs/packages/sidepanel/api)。
```ts ```ts
import { defineConfig } from 'vitepress' import { defineConfig } from 'vitepress'
@ -267,9 +270,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
// 镜像 @docsearch/sidepanel-js SidepanelProps API // 镜像 @docsearch/sidepanel-js SidepanelProps API
panel: { panel: {
@ -299,9 +302,9 @@ export default defineConfig({
options: { options: {
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...', indices: ['...'],
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
sidePanel: { sidePanel: {
keyboardShortcuts: { keyboardShortcuts: {
'Ctrl/Cmd+I': false 'Ctrl/Cmd+I': false
@ -337,10 +340,9 @@ export default defineConfig({
options: { options: {
mode: 'sidePanel', mode: 'sidePanel',
askAi: { askAi: {
assistantId: 'XXXYYY', agentId: 'XXXYYY',
appId: '...', appId: '...',
apiKey: '...', apiKey: '...',
indexName: '...',
sidePanel: true sidePanel: true
} }
} }

@ -93,9 +93,9 @@
"*": "prettier --experimental-cli --ignore-unknown --write" "*": "prettier --experimental-cli --ignore-unknown --write"
}, },
"dependencies": { "dependencies": {
"@docsearch/css": "^4.7.0", "@docsearch/css": "^5.0.4",
"@docsearch/js": "^4.7.0", "@docsearch/js": "^5.0.4",
"@docsearch/sidepanel-js": "^4.7.0", "@docsearch/sidepanel-js": "^5.0.4",
"@iconify-json/simple-icons": "^1.2.93", "@iconify-json/simple-icons": "^1.2.93",
"@shikijs/transformers": "^4.4.3", "@shikijs/transformers": "^4.4.3",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",

@ -15,14 +15,14 @@ importers:
.: .:
dependencies: dependencies:
'@docsearch/css': '@docsearch/css':
specifier: ^4.7.0 specifier: ^5.0.4
version: 4.7.0 version: 5.0.4
'@docsearch/js': '@docsearch/js':
specifier: ^4.7.0 specifier: ^5.0.4
version: 4.7.0 version: 5.0.4
'@docsearch/sidepanel-js': '@docsearch/sidepanel-js':
specifier: ^4.7.0 specifier: ^5.0.4
version: 4.7.0 version: 5.0.4
'@iconify-json/simple-icons': '@iconify-json/simple-icons':
specifier: ^1.2.93 specifier: ^1.2.93
version: 1.2.93 version: 1.2.93
@ -385,14 +385,14 @@ packages:
resolution: {integrity: sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==} resolution: {integrity: sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==}
engines: {node: '>=22'} engines: {node: '>=22'}
'@docsearch/css@4.7.0': '@docsearch/css@5.0.4':
resolution: {integrity: sha512-Sk5xkdRFeE7PeWjG9l4AfTwdvMfr9wHiwNNCpHXT4v4SNyNMKdHGvEILc31BgaVFGDDNbv5u/a73tofRiwbEZw==} resolution: {integrity: sha512-Bg2VmrPbhBmqKBrt6FL8bvd66f9IaU448Ip9K+elLlX2rivtV5FvBJq7iGsMvvj42etsdJe6VqisoTKZMv0Qdg==}
'@docsearch/js@4.7.0': '@docsearch/js@5.0.4':
resolution: {integrity: sha512-x5lCqu1tetgsJFkjQ6VSocbHldsRkGEgwg5N98Vx21sq/V5wcmj4u226PY9k+TEpIgQ772zlYbPLTPicWyGnpA==} resolution: {integrity: sha512-eKoVwAWKWXYPDZiDXBX4g1RVQnvHHCX91+Z+1ZEJuHPkZjTANk+L4/9t6xM5wxiiNcwD6YjfDMhcE/99sWT4SA==}
'@docsearch/sidepanel-js@4.7.0': '@docsearch/sidepanel-js@5.0.4':
resolution: {integrity: sha512-A8r34jCU8kcIk2viECEn2msA28ojUF1BLi/3v5OWWc5G2N3jOuuumBXoeYjfr8dA0UxgFSy5R2bt12dnFJQSyA==} resolution: {integrity: sha512-RZ6YO4SvLqfjlz49fNlYtlHARSuD0YxAQ8HTl5DDbiW5iHNu8Ichu0teughu1uLBVg06t4OELP3RRU6O7f0fHQ==}
'@hapi/address@5.1.1': '@hapi/address@5.1.1':
resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==}
@ -2891,11 +2891,11 @@ snapshots:
'@conventional-changelog/template@1.2.1': {} '@conventional-changelog/template@1.2.1': {}
'@docsearch/css@4.7.0': {} '@docsearch/css@5.0.4': {}
'@docsearch/js@4.7.0': {} '@docsearch/js@5.0.4': {}
'@docsearch/sidepanel-js@4.7.0': {} '@docsearch/sidepanel-js@5.0.4': {}
'@hapi/address@5.1.1': '@hapi/address@5.1.1':
dependencies: dependencies:

@ -13,8 +13,6 @@ import {
validateCredentials validateCredentials
} from '../support/docsearch' } from '../support/docsearch'
import '../styles/docsearch.css'
const props = defineProps<{ const props = defineProps<{
algoliaOptions: DefaultTheme.AlgoliaSearchOptions algoliaOptions: DefaultTheme.AlgoliaSearchOptions
openRequest?: { openRequest?: {
@ -31,7 +29,8 @@ let docsearchInstance: DocSearchInstance | undefined
let sidepanelInstance: SidepanelInstance | undefined let sidepanelInstance: SidepanelInstance | undefined
let openOnReady: 'search' | 'askAi' | null = null let openOnReady: 'search' | 'askAi' | null = null
let initializeCount = 0 let initializeCount = 0
let docsearchLoader: Promise<typeof import('@docsearch/js')> | undefined let docsearchLoader: Promise<typeof import('@docsearch/js/docsearch')> | undefined
let docsearchAiLoader: Promise<typeof import('@docsearch/js')> | undefined
let sidepanelLoader: Promise<typeof import('@docsearch/sidepanel-js')> | undefined let sidepanelLoader: Promise<typeof import('@docsearch/sidepanel-js')> | undefined
let lastFocusedElement: HTMLElement | null = null let lastFocusedElement: HTMLElement | null = null
let skipEventDocsearch = false let skipEventDocsearch = false
@ -82,11 +81,13 @@ async function update(options: DefaultTheme.AlgoliaSearchOptions) {
const { valid, ...credentials } = validateCredentials({ const { valid, ...credentials } = validateCredentials({
appId: options.appId ?? askAi?.appId, appId: options.appId ?? askAi?.appId,
apiKey: options.apiKey ?? askAi?.apiKey, apiKey: options.apiKey ?? askAi?.apiKey,
indexName: options.indexName ?? askAi?.indexName indices: options.indices,
mode: options.mode,
askAi: options.askAi
}) })
if (!valid) { if (!valid) {
console.warn('[vitepress] Algolia search cannot be initialized: missing appId/apiKey/indexName.') console.warn('[vitepress] Algolia search cannot be initialized: missing appId/apiKey/indices.')
return return
} }
@ -99,16 +100,24 @@ async function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) {
// Always tear down previous instances first (e.g. on locale changes) // Always tear down previous instances first (e.g. on locale changes)
cleanup() cleanup()
const { useSidePanel } = resolveMode(userOptions) const { useSidePanel, showKeywordSearch } = resolveMode(userOptions)
const askAi = userOptions.askAi as DocSearchAskAi | undefined const askAi = userOptions.askAi as DocSearchAskAi | undefined
const usingAskAi = !!askAi
const usingSidepanel = useSidePanel && !!askAi?.sidePanel
// Load everything that is needed in parallel
const [, docsearchModule, sidepanelModule] = await Promise.all([
loadDocSearchCssBundles({ usingAskAi, usingSidepanel }),
showKeywordSearch ? loadDocsearch(usingAskAi) : undefined,
usingSidepanel ? loadSidepanel() : undefined
])
const { default: docsearch } = await loadDocsearch()
if (currentInitialize !== initializeCount) return if (currentInitialize !== initializeCount) return
if (useSidePanel && askAi?.sidePanel) { const docsearch = docsearchModule?.default
const { default: sidepanel } = await loadSidepanel() const sidepanel = sidepanelModule?.default
if (currentInitialize !== initializeCount) return
if (usingSidepanel && sidepanel) {
sidepanelInstance = sidepanel({ sidepanelInstance = sidepanel({
...buildSidePanelProps(askAi, userOptions), ...buildSidePanelProps(askAi, userOptions),
onOpen: focusInput, onOpen: focusInput,
@ -125,41 +134,43 @@ async function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) {
}) })
} }
const options = { if (docsearch) {
...userOptions, const options = {
container: '#vp-docsearch', ...userOptions,
navigator: { container: '#vp-docsearch',
navigate(item) { navigator: {
router.go(item.itemUrl) navigate(item) {
} router.go(item.itemUrl)
}, }
transformItems: (items) => items.map((item) => ({ ...item, url: getRelativePath(item.url) })), },
// When sidepanel is enabled, intercept Ask AI events to open it instead (hybrid mode) transformItems: (items) => items.map((item) => ({ ...item, url: getRelativePath(item.url) })),
...(useSidePanel && sidepanelInstance && { // When sidepanel is enabled, intercept Ask AI events to open it instead (hybrid mode)
interceptAskAiEvent: (initialMessage) => { ...(useSidePanel && sidepanelInstance && {
onBeforeOpen('sidepanel', () => sidepanelInstance?.open(initialMessage)) interceptAskAiEvent: (initialMessage) => {
return true onBeforeOpen('sidepanel', () => sidepanelInstance?.open(initialMessage))
} return true
}), }
onOpen: focusInput, }),
onClose: onClose.bind(null, 'docsearch'), onOpen: focusInput,
onReady: () => { onClose: onClose.bind(null, 'docsearch'),
if (openOnReady === 'search') { onReady: () => {
openOnReady = null if (openOnReady === 'search') {
onBeforeOpen('docsearch', () => docsearchInstance?.open()) openOnReady = null
} else if (openOnReady === 'askAi' && !sidepanelInstance) { onBeforeOpen('docsearch', () => docsearchInstance?.open())
// No sidepanel configured, use docsearch modal for askAi } else if (openOnReady === 'askAi' && !sidepanelInstance) {
openOnReady = null // No sidepanel configured, use docsearch modal for askAi
onBeforeOpen('docsearch', () => docsearchInstance?.openAskAi()) openOnReady = null
onBeforeOpen('docsearch', () => docsearchInstance?.openAskAi())
}
},
keyboardShortcuts: {
'/': false,
'Ctrl/Cmd+K': false
} }
}, } as DocSearchProps
keyboardShortcuts: {
'/': false,
'Ctrl/Cmd+K': false
}
} as DocSearchProps
docsearchInstance = docsearch(options) docsearchInstance = docsearch(options)
}
cleanup = () => { cleanup = () => {
docsearchInstance?.destroy() docsearchInstance?.destroy()
@ -221,10 +232,34 @@ function onClose(target: 'docsearch' | 'sidepanel') {
} }
} }
function loadDocsearch() { // DocSearch V5 now splits it's CSS bundles, namely that `modal.css` does not contain Ask AI related CSS.
if (!docsearchLoader) { // With this, we can better target load the needed CSS bundles to save some page load.
docsearchLoader = import('@docsearch/js') async function loadDocSearchCssBundles({ usingAskAi, usingSidepanel }: { usingAskAi: boolean; usingSidepanel: boolean; }) {
if (usingAskAi) {
await import('@docsearch/css/dist/style.css')
} else {
await import('@docsearch/css/dist/_variables.css')
await import('@docsearch/css/dist/modal.css')
}
if (usingSidepanel) {
await import('@docsearch/css/dist/sidepanel.css')
} }
await import('../styles/docsearch.css')
}
// DocSearch V5 now splits it's bundles between Ask AI or search only.
// As such, we can better target load the needed bundle.
function loadDocsearch(usingAskAi: boolean) {
// The default export includes Ask AI
if (usingAskAi) {
docsearchAiLoader ??= import('@docsearch/js')
return docsearchAiLoader
}
docsearchLoader ??= import('@docsearch/js/docsearch')
return docsearchLoader return docsearchLoader
} }

@ -1,6 +1,3 @@
@import '@docsearch/css/dist/style.css';
@import '@docsearch/css/dist/sidepanel.css';
#vp-docsearch, #vp-docsearch,
#vp-docsearch-sidepanel, #vp-docsearch-sidepanel,
.DocSearch-SidepanelButton { .DocSearch-SidepanelButton {
@ -10,11 +7,14 @@
:root:root { :root:root {
--docsearch-actions-height: auto; --docsearch-actions-height: auto;
--docsearch-actions-width: auto; --docsearch-actions-width: auto;
--docsearch-background-color: var(--vp-c-bg-soft); --docsearch-background-color: var(--vp-c-divider);
--docsearch-border-radius: 0.25rem; --docsearch-border-radius: 0.25rem;
--docsearch-modal-radius: 0.25rem;
--docsearch-container-background: var(--vp-backdrop-bg-color); --docsearch-container-background: var(--vp-backdrop-bg-color);
--docsearch-dropdown-menu-background: var(--vp-c-bg-elv); --docsearch-dropdown-menu-background: var(--vp-c-bg-alt);
--docsearch-dropdown-menu-item-hover-background: var(--vp-c-default-soft); --docsearch-dropdown-menu-item-hover-background: var(--vp-c-default-soft);
--docsearch-popover-background: var(--vp-c-bg-alt);
--docsearch-popover-arrow-color: var(--docsearch-popover-background);
--docsearch-focus-color: var(--vp-c-brand-1); --docsearch-focus-color: var(--vp-c-brand-1);
--docsearch-footer-background: var(--vp-c-bg-alt); --docsearch-footer-background: var(--vp-c-bg-alt);
--docsearch-footer-height: 3.25rem; --docsearch-footer-height: 3.25rem;
@ -23,6 +23,7 @@
--docsearch-hit-color: var(--vp-c-text-1); --docsearch-hit-color: var(--vp-c-text-1);
--docsearch-hit-height: 3.5rem; --docsearch-hit-height: 3.5rem;
--docsearch-hit-highlight-color: var(--vp-c-brand-soft); --docsearch-hit-highlight-color: var(--vp-c-brand-soft);
--docsearch-hit-focus-background: var(--vp-c-brand-soft);
--docsearch-icon-color: var(--vp-c-text-2); --docsearch-icon-color: var(--vp-c-text-2);
--docsearch-key-background: var(--vp-code-bg); --docsearch-key-background: var(--vp-code-bg);
--docsearch-modal-background: var(--vp-c-bg-soft); --docsearch-modal-background: var(--vp-c-bg-soft);
@ -43,6 +44,7 @@
--docsearch-subtle-color: var(--vp-c-divider); --docsearch-subtle-color: var(--vp-c-divider);
--docsearch-success-color: var(--vp-c-brand-soft); --docsearch-success-color: var(--vp-c-brand-soft);
--docsearch-text-color: var(--vp-c-text-1); --docsearch-text-color: var(--vp-c-text-1);
--docsearch-code-block-background: var(--vp-code-bg);
} }
/* mirror the tightened mobile values from @docsearch/css, which the /* mirror the tightened mobile values from @docsearch/css, which the
@ -135,3 +137,8 @@
font-size: revert; font-size: revert;
line-height: revert; line-height: revert;
} }
.DocSearch-Feedback-Panel-Submit {
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
}

@ -10,7 +10,7 @@ export interface ValidatedCredentials {
valid: boolean valid: boolean
appId?: string appId?: string
apiKey?: string apiKey?: string
indexName?: string indices?: DefaultTheme.AlgoliaSearchOptions['indices']
} }
export type DocSearchMode = 'auto' | 'sidePanel' | 'hybrid' | 'modal' export type DocSearchMode = 'auto' | 'sidePanel' | 'hybrid' | 'modal'
@ -21,14 +21,6 @@ export interface ResolvedMode {
useSidePanel: boolean useSidePanel: boolean
} }
// FIXME: remove when https://github.com/algolia/docsearch/pull/2906 is released
export type ResolvedSidePanelProps = SidepanelProps & {
agentStudio?: boolean
searchParameters?: DocSearchAskAi['searchParameters']
suggestedQuestions?: boolean
useStagingEnv?: boolean
}
/** /**
* Resolves the effective mode based on config and available features. * Resolves the effective mode based on config and available features.
* *
@ -40,7 +32,7 @@ export type ResolvedSidePanelProps = SidepanelProps & {
export function resolveMode( export function resolveMode(
options: Pick< options: Pick<
DefaultTheme.AlgoliaSearchOptions, DefaultTheme.AlgoliaSearchOptions,
'appId' | 'apiKey' | 'indexName' | 'askAi' | 'mode' 'appId' | 'apiKey' | 'indices' | 'askAi' | 'mode'
> >
): ResolvedMode { ): ResolvedMode {
const mode = options.mode ?? 'auto' const mode = options.mode ?? 'auto'
@ -63,7 +55,7 @@ export function resolveMode(
// Force hybrid - keyword search must be configured // Force hybrid - keyword search must be configured
if (!hasKeyword) { if (!hasKeyword) {
console.error( console.error(
'[vitepress] mode: "hybrid" requires keyword search credentials (appId, apiKey, indexName).' '[vitepress] mode: "hybrid" requires keyword search credentials (appId, apiKey, indices).'
) )
} }
return { return {
@ -94,10 +86,15 @@ export function resolveMode(
export function hasKeywordSearch( export function hasKeywordSearch(
options: Pick< options: Pick<
DefaultTheme.AlgoliaSearchOptions, DefaultTheme.AlgoliaSearchOptions,
'appId' | 'apiKey' | 'indexName' 'appId' | 'apiKey' | 'indices'
> >
): boolean { ): boolean {
return Boolean(options.appId && options.apiKey && options.indexName) return Boolean(
options.appId &&
options.apiKey &&
options.indices &&
options.indices.length > 0
)
} }
export function hasAskAi( export function hasAskAi(
@ -105,7 +102,40 @@ export function hasAskAi(
): boolean { ): boolean {
if (!askAi) return false if (!askAi) return false
if (typeof askAi === 'string') return askAi.length > 0 if (typeof askAi === 'string') return askAi.length > 0
return Boolean(askAi.assistantId) return Boolean(askAi.agentId)
}
const LANG_FILTER_REGEXP =
/"(?:\\.|[^"\\])*"|(^|[\s(])((?:NOT\s+)?lang:(?:"(?:\\.|[^"\\])*"|[^\s()]+))/gi
/**
* Normalizes existing positive `lang:` filters and applies `lang:${lang}` to
* the complete expression.
*/
export function mergeLangFilters(
existing: string | undefined,
lang: string
): string {
const langFilter = `lang:${lang}`
if (!existing) {
return langFilter
}
const normalized = existing.replace(
LANG_FILTER_REGEXP,
(match, prefix, predicate) => {
if (!predicate) {
return match
}
if (/^NOT\b/i.test(predicate)) return match
return `${prefix ?? ''}${langFilter}`
}
)
return `(${normalized}) AND ${langFilter}`
} }
/** /**
@ -143,24 +173,53 @@ export function mergeLangFacetFilters(
return [...filtered, `lang:${lang}`] return [...filtered, `lang:${lang}`]
} }
type CredentialOptions = Pick<
DefaultTheme.AlgoliaSearchOptions,
'appId' | 'apiKey' | 'indices' | 'mode' | 'askAi'
>
/** /**
* Validates that required Algolia credentials are present. * Validates that required Algolia credentials are present.
*/ */
export function validateCredentials( export function validateCredentials(
options: Pick< options: CredentialOptions
DefaultTheme.AlgoliaSearchOptions,
'appId' | 'apiKey' | 'indexName'
>
): ValidatedCredentials { ): ValidatedCredentials {
const appId = options.appId const appId = options.appId
const apiKey = options.apiKey const apiKey = options.apiKey
const indexName = options.indexName const indices = options.indices
const askAiConfigured = options.askAi !== undefined
const hasValidAskAi = hasAskAi(options.askAi)
const mode = options.mode || 'auto'
const hasSidepanel =
typeof options.askAi === 'object' && Boolean(options.askAi.sidePanel)
const requiresSidepanel = mode === 'sidePanel' || mode === 'hybrid'
const canOmitIndices =
mode === 'sidePanel' || (mode === 'auto' && hasSidepanel)
let isValid = true
if (askAiConfigured && !hasValidAskAi) {
isValid = false
}
if (requiresSidepanel && !hasSidepanel) {
isValid = false
}
// Sidepanel only (or auto mode with sidepanel) does not require `indices` since there could be no search
if (!canOmitIndices && !indices?.length) {
isValid = false
}
if (!appId || !apiKey) {
isValid = false
}
return { return {
valid: Boolean(appId && apiKey && indexName), valid: isValid,
appId, appId,
apiKey, apiKey,
indexName indices
} }
} }
@ -174,41 +233,45 @@ export function buildAskAiConfig(
): DocSearchAskAi { ): DocSearchAskAi {
const isAskAiString = typeof askAiProp === 'string' const isAskAiString = typeof askAiProp === 'string'
const askAiSearchParameters = let askAiSearchParameters: DocSearchAskAi['searchParameters']
!isAskAiString && askAiProp.searchParameters
? { ...askAiProp.searchParameters } if (!isAskAiString) {
: undefined const mergedSearchParameters: NonNullable<
const isAgentStudio = !isAskAiString && askAiProp.agentStudio === true DocSearchAskAi['searchParameters']
> = {}
const askAiFacetFiltersSource =
askAiSearchParameters?.facetFilters ?? const indexes = new Set([
options.searchParameters?.facetFilters ...(askAiProp.indices ?? []),
const askAiFacetFilters = mergeLangFacetFilters( ...Object.keys(askAiProp.searchParameters ?? {})
askAiFacetFiltersSource as FacetFilter | FacetFilter[] | undefined, ])
lang
)
const mergedAskAiSearchParameters = isAgentStudio for (const indexName of indexes) {
? askAiSearchParameters const searchParameters = askAiProp.searchParameters?.[indexName] ?? {}
: {
...askAiSearchParameters, mergedSearchParameters[indexName] = {
facetFilters: askAiFacetFilters.length ? askAiFacetFilters : undefined ...searchParameters,
filters: mergeLangFilters(searchParameters.filters, lang)
} }
}
if (indexes.size > 0) {
askAiSearchParameters = mergedSearchParameters
}
}
const result: Record<string, any> = { const result: Record<string, any> = {
...(isAskAiString ? {} : askAiProp), ...(isAskAiString ? {} : askAiProp),
indexName: isAskAiString ? options.indexName : askAiProp.indexName,
apiKey: isAskAiString ? options.apiKey : askAiProp.apiKey, apiKey: isAskAiString ? options.apiKey : askAiProp.apiKey,
appId: isAskAiString ? options.appId : askAiProp.appId, appId: isAskAiString ? options.appId : askAiProp.appId,
assistantId: isAskAiString ? askAiProp : askAiProp.assistantId agentId: isAskAiString ? askAiProp : askAiProp.agentId
} }
// Keep `searchParameters` undefined unless it has at least one key. // Keep `searchParameters` undefined unless it has at least one key.
if ( if (
mergedAskAiSearchParameters && askAiSearchParameters &&
Object.values(mergedAskAiSearchParameters).some((v) => v != null) Object.values(askAiSearchParameters).some((v) => v != null)
) { ) {
result.searchParameters = mergedAskAiSearchParameters result.searchParameters = askAiSearchParameters
} }
return result return result
@ -220,19 +283,18 @@ export function buildAskAiConfig(
export function buildSidePanelProps( export function buildSidePanelProps(
askAi: DocSearchAskAi, askAi: DocSearchAskAi,
options: DefaultTheme.AlgoliaSearchOptions options: DefaultTheme.AlgoliaSearchOptions
): ResolvedSidePanelProps { ): SidepanelProps {
const { sidePanel, ...askAiRest } = JSON.parse( const { sidePanel, ...askAiRest } = JSON.parse(
JSON.stringify(askAi) JSON.stringify(askAi)
) as DocSearchAskAi ) as DocSearchAskAi
return { return {
container: '#vp-docsearch-sidepanel', container: '#vp-docsearch-sidepanel',
indexName: options.indexName,
appId: options.appId, appId: options.appId,
apiKey: options.apiKey, apiKey: options.apiKey,
...askAiRest, ...askAiRest,
...(sidePanel && sidePanel !== true ? sidePanel : {}) ...(sidePanel && sidePanel !== true ? sidePanel : {})
} as ResolvedSidePanelProps } as SidepanelProps
} }
/** /**
@ -246,17 +308,33 @@ export function resolveOptionsForLanguage(
): DefaultTheme.AlgoliaSearchOptions { ): DefaultTheme.AlgoliaSearchOptions {
options = deepMerge(options, options.locales?.[localeIndex] || {}) options = deepMerge(options, options.locales?.[localeIndex] || {})
const facetFilters = mergeLangFacetFilters( const indices = (options.indices ?? []).map((index) => {
options.searchParameters?.facetFilters, if (typeof index === 'string') {
lang return {
) name: index,
searchParameters: { facetFilters: [`lang:${lang}`] }
}
}
return {
name: index.name,
searchParameters: {
...index.searchParameters,
facetFilters: mergeLangFacetFilters(
index.searchParameters?.facetFilters,
lang
)
}
}
})
const askAi = options.askAi const askAi = options.askAi
? buildAskAiConfig(options.askAi, options, lang) ? buildAskAiConfig(options.askAi, options, lang)
: undefined : undefined
return { return {
...options, ...options,
searchParameters: { ...options.searchParameters, facetFilters }, indices,
askAi askAi
} }
} }

@ -13,22 +13,16 @@ export type DocSearchProps = Partial<
| 'translations' | 'translations'
| 'recentSearchesLimit' | 'recentSearchesLimit'
| 'recentSearchesWithFavoritesLimit' | 'recentSearchesWithFavoritesLimit'
| 'indices'
| 'facets'
> >
> & { > & {
/**
* Name of the algolia index to query.
*/
indexName?: string
/**
* Additional algolia search parameters to merge into each query.
*/
searchParameters?: DocSearchPropsJS['searchParameters']
/** /**
* Insights client integration options to send analytics events. * Insights client integration options to send analytics events.
*/ */
insights?: boolean insights?: boolean
/** /**
* Configuration or assistant id to enable ask ai mode. Pass a string assistant id or a full config object. * Configuration or agent id to enable ask ai mode. Pass a string agent id or a full config object.
*/ */
askAi?: DocSearchAskAi | string askAi?: DocSearchAskAi | string
/** /**

Loading…
Cancel
Save