diff --git a/__tests__/unit/client/theme-default/support/docsearch.test.ts b/__tests__/unit/client/theme-default/support/docsearch.test.ts index 78c5ee68..563540a2 100644 --- a/__tests__/unit/client/theme-default/support/docsearch.test.ts +++ b/__tests__/unit/client/theme-default/support/docsearch.test.ts @@ -3,6 +3,7 @@ import { buildSidePanelProps, hasAskAi, hasKeywordSearch, + mergeLangFilters, mergeLangFacetFilters, validateCredentials } 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', () => { test('returns true when all credentials are provided', () => { expect( hasKeywordSearch({ appId: 'app', apiKey: 'key', - indexName: 'index' + indices: ['index'] }) ).toBe(true) }) @@ -57,41 +100,48 @@ describe('client/theme-default/support/docsearch', () => { hasKeywordSearch({ appId: undefined, apiKey: 'key', - indexName: 'index' + indices: ['index'] }) ).toBe(false) expect( hasKeywordSearch({ appId: 'app', apiKey: undefined, - indexName: 'index' + indices: ['index'] + }) + ).toBe(false) + expect( + hasKeywordSearch({ + appId: 'app', + apiKey: 'key', + indices: undefined }) ).toBe(false) expect( hasKeywordSearch({ appId: 'app', apiKey: 'key', - indexName: undefined + indices: [] }) ).toBe(false) }) }) describe('hasAskAi', () => { - test('returns true for valid string assistantId', () => { - expect(hasAskAi('assistant123')).toBe(true) + test('returns true for valid string agentId', () => { + expect(hasAskAi('agent123')).toBe(true) }) - test('returns false for empty string assistantId', () => { + test('returns false for empty string agentId', () => { expect(hasAskAi('')).toBe(false) }) - test('returns true for object with assistantId', () => { - expect(hasAskAi({ assistantId: 'assistant123' } as any)).toBe(true) + test('returns true for object with agentId', () => { + expect(hasAskAi({ agentId: 'agent123' } as any)).toBe(true) }) - test('returns false for object without assistantId', () => { - expect(hasAskAi({ assistantId: null } as any)).toBe(false) + test('returns false for object without agentId', () => { + expect(hasAskAi({ agentId: null } as any)).toBe(false) expect(hasAskAi({} as any)).toBe(false) }) @@ -105,12 +155,12 @@ describe('client/theme-default/support/docsearch', () => { const result = validateCredentials({ appId: 'app', apiKey: 'key', - indexName: 'index' + indices: ['index'] }) expect(result.valid).toBe(true) expect(result.appId).toBe('app') expect(result.apiKey).toBe('key') - expect(result.indexName).toBe('index') + expect(result.indices).toEqual(['index']) }) test('invalidates incomplete credentials', () => { @@ -118,87 +168,140 @@ describe('client/theme-default/support/docsearch', () => { validateCredentials({ appId: undefined, apiKey: 'key', - indexName: 'index' + indices: ['index'] }).valid ).toBe(false) }) }) describe('buildAskAiConfig', () => { - test('builds config from string assistantId', () => { + test('builds config from string agentId', () => { const result = buildAskAiConfig( - 'assistant123', + 'agent123', { appId: 'app', apiKey: 'key', - indexName: 'index' + indices: ['index'] } as any, 'en' ) - expect(result.assistantId).toBe('assistant123') + expect(result.agentId).toBe('agent123') expect(result.appId).toBe('app') expect(result.apiKey).toBe('key') - expect(result.indexName).toBe('index') + expect(result.indices).toBeUndefined() }) test('builds config from object with overrides', () => { const result = buildAskAiConfig( { - assistantId: 'assistant123', + agentId: 'agent123', appId: 'custom-app', - apiKey: 'custom-key', - indexName: 'custom-index' + apiKey: 'custom-key' } as any, { appId: 'default-app', apiKey: 'default-key', - indexName: 'default-index' + indices: ['default-index'] } as any, 'en' ) - expect(result.assistantId).toBe('assistant123') + expect(result.agentId).toBe('agent123') expect(result.appId).toBe('custom-app') 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( { - assistantId: 'assistant123', + agentId: 'agent123', + indices: ['ai_index'], searchParameters: { - facetFilters: ['tag:docs'] + ai_index: { + filters: 'tag:docs' + } } - } as any, + }, { appId: 'app', apiKey: 'key', - indexName: 'index' - } as any, + indices: ['index'] + }, + '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' ) - 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( - 'assistant123', + { + agentId: 'agent123', + indices: ['configured_index'], + searchParameters: { + parameter_index: { + distinct: false + } + } + }, { appId: 'app', 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, '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( { - assistantId: 'assistant123', - agentStudio: true, + agentId: 'agent123', searchParameters: { index: { distinct: false @@ -208,7 +311,7 @@ describe('client/theme-default/support/docsearch', () => { { appId: 'app', apiKey: 'key', - indexName: 'index', + indices: ['index'], searchParameters: { facetFilters: ['tag:docs'] } @@ -218,7 +321,8 @@ describe('client/theme-default/support/docsearch', () => { expect(result.searchParameters).toEqual({ index: { - distinct: false + distinct: false, + filters: 'lang:en' } }) 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', () => { const result = buildAskAiConfig( { - assistantId: 'assistant123', - agentStudio: true + agentId: 'agent123' } as any, { appId: 'app', apiKey: 'key', - indexName: 'index', + indices: ['index'], searchParameters: { facetFilters: ['tag:docs'] } @@ -249,15 +352,13 @@ describe('client/theme-default/support/docsearch', () => { test('passes resolved Ask AI options to the side panel', () => { const result = buildSidePanelProps( { - assistantId: 'assistant123', - agentStudio: true, + agentId: 'agent123', searchParameters: { index: { facetFilters: ['lang:en'] } }, suggestedQuestions: true, - useStagingEnv: true, sidePanel: { button: { variant: 'inline' @@ -271,7 +372,7 @@ describe('client/theme-default/support/docsearch', () => { { appId: 'app', apiKey: 'key', - indexName: 'index' + indices: ['index'] } as any ) @@ -279,16 +380,13 @@ describe('client/theme-default/support/docsearch', () => { container: '#vp-docsearch-sidepanel', appId: 'app', apiKey: 'key', - indexName: 'index', - assistantId: 'assistant123', - agentStudio: true, + agentId: 'agent123', searchParameters: { index: { facetFilters: ['lang:en'] } }, suggestedQuestions: true, - useStagingEnv: true, button: { variant: 'inline' }, diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 64158acf..ef0ad6cf 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -89,11 +89,13 @@ export default defineConfig({ options: { appId: '8J64VVRP8K', apiKey: '52f578a92b88ad6abde815aae2b0ad7c', - indexName: 'vitepress', - askAi: { - assistantId: 'YaVSonfX5bS8', - sidePanel: true - } + indices: ['vitepress'] + // TODO: Update to use Agent Studio `agentId` once created. + // For now the Ask AI functionality won't work as DocSearch v5 only support Agent Studio. + // askAi: { + // agentId: '', + // sidePanel: true + // } } }, diff --git a/docs/en/reference/default-theme-search.md b/docs/en/reference/default-theme-search.md index e497d84c..220ee2bd 100644 --- a/docs/en/reference/default-theme-search.md +++ b/docs/en/reference/default-theme-search.md @@ -208,7 +208,7 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...' + indices: ['...'] } } } @@ -226,10 +226,14 @@ You can use a config like this to use multilingual search: -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} +::: 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`: ```ts @@ -242,16 +246,15 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', - // askAi: "YOUR-ASSISTANT-ID" + indices: ['...'], + // askAi: "YOUR-AGENT-ID" // OR askAi: { - // at minimum you must provide the assistantId you received from Algolia - assistantId: 'XXXYYY', - // optional overrides – if omitted, the top-level appId/apiKey/indexName values are reused + // at minimum you must provide the agentId you received from Algolia + agentId: 'XXXYYY', + // optional overrides – if omitted, the top-level appId/apiKey values are reused // apiKey: '...', // 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} -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 import { defineConfig } from 'vitepress' @@ -277,9 +280,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { panel: { variant: 'floating', // or 'inline' @@ -314,9 +317,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { keyboardShortcuts: { 'Ctrl/Cmd+I': false @@ -352,10 +355,9 @@ export default defineConfig({ options: { mode: 'sidePanel', askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', appId: '...', apiKey: '...', - indexName: '...', sidePanel: true } } diff --git a/docs/es/reference/default-theme-search.md b/docs/es/reference/default-theme-search.md index 7d5a719d..281e02ea 100644 --- a/docs/es/reference/default-theme-search.md +++ b/docs/es/reference/default-theme-search.md @@ -198,7 +198,7 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...' + indices: ['...'] } } } @@ -216,10 +216,14 @@ Puedes utilizar una configuración como esta para utilizar la búsqueda multilin -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} +::: 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`: ```ts @@ -232,16 +236,15 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', - // askAi: "TU-ID-DE-ASISTENTE" + indices: ['...'], + // askAi: "TU-ID-DE-AGENTE" // O askAi: { - // como mínimo debes proporcionar el assistantId que recibiste de Algolia - assistantId: 'XXXYYY', - // anulaciones opcionales — si se omiten, se reutilizan los valores appId/apiKey/indexName de nivel superior + // como mínimo debes proporcionar el agentId que recibiste de Algolia + agentId: 'XXXYYY', + // anulaciones opcionales — si se omiten, se reutilizan los valores appId/apiKey de nivel superior // apiKey: '...', // 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} -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 import { defineConfig } from 'vitepress' @@ -267,9 +270,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { // Refleja la API de @docsearch/sidepanel-js SidepanelProps panel: { @@ -299,9 +302,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { keyboardShortcuts: { 'Ctrl/Cmd+I': false @@ -337,10 +340,9 @@ export default defineConfig({ options: { mode: 'sidePanel', askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', appId: '...', apiKey: '...', - indexName: '...', sidePanel: true } } diff --git a/docs/fa/reference/default-theme-search.md b/docs/fa/reference/default-theme-search.md index 0ef6902e..9cd0783a 100644 --- a/docs/fa/reference/default-theme-search.md +++ b/docs/fa/reference/default-theme-search.md @@ -198,7 +198,7 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...' + indices: ['...'] } } } @@ -216,10 +216,14 @@ export default defineConfig({ -برای اطلاعات بیشتر به [مستندات رسمی 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} +::: 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` را اضافه کنید: ```ts @@ -232,16 +236,15 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', - // askAi: "شناسه-دستیار-شما" + indices: ['...'], + // askAi: "شناسه-عامل-شما" // یا askAi: { - // حداقل باید assistantId دریافت شده از Algolia را ارائه کنید - assistantId: 'XXXYYY', - // بازنویسی های اختیاری — اگر حذف شوند، مقادیر appId/apiKey/indexName سطح بالا دوباره استفاده می شوند + // حداقل باید agentId دریافت شده از Algolia را ارائه کنید + agentId: 'XXXYYY', + // بازنویسی های اختیاری — اگر حذف شوند، مقادیر appId/apiKey سطح بالا دوباره استفاده می شوند // apiKey: '...', // appId: '...', - // indexName: '...' } } } @@ -255,7 +258,7 @@ export default defineConfig({ ### پنل کناری 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 import { defineConfig } from 'vitepress' @@ -267,9 +270,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { // آینه API @docsearch/sidepanel-js SidepanelProps panel: { @@ -299,9 +302,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { keyboardShortcuts: { 'Ctrl/Cmd+I': false @@ -337,10 +340,9 @@ export default defineConfig({ options: { mode: 'sidePanel', askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', appId: '...', apiKey: '...', - indexName: '...', sidePanel: true } } diff --git a/docs/ja/reference/default-theme-search.md b/docs/ja/reference/default-theme-search.md index 4e6150b2..1890d8b0 100644 --- a/docs/ja/reference/default-theme-search.md +++ b/docs/ja/reference/default-theme-search.md @@ -204,7 +204,7 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...' + indices: ['...'] } } } @@ -222,10 +222,14 @@ export default defineConfig({ -詳しくは[公式 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} +::: 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` オプション(またはその一部)を指定します。 ```ts @@ -238,16 +242,15 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', - // askAi: "あなたのアシスタントID" + indices: ['...'], + // askAi: "あなたのエージェントID" // または askAi: { - // 最低限、Algolia から受け取った assistantId を指定する必要があります - assistantId: 'XXXYYY', - // 任意の上書き — 省略した場合は上位の appId/apiKey/indexName を再利用 + // 最低限、Algolia から受け取った agentId を指定する必要があります + agentId: 'XXXYYY', + // 任意の上書き — 省略した場合は上位の appId/apiKey の値を再利用 // apiKey: '...', // appId: '...', - // indexName: '...' } } } @@ -261,7 +264,7 @@ export default defineConfig({ ### 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 import { defineConfig } from 'vitepress' @@ -273,9 +276,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { // @docsearch/sidepanel-js SidepanelProps API をミラー panel: { @@ -305,9 +308,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { keyboardShortcuts: { 'Ctrl/Cmd+I': false @@ -343,10 +346,9 @@ export default defineConfig({ options: { mode: 'sidePanel', askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', appId: '...', apiKey: '...', - indexName: '...', sidePanel: true } } diff --git a/docs/ko/reference/default-theme-search.md b/docs/ko/reference/default-theme-search.md index a65f1a6a..86bedd10 100644 --- a/docs/ko/reference/default-theme-search.md +++ b/docs/ko/reference/default-theme-search.md @@ -198,7 +198,7 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...' + indices: ['...'] } } } @@ -216,10 +216,14 @@ export default defineConfig({ -자세한 내용은 [공식 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} +::: 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` 옵션을 추가하세요: ```ts @@ -232,16 +236,15 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', - // askAi: "내-어시스턴트-ID" + indices: ['...'], + // askAi: "내-에이전트-ID" // 또는 askAi: { - // 최소한 Algolia에서 받은 assistantId를 제공해야 합니다 - assistantId: 'XXXYYY', - // 선택적 재정의 — 생략하면 상위 appId/apiKey/indexName 값이 재사용됩니다 + // 최소한 Algolia에서 받은 agentId를 제공해야 합니다 + agentId: 'XXXYYY', + // 선택적 재정의 — 생략하면 상위 appId/apiKey 값이 재사용됩니다 // apiKey: '...', // appId: '...', - // indexName: '...' } } } @@ -255,7 +258,7 @@ Ask AI를 사용하지 않으려면 `askAi` 옵션을 생략하면 됩니다. ### 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 import { defineConfig } from 'vitepress' @@ -267,9 +270,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { // @docsearch/sidepanel-js SidepanelProps API 반영 panel: { @@ -299,9 +302,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { keyboardShortcuts: { 'Ctrl/Cmd+I': false @@ -337,10 +340,9 @@ export default defineConfig({ options: { mode: 'sidePanel', askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', appId: '...', apiKey: '...', - indexName: '...', sidePanel: true } } diff --git a/docs/pt/reference/default-theme-search.md b/docs/pt/reference/default-theme-search.md index 69beec9d..91637b67 100644 --- a/docs/pt/reference/default-theme-search.md +++ b/docs/pt/reference/default-theme-search.md @@ -198,7 +198,7 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...' + indices: ['...'] } } } @@ -216,10 +216,14 @@ Você pode usar uma configuração como esta para usar a pesquisa multilínguas: -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} +::: 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`: ```ts @@ -232,16 +236,15 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', - // askAi: "SEU-ID-DO-ASSISTENTE" + indices: ['...'], + // askAi: "SEU-ID-DO-AGENTE" // OU askAi: { - // no mínimo, você deve fornecer o assistantId recebido da Algolia - assistantId: 'XXXYYY', - // substituições opcionais — se omitidas, os valores appId/apiKey/indexName de nível superior são reutilizados + // no mínimo, você deve fornecer o agentId recebido da Algolia + agentId: 'XXXYYY', + // substituições opcionais — se omitidas, os valores appId/apiKey de nível superior são reutilizados // apiKey: '...', // 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} -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 import { defineConfig } from 'vitepress' @@ -267,9 +270,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { // Espelha a API do @docsearch/sidepanel-js SidepanelProps panel: { @@ -299,9 +302,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { keyboardShortcuts: { 'Ctrl/Cmd+I': false @@ -337,10 +340,9 @@ export default defineConfig({ options: { mode: 'sidePanel', askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', appId: '...', apiKey: '...', - indexName: '...', sidePanel: true } } diff --git a/docs/ru/reference/default-theme-search.md b/docs/ru/reference/default-theme-search.md index cd302734..64e924d3 100644 --- a/docs/ru/reference/default-theme-search.md +++ b/docs/ru/reference/default-theme-search.md @@ -208,7 +208,7 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...' + indices: ['...'] } } } @@ -226,10 +226,14 @@ export default defineConfig({ -Подробности см. в [официальной документации 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} +::: 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`: ```ts @@ -242,16 +246,15 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', - // askAi: "ВАШ-ID-АССИСТЕНТА" + indices: ['...'], + // askAi: "ВАШ-ID-АГЕНТА" // ИЛИ askAi: { - // как минимум нужно указать assistantId, полученный от Algolia - assistantId: 'XXXYYY', - // необязательные переопределения — если их нет, используются значения appId/apiKey/indexName верхнего уровня + // как минимум нужно указать agentId, полученный от Algolia + agentId: 'XXXYYY', + // необязательные переопределения — если их нет, используются значения appId/apiKey верхнего уровня // apiKey: '...', // appId: '...', - // indexName: '...' } } } @@ -265,7 +268,7 @@ export default defineConfig({ ### Боковая панель 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 import { defineConfig } from 'vitepress' @@ -277,9 +280,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { panel: { variant: 'floating', // или 'inline' @@ -310,9 +313,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { keyboardShortcuts: { 'Ctrl/Cmd+I': false @@ -348,10 +351,9 @@ export default defineConfig({ options: { mode: 'sidePanel', askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', appId: '...', apiKey: '...', - indexName: '...', sidePanel: true } } diff --git a/docs/snippets/algolia-i18n.ts b/docs/snippets/algolia-i18n.ts index 0dfa5107..cd8f1603 100644 --- a/docs/snippets/algolia-i18n.ts +++ b/docs/snippets/algolia-i18n.ts @@ -7,7 +7,7 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], locales: { zh: { translations: { @@ -33,6 +33,14 @@ export default defineConfig({ viewConversationHistoryText: '对话历史', threadDepthErrorPlaceholder: '对话已达上限' }, + facets: { + defaultValueLabel: '全部', + facetMenuTriggerAriaLabel: '已选择', + clearAllLabel: '清除全部', + facetsAriaLabel: '搜索筛选条件', + selectedFacetsAriaLabel: '已选择的搜索筛选条件', + clearFacetAriaLabel: '清除筛选条件:' + }, newConversation: { newConversationTitle: '我今天能帮你什么?', newConversationDescription: @@ -72,7 +80,11 @@ export default defineConfig({ }, resultsScreen: { askAiPlaceholder: '询问 AI:', - noResultsAskAiPlaceholder: '文档里没找到?让 Ask AI 帮忙:' + noResultsAskAiPlaceholder: '文档里没找到?让 Ask AI 帮忙:', + resultsSectionTitle: '搜索结果', + askAiResultsTitle: '询问 AI 助手', + recentConversationTimestampFallback: '不久前', + resultBadgeLabelText: '类别' }, askAiScreen: { disclaimerText: '回答由 AI 生成,可能会出错。请核实。', @@ -89,7 +101,24 @@ export default defineConfig({ afterToolCallText: '已搜索', stoppedStreamingText: '你已停止此回复', 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: '再问一个问题', promptDisclaimerText: '回答由 AI 生成,可能会出错。', promptLabelText: '按回车发送,Shift+回车换行。', - promptAriaLabelText: '问题输入' + promptAriaLabelText: '问题输入', + startNewConversationButtonText: '开始新的对话', + blockingErrorContinueText: '以继续。', + blockingErrorFallbackText: '此对话无法继续。' }, conversationScreen: { preToolCallText: '搜索中...', @@ -132,7 +164,22 @@ export default defineConfig({ likeButtonTitle: '喜欢', dislikeButtonTitle: '不喜欢', thanksForFeedbackText: '感谢你的反馈!', - errorTitleText: '聊天错误' + errorTitleText: '聊天错误', + relatedSourcesTextPlural: '相关来源', + savedMemoryToolResultText: '已保存到记忆', + memoryToolResultText: '已使用记忆增强结果', + feedbackPanelTitle: '哪里出了问题?(可选)', + feedbackDetailsPlaceholder: '请分享更多细节...', + feedbackDisclaimerText: '反馈中将包含此对话的副本。', + feedbackSubmitButtonText: '提交', + feedbackCloseButtonTitle: '关闭', + feedbackTagIncorrect: '不正确或不完整', + feedbackTagNotWhatIAsked: '不是我想问的', + feedbackTagSlowOrBuggy: '响应缓慢或存在故障', + feedbackTagStyleOrTone: '风格或语气', + feedbackTagSafetyOrLegal: '安全或法律问题', + feedbackTagOther: '其他', + suggestedPromptsTitleText: '推荐问题' }, newConversationScreen: { titleText: '我今天能帮你什么?', diff --git a/docs/zh/reference/default-theme-search.md b/docs/zh/reference/default-theme-search.md index 859a2a23..d59d7cf3 100644 --- a/docs/zh/reference/default-theme-search.md +++ b/docs/zh/reference/default-theme-search.md @@ -198,7 +198,7 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...' + indices: ['...'] } } } @@ -216,10 +216,14 @@ export default defineConfig({ -更多信息请参考[官方 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} +::: 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`: ```ts @@ -232,16 +236,15 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', - // askAi: "你的助手ID" + indices: ['...'], + // askAi: "你的智能体ID" // 或 askAi: { - // 至少需要提供从 Algolia 获取的 assistantId - assistantId: 'XXXYYY', - // 可选覆盖 — 若省略,将复用顶层 appId/apiKey/indexName 的值 + // 至少需要提供从 Algolia 获取的 agentId + agentId: 'XXXYYY', + // 可选覆盖 — 若省略,将复用顶层 appId/apiKey 的值 // apiKey: '...', - // appId: '...', - // indexName: '...' + // appId: '...' } } } @@ -255,7 +258,7 @@ export default defineConfig({ ### 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 import { defineConfig } from 'vitepress' @@ -267,9 +270,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { // 镜像 @docsearch/sidepanel-js SidepanelProps API panel: { @@ -299,9 +302,9 @@ export default defineConfig({ options: { appId: '...', apiKey: '...', - indexName: '...', + indices: ['...'], askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', sidePanel: { keyboardShortcuts: { 'Ctrl/Cmd+I': false @@ -337,10 +340,9 @@ export default defineConfig({ options: { mode: 'sidePanel', askAi: { - assistantId: 'XXXYYY', + agentId: 'XXXYYY', appId: '...', apiKey: '...', - indexName: '...', sidePanel: true } } diff --git a/package.json b/package.json index a36f5a63..b3b7f5ba 100644 --- a/package.json +++ b/package.json @@ -93,9 +93,9 @@ "*": "prettier --experimental-cli --ignore-unknown --write" }, "dependencies": { - "@docsearch/css": "^4.7.0", - "@docsearch/js": "^4.7.0", - "@docsearch/sidepanel-js": "^4.7.0", + "@docsearch/css": "^5.0.4", + "@docsearch/js": "^5.0.4", + "@docsearch/sidepanel-js": "^5.0.4", "@iconify-json/simple-icons": "^1.2.93", "@shikijs/transformers": "^4.4.3", "@types/markdown-it": "^14.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a714130a..0757a9ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,14 +15,14 @@ importers: .: dependencies: '@docsearch/css': - specifier: ^4.7.0 - version: 4.7.0 + specifier: ^5.0.4 + version: 5.0.4 '@docsearch/js': - specifier: ^4.7.0 - version: 4.7.0 + specifier: ^5.0.4 + version: 5.0.4 '@docsearch/sidepanel-js': - specifier: ^4.7.0 - version: 4.7.0 + specifier: ^5.0.4 + version: 5.0.4 '@iconify-json/simple-icons': specifier: ^1.2.93 version: 1.2.93 @@ -385,14 +385,14 @@ packages: resolution: {integrity: sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==} engines: {node: '>=22'} - '@docsearch/css@4.7.0': - resolution: {integrity: sha512-Sk5xkdRFeE7PeWjG9l4AfTwdvMfr9wHiwNNCpHXT4v4SNyNMKdHGvEILc31BgaVFGDDNbv5u/a73tofRiwbEZw==} + '@docsearch/css@5.0.4': + resolution: {integrity: sha512-Bg2VmrPbhBmqKBrt6FL8bvd66f9IaU448Ip9K+elLlX2rivtV5FvBJq7iGsMvvj42etsdJe6VqisoTKZMv0Qdg==} - '@docsearch/js@4.7.0': - resolution: {integrity: sha512-x5lCqu1tetgsJFkjQ6VSocbHldsRkGEgwg5N98Vx21sq/V5wcmj4u226PY9k+TEpIgQ772zlYbPLTPicWyGnpA==} + '@docsearch/js@5.0.4': + resolution: {integrity: sha512-eKoVwAWKWXYPDZiDXBX4g1RVQnvHHCX91+Z+1ZEJuHPkZjTANk+L4/9t6xM5wxiiNcwD6YjfDMhcE/99sWT4SA==} - '@docsearch/sidepanel-js@4.7.0': - resolution: {integrity: sha512-A8r34jCU8kcIk2viECEn2msA28ojUF1BLi/3v5OWWc5G2N3jOuuumBXoeYjfr8dA0UxgFSy5R2bt12dnFJQSyA==} + '@docsearch/sidepanel-js@5.0.4': + resolution: {integrity: sha512-RZ6YO4SvLqfjlz49fNlYtlHARSuD0YxAQ8HTl5DDbiW5iHNu8Ichu0teughu1uLBVg06t4OELP3RRU6O7f0fHQ==} '@hapi/address@5.1.1': resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} @@ -2891,11 +2891,11 @@ snapshots: '@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': dependencies: diff --git a/src/client/theme-default/components/VPAlgoliaSearchBox.vue b/src/client/theme-default/components/VPAlgoliaSearchBox.vue index 0ef69015..d1eaba0d 100644 --- a/src/client/theme-default/components/VPAlgoliaSearchBox.vue +++ b/src/client/theme-default/components/VPAlgoliaSearchBox.vue @@ -13,8 +13,6 @@ import { validateCredentials } from '../support/docsearch' -import '../styles/docsearch.css' - const props = defineProps<{ algoliaOptions: DefaultTheme.AlgoliaSearchOptions openRequest?: { @@ -31,7 +29,8 @@ let docsearchInstance: DocSearchInstance | undefined let sidepanelInstance: SidepanelInstance | undefined let openOnReady: 'search' | 'askAi' | null = null let initializeCount = 0 -let docsearchLoader: Promise | undefined +let docsearchLoader: Promise | undefined +let docsearchAiLoader: Promise | undefined let sidepanelLoader: Promise | undefined let lastFocusedElement: HTMLElement | null = null let skipEventDocsearch = false @@ -82,11 +81,13 @@ async function update(options: DefaultTheme.AlgoliaSearchOptions) { const { valid, ...credentials } = validateCredentials({ appId: options.appId ?? askAi?.appId, apiKey: options.apiKey ?? askAi?.apiKey, - indexName: options.indexName ?? askAi?.indexName + indices: options.indices, + mode: options.mode, + askAi: options.askAi }) 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 } @@ -99,16 +100,24 @@ async function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) { // Always tear down previous instances first (e.g. on locale changes) cleanup() - const { useSidePanel } = resolveMode(userOptions) + const { useSidePanel, showKeywordSearch } = resolveMode(userOptions) 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 (useSidePanel && askAi?.sidePanel) { - const { default: sidepanel } = await loadSidepanel() - if (currentInitialize !== initializeCount) return + const docsearch = docsearchModule?.default + const sidepanel = sidepanelModule?.default + if (usingSidepanel && sidepanel) { sidepanelInstance = sidepanel({ ...buildSidePanelProps(askAi, userOptions), onOpen: focusInput, @@ -125,41 +134,43 @@ async function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) { }) } - const options = { - ...userOptions, - container: '#vp-docsearch', - navigator: { - 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) - ...(useSidePanel && sidepanelInstance && { - interceptAskAiEvent: (initialMessage) => { - onBeforeOpen('sidepanel', () => sidepanelInstance?.open(initialMessage)) - return true - } - }), - onOpen: focusInput, - onClose: onClose.bind(null, 'docsearch'), - onReady: () => { - if (openOnReady === 'search') { - openOnReady = null - onBeforeOpen('docsearch', () => docsearchInstance?.open()) - } else if (openOnReady === 'askAi' && !sidepanelInstance) { - // No sidepanel configured, use docsearch modal for askAi - openOnReady = null - onBeforeOpen('docsearch', () => docsearchInstance?.openAskAi()) + if (docsearch) { + const options = { + ...userOptions, + container: '#vp-docsearch', + navigator: { + 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) + ...(useSidePanel && sidepanelInstance && { + interceptAskAiEvent: (initialMessage) => { + onBeforeOpen('sidepanel', () => sidepanelInstance?.open(initialMessage)) + return true + } + }), + onOpen: focusInput, + onClose: onClose.bind(null, 'docsearch'), + onReady: () => { + if (openOnReady === 'search') { + openOnReady = null + onBeforeOpen('docsearch', () => docsearchInstance?.open()) + } else if (openOnReady === 'askAi' && !sidepanelInstance) { + // No sidepanel configured, use docsearch modal for askAi + openOnReady = null + onBeforeOpen('docsearch', () => docsearchInstance?.openAskAi()) + } + }, + keyboardShortcuts: { + '/': false, + 'Ctrl/Cmd+K': false } - }, - keyboardShortcuts: { - '/': false, - 'Ctrl/Cmd+K': false - } - } as DocSearchProps + } as DocSearchProps - docsearchInstance = docsearch(options) + docsearchInstance = docsearch(options) + } cleanup = () => { docsearchInstance?.destroy() @@ -221,10 +232,34 @@ function onClose(target: 'docsearch' | 'sidepanel') { } } -function loadDocsearch() { - if (!docsearchLoader) { - docsearchLoader = import('@docsearch/js') +// DocSearch V5 now splits it's CSS bundles, namely that `modal.css` does not contain Ask AI related CSS. +// With this, we can better target load the needed CSS bundles to save some page load. +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 } diff --git a/src/client/theme-default/styles/docsearch.css b/src/client/theme-default/styles/docsearch.css index f011a408..1802c47a 100644 --- a/src/client/theme-default/styles/docsearch.css +++ b/src/client/theme-default/styles/docsearch.css @@ -1,6 +1,3 @@ -@import '@docsearch/css/dist/style.css'; -@import '@docsearch/css/dist/sidepanel.css'; - #vp-docsearch, #vp-docsearch-sidepanel, .DocSearch-SidepanelButton { @@ -10,11 +7,14 @@ :root:root { --docsearch-actions-height: 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-modal-radius: 0.25rem; --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-popover-background: var(--vp-c-bg-alt); + --docsearch-popover-arrow-color: var(--docsearch-popover-background); --docsearch-focus-color: var(--vp-c-brand-1); --docsearch-footer-background: var(--vp-c-bg-alt); --docsearch-footer-height: 3.25rem; @@ -23,6 +23,7 @@ --docsearch-hit-color: var(--vp-c-text-1); --docsearch-hit-height: 3.5rem; --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-key-background: var(--vp-code-bg); --docsearch-modal-background: var(--vp-c-bg-soft); @@ -43,6 +44,7 @@ --docsearch-subtle-color: var(--vp-c-divider); --docsearch-success-color: var(--vp-c-brand-soft); --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 @@ -135,3 +137,8 @@ font-size: revert; line-height: revert; } + +.DocSearch-Feedback-Panel-Submit { + background: var(--vp-c-brand-soft); + color: var(--vp-c-brand-1); +} diff --git a/src/client/theme-default/support/docsearch.ts b/src/client/theme-default/support/docsearch.ts index aef6866e..8956551b 100644 --- a/src/client/theme-default/support/docsearch.ts +++ b/src/client/theme-default/support/docsearch.ts @@ -10,7 +10,7 @@ export interface ValidatedCredentials { valid: boolean appId?: string apiKey?: string - indexName?: string + indices?: DefaultTheme.AlgoliaSearchOptions['indices'] } export type DocSearchMode = 'auto' | 'sidePanel' | 'hybrid' | 'modal' @@ -21,14 +21,6 @@ export interface ResolvedMode { 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. * @@ -40,7 +32,7 @@ export type ResolvedSidePanelProps = SidepanelProps & { export function resolveMode( options: Pick< DefaultTheme.AlgoliaSearchOptions, - 'appId' | 'apiKey' | 'indexName' | 'askAi' | 'mode' + 'appId' | 'apiKey' | 'indices' | 'askAi' | 'mode' > ): ResolvedMode { const mode = options.mode ?? 'auto' @@ -63,7 +55,7 @@ export function resolveMode( // Force hybrid - keyword search must be configured if (!hasKeyword) { console.error( - '[vitepress] mode: "hybrid" requires keyword search credentials (appId, apiKey, indexName).' + '[vitepress] mode: "hybrid" requires keyword search credentials (appId, apiKey, indices).' ) } return { @@ -94,10 +86,15 @@ export function resolveMode( export function hasKeywordSearch( options: Pick< DefaultTheme.AlgoliaSearchOptions, - 'appId' | 'apiKey' | 'indexName' + 'appId' | 'apiKey' | 'indices' > ): boolean { - return Boolean(options.appId && options.apiKey && options.indexName) + return Boolean( + options.appId && + options.apiKey && + options.indices && + options.indices.length > 0 + ) } export function hasAskAi( @@ -105,7 +102,40 @@ export function hasAskAi( ): boolean { if (!askAi) return false 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}`] } +type CredentialOptions = Pick< + DefaultTheme.AlgoliaSearchOptions, + 'appId' | 'apiKey' | 'indices' | 'mode' | 'askAi' +> + /** * Validates that required Algolia credentials are present. */ export function validateCredentials( - options: Pick< - DefaultTheme.AlgoliaSearchOptions, - 'appId' | 'apiKey' | 'indexName' - > + options: CredentialOptions ): ValidatedCredentials { const appId = options.appId 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 { - valid: Boolean(appId && apiKey && indexName), + valid: isValid, appId, apiKey, - indexName + indices } } @@ -174,41 +233,45 @@ export function buildAskAiConfig( ): DocSearchAskAi { const isAskAiString = typeof askAiProp === 'string' - const askAiSearchParameters = - !isAskAiString && askAiProp.searchParameters - ? { ...askAiProp.searchParameters } - : undefined - const isAgentStudio = !isAskAiString && askAiProp.agentStudio === true - - const askAiFacetFiltersSource = - askAiSearchParameters?.facetFilters ?? - options.searchParameters?.facetFilters - const askAiFacetFilters = mergeLangFacetFilters( - askAiFacetFiltersSource as FacetFilter | FacetFilter[] | undefined, - lang - ) + let askAiSearchParameters: DocSearchAskAi['searchParameters'] + + if (!isAskAiString) { + const mergedSearchParameters: NonNullable< + DocSearchAskAi['searchParameters'] + > = {} + + const indexes = new Set([ + ...(askAiProp.indices ?? []), + ...Object.keys(askAiProp.searchParameters ?? {}) + ]) - const mergedAskAiSearchParameters = isAgentStudio - ? askAiSearchParameters - : { - ...askAiSearchParameters, - facetFilters: askAiFacetFilters.length ? askAiFacetFilters : undefined + for (const indexName of indexes) { + const searchParameters = askAiProp.searchParameters?.[indexName] ?? {} + + mergedSearchParameters[indexName] = { + ...searchParameters, + filters: mergeLangFilters(searchParameters.filters, lang) } + } + + if (indexes.size > 0) { + askAiSearchParameters = mergedSearchParameters + } + } 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 + agentId: isAskAiString ? askAiProp : askAiProp.agentId } // Keep `searchParameters` undefined unless it has at least one key. if ( - mergedAskAiSearchParameters && - Object.values(mergedAskAiSearchParameters).some((v) => v != null) + askAiSearchParameters && + Object.values(askAiSearchParameters).some((v) => v != null) ) { - result.searchParameters = mergedAskAiSearchParameters + result.searchParameters = askAiSearchParameters } return result @@ -220,19 +283,18 @@ export function buildAskAiConfig( export function buildSidePanelProps( askAi: DocSearchAskAi, options: DefaultTheme.AlgoliaSearchOptions -): ResolvedSidePanelProps { +): SidepanelProps { const { sidePanel, ...askAiRest } = JSON.parse( JSON.stringify(askAi) ) as DocSearchAskAi return { container: '#vp-docsearch-sidepanel', - indexName: options.indexName, appId: options.appId, apiKey: options.apiKey, ...askAiRest, ...(sidePanel && sidePanel !== true ? sidePanel : {}) - } as ResolvedSidePanelProps + } as SidepanelProps } /** @@ -246,17 +308,33 @@ export function resolveOptionsForLanguage( ): DefaultTheme.AlgoliaSearchOptions { options = deepMerge(options, options.locales?.[localeIndex] || {}) - const facetFilters = mergeLangFacetFilters( - options.searchParameters?.facetFilters, - lang - ) + const indices = (options.indices ?? []).map((index) => { + if (typeof index === 'string') { + return { + name: index, + searchParameters: { facetFilters: [`lang:${lang}`] } + } + } + + return { + name: index.name, + searchParameters: { + ...index.searchParameters, + facetFilters: mergeLangFacetFilters( + index.searchParameters?.facetFilters, + lang + ) + } + } + }) + const askAi = options.askAi ? buildAskAiConfig(options.askAi, options, lang) : undefined return { ...options, - searchParameters: { ...options.searchParameters, facetFilters }, + indices, askAi } } diff --git a/types/docsearch.d.ts b/types/docsearch.d.ts index 9fbc4cab..8d4a4955 100644 --- a/types/docsearch.d.ts +++ b/types/docsearch.d.ts @@ -13,22 +13,16 @@ export type DocSearchProps = Partial< | 'translations' | 'recentSearchesLimit' | '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?: 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 /**