Merge branch 'v5' into feat/add-docs-mcp-callout
This commit is contained in:
commit
d99cbf683e
142 changed files with 16803 additions and 3817 deletions
|
|
@ -233,7 +233,10 @@ Toolchain is pinned in `.tool-versions`: Node `24.13.1` (managed via `fnm`) and
|
|||
|
||||
Non-obvious caveats:
|
||||
|
||||
- The **Docusaurus website does not run under Bun on this branch**. Both `bun run website:build` and the dev server (`bun run website:start` / `website:test`) fail at plugin load with `ERR_PACKAGE_PATH_NOT_EXPORTED` (e.g. `entities/lib/decode.js`, from `docusaurus` → `cheerio`/`htmlparser2`) because of how Bun hoists transitive deps. Consequently, **`bun run build` (which ends with `website:build`) and Playwright E2E (`bun run pw:*`, which boots the website first) cannot complete as-is.** Build the libraries only with: `bun run --sequential build:stage:base build:stage:react build:stage:consumers build:stage:adapter`.
|
||||
- **To run/demo the widget, use the React playground:** `bun run playground:start` serves at `http://localhost:5173` (Vite). `bun run playground-js:start` serves the vanilla-JS demo. These connect to Algolia's hosted index using public credentials baked into the demo, so **outbound internet is required** for live search results.
|
||||
- Run unit tests non-interactively with `bun run test --run` (plain `bun run test` starts Vitest watch mode).
|
||||
- `bun run lint:css` reports many pre-existing CSS lint violations in the repo; these are not environment problems.
|
||||
|
||||
## Documentation
|
||||
|
||||
- When writing or working on the documentation website (`packages/website`), MUST adhere to the writing guidelines in @packages/website/WRITING_GUIDE.md
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export default App;
|
|||
|
||||
## MCP plugins
|
||||
|
||||
Client plugins for the public DocSearch MCP endpoint are in [`mcp/plugins/docsearch`](mcp/plugins/docsearch). They connect Cursor and Claude Code to `https://mcp.algolia.com/1/docsearch/mcp` for current public developer documentation.
|
||||
Client plugins for the public DocSearch MCP endpoint are in [`mcp/plugins/docsearch`](mcp/plugins/docsearch). They connect ChatGPT, Codex, Cursor, and Claude Code to `https://mcp.algolia.com/1/docsearch/mcp` for current public developer documentation.
|
||||
|
||||
## Related projects
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
var(--ifm-color-emphasis-200) 100%);
|
||||
|
||||
/* Sidepanel */
|
||||
--docsearch-sidepanel-background: var(--ifm-color-emphasis-100);
|
||||
--docsearch-sidepanel-background: var(--ifm-background-color);
|
||||
--docsearch-sidepanel-background-dark: var(--docsearch-searchbox-background);
|
||||
--docsearch-sidepanel-white: var(--ifm-background-color);
|
||||
--docsearch-sidepanel-primary: var(--docsearch-primary-color);
|
||||
|
|
@ -116,6 +116,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.DocSearch-AskAiScreen-Query {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.DocSearch-Container {
|
||||
z-index: calc(var(--ifm-z-index-fixed) + 1);
|
||||
}
|
||||
|
|
|
|||
121
bun.lock
121
bun.lock
|
|
@ -5,6 +5,7 @@
|
|||
"": {
|
||||
"name": "@docsearch/monorepo",
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.12.1",
|
||||
"@changesets/cli": "^2.29.7",
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
|
||||
"@playwright/test": "1.49.1",
|
||||
|
|
@ -18,7 +19,6 @@
|
|||
"oxlint": "^1.74.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"start-server-and-test": "2.0.10",
|
||||
"stylelint": "17.14.0",
|
||||
"stylelint-config-sass-guidelines": "13.0.0",
|
||||
"stylelint-config-standard": "40.0.0",
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
},
|
||||
"adapters/docusaurus-theme-search-algolia": {
|
||||
"name": "@docsearch/docusaurus-adapter",
|
||||
"version": "5.0.0-beta.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"dependencies": {
|
||||
"@algolia/autocomplete-core": "1.19.2",
|
||||
"@docsearch/core": "workspace:*",
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
},
|
||||
"packages/docsearch-core": {
|
||||
"name": "@docsearch/core",
|
||||
"version": "5.0.0-beta.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.2.0",
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
},
|
||||
"packages/docsearch-css": {
|
||||
"name": "@docsearch/css",
|
||||
"version": "5.0.0-beta.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"devDependencies": {
|
||||
"browserslist": "4.28.2",
|
||||
"lightningcss": "1.32.0",
|
||||
|
|
@ -146,7 +146,7 @@
|
|||
},
|
||||
"packages/docsearch-js": {
|
||||
"name": "@docsearch/js",
|
||||
"version": "5.0.0-beta.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"dependencies": {
|
||||
"@docsearch/core": "4.6.0",
|
||||
"@docsearch/react": "4.6.0",
|
||||
|
|
@ -160,7 +160,7 @@
|
|||
},
|
||||
"packages/docsearch-modal": {
|
||||
"name": "@docsearch/modal",
|
||||
"version": "5.0.0-beta.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"dependencies": {
|
||||
"@docsearch/core": "4.6.0",
|
||||
"@docsearch/react": "4.6.0",
|
||||
|
|
@ -185,13 +185,13 @@
|
|||
},
|
||||
"packages/docsearch-react": {
|
||||
"name": "@docsearch/react",
|
||||
"version": "5.0.0-beta.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"dependencies": {
|
||||
"@ai-sdk/react": "^2.0.30",
|
||||
"@algolia/autocomplete-core": "1.19.2",
|
||||
"@base-ui/react": "^1.5.0",
|
||||
"@docsearch/core": "5.0.0-beta.0",
|
||||
"@docsearch/css": "5.0.0-beta.0",
|
||||
"@docsearch/core": "5.0.0-beta.1",
|
||||
"@docsearch/css": "5.0.0-beta.1",
|
||||
"ai": "^5.0.30",
|
||||
"algoliasearch": "^5.28.0",
|
||||
"marked": "^16.3.0",
|
||||
|
|
@ -199,7 +199,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@algolia/autocomplete-core": "1.19.2",
|
||||
"@docsearch/core": "5.0.0-beta.0",
|
||||
"@docsearch/core": "5.0.0-beta.1",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.2.0",
|
||||
"preact": "11.0.0-beta.0",
|
||||
|
|
@ -222,7 +222,7 @@
|
|||
},
|
||||
"packages/docsearch-sidepanel": {
|
||||
"name": "@docsearch/sidepanel",
|
||||
"version": "5.0.0-beta.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"dependencies": {
|
||||
"@docsearch/core": "4.6.0",
|
||||
"@docsearch/css": "4.6.0",
|
||||
|
|
@ -248,7 +248,7 @@
|
|||
},
|
||||
"packages/docsearch-sidepanel-js": {
|
||||
"name": "@docsearch/sidepanel-js",
|
||||
"version": "5.0.0-beta.0",
|
||||
"version": "5.0.0-beta.1",
|
||||
"dependencies": {
|
||||
"@docsearch/core": "4.6.0",
|
||||
"@docsearch/react": "4.6.0",
|
||||
|
|
@ -263,20 +263,21 @@
|
|||
"name": "@docsearch/website",
|
||||
"version": "4.6.0",
|
||||
"dependencies": {
|
||||
"@docsearch/core": "workspace:*",
|
||||
"@docsearch/css": "workspace:*",
|
||||
"@docsearch/docusaurus-adapter": "workspace:*",
|
||||
"@docsearch/modal": "workspace:*",
|
||||
"@docsearch/react": "workspace:*",
|
||||
"@docusaurus/core": "3.10.2",
|
||||
"@docusaurus/preset-classic": "3.10.2",
|
||||
"@docusaurus/theme-common": "3.10.2",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@segment/analytics-next": "^1.84.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cobe": "^0.6.4",
|
||||
"file-loader": "6.2.0",
|
||||
"lucide-react": "^0.525.0",
|
||||
"iconoir-react": "^7.11.1",
|
||||
"mini-svg-data-uri": "^1.4.4",
|
||||
"motion": "^12.23.3",
|
||||
"postcss": "8.5.6",
|
||||
|
|
@ -356,6 +357,8 @@
|
|||
|
||||
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
|
||||
|
||||
"@axe-core/playwright": ["@axe-core/playwright@4.12.1", "", { "dependencies": { "axe-core": "~4.12.1" }, "peerDependencies": { "playwright-core": ">= 1.0.0" } }, "sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
|
||||
|
|
@ -1328,10 +1331,6 @@
|
|||
|
||||
"@szmarczak/http-timer": ["@szmarczak/http-timer@5.0.1", "", { "dependencies": { "defer-to-connect": "^2.0.1" } }, "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw=="],
|
||||
|
||||
"@tabler/icons": ["@tabler/icons@3.44.0", "", {}, "sha512-Wn0AOZG9sg0L+bjfMqq4eNhC6pQjIrk94LvvWYNYkY8KH8wC3YILRzQlrnVJc4FUeMxH/AK97QsYCX35H3LndA=="],
|
||||
|
||||
"@tabler/icons-react": ["@tabler/icons-react@3.44.0", "", { "dependencies": { "@tabler/icons": "3.44.0" }, "peerDependencies": { "react": ">= 16" } }, "sha512-8+rvzBbVm/1Z3sG3x7GUNAaxIKxwgz8xaMhRs23nrCnMTKRFAhEC+82zAIFeAA0seXdrAGX5HFCkaLpGK2rVHg=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="],
|
||||
|
|
@ -1662,7 +1661,7 @@
|
|||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
"axios": ["axios@1.18.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g=="],
|
||||
"axe-core": ["axe-core@4.12.1", "", {}, "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA=="],
|
||||
|
||||
"babel-loader": ["babel-loader@9.2.1", "", { "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" }, "peerDependencies": { "@babel/core": "^7.12.0", "webpack": ">=5" } }, "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA=="],
|
||||
|
||||
|
|
@ -1702,8 +1701,6 @@
|
|||
|
||||
"bl": ["bl@1.2.3", "", { "dependencies": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" } }, "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww=="],
|
||||
|
||||
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
|
||||
|
||||
"body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="],
|
||||
|
||||
"bonjour-service": ["bonjour-service@1.4.3", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-2Kd5UYlFUVgAKMTyuBLl6w49wqfOnbxHqmuH0oCl/n7TfAikR0zoowNOP5BU4dfXmm+Vr9JyEN370auSMx+CNg=="],
|
||||
|
|
@ -1788,8 +1785,6 @@
|
|||
|
||||
"check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
|
||||
|
||||
"check-more-types": ["check-more-types@2.24.0", "", {}, "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA=="],
|
||||
|
||||
"cheerio": ["cheerio@1.0.0-rc.12", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "htmlparser2": "^8.0.1", "parse5": "^7.0.0", "parse5-htmlparser2-tree-adapter": "^7.0.0" } }, "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q=="],
|
||||
|
||||
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
|
||||
|
|
@ -1934,7 +1929,7 @@
|
|||
|
||||
"debounce": ["debounce@1.2.1", "", {}, "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="],
|
||||
|
||||
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
|
||||
|
||||
|
|
@ -2128,8 +2123,6 @@
|
|||
|
||||
"eval": ["eval@0.1.8", "", { "dependencies": { "@types/node": "*", "require-like": ">= 0.1.1" } }, "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw=="],
|
||||
|
||||
"event-stream": ["event-stream@3.3.4", "", { "dependencies": { "duplexer": "~0.1.1", "from": "~0", "map-stream": "~0.1.0", "pause-stream": "0.0.11", "split": "0.3", "stream-combiner": "~0.0.4", "through": "~2.3.1" } }, "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
||||
|
||||
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||
|
|
@ -2234,8 +2227,6 @@
|
|||
|
||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"from": ["from@0.1.7", "", {}, "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g=="],
|
||||
|
||||
"from2": ["from2@2.3.0", "", { "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" } }, "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g=="],
|
||||
|
||||
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||
|
|
@ -2390,6 +2381,8 @@
|
|||
|
||||
"hyperdyperid": ["hyperdyperid@1.2.0", "", {}, "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A=="],
|
||||
|
||||
"iconoir-react": ["iconoir-react@7.11.1", "", { "peerDependencies": { "react": "18 || 19" } }, "sha512-uUdoKZ5SvvleMWf+mJWbAxLt5WaEcvBAU3YmO5Ho+JA5JLxBKntT0KNXpJqQArq7b5DxiN5xobIVoDBIPAvA/w=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="],
|
||||
|
||||
"icss-utils": ["icss-utils@5.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA=="],
|
||||
|
|
@ -2596,8 +2589,6 @@
|
|||
|
||||
"launch-editor": ["launch-editor@2.14.1", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.4" } }, "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA=="],
|
||||
|
||||
"lazy-ass": ["lazy-ass@1.6.0", "", {}, "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw=="],
|
||||
|
||||
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
|
@ -2662,16 +2653,12 @@
|
|||
|
||||
"lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.525.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ=="],
|
||||
|
||||
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="],
|
||||
|
||||
"map-stream": ["map-stream@0.1.0", "", {}, "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g=="],
|
||||
|
||||
"markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
|
||||
|
||||
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
|
||||
|
|
@ -3008,8 +2995,6 @@
|
|||
|
||||
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
|
||||
|
||||
"pause-stream": ["pause-stream@0.0.11", "", { "dependencies": { "through": "~2.3" } }, "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A=="],
|
||||
|
||||
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
|
||||
|
||||
"phenomenon": ["phenomenon@1.6.0", "", {}, "sha512-7h9/fjPD3qNlgggzm88cY58l9sudZ6Ey+UmZsizfhtawO6E3srZQXywaNm2lBwT72TbpHYRPy7ytIHeBUD/G0A=="],
|
||||
|
|
@ -3214,10 +3199,6 @@
|
|||
|
||||
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
|
||||
|
||||
"ps-tree": ["ps-tree@1.2.0", "", { "dependencies": { "event-stream": "=3.3.4" }, "bin": { "ps-tree": "./bin/ps-tree.js" } }, "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA=="],
|
||||
|
||||
"pseudomap": ["pseudomap@1.0.2", "", {}, "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="],
|
||||
|
||||
"pstree.remy": ["pstree.remy@1.1.8", "", {}, "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="],
|
||||
|
|
@ -3392,8 +3373,6 @@
|
|||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
|
@ -3500,8 +3479,6 @@
|
|||
|
||||
"spdy-transport": ["spdy-transport@3.0.0", "", { "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", "hpack.js": "^2.1.6", "obuf": "^1.1.2", "readable-stream": "^3.0.6", "wbuf": "^1.7.3" } }, "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw=="],
|
||||
|
||||
"split": ["split@0.3.3", "", { "dependencies": { "through": "2" } }, "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
||||
|
||||
"srcset": ["srcset@4.0.0", "", {}, "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw=="],
|
||||
|
|
@ -3510,14 +3487,10 @@
|
|||
|
||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"start-server-and-test": ["start-server-and-test@2.0.10", "", { "dependencies": { "arg": "^5.0.2", "bluebird": "3.7.2", "check-more-types": "2.24.0", "debug": "4.4.0", "execa": "5.1.1", "lazy-ass": "1.6.0", "ps-tree": "1.2.0", "wait-on": "8.0.2" }, "bin": { "start-test": "src/bin/start.js", "server-test": "src/bin/start.js", "start-server-and-test": "src/bin/start.js" } }, "sha512-nZphcfcqGqwk74lbZkqSwClkYz+M5ZPGOMgWxNVJrdztPKN96qe6HooRu6L3TpwITn0lKJJdKACqHbJtqythOQ=="],
|
||||
|
||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
|
||||
|
||||
"stream-combiner": ["stream-combiner@0.0.4", "", { "dependencies": { "duplexer": "~0.1.1" } }, "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw=="],
|
||||
|
||||
"stream-shift": ["stream-shift@1.0.3", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="],
|
||||
|
||||
"strict-uri-encode": ["strict-uri-encode@1.1.0", "", {}, "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ=="],
|
||||
|
|
@ -3778,8 +3751,6 @@
|
|||
|
||||
"w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
|
||||
|
||||
"wait-on": ["wait-on@8.0.2", "", { "dependencies": { "axios": "^1.7.9", "joi": "^17.13.3", "lodash": "^4.17.21", "minimist": "^1.2.8", "rxjs": "^7.8.1" }, "bin": { "wait-on": "bin/wait-on" } }, "sha512-qHlU6AawrgAIHlueGQHQ+ETcPLAauXbnoTKl3RKq20W0T8x0DKVAo5xWIYjHSyvHxQlcYbFdR0jp4T9bDVITFA=="],
|
||||
|
||||
"watch": ["watch@1.0.2", "", { "dependencies": { "exec-sh": "^0.2.0", "minimist": "^1.2.0" }, "bin": { "watch": "./cli.js" } }, "sha512-1u+Z5n9Jc1E2c7qDO8SinPoZuHj7FgbgU1olSFoyaklduDvvtX7GMMtlE6OC9FTXq4KvNAOfj6Zu4vI1e9bAKA=="],
|
||||
|
||||
"watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="],
|
||||
|
|
@ -3878,8 +3849,6 @@
|
|||
|
||||
"@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@babel/core/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-compilation-targets/browserslist": ["browserslist@4.28.6", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", "electron-to-chromium": "^1.5.389", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw=="],
|
||||
|
|
@ -3892,16 +3861,12 @@
|
|||
|
||||
"@babel/helper-create-regexp-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-define-polyfill-provider/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/preset-env/babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.2", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g=="],
|
||||
|
||||
"@babel/preset-env/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/traverse/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"@cacheable/memory/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
|
||||
|
||||
"@cacheable/utils/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
|
||||
|
|
@ -4036,16 +4001,12 @@
|
|||
|
||||
"@eslint/eslintrc/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||
|
||||
"@eslint/eslintrc/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"@eslint/eslintrc/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
|
||||
|
||||
"@eslint/eslintrc/import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"@eslint/eslintrc/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"@humanwhocodes/config-array/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"@humanwhocodes/config-array/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"@jsonjoy.com/fs-snapshot/@jsonjoy.com/json-pack": ["@jsonjoy.com/json-pack@17.67.0", "", { "dependencies": { "@jsonjoy.com/base64": "17.67.0", "@jsonjoy.com/buffers": "17.67.0", "@jsonjoy.com/codegen": "17.67.0", "@jsonjoy.com/json-pointer": "17.67.0", "@jsonjoy.com/util": "17.67.0", "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w=="],
|
||||
|
|
@ -4108,10 +4069,6 @@
|
|||
|
||||
"@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
|
||||
|
||||
"@typescript-eslint/project-service/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"@vitest/snapshot/@vitest/pretty-format": ["@vitest/pretty-format@3.0.2", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-yBohcBw/T/p0/JRgYD+IYcjCmuHzjC3WLAKsVE4/LwiubzZkE8N49/xIQ/KGQwDRA8PaviF8IRO8JMWMngdVVQ=="],
|
||||
|
|
@ -4130,8 +4087,6 @@
|
|||
|
||||
"autoprefixer/browserslist": ["browserslist@4.28.6", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", "electron-to-chromium": "^1.5.389", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw=="],
|
||||
|
||||
"axios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
|
||||
|
||||
"babel-loader/schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
|
@ -4152,8 +4107,6 @@
|
|||
|
||||
"body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||
|
||||
"body-parser/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"boxen/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"bundlesize2/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
|
@ -4256,8 +4209,6 @@
|
|||
|
||||
"eslint/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||
|
||||
"eslint/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"eslint/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"eslint/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
|
||||
|
|
@ -4274,8 +4225,6 @@
|
|||
|
||||
"executable/pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
|
||||
|
||||
"express/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
|
@ -4284,8 +4233,6 @@
|
|||
|
||||
"file-loader/schema-utils": ["schema-utils@3.3.0", "", { "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } }, "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg=="],
|
||||
|
||||
"finalhandler/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"from2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
|
||||
"global-dirs/ini": ["ini@2.0.0", "", {}, "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA=="],
|
||||
|
|
@ -4312,12 +4259,8 @@
|
|||
|
||||
"htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"http-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"http-proxy-middleware/is-plain-obj": ["is-plain-obj@3.0.0", "", {}, "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA=="],
|
||||
|
||||
"https-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"imagemin/globby": ["globby@10.0.2", "", { "dependencies": { "@types/glob": "^7.1.1", "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.0.3", "glob": "^7.1.3", "ignore": "^5.1.1", "merge2": "^1.2.3", "slash": "^3.0.0" } }, "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg=="],
|
||||
|
||||
"imagemin-gifsicle/execa": ["execa@1.0.0", "", { "dependencies": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" } }, "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA=="],
|
||||
|
|
@ -4348,8 +4291,6 @@
|
|||
|
||||
"mdast-util-gfm-autolink-literal/micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
||||
|
||||
"micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"micromark/micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
|
||||
|
||||
"micromark/micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
||||
|
|
@ -4478,8 +4419,6 @@
|
|||
|
||||
"node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"nodemon/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"npm-conf/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="],
|
||||
|
||||
"null-loader/schema-utils": ["schema-utils@3.3.0", "", { "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } }, "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg=="],
|
||||
|
|
@ -4574,8 +4513,6 @@
|
|||
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
|
||||
|
||||
"router/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
||||
|
||||
"schema-utils/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||
|
|
@ -4584,8 +4521,6 @@
|
|||
|
||||
"semver-truncate/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
|
||||
|
||||
"send/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||
|
||||
"serve-handler/bytes": ["bytes@3.0.0", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="],
|
||||
|
|
@ -4612,10 +4547,6 @@
|
|||
|
||||
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"spdy/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"spdy-transport/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"spdy-transport/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
|
|
@ -4628,8 +4559,6 @@
|
|||
|
||||
"stylelint/cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
|
||||
|
||||
"stylelint/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"stylelint/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="],
|
||||
|
||||
"stylelint/postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="],
|
||||
|
|
@ -4682,10 +4611,6 @@
|
|||
|
||||
"vite-node/cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"vite-node/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"vitest/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"vitest/tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
|
||||
|
||||
"webpack/es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
|
||||
|
|
@ -4828,10 +4753,6 @@
|
|||
|
||||
"ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||
|
||||
"axios/https-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"babel-loader/schema-utils/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
|
||||
|
||||
"babel-loader/schema-utils/ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="],
|
||||
|
|
|
|||
36
e2e/a11y.test.ts
Normal file
36
e2e/a11y.test.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { test, expect } from './fixtures';
|
||||
|
||||
test.describe('a11y', () => {
|
||||
test.beforeEach(async ({ docSearch }) => {
|
||||
await docSearch.goto();
|
||||
await docSearch.waitForLoad();
|
||||
});
|
||||
|
||||
test.fail('Smoke test > Modal', async ({ docSearch, axe }, testInfo) => {
|
||||
await docSearch.openModal();
|
||||
|
||||
const scanResults = await axe().include('.DocSearch-Container').analyze();
|
||||
|
||||
await testInfo.attach('a11y-scan-results-modal', {
|
||||
body: JSON.stringify(scanResults.violations, null, 2),
|
||||
contentType: 'application/json',
|
||||
});
|
||||
|
||||
expect(scanResults.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test.fail('Smoke test > Sidepanel', async ({ sidepanel, axe }, testInfo) => {
|
||||
await sidepanel.openSidepanel();
|
||||
|
||||
const scanResults = await axe()
|
||||
.include('.DocSearch-Sidepanel-Container')
|
||||
.analyze();
|
||||
|
||||
await testInfo.attach('a11y-scan-results-sidepanel', {
|
||||
body: JSON.stringify(scanResults.violations, null, 2),
|
||||
contentType: 'application/json',
|
||||
});
|
||||
|
||||
expect(scanResults.violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
// oxlint-disable max-classes-per-file react-hooks/rules-of-hooks
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
import {
|
||||
test as base,
|
||||
expect,
|
||||
|
|
@ -29,7 +31,7 @@ export class DocSearchPage {
|
|||
}
|
||||
|
||||
async goto(): Promise<void> {
|
||||
await this.page.goto('/');
|
||||
await this.page.goto('/docs/what-is-docsearch');
|
||||
}
|
||||
|
||||
async waitForLoad(): Promise<void> {
|
||||
|
|
@ -91,12 +93,50 @@ export class DocSearchPage {
|
|||
}
|
||||
}
|
||||
|
||||
export const test = base.extend<{ docSearch: DocSearchPage }>({
|
||||
export class SidepanelPage {
|
||||
readonly page: Page;
|
||||
readonly sidepanelButton: Locator;
|
||||
readonly sidepanel: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.sidepanelButton = page.locator('.DocSearch-SidepanelButton');
|
||||
this.sidepanel = page.locator('.DocSearch-Sidepanel-Container');
|
||||
}
|
||||
|
||||
async openSidepanel(): Promise<void> {
|
||||
await this.sidepanelButton.click();
|
||||
await expect(this.sidepanel).toHaveClass(/is-open/, {
|
||||
timeout: 10000,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const test = base.extend<{
|
||||
docSearch: DocSearchPage;
|
||||
sidepanel: SidepanelPage;
|
||||
axe: () => AxeBuilder;
|
||||
}>({
|
||||
docSearch: async ({ page }, use) => {
|
||||
const docSearch = new DocSearchPage(page);
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(docSearch);
|
||||
},
|
||||
sidepanel: async ({ page }, use) => {
|
||||
const sidepanel = new SidepanelPage(page);
|
||||
await use(sidepanel);
|
||||
},
|
||||
axe: async ({ page }, use) => {
|
||||
const makeBuilder = () =>
|
||||
new AxeBuilder({ page }).withTags([
|
||||
'wcag2a',
|
||||
'wcag2aa',
|
||||
'wcag21a',
|
||||
'wcag21aa',
|
||||
'wcag22a',
|
||||
'wcag22aa',
|
||||
]);
|
||||
await use(makeBuilder);
|
||||
},
|
||||
});
|
||||
|
||||
export { expect };
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ The plugins connect AI coding clients to the public DocSearch documentation corp
|
|||
|
||||
## Packages
|
||||
|
||||
- `mcp/plugins/docsearch/chatgpt/algolia-docsearch` - Universal ChatGPT and Codex plugin package with manifest, MCP config, skill, assets, and submission materials.
|
||||
- `mcp/plugins/docsearch/cursor/algolia-docsearch` - Cursor plugin package with MCP config, rule, skill, and README.
|
||||
- `mcp/plugins/docsearch/claude/algolia-docsearch` - Claude Code plugin package with MCP config, skill, command, and README.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "algolia-docsearch",
|
||||
"version": "1.0.0",
|
||||
"description": "Search current public developer documentation from the Algolia DocSearch corpus.",
|
||||
"author": {
|
||||
"name": "Algolia",
|
||||
"url": "https://www.algolia.com"
|
||||
},
|
||||
"homepage": "https://docsearch.algolia.com/docs/mcp/overview",
|
||||
"repository": "https://github.com/algolia/docsearch",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"algolia",
|
||||
"developer-docs",
|
||||
"documentation",
|
||||
"docsearch",
|
||||
"mcp"
|
||||
],
|
||||
"skills": "./skills/",
|
||||
"mcpServers": "./.mcp.json",
|
||||
"interface": {
|
||||
"displayName": "Algolia DocSearch",
|
||||
"shortDescription": "Current public developer documentation from the DocSearch corpus",
|
||||
"longDescription": "Search current public documentation for libraries, frameworks, SDKs, APIs, CLIs, and cloud services. DocSearch resolves the relevant documentation set, returns ranked excerpts, and provides source URLs for grounded answers.",
|
||||
"developerName": "Algolia",
|
||||
"category": "Developer Tools",
|
||||
"capabilities": [
|
||||
"Interactive",
|
||||
"Read"
|
||||
],
|
||||
"websiteURL": "https://docsearch.algolia.com/docs/mcp/overview",
|
||||
"privacyPolicyURL": "https://www.algolia.com/policies/privacy",
|
||||
"termsOfServiceURL": "https://www.algolia.com/policies/terms",
|
||||
"defaultPrompt": [
|
||||
"Explain middleware matchers from the current Next.js docs.",
|
||||
"Show how React's useTransition hook works.",
|
||||
"Find Algolia guidance for searchable attributes."
|
||||
],
|
||||
"brandColor": "#5468FF",
|
||||
"composerIcon": "./assets/docsearch-monogram.svg",
|
||||
"logo": "./assets/docsearch-monogram.svg",
|
||||
"screenshots": []
|
||||
}
|
||||
}
|
||||
1
mcp/plugins/docsearch/chatgpt/algolia-docsearch/.gitignore
vendored
Normal file
1
mcp/plugins/docsearch/chatgpt/algolia-docsearch/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
SUBMISSION.md
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"algolia-docsearch": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.algolia.com/1/docsearch/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
57
mcp/plugins/docsearch/chatgpt/algolia-docsearch/README.md
Normal file
57
mcp/plugins/docsearch/chatgpt/algolia-docsearch/README.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Algolia DocSearch Plugin for ChatGPT and Codex
|
||||
|
||||
Algolia DocSearch connects ChatGPT and Codex to the public DocSearch MCP endpoint for current public developer documentation.
|
||||
|
||||
## What's included
|
||||
|
||||
- **Plugin manifest** - Identifies the universal ChatGPT and Codex plugin and its listing metadata.
|
||||
- **MCP server** - Connects to `https://mcp.algolia.com/1/docsearch/mcp` without user authentication.
|
||||
- **Skill** - Routes public developer documentation questions through DocSearch and requires source-grounded answers.
|
||||
|
||||
The plugin teaches the one-shot `search_docs` lookup and the `resolve_docset` plus `query_docs` flow.
|
||||
|
||||
## Public submission
|
||||
|
||||
Submit this as **With MCP** using the universal MCP URL:
|
||||
|
||||
```text
|
||||
https://mcp.algolia.com/1/docsearch/mcp
|
||||
```
|
||||
|
||||
Do not submit an existing ChatGPT integration ID. The OpenAI submission portal scans the production MCP URL directly. See [SUBMISSION.md](SUBMISSION.md) for listing copy, tool annotation requirements, starter prompts, test cases, and the pre-submission checklist.
|
||||
|
||||
## Local ChatGPT testing
|
||||
|
||||
1. Enable Developer mode in ChatGPT under **Settings → Security and login**.
|
||||
2. Open ChatGPT Plugins, add a connection, and enter the MCP URL above.
|
||||
3. Test each tool and the bundled skill in a new chat.
|
||||
|
||||
If a packaged local plugin must reference the registered ChatGPT connection, copy its technical ID from the browser URL and add a `.app.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"apps": {
|
||||
"algolia-docsearch": {
|
||||
"id": "plugin_asdk_app_<registered-id>"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then add `"apps": "./.app.json"` to `.codex-plugin/plugin.json`. The committed package intentionally omits this machine-specific development ID.
|
||||
|
||||
## Available tools
|
||||
|
||||
### `algolia_docsearch_search_docs`
|
||||
|
||||
The default one-shot lookup. It resolves a documentation set from `library`, searches it with `query`, and returns ranked documentation chunks with source URLs.
|
||||
|
||||
### `algolia_docsearch_resolve_docset`
|
||||
|
||||
Finds candidate documentation sets for an official product, library, SDK, or platform.
|
||||
|
||||
### `algolia_docsearch_query_docs`
|
||||
|
||||
Queries one or more selected `docset_id` values. Use it after resolving an ambiguous library or when a question spans products.
|
||||
|
||||
This plugin is limited to public developer documentation. It does not expose crawler operations, DocSearch administration, private documentation, or Algolia index credentials.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 96 98">
|
||||
<path fill="#5468ff" d="M36.493 77.289H3.074A3.067 3.067 0 0 1 0 74.228h36.523c12.769.062 24.6-6.663 31.038-17.642a35.45 35.45 0 0 0 .174-35.584C61.404 9.962 49.639 3.122 36.87 3.061H0A3.067 3.067 0 0 1 3.074 0H36.87c10.31.01 20.193 4.1 27.473 11.369a38.6 38.6 0 0 1 11.341 27.384c-.06 21.366-17.741 38.536-39.19 38.536z"/>
|
||||
<path fill="#5468ff" d="M0 69.045h23.711a11.78 11.78 0 0 0 7.933-3.031H0zM0 60.8h36.374c.734-.987 1.418-1.975 2.053-3.07H0zm0-8.254h41.025a30 30 0 0 0 1.091-3.06H0zm0-8.244h43.306c.149-.987.268-2.034.337-3.061H0zm0-8.244h43.633a30 30 0 0 1-.337-3.071H0zm0-8.254h42.116a36 36 0 0 0-1.09-3.061H0zm0-8.245h38.427a33 33 0 0 1-2.053-3.06H0zM0 8.244v3.071h31.674a19.04 19.04 0 0 0-7.934-3.031z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 791 B |
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
name: algolia-docsearch-mcp
|
||||
description: Use when the user asks about public developer documentation for a library, framework, SDK, API, CLI tool, or cloud service. Fetch current docs from Algolia DocSearch MCP and cite source URLs.
|
||||
---
|
||||
|
||||
Use Algolia DocSearch MCP when the answer depends on current public developer documentation.
|
||||
|
||||
## Scope
|
||||
|
||||
Use this skill for:
|
||||
|
||||
- Setup and configuration questions about public libraries, frameworks, SDKs, APIs, CLI tools, and cloud services.
|
||||
- API reference details, migrations, and code examples that should match current documentation.
|
||||
- Questions that ask for documentation sources or citations.
|
||||
|
||||
Do not use this skill for:
|
||||
|
||||
- Private or internal documentation.
|
||||
- Local code review or business logic.
|
||||
- General programming concepts that do not require external documentation.
|
||||
- Questions already answered by documentation the user supplied.
|
||||
- DocSearch crawler operations, administration, or private Algolia indices.
|
||||
|
||||
## Default flow
|
||||
|
||||
For most questions, call `algolia_docsearch_search_docs`.
|
||||
|
||||
1. Set `library` to the official product, library, SDK, or platform name.
|
||||
2. Set `query` to the actual documentation question in natural language.
|
||||
3. Answer only from the returned documentation chunks.
|
||||
4. Include returned source URLs when available.
|
||||
|
||||
If the selected documentation set is ambiguous or irrelevant, do not answer from it. Retry with the exact official product name or use the manual flow.
|
||||
|
||||
## Manual flow
|
||||
|
||||
Use `algolia_docsearch_resolve_docset` followed by `algolia_docsearch_query_docs` when:
|
||||
|
||||
- The question spans multiple products.
|
||||
- The right documentation set is ambiguous.
|
||||
- The user asks to compare guidance across products.
|
||||
|
||||
Resolve the documentation set once, select the official vendor's best match, and pass its `docset_id` to `algolia_docsearch_query_docs`. Pass multiple IDs when the question genuinely spans products.
|
||||
|
||||
## Answering
|
||||
|
||||
- Ground the answer only in returned documentation content.
|
||||
- Prefer official vendor documentation sets over third-party mentions.
|
||||
- Include source links next to the claims they support.
|
||||
- If no relevant documentation is returned, say so and ask for a more specific product name or documentation source.
|
||||
- Never invent citations, credentials, private data, or unsupported capabilities.
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
{
|
||||
"name": "algolia-docsearch",
|
||||
"source": ".",
|
||||
"description": "up-to-date public developer documentation from the DocSearch corpus.",
|
||||
"description": "Fresh, chunked and ranked excerpts from up-to-date public developer documentation from the DocSearch corpus.",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
"sync:docsearch-version": "bun scripts/updateDocsearchVersion.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.12.1",
|
||||
"@changesets/cli": "^2.29.7",
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
|
||||
"@playwright/test": "1.49.1",
|
||||
|
|
@ -73,7 +74,6 @@
|
|||
"oxlint": "^1.74.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"start-server-and-test": "2.0.10",
|
||||
"stylelint": "17.14.0",
|
||||
"stylelint-config-sass-guidelines": "13.0.0",
|
||||
"stylelint-config-standard": "40.0.0",
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export async function upsertJsonServerEntry(
|
|||
|
||||
const alreadyExists = serverEntry !== undefined;
|
||||
const mergedEntry = {
|
||||
...(serverEntry ?? {}),
|
||||
...serverEntry,
|
||||
...entry,
|
||||
};
|
||||
const edits = modify(
|
||||
|
|
|
|||
|
|
@ -168,7 +168,6 @@
|
|||
width: 100%;
|
||||
gap: 0.8125rem;
|
||||
font-size: 0.8em;
|
||||
background: var(--docsearch-hit-background);
|
||||
padding: 16px;
|
||||
color: var(--docsearch-text-color);
|
||||
border-radius: 4px;
|
||||
|
|
|
|||
|
|
@ -839,7 +839,7 @@
|
|||
stroke-width: var(--docsearch-icon-stroke-width);
|
||||
width: 24px;
|
||||
align-self: flex-start;
|
||||
margin-block-start: 0.5em;
|
||||
margin-block-start: 0.8em;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-icon svg {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export function useFacetValues({
|
|||
searchClient
|
||||
.search<DocSearchHit>({
|
||||
requests: indexes.map((index) => ({
|
||||
...(index.searchParameters ?? {}),
|
||||
...index.searchParameters,
|
||||
indexName: index.name,
|
||||
query: '',
|
||||
hitsPerPage: 0,
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ export async function buildQuerySources({
|
|||
highlightPostTag: searchParams?.highlightPostTag ?? '</mark>',
|
||||
hitsPerPage: searchParams?.hitsPerPage ?? 20,
|
||||
clickAnalytics: searchParams?.clickAnalytics ?? insightsActive,
|
||||
...(searchParams ?? {}),
|
||||
...searchParams,
|
||||
...(facetFilters ? { facetFilters } : {}),
|
||||
};
|
||||
}),
|
||||
|
|
|
|||
91
packages/website/WRITING_GUIDE.md
Normal file
91
packages/website/WRITING_GUIDE.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Style guide
|
||||
|
||||
## Core principles
|
||||
|
||||
- Help users succeed with Algolia.
|
||||
- Docs must stay correct, clear, complete, current.
|
||||
- Missing rule: use [Google developer docs style guide](https://developers.google.com/style). API refs: use [API docs style guide](https://api-clients-automation.netlify.app/docs/add-a-new-api/api-documentation-guidelines).
|
||||
- Write simple, consistent, concise. Explain what users need. No extra.
|
||||
- ALWAYS lean on using standardized markdown templating.
|
||||
|
||||
## Voice and tone
|
||||
|
||||
- Write like teaching new teammate. Then cut fluff.
|
||||
- Use contractions, short sentences, short paragraphs, imperative mood, active voice.
|
||||
- Focus user goals, not feature internals.
|
||||
- Give guidance + best practices. Samples should feel production-ready.
|
||||
- No sarcasm, humor, emojis, double negatives, idioms, `easy`, `simply`, `quick`.
|
||||
- Write accessible, global, inclusive docs.
|
||||
|
||||
## Version references
|
||||
|
||||
- When referencing installing a `v5` package, MUST use the caret (`^`) based versioning: `@^5.0.0-beta`
|
||||
- When displaying install commands, ONLY include `@docsearch/*` packages, there's no need to include any other packages unless specified
|
||||
|
||||
## Pages
|
||||
|
||||
- Every page MUST have a title
|
||||
- Every page MUST have a concise description describing at a high level the information that is on the page
|
||||
- If you are about to explain a concept or property that is better explained on a different page, prefer to link to that current section on the other page instead
|
||||
|
||||
## Tabs
|
||||
|
||||
- Use Docusaurus theme `<Tabs />` and `<TabItem />` components when writing the following:
|
||||
- Code snippets: One tab for React/TSX based snippet, one tab for vanilla JS snippet
|
||||
- vanilla js -> label: 'JavaScript', value: 'js'
|
||||
- react -> label: 'React', value: 'react'
|
||||
- Install commands (npm/yarn/pnpm/bun) with each package manager being it's own tab
|
||||
- Always give each tab a title
|
||||
- "React" tab MUST always be the first one listed
|
||||
|
||||
**EXAMPLE**:
|
||||
|
||||
```tsx
|
||||
<Tabs groupId="language" defaultValue="js">
|
||||
<TabItem value="js" label="JavaScript"></TabItem>
|
||||
<TabItem value="react" label="React"></TabItem>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- Prefer to use shared links on pages
|
||||
|
||||
**EXAMPLE**:
|
||||
|
||||
```mdx
|
||||
This is a test [Website][1]
|
||||
|
||||
[1]: https://example.com
|
||||
```
|
||||
|
||||
## API References
|
||||
|
||||
- API reference pages should focus on detailing out the specification for the related package/domain
|
||||
- If explaining exports/imports of a package, those definitions MUST appear at the bottom of the page
|
||||
- exports/imports here are the rare occassion where tables ARE THE PREFERRED DISPLAY TYPE
|
||||
- Property definition as defined below is preferred over tables
|
||||
|
||||
<property-definition>
|
||||
## `PROPERTY_NAME`
|
||||
|
||||
> `type: TYPE_PRIMITIVE|ROUGH_TYPE_DEFINITION_FROM_SOURCE` | **required** | **optional** | **experimental**
|
||||
|
||||
CONCISE DESCRIPTION OF THE PROPERTY
|
||||
</property-definition>
|
||||
|
||||
"type" in the property definition is a static string that should always be included, it tells the reader that the following information is a type definition.
|
||||
The different tags (required/optional/experimental) are optional on their own, but MUST be used correctly
|
||||
|
||||
## Tables
|
||||
|
||||
- AVOID tables as much as possible
|
||||
- IF a table is required, or absolutely the best option for display, it MUST be contained to a MAX of 3 columns
|
||||
|
||||
## Package names
|
||||
|
||||
- When referencing @docsearch/sidepanel package, the component MUST be named `Sidepanel` (capital 'S')
|
||||
|
||||
## Code blocks
|
||||
|
||||
- A Codeblock should prefer to have a title to it, ideally a realtively similar filename (app.tsx, Search.tsx, load-docsearch.js)
|
||||
162
packages/website/docs/agent-studio/dynamic-indices.mdx
Normal file
162
packages/website/docs/agent-studio/dynamic-indices.mdx
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
---
|
||||
title: Configure dynamic indices
|
||||
description: Select Agent Studio search indices dynamically at runtime.
|
||||
---
|
||||
|
||||
Pass `askAi.indices` when the Agent Studio search tool needs index definitions at request time. This lets your application choose indices, descriptions, fixed search parameters, and model-controlled parameters for each DocSearch instance.
|
||||
|
||||
This property isn't the same as the top-level `indices` property:
|
||||
|
||||
- Top-level `indices` configures DocSearch keyword search. Entries are index names or objects with a `name` property.
|
||||
- `askAi.indices` configures Agent Studio search. Entries use an `index` property and require a `description`.
|
||||
|
||||
## Define an index
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
<DocSearchAI
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['docs']}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
indices: [
|
||||
{
|
||||
index: 'docs_markdown',
|
||||
description: 'Published product documentation.',
|
||||
enhancedDescription:
|
||||
'Use this index for installation, configuration, and API questions.',
|
||||
searchParameters: {
|
||||
filters: 'visibility:public',
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
},
|
||||
searchControls: {
|
||||
query: { exposed: true },
|
||||
hits_per_page: {
|
||||
exposed: false,
|
||||
default: 7,
|
||||
constraint: { min: 1, max: 10 },
|
||||
},
|
||||
attributesToRetrieve: {
|
||||
exposed: false,
|
||||
default: ['title', 'content', 'url'],
|
||||
constraint: {
|
||||
values: ['title', 'content', 'url'],
|
||||
},
|
||||
},
|
||||
facets: {
|
||||
exposed: false,
|
||||
default: ['language', 'version'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Each index accepts these properties:
|
||||
|
||||
### `index`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Names the Algolia index.
|
||||
|
||||
### `description`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Tells the agent what the index contains.
|
||||
|
||||
### `enhancedDescription`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Gives the agent more guidance about when and how to use the index.
|
||||
|
||||
### `searchParameters`
|
||||
|
||||
> `type: SearchParamsObject` | **optional**
|
||||
|
||||
Sets defaults for the internal, non-MCP search path. It accepts Algolia search parameters.
|
||||
|
||||
### `searchControls`
|
||||
|
||||
> `type: AgentStudioSearchControls` | **optional**
|
||||
|
||||
Configures parameters for the MCP-based search path.
|
||||
|
||||
Write specific descriptions. Agent Studio uses them to select an index and plan a search. See [Algolia Search tool configuration](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/tools/algolia-search) for guidance.
|
||||
|
||||
## Configure search controls
|
||||
|
||||
A search control describes whether the model can provide a value and which default or constraints apply.
|
||||
|
||||
### `query`
|
||||
|
||||
> `type: { exposed, default? }` | **optional**
|
||||
|
||||
Sets the search query control. It has no DocSearch default when omitted.
|
||||
|
||||
### `hits_per_page`
|
||||
|
||||
> `type: { exposed, default?, constraint?: { min?, max? } }` | **optional**
|
||||
|
||||
Sets the number of results per page. When omitted, DocSearch uses `{ exposed: false, default: 7 }`.
|
||||
|
||||
### `page`
|
||||
|
||||
> `type: { exposed, default?, constraint?: { min?, max? } }` | **optional**
|
||||
|
||||
Sets the requested page. When omitted, DocSearch uses `{ exposed: false, default: 0 }`.
|
||||
|
||||
### `attributesToRetrieve`
|
||||
|
||||
> `type: { exposed, default?, constraint?: { values? }, merge? }` | **optional**
|
||||
|
||||
Sets the attributes returned for each hit. When omitted, DocSearch uses `{ exposed: false, default: ['*'] }`.
|
||||
|
||||
### `responseFields`
|
||||
|
||||
> `type: { exposed, default?, constraint?: { values? }, merge? }` | **optional**
|
||||
|
||||
Sets the fields returned in the search response. When omitted, DocSearch uses `{ exposed: false, default: ['hits', 'nbHits', 'page', 'nbPages', 'hitsPerPage', 'facets'] }`.
|
||||
|
||||
### `facets`
|
||||
|
||||
> `type: { exposed: false, default? }` | **optional**
|
||||
|
||||
Sets the facets requested with the search. It has no DocSearch default when omitted.
|
||||
|
||||
### `custom`
|
||||
|
||||
> `type: Record<string, unknown>` | **optional**
|
||||
|
||||
Sets extra tool parameters. It has no DocSearch default when omitted.
|
||||
|
||||
Use `exposed: true` only for values the model should choose from conversation context. Use fixed defaults for business rules and response limits. For the distinction between runtime and predefined parameters, see [Agent Studio tools](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/tools/overview#configure-runtime-parameters-for-dynamic-tool-execution).
|
||||
|
||||
## Set request-wide search parameters
|
||||
|
||||
`askAi.searchParameters` is keyed by index name:
|
||||
|
||||
```js title="docsearch.js"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
searchParameters: {
|
||||
docs_markdown: {
|
||||
filters: 'visibility:public',
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
distinct: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Don't use the flat search-parameter shape from earlier DocSearch AI integrations.
|
||||
|
||||
## Docusaurus validation
|
||||
|
||||
The v5 Docusaurus adapter requires at least one entry when you set `askAi.indices`. Every entry must include `index` and `description`. It rejects unknown `searchControls` properties, except values nested under `custom`.
|
||||
|
||||
See the [React package reference](/docs/packages/react/api-reference) and [Docusaurus adapter guide](/docs/packages/docusaurus-adapter/getting-started) for the complete types.
|
||||
82
packages/website/docs/agent-studio/feedback.mdx
Normal file
82
packages/website/docs/agent-studio/feedback.mdx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
title: Collect response feedback
|
||||
description: Collect user feedback on Agent Studio responses.
|
||||
---
|
||||
|
||||
DocSearch v5 includes feedback controls for completed Agent Studio answers. You don't need to add an `askAi` option to enable them.
|
||||
|
||||
Use an API key with the `search` ACL. Agent Studio accepts one vote per message. See [Agent Studio feedback](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/feedback) for service requirements and reporting details.
|
||||
|
||||
## Understand the ratings
|
||||
|
||||
Thumbs up submits positive feedback immediately:
|
||||
|
||||
```ts
|
||||
{
|
||||
thumbs: 1;
|
||||
}
|
||||
```
|
||||
|
||||
Thumbs down opens a form before submission. A user can select zero or more reason tags and add an optional note:
|
||||
|
||||
```ts
|
||||
{
|
||||
thumbs: 0;
|
||||
tags?: AskAiFeedbackReason[];
|
||||
notes?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Closing or canceling the form doesn't submit feedback. Submitting without a reason or note is valid. DocSearch trims notes, omits an empty note, and limits notes to 1,000 characters.
|
||||
|
||||
DocSearch supports these reason values:
|
||||
|
||||
| Value | Default label |
|
||||
| ------------------ | ----------------------- |
|
||||
| `incorrect` | Incorrect or incomplete |
|
||||
| `not_what_i_asked` | Not what I asked for |
|
||||
| `slow_or_buggy` | Slow or buggy |
|
||||
| `style_or_tone` | Style or tone |
|
||||
| `safety_or_legal` | Safety or legal concern |
|
||||
| `other` | Other |
|
||||
|
||||
Reason tags and notes are collected only for negative feedback.
|
||||
|
||||
## Understand submission behavior
|
||||
|
||||
DocSearch sends the Agent Studio agent ID, message ID, vote, and optional negative-feedback details to Agent Studio. After a successful request, it shows a thank-you state and records the rating with the locally stored conversation.
|
||||
|
||||
If submission fails, DocSearch keeps the negative-feedback form open and displays the error so the user can retry.
|
||||
|
||||
## Change feedback labels
|
||||
|
||||
Override feedback text through `translations.modal.askAiScreen`:
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
<DocSearchAI
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['docs']}
|
||||
askAi={{ assistantId: 'YOUR_AGENT_ID' }}
|
||||
translations={{
|
||||
modal: {
|
||||
askAiScreen: {
|
||||
likeButtonTitle: 'Helpful',
|
||||
dislikeButtonTitle: 'Not helpful',
|
||||
thanksForFeedbackText: 'Feedback received',
|
||||
feedbackPanelTitle: 'Tell us what went wrong',
|
||||
feedbackDetailsPlaceholder: 'Add details',
|
||||
feedbackSubmitButtonText: 'Send feedback',
|
||||
feedbackTagIncorrect: 'Incorrect or incomplete',
|
||||
feedbackTagNotWhatIAsked: 'Not what I asked for',
|
||||
feedbackTagSlowOrBuggy: 'Slow or buggy',
|
||||
feedbackTagStyleOrTone: 'Style or tone',
|
||||
feedbackTagSafetyOrLegal: 'Safety or legal concern',
|
||||
feedbackTagOther: 'Other',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
See the [React package reference](/docs/packages/react/api-reference) for all feedback translation keys and the exported feedback types.
|
||||
158
packages/website/docs/agent-studio/getting-started.mdx
Normal file
158
packages/website/docs/agent-studio/getting-started.mdx
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
---
|
||||
title: Get started with Agent Studio
|
||||
description: Configure Agent Studio to answer questions from your documentation.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
DocSearch v5 uses [Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio) as its only AI backend. The public option is still named `askAi`, so you can add AI without learning a second DocSearch configuration shape.
|
||||
|
||||
Don't add an `agentStudio` flag. There's no AI backend selector in v5.
|
||||
|
||||
## Before you begin
|
||||
|
||||
1. [Create and publish an Agent Studio agent](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/quickstart).
|
||||
2. Copy its agent ID. DocSearch calls this value `assistantId` in its public API.
|
||||
3. Create a Search API key that can search the indices used by DocSearch and Agent Studio. Don't expose an Admin API key.
|
||||
4. Note your Algolia application ID and DocSearch index name.
|
||||
|
||||
## Add DocSearch
|
||||
|
||||
<Tabs groupId="language" aria-label="Programming language">
|
||||
<TabItem value="react" label="React">
|
||||
|
||||
Install the React package and styles:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Render `DocSearchAI`, not the search-only `DocSearch` component:
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
import { DocSearchAI } from '@docsearch/react';
|
||||
import '@docsearch/css';
|
||||
|
||||
export function Search() {
|
||||
return (
|
||||
<DocSearchAI
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_DOCSEARCH_INDEX']}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
See the [React package reference](/docs/packages/react/api-reference) for all `DocSearchAI` props and exported types.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
Install the JavaScript package and styles:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Add a container to your page:
|
||||
|
||||
```html title="index.html"
|
||||
<div id="docsearch"></div>
|
||||
```
|
||||
|
||||
Initialize DocSearch with your Agent Studio agent:
|
||||
|
||||
```js title="load-docsearch.js"
|
||||
import docsearch from '@docsearch/js';
|
||||
import '@docsearch/css';
|
||||
|
||||
docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['YOUR_DOCSEARCH_INDEX'],
|
||||
askAi: {
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
See the [JavaScript package reference](/docs/packages/js/api-reference) for the complete API.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Configure more Agent Studio features
|
||||
|
||||
Use the `askAi` object to add:
|
||||
|
||||
- [Dynamic indices and search controls](/docs/agent-studio/dynamic-indices)
|
||||
- [Client-side and MCP tools](/docs/agent-studio/tools)
|
||||
- [User-scoped memory](/docs/agent-studio/memory)
|
||||
- [Suggested prompts and follow-ups](/docs/agent-studio/prompt-suggestions)
|
||||
- [Response feedback](/docs/agent-studio/feedback)
|
||||
|
||||
## Use the Docusaurus adapter
|
||||
|
||||
The v5 Docusaurus adapter requires `themeConfig.docsearch.askAi` to be an object with `assistantId`. It rejects the string shorthand and the removed `agentStudio` property. Keep `appId`, `apiKey`, and keyword-search `indices` at the top level.
|
||||
|
||||
See the [Docusaurus adapter guide](/docs/packages/docusaurus-adapter/getting-started) for its full configuration and validation rules.
|
||||
68
packages/website/docs/agent-studio/memory.mdx
Normal file
68
packages/website/docs/agent-studio/memory.mdx
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
title: Add user-scoped memory
|
||||
description: Store and retrieve user-scoped memory in Agent Studio conversations.
|
||||
---
|
||||
|
||||
Agent Studio memory lets an agent retain useful context for a user across conversations. Configure memory in Agent Studio first, then give DocSearch a secure user token so Agent Studio can isolate each user's data.
|
||||
|
||||
Read the [Agent Studio memory overview](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/memory/overview) before enabling memory in production.
|
||||
|
||||
## Generate a secure user token
|
||||
|
||||
Generate the JWT on your backend after authenticating the user. Follow the [Agent Studio user authentication guide](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/user-authentication) for the required claims, signing algorithm, and key ID.
|
||||
|
||||
Protect the token flow:
|
||||
|
||||
- Never generate the token in browser code.
|
||||
- Never expose the Algolia secret key used to sign it.
|
||||
- Use a stable, non-sensitive user identifier in the JWT `sub` claim.
|
||||
- Set an expiration and refresh expired tokens.
|
||||
- Serve the token only over HTTPS.
|
||||
- Don't put sensitive user data in JWT claims. JWT payloads aren't encrypted.
|
||||
|
||||
## Pass the token to DocSearch
|
||||
|
||||
Fetch a token from your backend and pass it as `memory.userToken`:
|
||||
|
||||
```js title="load-docsearch.js"
|
||||
const response = await fetch('/api/agent-studio-token', {
|
||||
credentials: 'include',
|
||||
});
|
||||
const { userToken } = await response.json();
|
||||
|
||||
docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['docs'],
|
||||
askAi: {
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
memory: {
|
||||
enabled: true,
|
||||
userToken,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Use the same `askAi.memory` object with React's `DocSearchAI`.
|
||||
|
||||
When `userToken` is present, DocSearch sends it in the `X-Algolia-Secure-User-Token` request header. When it's absent, DocSearch omits the header. Don't use unscoped memory for a multi-user application.
|
||||
|
||||
## `memory.enabled`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Controls whether DocSearch displays Agent Studio's built-in memory tool activity:
|
||||
|
||||
- `algolia_memorize` displays that information was saved.
|
||||
- `algolia_ponder` and `algolia_memory_search` display that memory was used.
|
||||
- Memory tool errors aren't displayed.
|
||||
|
||||
The default is `false`.
|
||||
|
||||
This option doesn't enable memory in Agent Studio and doesn't create a user identity. Configure the feature on the agent and pass `userToken` for user isolation. DocSearch sends `userToken` when provided even if `enabled` is `false`.
|
||||
|
||||
A custom entry in `askAi.tools` with the same memory tool name replaces the built-in memory rendering.
|
||||
|
||||
See the [JavaScript package reference](/docs/packages/js/api-reference) or [React package reference](/docs/packages/react/api-reference) for the `Memory` configuration type.
|
||||
69
packages/website/docs/agent-studio/prompt-suggestions.mdx
Normal file
69
packages/website/docs/agent-studio/prompt-suggestions.mdx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
title: Configure prompt suggestions
|
||||
description: Display suggested prompts for Agent Studio conversations.
|
||||
---
|
||||
|
||||
DocSearch v5 can show questions in three places. Each source has a different purpose and configuration.
|
||||
|
||||
- **Suggested questions** appear on the new-conversation screen. They come from published records for the agent in the fixed suggested-questions index. Configure them with `suggestedQuestions: true`.
|
||||
- **Keyword prompt suggestions** appear while the user types in keyword search. They come from your Algolia prompt-suggestions index. Configure them with `promptSuggestions`.
|
||||
- **Streamed follow-ups** appear below the latest completed answer. They come from `data-suggestions` streamed by Agent Studio. Configure suggestions on the agent.
|
||||
|
||||
## Show suggested questions
|
||||
|
||||
Enable curated starting questions with `suggestedQuestions`:
|
||||
|
||||
```js title="docsearch.js"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
suggestedQuestions: true,
|
||||
}
|
||||
```
|
||||
|
||||
DocSearch queries the `algolia_ask_ai_suggested_questions` index for records where `state` is `published` and `assistantId` matches the configured agent. It requests up to three records.
|
||||
|
||||
A suggested-question record has this shape, plus its Algolia `objectID`:
|
||||
|
||||
```ts
|
||||
{
|
||||
appId: string;
|
||||
assistantId: string;
|
||||
question: string;
|
||||
locale?: string;
|
||||
state: 'published';
|
||||
source: string;
|
||||
order: number;
|
||||
}
|
||||
```
|
||||
|
||||
When a user selects one, DocSearch sends its `objectID` as `suggestedQuestionId` with the question.
|
||||
|
||||
## Show prompt suggestions during keyword search
|
||||
|
||||
Create an Algolia index whose records contain a `prompt` string, then configure its name:
|
||||
|
||||
```js title="docsearch.js"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
promptSuggestions: {
|
||||
indexName: 'docs_prompt_suggestions',
|
||||
hitsPerPage: 3,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
As the user types, DocSearch searches this index with the entered query and retrieves only the `prompt` attribute. It places matching prompts after the action that asks the entered text directly.
|
||||
|
||||
`hitsPerPage` defaults to `3`. The Docusaurus adapter requires a positive value. If this search fails, DocSearch keeps the direct Ask AI action and omits the suggestions.
|
||||
|
||||
Selecting a prompt starts Agent Studio with that prompt. It doesn't send a `suggestedQuestionId`.
|
||||
|
||||
## Show streamed follow-ups
|
||||
|
||||
Configure contextual suggestions in the Agent Studio agent editor. See [write and configure agent prompts](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/dashboard#write-and-configure-agent-prompts).
|
||||
|
||||
Agent Studio streams these follow-ups in a `data-suggestions` part. DocSearch renders its `suggestions` array below the latest answer only. Selecting one sends it as the next user message in the same conversation.
|
||||
|
||||
There's no DocSearch `askAi` option for streamed follow-ups. If Agent Studio doesn't stream `data-suggestions`, DocSearch doesn't show them.
|
||||
|
||||
See the [JavaScript package reference](/docs/packages/js/api-reference) and [React package reference](/docs/packages/react/api-reference) for the suggestion options and translation keys.
|
||||
106
packages/website/docs/agent-studio/tools.mdx
Normal file
106
packages/website/docs/agent-studio/tools.mdx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
title: Add tools
|
||||
description: Extend Agent Studio with tools for custom actions and data.
|
||||
---
|
||||
|
||||
Use tools to let your agent read live data or perform actions. Configure the tool and its input schema in Agent Studio, then add a matching entry to `askAi.tools` when DocSearch needs to run or render it.
|
||||
|
||||
See [Agent Studio tools](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/tools/overview) before exposing a tool to users.
|
||||
|
||||
## Add a client-side tool
|
||||
|
||||
The key in `tools` must match the tool name configured in Agent Studio.
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
import { DocSearchAI, type ToolCalls } from '@docsearch/react';
|
||||
|
||||
const tools: ToolCalls = {
|
||||
getAccountPlan: {
|
||||
translations: {
|
||||
callingToolText: 'Checking your account',
|
||||
},
|
||||
render({ message: { input, output } }) {
|
||||
const result = output as { plan?: string } | undefined;
|
||||
return result?.plan ? `Account plan: ${result.plan}` : '';
|
||||
},
|
||||
async onToolCall({ input, addToolOutput }) {
|
||||
const response = await fetch('/api/account-plan', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
|
||||
const output = await response.json();
|
||||
await addToolOutput({ output });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function Search() {
|
||||
return (
|
||||
<DocSearchAI
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['docs']}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
tools,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The JavaScript package accepts the same `askAi.tools` object.
|
||||
|
||||
Every tool definition requires `render`. DocSearch calls it after output is available and passes both `input` and `output`. Return a string to show a result, or an empty string to show nothing.
|
||||
|
||||
Add `onToolCall` for a tool that runs in the browser. It receives:
|
||||
|
||||
- `input`: the arguments selected by the agent. Treat this value as untrusted and validate it.
|
||||
- `toolCallId`: the current call ID.
|
||||
- `toolName`: the configured tool name.
|
||||
- `dynamic`: whether the streamed call was marked as dynamic, when provided.
|
||||
- `addToolOutput`: the function that returns the result to the conversation.
|
||||
|
||||
Call `addToolOutput({ output })` after your work finishes. DocSearch invokes `onToolCall` without waiting for its returned promise, so handle failures inside the callback.
|
||||
|
||||
Keep browser tools within the signed-in user's permissions. Don't place service credentials in the tool definition. Call your backend for privileged work. See [client-side tool security](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/tools/security).
|
||||
|
||||
For React, define `tools` outside the component or memoize it. Recreating the object doesn't change behavior, but a stable object avoids rebuilding dependent configuration.
|
||||
|
||||
## Render server-side or MCP tools
|
||||
|
||||
If Agent Studio executes a tool, omit `onToolCall` and provide `render` under the streamed tool name. DocSearch uses the output returned by Agent Studio.
|
||||
|
||||
```js title="tools.js"
|
||||
const tools = {
|
||||
get_release_status: {
|
||||
render({ message: { output } }) {
|
||||
const status = output?.status;
|
||||
return status ? `Release status: ${status}` : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Understand Algolia MCP search tools
|
||||
|
||||
DocSearch recognizes these Agent Studio search tool names without a `tools` entry:
|
||||
|
||||
- `algolia_search_index`
|
||||
- Any name beginning with `algolia_search_index_`
|
||||
|
||||
The recognized input includes `query`, `index`, optional `number_of_results`, and optional `facet_filters`. The output can include `hits`, `nbHits`, and `queryId`.
|
||||
|
||||
DocSearch shows search progress and the query used by the tool. It combines consecutive completed search calls into one search summary when more than one call has a nonempty query. A non-search message part ends the group.
|
||||
|
||||
You can add a matching `tools` entry to replace the built-in rendering for a search or memory tool. Your custom renderer takes precedence.
|
||||
|
||||
To connect other MCP servers to an agent, see [MCP tools in Agent Studio](https://www.algolia.com/doc/guides/algolia-ai/agent-studio/how-to/tools/mcp-tools). See the [React package reference](/docs/packages/react/api-reference) for `ToolCalls` and `ToolDefinition`.
|
||||
|
||||
## Use tools with Docusaurus
|
||||
|
||||
Docusaurus removes functions while serializing theme configuration. The v5 adapter rejects `askAi.tools` and `sidePanel.tools` in `docusaurus.config`. Swizzle `@theme/SearchBar` and pass tools to the modal or Sidepanel from React instead.
|
||||
|
||||
See the [Docusaurus adapter guide](/docs/packages/docusaurus-adapter/getting-started) for the supported configuration.
|
||||
|
|
@ -1,882 +1,28 @@
|
|||
---
|
||||
title: API Reference
|
||||
title: API references
|
||||
description: Browse API references for DocSearch packages and integrations.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
Choose the reference for the package in your application:
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
- [`@docsearch/js` API](/docs/packages/js/api-reference)
|
||||
- [`@docsearch/react` API](/docs/packages/react/api-reference)
|
||||
- [`@docsearch/modal` API](/docs/packages/modal/api)
|
||||
- [`@docsearch/sidepanel` API](/docs/packages/sidepanel/api)
|
||||
- [`@docsearch/sidepanel-js` API](/docs/packages/sidepanel-js/api)
|
||||
- [`@docsearch/core` API](/docs/packages/core/api)
|
||||
- [Docusaurus adapter configuration](/docs/packages/docusaurus-adapter/configuration-reference)
|
||||
|
||||
For an end-to-end example, start with the [package overview](/docs/packages/overview).
|
||||
|
||||
## `container`
|
||||
|
||||
> `type: string | HTMLElement` | **required**
|
||||
See the [`@docsearch/js` API](/docs/packages/js/api-reference#container) for the JavaScript container option.
|
||||
|
||||
The container for the DocSearch search box. You can either pass a [CSS selector][5] or an [Element][6]. If there are several containers matching the selector, DocSearch picks up the first one.
|
||||
## Translations
|
||||
|
||||
## `environment`
|
||||
|
||||
> `type: typeof window` | `default: window` | **optional**
|
||||
|
||||
The environment in which your application is running.
|
||||
|
||||
This is useful if you’re using DocSearch in a different context than window.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## `appId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Your Algolia application ID.
|
||||
|
||||
## `apiKey`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Your Algolia Search API key.
|
||||
|
||||
## `indices`
|
||||
|
||||
> `type: Array<string | DocSearchIndex>` | **required**
|
||||
|
||||
The list of indices and their _optional_ `searchParameters` to be used for keyword search.
|
||||
|
||||
[Algolia Search Parameters][7]
|
||||
|
||||
:::tip
|
||||
|
||||
The ordering matters in the list, as results are ordered based on `indices` order.
|
||||
|
||||
:::
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
indices: ['YOUR_ALGOLIA_INDEX'],
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
in case you want to use custom `searchParameters` for the index
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
indices: [
|
||||
{
|
||||
name: 'YOUR_ALGOLIA_INDEX',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
// ...
|
||||
},
|
||||
},
|
||||
],
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
indices={['YOUR_ALGOLIA_INDEX']}
|
||||
// ...
|
||||
/>
|
||||
```
|
||||
|
||||
in case you want to use custom `searchParameters` for the index
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
indices={[
|
||||
{
|
||||
name: 'YOUR_ALGOLIA_INDEX',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
// ...
|
||||
},
|
||||
},
|
||||
]}
|
||||
// ...
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## `placeholder`
|
||||
|
||||
> `type: string` | `default: "Search docs"` | **optional**
|
||||
|
||||
The placeholder of the input of the DocSearch pop-up modal. Note: If you add a placeholder it will replace the dynamic placeholder based on askAi, It would be better to edit [translations](#translations)
|
||||
See the [React API](/docs/packages/react/api-reference#translations) for the shared translation structure.
|
||||
|
||||
## `askAi`
|
||||
|
||||
> `type: AskAiObject` | `string` | **optional**
|
||||
|
||||
Your Algolia Assistant ID.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
askAi: 'YOUR_ALGOLIA_ASSISTANT_ID',
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
or if you want to use different credentials for askAi and add search parameters
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
askAi: {
|
||||
indexName: 'ANOTHER_INDEX_NAME',
|
||||
apiKey: 'ANOTHER_SEARCH_API_KEY',
|
||||
appId: 'ANOTHER_APP_ID',
|
||||
assistantId: 'YOUR_ALGOLIA_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
// Filtering parameters
|
||||
facetFilters: ['language:en', 'version:latest'],
|
||||
filters: 'type:content AND language:en',
|
||||
|
||||
// Content control parameters
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
restrictSearchableAttributes: ['title', 'content'],
|
||||
|
||||
// Deduplication
|
||||
distinct: true,
|
||||
},
|
||||
|
||||
// Enables/disables showing suggested questions on Ask AI's new conversation screen
|
||||
// NOTE: Only available with version >= 4.3
|
||||
suggestedQuestions: true,
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
askAi="YOUR_ALGOLIA_ASSISTANT_ID"
|
||||
/>
|
||||
```
|
||||
|
||||
in case you want to use different credentials for askAi
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
askAi={{
|
||||
indexName: 'ANOTHER_INDEX_NAME',
|
||||
apiKey: 'ANOTHER_SEARCH_API_KEY',
|
||||
appId: 'ANOTHER_APP_ID',
|
||||
assistantId: 'YOUR_ALGOLIA_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
// Filtering parameters
|
||||
facetFilters: ['language:en', 'version:latest'],
|
||||
filters: 'type:content AND language:en',
|
||||
|
||||
// Content control parameters
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
restrictSearchableAttributes: ['title', 'content'],
|
||||
|
||||
// Deduplication
|
||||
distinct: true,
|
||||
},
|
||||
|
||||
// Enables/disables showing suggested questions on Ask AI's new conversation screen
|
||||
// NOTE: Only available with version >= 4.3
|
||||
suggestedQuestions: true,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip[Ask AI supports these essential search parameters for optimal performance:]
|
||||
|
||||
- **Filtering**: `facetFilters: ['type:content']` - Filter by language, version, or content type
|
||||
- **Complex filtering**: `filters: 'type:content AND language:en'` - Apply complex filtering rules
|
||||
- **Content control**: `attributesToRetrieve: ['title', 'content', 'url']` - Control which attributes are retrieved
|
||||
- **Search scope**: `restrictSearchableAttributes: ['title', 'content']` - Limit search to specific fields
|
||||
- **Deduplication**: `distinct: true` - Remove duplicate results
|
||||
|
||||
These parameters provide the essential functionality for Ask AI while keeping the API simple and focused.
|
||||
|
||||
:::
|
||||
|
||||
## `agentStudio`
|
||||
|
||||
> `type: boolean` | **optional** | **experimental**
|
||||
|
||||
:::warning[Experimental]
|
||||
|
||||
`agentStudio` is currently an experimental property. It is targeted to be stable in release `5.0.0`.
|
||||
|
||||
:::
|
||||
|
||||
If `agentStudio` is true, the Ask AI chat will use Algolia's [Agent Studio][12] as the chat backend instead of the Ask AI backend. Learn more on [Algolia Agent Studio Docs][13].
|
||||
|
||||
## `transformItems`
|
||||
|
||||
> `type: function` | `default: items => items` | **optional**
|
||||
|
||||
Receives the items from the search response, and is called before displaying them. Should return a new array with the same shape as the original array. Useful for mapping over the items to transform, and remove or reorder them.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
transformItems(items) {
|
||||
return items.map((item) => ({
|
||||
...item,
|
||||
content: item.content.toUpperCase(),
|
||||
}));
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
transformItems={(items) => {
|
||||
return items.map((item) => ({
|
||||
...item,
|
||||
content: item.content.toUpperCase(),
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## `hitComponent`
|
||||
|
||||
> `type: ({ hit, children }, { html }) => JSX.Element | string | Function` | `default: Hit` | **optional**
|
||||
|
||||
The component to display each item. Supports template patterns:
|
||||
|
||||
- **HTML strings with html helper** (recommended for JS CDN): `({ hit, children }, { html }) => html...`
|
||||
- **JSX templates** (for React/Preact): `({ hit, children }) => <div>...</div>`
|
||||
- **Function-based templates**: `(props) => string | JSX.Element | Function`
|
||||
|
||||
You get access to the `hit` object which contains all the data for the search result, and `children` which is the default rendered content.
|
||||
|
||||
See the [default implementation][8].
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
hitComponent({ hit, children }, { html }) {
|
||||
// Using HTML strings with html helper
|
||||
return html`
|
||||
<a href="${hit.url}" class="custom-hit-class">
|
||||
<div class="hit-icon">🔍</div>
|
||||
<div class="hit-content">${children}</div>
|
||||
</a>
|
||||
`;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
hitComponent={({ hit, children }) => {
|
||||
// Using JSX templates
|
||||
return (
|
||||
<a href={hit.url} className="custom-hit-class">
|
||||
<div className="hit-icon">🔍</div>
|
||||
<div className="hit-content">{children}</div>
|
||||
</a>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## `transformSearchClient`
|
||||
|
||||
> `type: function` | `default: DocSearchTransformClient => DocSearchTransformClient` | **optional**
|
||||
|
||||
Useful for transforming the [Algolia Search Client][10], for example to [debounce search queries][9]
|
||||
|
||||
## `disableUserPersonalization`
|
||||
|
||||
> `type: boolean` | `default: false` | **optional**
|
||||
|
||||
Disable saving recent searches and favorites to the local storage.
|
||||
|
||||
## `initialQuery`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
The search input initial query.
|
||||
|
||||
## `navigator`
|
||||
|
||||
> `type: Navigator` | **optional**
|
||||
|
||||
An implementation of [Algolia Autocomplete][1]’s Navigator API to redirect the user when opening a link.
|
||||
|
||||
Learn more on the [Navigator API][11] documentation.
|
||||
|
||||
## `translations`
|
||||
|
||||
> `type: Partial<DocSearchTranslations>` | `default: docSearchTranslations` | **optional**
|
||||
|
||||
Allow translations of any raw text and aria-labels present in the DocSearch button or modal components.
|
||||
|
||||
<details>
|
||||
<summary>docSearchTranslations</summary>
|
||||
<div>
|
||||
|
||||
```ts
|
||||
const translations: DocSearchTranslations = {
|
||||
button: {
|
||||
buttonText: 'Search',
|
||||
buttonAriaLabel: 'Search',
|
||||
},
|
||||
modal: {
|
||||
searchBox: {
|
||||
clearButtonTitle: 'Clear',
|
||||
clearButtonAriaLabel: 'Clear the query',
|
||||
closeButtonText: 'Close',
|
||||
closeButtonAriaLabel: 'Close',
|
||||
placeholderText: undefined, // fallback: 'Search docs' or 'Search docs or ask AI a question'
|
||||
placeholderTextAskAi: undefined, // fallback: 'Ask another question...'
|
||||
placeholderTextAskAiStreaming: 'Answering...',
|
||||
// can only be one of the following
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/enterkeyhint#value
|
||||
enterKeyHint: 'search',
|
||||
enterKeyHintAskAi: 'enter',
|
||||
searchInputLabel: 'Search',
|
||||
backToKeywordSearchButtonText: 'Back to keyword search',
|
||||
backToKeywordSearchButtonAriaLabel: 'Back to keyword search',
|
||||
newConversationPlaceholder: 'Ask a question',
|
||||
conversationHistoryTitle: 'My conversation history',
|
||||
startNewConversationText: 'Start a new conversation',
|
||||
viewConversationHistoryText: 'Conversation history'
|
||||
},
|
||||
startScreen: {
|
||||
recentSearchesTitle: 'Recent',
|
||||
noRecentSearchesText: 'No recent searches',
|
||||
saveRecentSearchButtonTitle: 'Save this search',
|
||||
removeRecentSearchButtonTitle: 'Remove this search from history',
|
||||
favoriteSearchesTitle: 'Favorite',
|
||||
removeFavoriteSearchButtonTitle: 'Remove this search from favorites',
|
||||
recentConversationsTitle: 'Recent conversations',
|
||||
removeRecentConversationButtonTitle:
|
||||
'Remove this conversation from history',
|
||||
},
|
||||
errorScreen: {
|
||||
titleText: 'Unable to fetch results',
|
||||
helpText: 'You might want to check your network connection.',
|
||||
},
|
||||
noResultsScreen: {
|
||||
noResultsText: 'No results found for',
|
||||
suggestedQueryText: 'Try searching for',
|
||||
reportMissingResultsText: 'Believe this query should return results?',
|
||||
reportMissingResultsLinkText: 'Let us know.',
|
||||
},
|
||||
resultsScreen: {
|
||||
askAiPlaceholder: 'Ask AI: ',
|
||||
noResultsAskAiPlaceholder: 'Didn't find it in the docs? Ask AI to help: ',
|
||||
},
|
||||
askAiScreen: {
|
||||
disclaimerText:
|
||||
'Answers are generated with AI which can make mistakes. Verify responses.',
|
||||
relatedSourcesText: 'Related sources',
|
||||
thinkingText: 'Thinking...',
|
||||
copyButtonText: 'Copy',
|
||||
copyButtonCopiedText: 'Copied!',
|
||||
copyButtonTitle: 'Copy',
|
||||
likeButtonTitle: 'Like',
|
||||
dislikeButtonTitle: 'Dislike',
|
||||
thanksForFeedbackText: 'Thanks for your feedback!',
|
||||
preToolCallText: 'Searching...',
|
||||
duringToolCallText: 'Searching for ',
|
||||
afterToolCallText: 'Searched for',
|
||||
// If provided, these override the default rendering of aggregated tool calls:
|
||||
aggregatedToolCallNode: undefined, // (queries: string[], onSearchQueryClick: (query: string) => void) => React.ReactNode
|
||||
aggregatedToolCallText: undefined, // (queries: string[]) => { before?: string; separator?: string; lastSeparator?: string; after?: string }
|
||||
// Text to show when user has stopped streaming a message
|
||||
stoppedStreamingText: 'You stopped this response',
|
||||
},
|
||||
footer: {
|
||||
selectText: 'Select',
|
||||
submitQuestionText: 'Submit question',
|
||||
selectKeyAriaLabel: 'Enter key',
|
||||
navigateText: 'Navigate',
|
||||
navigateUpKeyAriaLabel: 'Arrow up',
|
||||
navigateDownKeyAriaLabel: 'Arrow down',
|
||||
closeText: 'Close',
|
||||
backToSearchText: 'Back to search',
|
||||
closeKeyAriaLabel: 'Escape key',
|
||||
poweredByText: 'Powered by',
|
||||
},
|
||||
newConversation: {
|
||||
newConversationTitle: 'How can I help you today?',
|
||||
newConversationDescription: 'I search through your documentation to help you find setup guides, feature details and troubleshooting tips, fast.'
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
</div>
|
||||
</details>
|
||||
|
||||
## `getMissingResultsUrl`
|
||||
|
||||
> `type: ({ query: string }) => string` | **optional**
|
||||
|
||||
Function to return the URL of your documentation repository.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
getMissingResultsUrl({ query }) {
|
||||
return `https://github.com/algolia/docsearch/issues/new?title=${query}`;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
getMissingResultsUrl={({ query }) => {
|
||||
return `https://github.com/algolia/docsearch/issues/new?title=${query}`;
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
When provided, an informative message wrapped with your link will be displayed on no results searches. The default text can be changed using the [translations](#translations) property.
|
||||
|
||||
<div className="uil-ta-center">
|
||||
<img
|
||||
src={useBaseUrl('img/assets/noResultsScreen.png')}
|
||||
alt="No results screen with informative message"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## `keyboardShortcuts`
|
||||
|
||||
> `type: KeyboardShortcuts` | **optional**
|
||||
|
||||
Configuration for keyboard shortcuts that trigger the search modal.
|
||||
|
||||
### Default behavior:
|
||||
|
||||
- `Ctrl/Cmd+K` - Opens and closes the search modal
|
||||
- `/` - Opens the search modal (doesn't close)
|
||||
|
||||
### Interface:
|
||||
|
||||
```typescript
|
||||
interface KeyboardShortcuts {
|
||||
'Ctrl/Cmd+K'?: boolean; // default: true
|
||||
'/'?: boolean; // default: true
|
||||
}
|
||||
```
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
// Default - all shortcuts enabled
|
||||
docsearch({
|
||||
// ...
|
||||
});
|
||||
|
||||
// Disable slash shortcut
|
||||
docsearch({
|
||||
// ...
|
||||
keyboardShortcuts: { '/': false },
|
||||
});
|
||||
|
||||
// Disable Ctrl/Cmd+K shortcut (also hides button hint)
|
||||
docsearch({
|
||||
// ...
|
||||
keyboardShortcuts: { 'Ctrl/Cmd+K': false },
|
||||
});
|
||||
|
||||
// Disable all keyboard shortcuts
|
||||
docsearch({
|
||||
// ...
|
||||
keyboardShortcuts: { 'Ctrl/Cmd+K': false, '/': false },
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
{
|
||||
/* Default - all shortcuts enabled */
|
||||
}
|
||||
<DocSearch
|
||||
// ...
|
||||
/>;
|
||||
|
||||
{
|
||||
/* Disable slash shortcut */
|
||||
}
|
||||
<DocSearch
|
||||
// ...
|
||||
keyboardShortcuts={{ '/': false }}
|
||||
/>;
|
||||
|
||||
{
|
||||
/* Disable Ctrl/Cmd+K shortcut (also hides button hint) */
|
||||
}
|
||||
<DocSearch
|
||||
// ...
|
||||
keyboardShortcuts={{ 'Ctrl/Cmd+K': false }}
|
||||
/>;
|
||||
|
||||
{
|
||||
/* Disable all keyboard shortcuts */
|
||||
}
|
||||
<DocSearch
|
||||
// ...
|
||||
keyboardShortcuts={{ 'Ctrl/Cmd+K': false, '/': false }}
|
||||
/>;
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::info[Keyboard Shortcut Behavior]
|
||||
|
||||
- **Ctrl/Cmd+K**: Toggle shortcut that both opens and closes the modal
|
||||
- **/**: Character shortcut that only opens the modal (prevents interference with search typing)
|
||||
- **Escape**: Always works to close the modal regardless of Configuration
|
||||
|
||||
:::
|
||||
|
||||
## `resultsFooterComponent`
|
||||
|
||||
> `type: ({ state }, { html }) => JSX.Element | string | Function` | **optional**
|
||||
|
||||
The component to display below the search results. Supports template patterns:
|
||||
|
||||
- **HTML strings with html helper** (recommended for JS CDN): `({ state }, { html }) => html...`
|
||||
- **JSX templates** (for React/Preact): `({ state }) => <div>...</div>`
|
||||
- **Function-based templates**: `(props) => string | JSX.Element | Function`
|
||||
|
||||
You get access to the [current state](https://github.com/algolia/autocomplete/blob/next/packages/autocomplete-core/src/types/AutocompleteState.ts) which allows you to retrieve the number of hits returned, the query etc.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
resultsFooterComponent({ state }, { html }) {
|
||||
// Using HTML strings with html helper
|
||||
return html`
|
||||
<div class="DocSearch-HitsFooter">
|
||||
<a href="https://docsearch.algolia.com/apply" target="_blank">
|
||||
See all ${state.context.nbHits} results
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
resultsFooterComponent={({ state }) => {
|
||||
// Using JSX templates
|
||||
return (
|
||||
<div className="DocSearch-HitsFooter">
|
||||
<a href="https://docsearch.algolia.com/apply" target="_blank">
|
||||
See all {state.context.nbHits} results
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## `maxResultsPerGroup`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
The maximum number of results to display per search group. Default is 5.
|
||||
|
||||
[You can find a working example without JSX in this sandbox](https://codesandbox.io/s/docsearch-v3-maxresultspergroup-without-jsx-ct9m22?file=/src/index.js)
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
maxResultsPerGroup: 7,
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## `recentSearchesLimit`
|
||||
|
||||
> `type: number` | `default: 7` | **optional**
|
||||
|
||||
The maximum number of recent searches that are stored for the user. Default is 7.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js' },
|
||||
{ label: 'React', value: 'react' }
|
||||
]}
|
||||
>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
recentSearchesLimit: 12,
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
recentSearchesLimit={12}
|
||||
// ...
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## `recentSearchesWithFavoritesLimit`
|
||||
|
||||
> `type: number` | `default: 4` | **optional**
|
||||
|
||||
The maximum number of recent searches that are stored when the user has favorited searches. Default is 4.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js' },
|
||||
{ label: 'React', value: 'react' }
|
||||
]}
|
||||
>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// ...
|
||||
recentSearchesWithFavoritesLimit: 5,
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
// ...
|
||||
recentSearchesWithFavoritesLimit={5}
|
||||
// ...
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## `portalContainer` (React-only)
|
||||
|
||||
> `type: Element | DocumentFragment` | `default: document.body` | **optional**
|
||||
|
||||
The element where the DocSearch modal will be portaled. Use this when you need the overlay to render in a custom DOM node—for example when working inside a shadow root, a specific layout container, or a modal manager. When omitted, the modal portals to `document.body`.
|
||||
|
||||
:::warning
|
||||
|
||||
This prop only exists in `@docsearch/react`. If you are using **`@docsearch/js`**, use the [`container`](#container) option instead—the value you pass there is both the **mount point** of the search button _and_ the portal target for the modal.
|
||||
|
||||
:::
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="react"
|
||||
values={[{ label: 'React', value: 'react' }, { label: 'JavaScript', value: 'js' }]}
|
||||
>
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
// assume you have a dedicated modal root in your html
|
||||
<div id="modal-root" />;
|
||||
|
||||
const portalEl = document.getElementById('modal-root');
|
||||
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
// render the modal inside #modal-root instead of document.body
|
||||
portalContainer={portalEl}
|
||||
/>;
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
// the element that will **contain the button** and **host the modal portal**
|
||||
container: '#modal-root',
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'],
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
[1]: https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/
|
||||
[2]: https://github.com/algolia/docsearch/
|
||||
[3]: https://github.com/algolia/docsearch/tree/master
|
||||
[4]: /docs/legacy/dropdown
|
||||
[5]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors
|
||||
[6]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
|
||||
[7]: https://www.algolia.com/doc/api-reference/search-api-parameters/
|
||||
[8]: https://github.com/algolia/docsearch/blob/main/packages/docsearch-react/src/Hit.tsx
|
||||
[9]: https://codesandbox.io/s/docsearch-v3-debounced-search-gnx87
|
||||
[10]: https://www.algolia.com/doc/api-client/getting-started/what-is-the-api-client/javascript/?client=javascript
|
||||
[11]: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/keyboard-navigation/
|
||||
[12]: https://www.algolia.com/products/ai/agent-studio
|
||||
[13]: https://www.algolia.com/doc/guides/algolia-ai/agent-studio
|
||||
See [Get started with Agent Studio](/docs/agent-studio/getting-started) for AI configuration.
|
||||
|
|
|
|||
|
|
@ -1,309 +1,412 @@
|
|||
---
|
||||
title: Composable API
|
||||
description: Build a DocSearch v5 interface from the button, keyword modal, Ask AI modal, and shared state provider.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
:::info
|
||||
The Composable API is available from version `>= 4.3`
|
||||
:::
|
||||
The composable API gives you control over where DocSearch renders, when its modal code loads, and how the rest of your application opens or closes it.
|
||||
|
||||
DocSearch has a new Composable API for rendering the DocSearch button and modal. This API was
|
||||
introduced to help with more explicit control over where and how the components are rendered within a page.
|
||||
Use this API in React applications. For the connected component reference, see the [modal package overview](/docs/packages/modal/overview).
|
||||
|
||||
## Introduction
|
||||
## Choose a modal
|
||||
|
||||
The Composable API was introduced to help give more flexibility on how you render and use DocSearch on your website. With it,
|
||||
you have more control of where, when and how you want to bundle the components and render them.
|
||||
DocSearch v5 provides two modal components. Render one modal for each `DocSearch` provider.
|
||||
|
||||
With Composable API comes two new NPM packages:
|
||||
| Component | Use it for |
|
||||
| --------------------- | ------------------------------------------- |
|
||||
| `DocSearchModal` | Keyword search without Ask AI |
|
||||
| `DocSearchAskAiModal` | Keyword search and Ask AI in the same modal |
|
||||
|
||||
- `@docsearch/core` - Shared core logic for managing different states of DocSearch
|
||||
- `@docsearch/modal` - The actual components used for the DocSearch Modal
|
||||
`DocSearchAskAiModal` includes keyword search. Don't render both modal components to add Ask AI.
|
||||
|
||||
:::warning
|
||||
Because of the nature of composability, this API is only available within React, and not within the `@docsearch/js` package.
|
||||
:::
|
||||
The composable components come from three packages:
|
||||
|
||||
## Getting Started
|
||||
- [`@docsearch/core`](/docs/packages/core/overview) provides `DocSearch`, shared state, keyboard handling, and the imperative ref.
|
||||
- [`@docsearch/modal`](/docs/packages/modal/overview) provides the provider-connected button and modal components.
|
||||
- [`@docsearch/css`](/docs/packages/css/styling) provides the styles.
|
||||
|
||||
In order to start using the Composable API, you will need to install the following three packages:
|
||||
## Install the packages
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{ label: 'npm', value: 'npm', },
|
||||
{ label: 'yarn', value: 'yarn', },
|
||||
{ label: 'pnpm', value: 'pnpm' },
|
||||
{ label: 'bun', value: 'bun' }
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
Install matching v5 versions of the DocSearch packages:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/core @docsearch/modal @docsearch/css
|
||||
npm install @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/core @docsearch/modal @docsearch/css
|
||||
yarn add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/core @docsearch/modal @docsearch/css
|
||||
pnpm add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="bun">
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/core @docsearch/modal @docsearch/css
|
||||
bun add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
> Or using your package manager of choice
|
||||
## Add keyword search
|
||||
|
||||
## Implementation
|
||||
Wrap the button and keyword modal in one `DocSearch` provider. Pass a public search-only API key. Prefer `indices` over the deprecated `indexName` and `searchParameters` props.
|
||||
|
||||
The most simple implementation would be as follows:
|
||||
|
||||
```tsx
|
||||
```tsx title="KeywordSearch.tsx"
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { DocSearchButton, DocSearchModal } from '@docsearch/modal';
|
||||
import '@docsearch/css/style.css';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
export function Search() {
|
||||
return (
|
||||
<DocSearch>
|
||||
<DocSearchButton />
|
||||
<DocSearchModal
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
import '@docsearch/css/dist/style.css';
|
||||
|
||||
interface KeywordSearchProps {
|
||||
appId: string;
|
||||
apiKey: string;
|
||||
indexName: string;
|
||||
}
|
||||
```
|
||||
|
||||
:::info
|
||||
The actual components MUST be rendered within the `<DocSearch>` Provider in order for them to communicate with the global state.
|
||||
:::
|
||||
|
||||
This setup is slightly more involved with now rendering three different components:
|
||||
|
||||
- `<DocSearch>` is the parent element which controls and shares all state with the child components
|
||||
- `<DocSearchButton />` is the actual button element that is rendered and triggers the DocSearch Modal to open
|
||||
- `<DocSearchModal />` is the main modal containing the search form, search results, and Ask AI
|
||||
|
||||
|
||||
### Ask AI
|
||||
|
||||
Using Ask AI with the Composable API is quite similar to the normal way of using DocSearch. All that is needed is the `askAi` configuration:
|
||||
|
||||
```tsx
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { DocSearchButton, DocSearchModal } from '@docsearch/modal';
|
||||
import '@docsearch/css/style.css';
|
||||
|
||||
export function Search() {
|
||||
export function KeywordSearch({
|
||||
appId,
|
||||
apiKey,
|
||||
indexName,
|
||||
}: KeywordSearchProps): JSX.Element {
|
||||
return (
|
||||
<DocSearch>
|
||||
<DocSearchButton />
|
||||
<DocSearchModal
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
// With just a simple assistant ID
|
||||
askAi="YOUR_ALGOLIA_ASSISTANT_ID"
|
||||
// Or with a more complex configuration
|
||||
askAi={{
|
||||
indexName: 'YOUR_MARKDOWN_INDEX', // Optional: use a different index for Ask AI
|
||||
apiKey: 'YOUR_SEARCH_API_KEY', // Optional: use a different API key for Ask AI
|
||||
appId: 'YOUR_APP_ID', // Optional: use a different App ID for Ask AI
|
||||
assistantId: 'YOUR_ALGOLIA_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en', 'version:1.0.0'], // Optional: filter Ask AI context
|
||||
},
|
||||
<DocSearchButton
|
||||
translations={{
|
||||
buttonText: 'Search docs',
|
||||
buttonAriaLabel: 'Search documentation',
|
||||
}}
|
||||
/>
|
||||
<DocSearchModal appId={appId} apiKey={apiKey} indices={[indexName]} />
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You can find more information on Ask AI, and it's setup in it's [dedicated docs][2].
|
||||
The provider opens the modal when a user selects the button, presses <kbd>Ctrl</kbd>/<kbd>Command</kbd>+<kbd>K</kbd>, or presses <kbd>/</kbd> outside an editable field. Closing the modal returns focus to `DocSearchButton`.
|
||||
|
||||
### Advanced
|
||||
## Add keyword search and Ask AI
|
||||
|
||||
Replace `DocSearchModal` with `DocSearchAskAiModal`. Create the assistant in [Agent Studio](/docs/agent-studio/getting-started), then pass its ID through `askAi`.
|
||||
|
||||
```tsx title="SearchWithAskAi.tsx"
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { DocSearchAskAiModal, DocSearchButton } from '@docsearch/modal';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
import '@docsearch/css/dist/style.css';
|
||||
|
||||
interface SearchWithAskAiProps {
|
||||
appId: string;
|
||||
apiKey: string;
|
||||
indexName: string;
|
||||
assistantId: string;
|
||||
}
|
||||
|
||||
export function SearchWithAskAi({
|
||||
appId,
|
||||
apiKey,
|
||||
indexName,
|
||||
assistantId,
|
||||
}: SearchWithAskAiProps): JSX.Element {
|
||||
return (
|
||||
<DocSearch>
|
||||
<DocSearchButton />
|
||||
<DocSearchAskAiModal
|
||||
appId={appId}
|
||||
apiKey={apiKey}
|
||||
indices={[indexName]}
|
||||
askAi={{ assistantId }}
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The `askAi` prop also accepts an assistant ID string. Use the object form when you need options such as `indices`, `searchParameters`, `suggestedQuestions`, `promptSuggestions`, `tools`, or `memory`. See the [React package reference](/docs/packages/react/api-reference) for those option types.
|
||||
|
||||
## Understand the shared state
|
||||
|
||||
`DocSearch` holds one state value and shares it with its descendants:
|
||||
|
||||
| State | Meaning |
|
||||
| -------------- | -------------------------------- |
|
||||
| `ready` | No modal or Sidepanel is open. |
|
||||
| `modal-search` | The keyword search view is open. |
|
||||
| `modal-askai` | Ask AI is open in the modal. |
|
||||
| `sidepanel` | The Ask AI Sidepanel is open. |
|
||||
|
||||
The connected components manage these transitions for you:
|
||||
|
||||
- `DocSearchButton` calls its own `onClick` handler, then opens keyword search.
|
||||
- Each modal registers itself with the provider and renders in a React portal only while a modal state is active.
|
||||
- `DocSearchModal` reads the provider's initial query and close action.
|
||||
- `DocSearchAskAiModal` also reads and updates the Ask AI state.
|
||||
|
||||
Use `useDocSearch` in a component under the provider when your application needs declarative access to this state.
|
||||
|
||||
```tsx
|
||||
export default function AdvancedSearch(): JSX.Element {
|
||||
import { useDocSearch } from '@docsearch/core';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
export function SearchControls(): JSX.Element {
|
||||
const { closeModal, docsearchState, openModal, onAskAiToggle } =
|
||||
useDocSearch();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Search state: {docsearchState}</span>
|
||||
<button type="button" onClick={openModal}>
|
||||
Open search
|
||||
</button>
|
||||
<button type="button" onClick={() => onAskAiToggle(true)}>
|
||||
Open Ask AI
|
||||
</button>
|
||||
<button type="button" onClick={closeModal}>
|
||||
Close search
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Only call `onAskAiToggle(true)` when the provider contains `DocSearchAskAiModal` or a compatible Ask AI view.
|
||||
|
||||
## Control DocSearch with a ref
|
||||
|
||||
Attach a `DocSearchRef` to the provider when non-React code or a parent component must control DocSearch.
|
||||
|
||||
```tsx
|
||||
import { DocSearch, type DocSearchRef } from '@docsearch/core';
|
||||
import { DocSearchAskAiModal, DocSearchButton } from '@docsearch/modal';
|
||||
import { useRef, type JSX } from 'react';
|
||||
|
||||
interface ControlledSearchProps {
|
||||
appId: string;
|
||||
apiKey: string;
|
||||
indexName: string;
|
||||
assistantId: string;
|
||||
onReady?: () => void;
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export function ControlledSearch(props: ControlledSearchProps): JSX.Element {
|
||||
const searchRef = useRef<DocSearchRef>(null);
|
||||
|
||||
return (
|
||||
<DocSearch
|
||||
keyboardShortcuts={{
|
||||
'/': false, // Disable opening/closing the DocSearchModal with '/' key
|
||||
}}
|
||||
ref={searchRef}
|
||||
onReady={props.onReady}
|
||||
onOpen={props.onOpen}
|
||||
onClose={props.onClose}
|
||||
>
|
||||
<DocSearchButton
|
||||
translations={{ buttonText: 'Advanced Search' }} // Change the displayed text on the DocSearchButton
|
||||
/>
|
||||
<DocSearchModal
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
askAi={{ // Enable Ask AI
|
||||
assistantId: 'YOUR_ALGOLIA_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
},
|
||||
}}
|
||||
portalContainer='#algolia-search' // Custom element that the DocSearchModal will be rendered into
|
||||
<DocSearchButton />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
searchRef.current?.openAskAi({
|
||||
query: 'How do I configure DocSearch?',
|
||||
})
|
||||
}
|
||||
>
|
||||
Ask a question
|
||||
</button>
|
||||
<DocSearchAskAiModal
|
||||
appId={props.appId}
|
||||
apiKey={props.apiKey}
|
||||
indices={[props.indexName]}
|
||||
askAi={{ assistantId: props.assistantId }}
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Bundle saving exports
|
||||
The ref exposes these methods and read-only values. `initialMessage` has a required `query` and optional `messageId` and `suggestedQuestionId` fields.
|
||||
|
||||
To help aid in trimming initial bundle size, the `@docsearch/modal` package exposes explicit file exports as well:
|
||||
The provider accepts `onReady`, `onOpen`, `onClose`, `onSidepanelOpen`, and `onSidepanelClose`. `onReady` runs after mount. The other callbacks run once when their corresponding view changes state, not every time React renders.
|
||||
|
||||
```ts
|
||||
### `open`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Opens keyword search.
|
||||
|
||||
### `close`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Returns the provider to `ready`.
|
||||
|
||||
### `openAskAi`
|
||||
|
||||
> `type: (initialMessage?: InitialAskAiMessage) => void`
|
||||
|
||||
Opens Ask AI in a registered Sidepanel on desktop, or in the modal otherwise.
|
||||
|
||||
### `openSidepanel`
|
||||
|
||||
> `type: (initialMessage?: InitialAskAiMessage) => void`
|
||||
|
||||
Opens a registered Sidepanel. It does nothing when no Sidepanel is registered.
|
||||
|
||||
### `isReady`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Reports whether the provider is mounted.
|
||||
|
||||
### `isOpen`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Reports whether a modal view is open.
|
||||
|
||||
### `isSidepanelOpen`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Reports whether the Sidepanel is open.
|
||||
|
||||
### `isSidepanelSupported`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Reports whether desktop hybrid mode is available.
|
||||
|
||||
## Load the modal on demand
|
||||
|
||||
Import the button eagerly and split the larger modal into another JavaScript chunk. The following Ask AI example preloads that chunk on hover, focus, or touch, then renders it only after the provider opens a modal state.
|
||||
|
||||
```tsx title="LazySearch.tsx"
|
||||
import { DocSearch, useDocSearch } from '@docsearch/core';
|
||||
import { DocSearchButton } from '@docsearch/modal/button';
|
||||
import { DocSearchModal } from '@docsearch/modal/modal';
|
||||
```
|
||||
import type { DocSearchAskAiModalProps } from '@docsearch/modal/askai';
|
||||
import { lazy, Suspense, type JSX } from 'react';
|
||||
|
||||
Here is a basic example of delaying the loading of the `DocSearchModal` code until the search button is clicked:
|
||||
import '@docsearch/css/dist/style.css';
|
||||
|
||||
```tsx
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { DocSearchButton } from '@docsearch/modal/button';
|
||||
import type { DocSearchModal as DocSearchModalType } from '@docsearch/modal/modal';
|
||||
import { useState } from 'react';
|
||||
let modalImport: Promise<typeof import('@docsearch/modal/askai')> | undefined;
|
||||
|
||||
let DocSearchModal: typeof DocSearchModalType | null = null;
|
||||
function loadModal(): Promise<typeof import('@docsearch/modal/askai')> {
|
||||
modalImport ??= import('@docsearch/modal/askai');
|
||||
return modalImport;
|
||||
}
|
||||
|
||||
async function importDocSearchModalIfNeeded() {
|
||||
if (DocSearchModal) {
|
||||
return;
|
||||
function preloadModal(): void {
|
||||
void loadModal().catch(() => {
|
||||
modalImport = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
const LazyDocSearchAskAiModal = lazy(() =>
|
||||
loadModal().then(({ DocSearchAskAiModal }) => ({
|
||||
default: DocSearchAskAiModal,
|
||||
}))
|
||||
);
|
||||
|
||||
function ModalWhenOpen(props: DocSearchAskAiModalProps): JSX.Element | null {
|
||||
const { isModalActive } = useDocSearch();
|
||||
|
||||
if (!isModalActive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { DocSearchModal: Modal } = await import('@docsearch/modal/modal');
|
||||
|
||||
DocSearchModal = Modal;
|
||||
return (
|
||||
<Suspense fallback={<span role="status">Loading search...</span>}>
|
||||
<LazyDocSearchAskAiModal {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DynamicModal() {
|
||||
const [modalLoaded, setModalLoaded] = useState(false);
|
||||
|
||||
const loadModal = () => {
|
||||
importDocSearchModalIfNeeded().then(() => {
|
||||
setModalLoaded(true);
|
||||
});
|
||||
};
|
||||
interface LazySearchProps {
|
||||
appId: string;
|
||||
apiKey: string;
|
||||
indexName: string;
|
||||
assistantId: string;
|
||||
}
|
||||
|
||||
export function LazySearch(props: LazySearchProps): JSX.Element {
|
||||
return (
|
||||
<DocSearch>
|
||||
<DocSearchButton onClick={loadModal} />
|
||||
{modalLoaded && DocSearchModal && (
|
||||
<DocSearchModal
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
/>
|
||||
)}
|
||||
<DocSearchButton
|
||||
onFocus={preloadModal}
|
||||
onMouseEnter={preloadModal}
|
||||
onTouchStart={preloadModal}
|
||||
/>
|
||||
<ModalWhenOpen
|
||||
appId={props.appId}
|
||||
apiKey={props.apiKey}
|
||||
indices={[props.indexName]}
|
||||
askAi={{ assistantId: props.assistantId }}
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Components
|
||||
Render the provider and connected components only in the browser. The modal wrappers read `document.body` and `window.scrollY` when they render.
|
||||
|
||||
### `<DocSearch />`
|
||||
## Use the exact entry points
|
||||
|
||||
The `<DocSearch />` component from the `@docsearch/core` package is the main state handler for all of DocSearch.
|
||||
It utilizes [React Context][1] to enable sharing it's state across nested components.
|
||||
Use the package root for convenience or a subpath to keep eager bundles focused.
|
||||
|
||||
#### Props
|
||||
| Import | Exports |
|
||||
| --- | --- |
|
||||
| `@docsearch/core` | `DocSearch`, `useDocSearch`, their types, keyboard utilities, and theme utilities |
|
||||
| `@docsearch/modal` | `DocSearchButton`, `DocSearchModal`, `DocSearchAskAiModal`, and their prop types |
|
||||
| `@docsearch/modal/button` | `DocSearchButton`, `DocSearchButtonProps` |
|
||||
| `@docsearch/modal/modal` | `DocSearchModal`, `DocSearchModalProps` |
|
||||
| `@docsearch/modal/askai` | `DocSearchAskAiModal`, `DocSearchAskAiModalProps` |
|
||||
|
||||
The lower-level React entry points are `@docsearch/react/button`, `@docsearch/react/modal`, and `@docsearch/react/askaiModal`. They don't connect themselves to the composable provider. Use them only when you intend to manage portal rendering, refs, `initialScrollY`, close behavior, and Ask AI state yourself.
|
||||
|
||||
## Load the styles
|
||||
|
||||
For either complete modal, import the combined stylesheet once:
|
||||
|
||||
```ts
|
||||
interface DocSearchProps {
|
||||
// React children to be rendered within the DocSearch Provider
|
||||
children: Array<JSX.Element | null> | JSX.Element | React.ReactNode | null;
|
||||
// Theme to be set enabling style changes for `light` or `dark` themes
|
||||
theme?: 'light' | 'dark';
|
||||
// Initial starting query for keyword search
|
||||
initialQuery?: string;
|
||||
// Manage supported keyboard shortcuts for opening/closing the DocSearch Modal
|
||||
keyboardShortcuts?: {
|
||||
'Ctrl/Cmd+K': boolean,
|
||||
'/': boolean,
|
||||
};
|
||||
}
|
||||
import '@docsearch/css/dist/style.css';
|
||||
```
|
||||
|
||||
### `<DocSearchButton />`
|
||||
The combined stylesheet contains variables, button styles, keyword modal styles, and Ask AI modal styles. It doesn't contain Sidepanel styles.
|
||||
|
||||
The main DocSearch search button to trigger the DocSearch Modal.
|
||||
|
||||
#### Props
|
||||
For a keyword-only CSS bundle, import the layers in this order:
|
||||
|
||||
```ts
|
||||
interface DocSearchButtonProps {
|
||||
// Optional callback for when the button is clicked. The original click event is passed.
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
// Translation strings specific to the button.
|
||||
translations: {
|
||||
buttonText?: string;
|
||||
buttonAriaLabel?: string;
|
||||
};
|
||||
}
|
||||
import '@docsearch/css/dist/_variables.css';
|
||||
import '@docsearch/css/dist/button.css';
|
||||
import '@docsearch/css/dist/modal.css';
|
||||
```
|
||||
|
||||
### `<DocSearchModal />`
|
||||
Add `@docsearch/css/dist/_askai.css` when you use `DocSearchAskAiModal`. Bundlers can also load the same files through `@docsearch/react/style`, `@docsearch/react/style/button`, `@docsearch/react/style/modal`, `@docsearch/react/style/askai`, and `@docsearch/react/style/variables`.
|
||||
|
||||
The main keyword search Modal used to search your documentation.
|
||||
## API summary
|
||||
|
||||
#### Props
|
||||
| API | Required configuration | Provider-managed behavior |
|
||||
| --- | --- | --- |
|
||||
| `DocSearch` | `children` | State, theme, initial query, shortcuts, focus restoration, lifecycle callbacks, and `DocSearchRef` |
|
||||
| `DocSearchButton` | None | Button ref, theme, shortcuts, and opening keyword search |
|
||||
| `DocSearchModal` | `appId`, `apiKey`, and at least one `indices` entry or deprecated `indexName` | Open state, close action, initial scroll position, initial query, theme, and shortcuts |
|
||||
| `DocSearchAskAiModal` | The keyword modal configuration plus `askAi` | Keyword modal behavior, Ask AI state, Ask AI transitions, and hybrid detection |
|
||||
| `useDocSearch` | A parent `DocSearch` provider | Reads the context and throws when used outside the provider |
|
||||
|
||||
```ts
|
||||
interface DocSearchModalProps {
|
||||
/**
|
||||
* Algolia application id used by the search client.
|
||||
*/
|
||||
appId: string;
|
||||
/**
|
||||
* Public api key with search permissions for the index.
|
||||
*/
|
||||
apiKey: string;
|
||||
/**
|
||||
* List of indices and _optional_ searchParameters to be used for search.
|
||||
*
|
||||
* @see {@link https://docsearch.algolia.com/docs/api#indices}
|
||||
*/
|
||||
indices: Array<DocSearchIndex | string>;
|
||||
/**
|
||||
* Configuration or assistant id to enable ask ai mode. Pass a string assistant id or a full config object.
|
||||
*/
|
||||
askAi?: DocSearchAskAi | string;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
`DocSearchButton` accepts native React button props and `translations`. The connected wrapper doesn't accept `theme` or `keyboardShortcuts`; set those on `DocSearch`.
|
||||
|
||||
More property documentation can be found in the [DocSearch API Reference][3] page.
|
||||
|
||||
[1]: https://react.dev/reference/react/createContext
|
||||
[2]: /docs/v4/askai
|
||||
[3]: /docs/api
|
||||
Both connected modal wrappers accept the corresponding low-level modal options, except the state and lifecycle fields supplied by the provider. Consult the [modal API](/docs/packages/modal/api) before adding options.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
---
|
||||
title: New Crawler UI/UX
|
||||
title: Crawler interface
|
||||
description: Use the Algolia Crawler interface to configure and monitor DocSearch crawls.
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
The Algolia Crawler Visual UI provides an updated, user-friendly way to manage your crawl settings and monitor your indexing process. This guide covers the main features of the new interface.
|
||||
Use the Algolia Crawler interface to manage crawl settings and monitor indexing. These settings are separate from your installed DocSearch frontend package version.
|
||||
|
||||
## DocSearch Tab
|
||||
## DocSearch tab
|
||||
|
||||
The Crawler UI now includes a dedicated **DocSearch** tab. This tab provides everything you need to implement DocSearch on your site, including:
|
||||
|
||||
- **Implementation code**: Copy-paste ready code snippets for integrating DocSearch into your frontend.
|
||||
- **Implementation code**: Starter snippets for integrating DocSearch into your frontend. For v5, compare the snippet with the current [package guide](/docs/packages/overview).
|
||||
- **API keys**: Your unique Application ID and Search API Key for connecting to your Algolia index.
|
||||
- **Quick links**: Access to review your records, explore documentation, and join the support Discord.
|
||||
- **Documentation links**: Access to review your records, explore documentation, and join the support Discord.
|
||||
|
||||
<div className="uil-ta-center">
|
||||
<img
|
||||
|
|
@ -47,8 +48,7 @@ The `Monitoring` section helps you find crawl errors or improve your search resu
|
|||
|
||||
You can update your crawler configuration in two ways:
|
||||
|
||||
**Visual Configuration UI:**
|
||||
Quickly edit common options without writing code using the new Visual Configuration interface.
|
||||
**Visual configuration:** Edit common options without writing code in the visual configuration interface.
|
||||
|
||||
<div className="uil-ta-center">
|
||||
<img
|
||||
|
|
@ -57,8 +57,7 @@ Quickly edit common options without writing code using the new Visual Configurat
|
|||
/>
|
||||
</div>
|
||||
|
||||
**Code Editor:**
|
||||
For advanced configuration, use the live code editor to directly modify your config file and test your URLs (`URL tester`).
|
||||
**Code editor:** For advanced configuration, use the live code editor to modify your configuration and test URLs with the URL tester.
|
||||
|
||||
<div className="uil-ta-center">
|
||||
<img
|
||||
|
|
@ -86,11 +85,11 @@ The **Suggestions** section in the Crawler UI provides actionable feedback to he
|
|||
- Addressing ignored or failed URLs
|
||||
- Adding missing sitemaps
|
||||
|
||||
Each suggestion includes a description, a solution, and quick links to relevant documentation or monitoring tools, so you can resolve issues efficiently and optimize your search experience.
|
||||
Each suggestion includes a description, a solution, and links to relevant documentation or monitoring tools.
|
||||
|
||||
<div className="uil-ta-center">
|
||||
<img
|
||||
src={useBaseUrl('img/assets/crawler-visual-suggestions.png')}
|
||||
alt="Algolia Crawler Suggestions Tab"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
---
|
||||
title: DocSearch x Algolia Crawler
|
||||
description: Resolve common DocSearch crawler questions and migration tasks.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
If you're not finding the answer to your question on this website, this page will help you. If you're still unsure, don't hesitate to connect with us on [Discord][1] or let our [support][3] team know.
|
||||
Use this page to resolve common crawler questions. If you need more help, contact us on [Discord][1] or through [Algolia support][3].
|
||||
|
||||
You can also read our [Crawler FAQ](https://www.algolia.com/doc/tools/crawler/troubleshooting/crawl-status/), to understand how it behaves:
|
||||
For more troubleshooting guidance, read the [Crawler FAQ][4]:
|
||||
|
||||
- [One of my pages wasn't crawled](https://www.algolia.com/doc/tools/crawler/troubleshooting/extraction-issues/#a-page-wasnt-crawled)
|
||||
- [Why are my pages skipped?](https://www.algolia.com/doc/tools/crawler/troubleshooting/fetching-issues/)
|
||||
- [One of my pages wasn't crawled][5]
|
||||
- [Why are my pages skipped?][6]
|
||||
|
||||
For questions related to the DocSearch program, please see our [DocSearch program FAQ](/docs/docsearch-program).
|
||||
|
||||
|
|
@ -22,61 +23,61 @@ Crawls are scheduled at a random time once a week. You can [configure this sched
|
|||
|
||||
This can happen when you have more than one URL pointing to the same content, for example with `./docs`, `./docs/` and `./docs/index.html`.
|
||||
|
||||
We recommend configuring canonical URLs on your website, you can read more on the ["Consolidate duplicate URLs" guide by Google](https://developers.google.com/search/docs/advanced/crawling/consolidate-duplicate-urls).
|
||||
Configure canonical URLs on your website. For more information, read Google's [Consolidate duplicate URLs][7] guide.
|
||||
|
||||
Ultimately, it is possible to set set the [`exclusionPatterns`](https://www.algolia.com/doc/tools/crawler/apis/configuration/exclusion-patterns/) to all the patterns you want to exclude.
|
||||
Use [`exclusionPatterns`][8] to exclude duplicate URL patterns.
|
||||
|
||||
## Are the [`docsearch-scraper`](https://github.com/algolia/docsearch-scraper) and [`docsearch-configs`](https://github.com/algolia/docsearch-configs) repository still maintained?
|
||||
## Are the [`docsearch-scraper`][9] and [`docsearch-configs`][10] repositories still maintained?
|
||||
|
||||
We've deprecated our legacy infrastructure, but you can still use it to [run your own instance](/docs/legacy/run-your-own) and plug it to [DocSearch v3](/docs/v3/docsearch)!
|
||||
The legacy scraper infrastructure is deprecated. You can still [run a legacy scraper instance][12] and use its index with the historical [DocSearch v3 UI][13]. For current frontend integrations, choose a [DocSearch v5 package][14]. The scraper or crawler version doesn't determine the frontend package version.
|
||||
|
||||
## How to migrate
|
||||
## How do I migrate?
|
||||
|
||||
> Every owners should have received a migration email from Algolia with the details. If you were not part of the previous `index` owners, or the maintainer has changed, you can request access via [our support page](https://www.algolia.com/support/).
|
||||
> Every owner should have received a migration email from Algolia with the details. If you were not part of the previous `index` owners, or the maintainer has changed, you can request access via [our support page](https://www.algolia.com/support/).
|
||||
|
||||
All the steps are detailed in the email you've received, but in order to use the new infrastructure you need to:
|
||||
Use the migration email to complete these steps:
|
||||
|
||||
- Join the Algolia application with the invite included in the email
|
||||
- Update your frontend integration with the credentials received in the email.
|
||||
- Update your frontend integration with the credentials received in the email. For v5, follow the [JavaScript][15] or [React][16] guide.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
aria-label="Programming language"
|
||||
defaultValue="react"
|
||||
values={[
|
||||
{ label: 'React', value: 'react', },
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js app.js
|
||||
docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_NEW_ALGOLIA_APP_ID',
|
||||
apiKey: 'YOUR_NEW_ALGOLIA_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'], // it does not change
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx App.js
|
||||
```jsx title="Search.tsx"
|
||||
<DocSearch
|
||||
appId="YOUR_NEW_ALGOLIA_APP_ID"
|
||||
apiKey="YOUR_NEW_ALGOLIA_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']} // it does not change
|
||||
indices={['YOUR_INDEX_NAME']} // The index name doesn't change.
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
```js title="search.js"
|
||||
docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_NEW_ALGOLIA_APP_ID',
|
||||
apiKey: 'YOUR_NEW_ALGOLIA_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'], // The index name doesn't change.
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## What should I do with my legacy config and credentials?
|
||||
|
||||
You can forget about them, we will do the cleaning once all of our users have migrated to the new infrastructure!
|
||||
Use the credentials for your current Algolia application. Don't use legacy credentials for new frontend integrations.
|
||||
|
||||
You should use [the dedicated web interface][2] to make any changes to your index.
|
||||
|
||||
|
|
@ -84,31 +85,31 @@ You should use [the dedicated web interface][2] to make any changes to your inde
|
|||
|
||||
We did not remove access to the legacy DocSearch application (`BH4D9OD16A`) to give you the time to get familiar with our new infrastructure. `BH4D9OD16A` will remain available until the migration has been completed for all the DocSearch users.
|
||||
|
||||
## Search yield no results
|
||||
## Search yields no results
|
||||
|
||||
If your search does not yield any results, but there is no error in [your browser developer tools](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools), there might be an issue with your index.
|
||||
|
||||
Make sure that:
|
||||
Check the following settings:
|
||||
|
||||
1. [Your Crawler config](/docs/record-extractor) matches your website structure
|
||||
1. Confirm that [your crawler configuration][17] matches your website structure.
|
||||
|
||||
We provide [config templates](/docs/templates) for many website generators, but you can also use them as a base. To debug your selectors, we recommend using [the URL tester](/docs/manage-your-crawls/#url-tester).
|
||||
Use a [configuration template][18] for your website generator as a starting point. Test your selectors with [the URL tester][19].
|
||||
|
||||
2. Your index settings are up to date (you'll see a banner in [the search preview](/docs/manage-your-crawls/#search-preview) if not)
|
||||
2. Confirm that your index settings are current. If they aren't, [the search preview][20] displays a banner.
|
||||
|
||||
The Crawler only applies `index settings` at index creation index, to keep the Algolia dashboard as the source of truth. If you have drastically changed your config, or moved to a website generator, we recommend you to delete your index from the Algolia dashboard before starting a new crawl.
|
||||
The crawler applies `index settings` only when it creates an index, which keeps the Algolia dashboard as the source of truth. If you changed your configuration significantly or moved to a different website generator, delete the index from the Algolia dashboard before starting a new crawl.
|
||||
|
||||
## Can I delete my crawler?
|
||||
|
||||
No. Well, you can but once you do things will not work correctly. We automatically create a default crawler that is associated with your DocSearch application and deleting it with the intention of creating a new one will not work as expected.
|
||||
Don't delete the default crawler associated with your DocSearch application. Creating a replacement might not preserve the program configuration.
|
||||
|
||||
## What if I delete my DocSearch Crawler?
|
||||
|
||||
The fastest way will be to connect with us on our [Discord](https://alg.li/discord). Alternatively, email us at the address below and we will get to it as soon as we can.
|
||||
Contact us on [Discord][1] or email us at the address below.
|
||||
|
||||
## Can I use the Crawler on password protected sites?
|
||||
## Can I use the crawler on password-protected sites?
|
||||
|
||||
The Crawler as used with DocSearch applications cannot be used for password protected sites that require a login. If you need this functionality, you need to utilize a regular Algolia plan https://www.algolia.com/pricing and add a crawler to it. Note that while it is free to add a pay-as-you-go crawler, the free tier does have limitations.
|
||||
The crawler included with DocSearch applications can't access password-protected sites that require a login. To crawl these sites, choose an [Algolia plan][11] and add a crawler. Pay-as-you-go plans include a crawler, but the free tier has usage limits.
|
||||
|
||||
## Links related to the migration
|
||||
|
||||
|
|
@ -118,3 +119,20 @@ The Crawler as used with DocSearch applications cannot be used for password prot
|
|||
[1]: https://alg.li/discord
|
||||
[2]: https://dashboard.algolia.com/crawler
|
||||
[3]: https://support.algolia.com/
|
||||
[4]: https://www.algolia.com/doc/tools/crawler/troubleshooting/crawl-status/
|
||||
[5]: https://www.algolia.com/doc/tools/crawler/troubleshooting/extraction-issues/#a-page-wasnt-crawled
|
||||
[6]: https://www.algolia.com/doc/tools/crawler/troubleshooting/fetching-issues/
|
||||
[7]: https://developers.google.com/search/docs/advanced/crawling/consolidate-duplicate-urls
|
||||
[8]: https://www.algolia.com/doc/tools/crawler/apis/configuration/exclusion-patterns/
|
||||
[9]: https://github.com/algolia/docsearch-scraper
|
||||
[10]: https://github.com/algolia/docsearch-configs
|
||||
[11]: https://www.algolia.com/pricing
|
||||
[12]: /docs/legacy/run-your-own
|
||||
[13]: /docs/v3/docsearch
|
||||
[14]: /docs/packages/overview
|
||||
[15]: /docs/packages/js/getting-started
|
||||
[16]: /docs/packages/react/getting-started
|
||||
[17]: /docs/record-extractor
|
||||
[18]: /docs/templates
|
||||
[19]: /docs/manage-your-crawls/#url-tester
|
||||
[20]: /docs/manage-your-crawls/#search-preview
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
---
|
||||
title: Create a New Crawler
|
||||
title: Create a new crawler
|
||||
description: Add, verify, and crawl a domain with the Algolia Crawler.
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
# Create a New Crawler
|
||||
# Create a new crawler
|
||||
|
||||
:::info
|
||||
New DocSearch apps created after **July 2nd, 2024** can now use the Algolia Crawler UI to set up and manage their crawls. This guide walks you through the process of adding your domain, verifying ownership, creating a crawler, and running your first test crawl. You can find the new Crawler UI at [dashboard.algolia.com/crawler](https://dashboard.algolia.com/crawler).
|
||||
|
||||
If you signed up before July 2nd, 2024, you can still use the Crawler UI, but creating and managing a Crawler is more streamlined for users who joined after that date.
|
||||
DocSearch apps created after **July 2, 2024** can use the Algolia Crawler UI to set up and manage crawls. This guide covers adding your domain, verifying ownership, creating a crawler, and running your first test crawl. Open the Crawler UI at [dashboard.algolia.com/crawler](https://dashboard.algolia.com/crawler).
|
||||
|
||||
If you signed up before July 2, 2024, you can still use the Crawler UI, but creating and managing a crawler is more streamlined for users who joined after that date.
|
||||
|
||||
Learn more about the [Crawler interface](./crawler-configuration-visual).
|
||||
|
||||
Learn more about the [New Crawler UI/UX features](./crawler-configuration-visual).
|
||||
:::
|
||||
|
||||
## Add domains
|
||||
|
|
@ -30,35 +33,40 @@ Learn more about the [New Crawler UI/UX features](./crawler-configuration-visual
|
|||
/>
|
||||
</div>
|
||||
|
||||
> **Note:** You must verify your domain within a 7-day grace period after adding it. Additionally, your domain must be approved for use by the DocSearch team before you can proceed with crawling.
|
||||
> **Note:** Verify your domain within 7 days of adding it. The DocSearch team must also approve the domain before you can crawl it.
|
||||
|
||||
## Verify your domain
|
||||
|
||||
You must verify ownership of each domain you want to crawl. The default method is email verification, but you can also use a meta tag, HTML file, robots.txt, or DNS record.
|
||||
|
||||
### Meta tag
|
||||
|
||||
1. In the **Meta tag** tab, click **Copy** to copy the verification tag.
|
||||
2. Add the tag to your site's `<head>` section.
|
||||
3. Publish your site and click **Verify now** in the Crawler dashboard.
|
||||
|
||||
### HTML file
|
||||
|
||||
1. In the **HTML file** tab, click **Copy** to copy the verification file content.
|
||||
2. Save it as a new HTML file and upload it to your web server.
|
||||
3. Add the file’s URL in the dashboard and click **Verify now**.
|
||||
|
||||
### robots.txt
|
||||
|
||||
1. In the **Robots.txt** tab, click **Copy** to copy the verification code.
|
||||
2. Paste it into your site's `robots.txt` file.
|
||||
3. Publish and click **Verify now**.
|
||||
|
||||
### DNS
|
||||
|
||||
1. In the **DNS** tab, copy the provided DNS TXT record.
|
||||
2. Add it to your DNS provider’s settings.
|
||||
3. Click **Verify now** after the record propagates (may take up to 72 hours).
|
||||
|
||||
## Create a new crawler
|
||||
|
||||
Once your domain is verified and approved by our DocSearch team:
|
||||
After you verify your domain and the DocSearch team approves it:
|
||||
|
||||
1. Go to the **Crawler** page in the dashboard.
|
||||
2. Click **New Crawler** and fill in:
|
||||
- **Crawler name** (descriptive)
|
||||
|
|
@ -69,10 +77,12 @@ Once your domain is verified and approved by our DocSearch team:
|
|||
|
||||
## Run the test crawl
|
||||
|
||||
The initial crawl will visit up to 100 URLs to test access and extraction. You can monitor progress in the **Overview** page. After completion, review the extracted records in the Algolia dashboard.
|
||||
The initial crawl visits up to 100 URLs to test access and extraction. Monitor its progress on the **Overview** page. After it finishes, review the extracted records in the Algolia dashboard.
|
||||
|
||||
Creating a crawler doesn't install or select a DocSearch frontend version. After indexing, choose a [DocSearch v5 package](/docs/packages/overview) or [framework integration](/docs/integrations).
|
||||
|
||||
## Next steps
|
||||
|
||||
- Edit your crawler configuration for scheduled crawls, inclusion/exclusion rules, and extraction settings.
|
||||
- Use the Crawler’s suggestions for further optimization.
|
||||
- For more details, see the [official Algolia documentation](https://www.algolia.com/doc/tools/crawler/getting-started/create-crawler/).
|
||||
- For more details, see the [Algolia Crawler documentation](https://www.algolia.com/doc/tools/crawler/getting-started/create-crawler/).
|
||||
|
|
|
|||
|
|
@ -1,96 +1,90 @@
|
|||
---
|
||||
title: DocSearch program
|
||||
description: Learn about DocSearch program eligibility, costs, data, and support.
|
||||
---
|
||||
|
||||
If you're not finding the answer to your question on this website, this page will help you. If you're still unsure, don't hesitate to connect with us on [Discord][1] or let our [support][4] team know.
|
||||
Use this page to learn about the DocSearch program. If you need more help, contact us on [Discord][1] or through [Algolia support][4].
|
||||
|
||||
For questions related to the DocSearch x Algolia Crawler, please see our [Crawler FAQ](/docs/crawler).
|
||||
For questions about the DocSearch crawler, see the [Crawler FAQ][5].
|
||||
|
||||
## What do I need to install on my side?
|
||||
|
||||
You just need to [implement DocSearch in your frontend](/docs/docsearch) with the credentials received by email when your application has been deployed.
|
||||
After your content is indexed, add a [DocSearch v5 frontend package][19] with the application ID, Search API key, and index name from your Algolia application.
|
||||
|
||||
DocSearch leverages the [Algolia Crawler](https://www.algolia.com/products/search-and-discovery/crawler/), which offers a web [interface](https://dashboard.algolia.com/crawler) to create, monitor, edit, start your Crawlers. If you have any questions regarding it, please see our [Crawler FAQ](/docs/crawler).
|
||||
DocSearch uses the [Algolia Crawler][6] to index your content. Use the [Crawler interface][7] to create, monitor, edit, and start crawlers. For crawler questions, see the [Crawler FAQ][5]. Crawler configuration and frontend package versions are independent.
|
||||
|
||||
## How much does it cost?
|
||||
|
||||
It's free!
|
||||
The DocSearch program is free.
|
||||
|
||||
We know that paying for search infrastructure is a cost not all open source projects can afford. That's why we decided to keep DocSearch free for everyone. All we ask in exchange is that you keep the "Search by [Algolia][2]" logo displayed next to the search results.
|
||||
Search infrastructure can be costly for open source projects, so the DocSearch program is free. In exchange, keep the "Search by [Algolia][2]" logo next to the search results.
|
||||
|
||||
If this is not possible for you, you're free to [open your own Algolia account](https://www.algolia.com/pricing) and run [DocSearch on your own][3] without this limitation. In that case, though, depending on the size of your documentation, you might need a paid account (free accounts can hold as much as 10k records).
|
||||
If you can't display the logo, [create an Algolia account][8] and run [DocSearch on your own][3]. Depending on the size of your documentation, you might need a paid plan. Free plans can hold up to 10,000 records.
|
||||
|
||||
## What data are you collecting?
|
||||
|
||||
We save the data we extract from your website markup, which we put in a custom JSON format instead of HTML. This is the data we put in the Algolia DocSearch index. The selectors in your config define what data to scrape.
|
||||
We extract data from your website markup and store it in a custom JSON format in your Algolia DocSearch index. The selectors in your crawler configuration determine what data to extract.
|
||||
|
||||
As the website owner, we also give you access to your own Algolia application. This will let you see how your website is indexed in Algolia, detailed analytics about the anonymized searches in your website, team managements, and more!
|
||||
As the website owner, you can access your Algolia application to review indexed data, analyze anonymized searches, and manage your team.
|
||||
|
||||
## Where is my data hosted?
|
||||
|
||||
We host the DocSearch data on Algolia's servers, with replications around the globe. You can find more details about the actual [server specs here](https://www.algolia.com/doc/guides/infrastructure/servers/), and more complete information in our [privacy policy](https://www.algolia.com/policies/privacy).
|
||||
We host DocSearch data on Algolia servers with replicas around the world. For more information, see the [Algolia infrastructure documentation][9] and [privacy policy][10].
|
||||
|
||||
## How do I upgrade my DocSearch app?
|
||||
|
||||
Depending on what you are looking for you have a few options!
|
||||
Choose an option based on your goal.
|
||||
|
||||
### Upgrade #1: I want a specific feature, like Rules, added to my existing DocSearch application
|
||||
|
||||
[Reach out to us](https://algolia.com/support) and we may be able to help!
|
||||
[Contact Algolia support][4] to discuss your requirements.
|
||||
|
||||
### Upgrade #2: I want to remove the Algolia logo
|
||||
|
||||
This would disqualify you from the free DocSearch program. We do offer an open-source
|
||||
[legacy version](https://docsearch.algolia.com/docs/legacy/run-your-own) of the DocSearch Crawler that you can use and
|
||||
host yourself or you can use our [API clients](https://www.algolia.com/doc/api-client/getting-started/install/javascript/?client=javascript) but you will need to use a new Algolia application and pay for its usage.
|
||||
Removing the logo makes the project ineligible for the free DocSearch program. Instead, host the open source [legacy DocSearch crawler][3] or use an [Algolia API client][11]. Both options require a new Algolia application, and usage charges may apply.
|
||||
|
||||
### Upgrade #3: Algolia is awesome, I want to use it for my whole site
|
||||
|
||||
That's awesome! Please reach out to our [sales team](https://www.algolia.com/contactus/)
|
||||
who can help you figure out the right plan for you. Once you have your new application
|
||||
created you can simply copy and paste [your Crawler config](https://docsearch.algolia.com/docs/templates) into your new application's
|
||||
Crawler.
|
||||
Contact the [Algolia sales team][12] to choose a plan. After creating your application, adapt a [crawler configuration template][20] for the new crawler.
|
||||
|
||||
## Can I use DocSearch on non-doc pages?
|
||||
|
||||
The free DocSearch we provide will **only** crawl open-source projects documentation pages or technical blogs. To use it on other parts of your website, you'll need to create your own Algolia account and either:
|
||||
The free DocSearch program crawls only documentation pages for open source projects and technical blogs. To index other parts of your website, create an Algolia account and choose one of these options:
|
||||
|
||||
- Run the [DocSearch crawler][3] on your own
|
||||
- Use one of our other [framework integrations or API clients](https://www.algolia.com/doc/api-client/getting-started/install/javascript/?client=javascript)
|
||||
- Use a [DocSearch frontend package][19], a supported [framework integration][21], or an [Algolia API client][11]
|
||||
|
||||
## Can you index code samples?
|
||||
|
||||
Yes, but we do not recommend it.
|
||||
|
||||
Code samples are a great way for humans to understand how people use a specific method. It often requires boilerplate code though, repeated across examples, which adds noise to the results.
|
||||
Code samples can help users understand a method, but repeated boilerplate adds noise to search results.
|
||||
|
||||
## A documentation website I like does not use DocSearch. What can I do?
|
||||
|
||||
We'd love to help!
|
||||
|
||||
If one of your favorite tool documentation websites is missing DocSearch, we encourage you to file an issue in their repository explaining how DocSearch could help. Feel free to [let us know on Discord][1] as well and we'll provide all the help we can.
|
||||
If a documentation website does not use DocSearch, file an issue in its repository that explains how DocSearch could help. You can also [contact us on Discord][1].
|
||||
|
||||
## How did we build this website?
|
||||
|
||||
We build this website with [Docusaurus v2](https://docusaurus.io/). We were helped by a great man who inspired us a lot, Endi. We want [to pay a tribute to this exceptional human being that will be always part of the DocSearch project](https://docusaurus.io/blog/2020/01/07/tribute-to-endi). Rest in peace mate!
|
||||
We built this website with [Docusaurus][13]. Endi helped inspire the project. Read our [tribute to Endi][14].
|
||||
|
||||
## Can I share the `apiKey` in my repo?
|
||||
|
||||
The `apiKey` the DocSearch team provides is [a search-only key](https://www.algolia.com/doc/guides/security/api-keys/#search-only-api-key) and can be safely shared publicly. You can track it in your version control system (e.g. git). If you are running the scraper on your own, please make sure to create a search-only key and [do not share your Admin key](https://www.algolia.com/doc/guides/security/api-keys/#admin-api-key).
|
||||
The `apiKey` from the DocSearch team is a [search-only API key][15], so you can share it publicly and track it in version control. If you run the scraper yourself, create a search-only key and [don't share your Admin API key][16].
|
||||
|
||||
## Why is the email API key different in the dashboard?
|
||||
|
||||
Every Algolia app comes with a default "Search API Key" which can be seen in the dashboard. That key allow you to list indices, settings, and search on **every** index owned by your application. In the case of a DocSearch application, in your acceptance email we provide a search **ONLY** API key scoped to only your DocSearch index. If for any reason you need to recover the API key sent in the email, just connect with our [support](https://algolia.com/support) team.
|
||||
Every Algolia application has a default "Search API Key" in the dashboard. That key lets you list indices, retrieve settings, and search every index in your application. For DocSearch applications, the acceptance email includes a search-only API key scoped to your DocSearch index. To recover this key, contact [Algolia support][4].
|
||||
|
||||
## How do I rotate my API keys?
|
||||
|
||||
Please reach out to our [support](https://algolia.com/support) team.
|
||||
Contact [Algolia support][4].
|
||||
|
||||
## Can I have multiple projects under the same Algolia application?
|
||||
|
||||
We recommend having a single Algolia application per project. Please [apply](https://dashboard.algolia.com/users/sign_up?selected_plan=docsearch&utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=docsearch&utm_content=apply) if you'd like to use DocSearch in an other project of yours.
|
||||
Use one Algolia application per project. [Apply to the DocSearch program][17] for each additional project.
|
||||
|
||||
### Why ?
|
||||
### Why?
|
||||
|
||||
The information of the initially applied project is used everywhere when we deploy your app:
|
||||
|
||||
|
|
@ -99,13 +93,13 @@ The information of the initially applied project is used everywhere when we depl
|
|||
- The indices we generate
|
||||
- The allowed domains of your Crawler
|
||||
|
||||
This allow us to easily scope issues when reaching out for support.
|
||||
This information helps us scope support requests to the correct project.
|
||||
|
||||
## Support
|
||||
|
||||
:::caution
|
||||
|
||||
Please make sure to **first read the documentation before reaching out**.
|
||||
Before contacting support, read the relevant documentation.
|
||||
|
||||
Here are some links to help you:
|
||||
|
||||
|
|
@ -114,18 +108,35 @@ Here are some links to help you:
|
|||
- [The DocSearch FAQ](/docs/docsearch-program)
|
||||
- [The Algolia documentation](https://www.algolia.com/doc/)
|
||||
|
||||
You can also take a look at [the Algolia academy](https://academy.algolia.com/trainings) to understand more about Algolia.
|
||||
Visit [Algolia Academy][18] to learn more about Algolia.
|
||||
|
||||
:::
|
||||
|
||||
Please be informed that while Algolia does not provide support for DocSearch itself, we can support requests for the following products:
|
||||
Algolia doesn't provide support for the DocSearch UI library, but support is available for these products:
|
||||
|
||||
- The Algolia Crawler, reach out [via the support page](https://algolia.com/support).
|
||||
- The Algolia Dashboard, reach out [via the support page](https://algolia.com/support).
|
||||
- Contact [Algolia support][4] for the Algolia Crawler.
|
||||
- Contact [Algolia support][4] for the Algolia dashboard.
|
||||
|
||||
For any issue related to [the DocSearch UI library](https://github.com/algolia/docsearch), please open a [GitHub issues](https://github.com/algolia/docsearch/issues).
|
||||
For any issue related to [the DocSearch UI library](https://github.com/algolia/docsearch), please open a [GitHub issue](https://github.com/algolia/docsearch/issues).
|
||||
|
||||
[1]: https://alg.li/discord
|
||||
[2]: https://www.algolia.com/
|
||||
[3]: /docs/legacy/run-your-own
|
||||
[4]: https://support.algolia.com/
|
||||
[5]: /docs/crawler
|
||||
[6]: https://www.algolia.com/products/search-and-discovery/crawler/
|
||||
[7]: https://dashboard.algolia.com/crawler
|
||||
[8]: https://www.algolia.com/pricing
|
||||
[9]: https://www.algolia.com/doc/guides/infrastructure/servers/
|
||||
[10]: https://www.algolia.com/policies/privacy
|
||||
[11]: https://www.algolia.com/doc/api-client/getting-started/install/javascript/?client=javascript
|
||||
[12]: https://www.algolia.com/contactus/
|
||||
[13]: https://docusaurus.io/
|
||||
[14]: https://docusaurus.io/blog/2020/01/07/tribute-to-endi
|
||||
[15]: https://www.algolia.com/doc/guides/security/api-keys/#search-only-api-key
|
||||
[16]: https://www.algolia.com/doc/guides/security/api-keys/#admin-api-key
|
||||
[17]: https://dashboard.algolia.com/users/sign_up?selected_plan=docsearch&utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=docsearch&utm_content=apply
|
||||
[18]: https://academy.algolia.com/trainings
|
||||
[19]: /docs/packages/overview
|
||||
[20]: /docs/templates
|
||||
[21]: /docs/integrations
|
||||
|
|
|
|||
|
|
@ -1,402 +1,19 @@
|
|||
---
|
||||
title: Getting Started with v5
|
||||
title: Get started with DocSearch
|
||||
description: Set up DocSearch keyword search or Agent Studio answers.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
DocSearch v5 provides separate packages for JavaScript, React, composable interfaces, and framework integrations.
|
||||
|
||||
## Introduction
|
||||
Start with the [package overview](/docs/packages/overview) to choose an integration:
|
||||
|
||||
DocSearch v5 provides a significant upgrade over previous versions, offering enhanced accessibility, responsiveness, and an improved search experience for your documentation. Built on [Algolia Autocomplete][1], DocSearch v5 ensures a seamless integration trusted by leading documentation sites worldwide.
|
||||
- Use [`@docsearch/js`](/docs/packages/js/getting-started) for a JavaScript application.
|
||||
- Use [`@docsearch/react`](/docs/packages/react/getting-started) for a React application.
|
||||
- Use the [Docusaurus adapter](/docs/packages/docusaurus-adapter/getting-started) for a Docusaurus site.
|
||||
- Use the [Composable API](/docs/composable-api) to control the React interface.
|
||||
|
||||
To upgrade an existing integration, follow [Migrating from v4](/docs/migrating-from-v4).
|
||||
|
||||
## Installation
|
||||
|
||||
> Looking for the Composable API documentation? You can find it [here][17].
|
||||
|
||||
DocSearch packages are available on the [npm registry][10].
|
||||
|
||||
### Docusaurus users
|
||||
|
||||
If your docs site is powered by Docusaurus, use [`@docsearch/docusaurus-adapter`](/docs/docusaurus-adapter) for the latest DocSearch features (including new Ask AI capabilities such as sidepanel support), while keeping `@docusaurus/preset-classic`.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/js@5
|
||||
```
|
||||
|
||||
### Without package manager
|
||||
|
||||
Include CSS in your website's `<head>`
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@5" />
|
||||
```
|
||||
|
||||
And the JavaScript at the end of your `<body>`:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/@docsearch/js@5"></script>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="react">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/react@5
|
||||
# or
|
||||
npm install @docsearch/react@5
|
||||
```
|
||||
|
||||
### Without package manager
|
||||
|
||||
Include CSS in your website's `<head>`:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@5" />
|
||||
```
|
||||
|
||||
And the JavaScript at the end of your `<body>`:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/@docsearch/react@5"></script>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
### Optimize first query performance
|
||||
|
||||
Enhance your users' first search experience by using `preconnect`, see [Performance optimization](#preconnect) below
|
||||
|
||||
## Implementation
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
DocSearch requires a dedicated container in your HTML
|
||||
|
||||
```html
|
||||
<div id="docsearch"></div>
|
||||
```
|
||||
|
||||
Initialize DocSearch by passing your container:
|
||||
|
||||
```js app.js
|
||||
import docsearch from '@docsearch/js';
|
||||
|
||||
import '@docsearch/css';
|
||||
|
||||
docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_APP_ID',
|
||||
indices: [{ name: 'YOUR_INDEX_NAME' }],
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
});
|
||||
```
|
||||
|
||||
DocSearch generates an accessible, fully-functional search input for you automatically.
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
Integrating DocSearch into your React app is straightforward:
|
||||
|
||||
```jsx App.js
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
|
||||
import '@docsearch/css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
indices={[{ name: 'YOUR_INDEX_NAME' }]}
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
DocSearch generates a fully accessible search input out-of-the-box.
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
### Quick Testing (without credentials)
|
||||
|
||||
If you'd like to test DocSearch immediately without your own credentials, use our demo configuration:
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
appId: 'PMZUYBQDAK',
|
||||
apiKey: '24b09689d5b4223813d9b8e48563c8f6',
|
||||
indices: [{ name: 'docsearch' }],
|
||||
askAi: {
|
||||
assistantId: 'askAIDemo',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[{ name: 'docsearch' }]}
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
Or use our new dedicated [DocSearch Playground](https://community.algolia.com/docsearch-playground/)
|
||||
|
||||
### Using DocSearch with Ask AI
|
||||
|
||||
DocSearch v5 supports Ask AI through Algolia Agent Studio. Ask AI enhances the user experience by providing contextually relevant and intelligent responses directly from your documentation.
|
||||
|
||||
To enable Ask AI, add your Agent Studio assistant ID:
|
||||
|
||||
<Tabs groupId="askai-format" defaultValue="js" values={[{ label: 'JavaScript', value: 'js' }, { label: 'React', value: 'react' }]}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
appId: 'YOUR_APP_ID',
|
||||
indices: [{ name: 'YOUR_INDEX_NAME' }],
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
askAi: {
|
||||
assistantId: 'YOUR_AGENT_STUDIO_ASSISTANT_ID',
|
||||
suggestedQuestions: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={[{ name: 'YOUR_INDEX_NAME' }]}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_AGENT_STUDIO_ASSISTANT_ID',
|
||||
suggestedQuestions: true,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
- Configure the content indices the assistant can search in Agent Studio.
|
||||
- The suggested questions feature is controlled on the [Dashboard](https://dashboard.algolia.com) in the Ask AI section.
|
||||
|
||||
### Filtering search results
|
||||
|
||||
#### Keyword search
|
||||
|
||||
If your website uses [DocSearch meta tags][13] or if you've added [custom variables to your config][14], you'll be able to use the [`facetFilters`][16] option to scope your search results to a [`facet`][15]
|
||||
|
||||
This is useful to limit the scope of the search to one language or one version.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
docsearch({
|
||||
indices: [
|
||||
{
|
||||
name: 'YOUR_INDEX_NAME',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en', 'version:1.0.0'],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
indices={[
|
||||
{
|
||||
name: 'YOUR_INDEX_NAME',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en', 'version:1.0.0'],
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
#### Ask AI
|
||||
|
||||
Filtering also applies when using Ask AI. This is useful to limit the scope of the LLM's search to only relevant results.
|
||||
|
||||
:::info
|
||||
We recommend using the `facetFilters` option when using Ask AI with multiple languages or any multi-faceted index.
|
||||
:::
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
```js
|
||||
docsearch({
|
||||
askAi: {
|
||||
assistantId: 'YOUR_AGENT_STUDIO_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
'YOUR_MARKDOWN_INDEX': {
|
||||
facetFilters: ['language:en', 'version:1.0.0'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
```jsx
|
||||
<DocSearch
|
||||
askAi={{
|
||||
assistantId: 'YOUR_AGENT_STUDIO_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
'YOUR_MARKDOWN_INDEX': {
|
||||
facetFilters: ['language:en', 'version:1.0.0'],
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
You can use `facetFilters: ['type:content']` to ensure Ask AI only uses records where the `type` attribute is `content` (i.e., only records that actually have content). This is useful if your index contains records for navigation, metadata, or other non-content types.
|
||||
:::
|
||||
|
||||
### Sending events
|
||||
|
||||
You can send search events to your DocSearch index by passing in the `insights` parameter when creating your DocSearch instance.
|
||||
|
||||
<Tabs
|
||||
groupId="language"
|
||||
defaultValue="js"
|
||||
values={[
|
||||
{ label: 'JavaScript', value: 'js', },
|
||||
{ label: 'React', value: 'react', }
|
||||
]
|
||||
}>
|
||||
<TabItem value="js">
|
||||
|
||||
```diff
|
||||
docsearch({
|
||||
// other options
|
||||
+ insights: true,
|
||||
});
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="react">
|
||||
|
||||
```diff
|
||||
<DocSearch
|
||||
// other options
|
||||
+ insights={true}
|
||||
/>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Performance optimization
|
||||
|
||||
### Preconnect
|
||||
|
||||
Improve the loading speed of your initial search request by adding this snippet into your website's `<head>` section:
|
||||
|
||||
```html
|
||||
<link rel="preconnect" href="https://YOUR_APP_ID-dsn.algolia.net" crossorigin />
|
||||
```
|
||||
|
||||
This helps the browser establish a quick connection with Algolia, enhancing user experience, especially on mobile devices.
|
||||
|
||||
[1]: https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/
|
||||
[2]: https://github.com/algolia/docsearch/
|
||||
[3]: https://github.com/algolia/docsearch/tree/master
|
||||
[4]: /docs/legacy/dropdown
|
||||
[5]: /docs/integrations
|
||||
[6]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors
|
||||
[7]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
|
||||
[8]: https://codesandbox.io/s/docsearch-js-v3-playground-z9oxj
|
||||
[9]: https://codesandbox.io/s/docsearch-react-v3-playground-619yg
|
||||
[10]: https://www.npmjs.com/
|
||||
[11]: /docs/api#container
|
||||
[12]: /docs/api
|
||||
[13]: /docs/required-configuration#introduce-global-information-as-meta-tags
|
||||
[14]: /docs/record-extractor#indexing-content-for-faceting
|
||||
[15]: https://www.algolia.com/doc/guides/managing-results/refine-results/faceting/
|
||||
[16]: https://www.algolia.com/doc/guides/managing-results/refine-results/filtering/#facetfilters
|
||||
[17]: /docs/composable-api
|
||||
Install instructions depend on your package. Open the package guide above for commands and required styles.
|
||||
|
|
|
|||
|
|
@ -1,118 +1,10 @@
|
|||
---
|
||||
title: Docusaurus Adapter (Recommended)
|
||||
title: Docusaurus adapter
|
||||
description: Configure DocSearch for a Docusaurus documentation site.
|
||||
---
|
||||
|
||||
If you use Docusaurus, install and configure `@docsearch/docusaurus-adapter` to get the latest DocSearch features on your current Docusaurus version.
|
||||
Use `@docsearch/docusaurus-adapter` to add DocSearch v5 to a Docusaurus site.
|
||||
|
||||
## Why this adapter exists
|
||||
|
||||
Docusaurus ships an excellent built-in Algolia integration (`@docusaurus/theme-search-algolia`), but Docusaurus (Meta-maintained) and DocSearch don't always release on the same cadence.
|
||||
|
||||
The DocSearch adapter lets us ship new DocSearch features (including Ask AI sidepanel support) without forcing users to wait for a Docusaurus integration update.
|
||||
|
||||
In practice, this means:
|
||||
|
||||
- Faster access to new DocSearch capabilities.
|
||||
- Better compatibility for Ask AI + sidepanel features.
|
||||
- A dedicated search integration path maintained in the DocSearch project.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/docusaurus-adapter
|
||||
# or
|
||||
npm install @docsearch/docusaurus-adapter
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Keep `@docusaurus/preset-classic`, add the adapter plugin, and configure search under `themeConfig.docsearch`:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
export default {
|
||||
plugins: ['@docsearch/docusaurus-adapter'],
|
||||
themeConfig: {
|
||||
docsearch: {
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: [{ name: 'YOUR_INDEX_NAME' }],
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
},
|
||||
sidePanel: true,
|
||||
contextualSearch: true,
|
||||
searchPage: { path: 'search' },
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Configuration Key
|
||||
|
||||
Use `themeConfig.docsearch` as the only adapter configuration key.
|
||||
|
||||
The adapter doesn't read `themeConfig.algolia`, which avoids built-in Docusaurus search-theme validation conflicts when you want newer DocSearch options like Agent Studio and the sidepanel.
|
||||
|
||||
### Client Side Tools
|
||||
|
||||
Docusaurus serializes `themeConfig` for the browser and removes function values. Configure `askAi.tools` and `sidePanel.tools` in a swizzled `@theme/SearchBar` component instead of `themeConfig.docsearch`:
|
||||
|
||||
```tsx title="src/theme/SearchBar/index.tsx"
|
||||
import type { ToolCalls } from '@docsearch/react';
|
||||
import SearchBar from '@theme-original/SearchBar';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const tools: ToolCalls = {
|
||||
logMessage: {
|
||||
render: () => 'Tool completed.',
|
||||
},
|
||||
};
|
||||
|
||||
export default function SearchBarWithTools(): ReactNode {
|
||||
return (
|
||||
<SearchBar
|
||||
askAi={{ assistantId: 'YOUR_ASSISTANT_ID', tools }}
|
||||
sidePanel={{ tools }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Pass `tools` through `askAi` to enable them in the modal, `sidePanel` to enable them in the side panel, or both for both interfaces. Include any other Ask AI or side-panel options when overriding the corresponding object.
|
||||
|
||||
## Search Page
|
||||
|
||||
The adapter ships a full search page (enabled by default at `/search`) with faceted filtering, an accessible "Load more" pagination, recent searches, and "Browse by section" shortcuts. Disable it with `searchPage: false`, or change its path with `searchPage: { path: 'search' }`.
|
||||
|
||||
### Facets
|
||||
|
||||
By default the sidebar exposes a single "Section" facet built from the `hierarchy.lvl0` attribute. Configure your own refinement lists with `searchPage.facets`:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
export default {
|
||||
themeConfig: {
|
||||
docsearch: {
|
||||
// ...
|
||||
searchPage: {
|
||||
path: 'search',
|
||||
facets: [
|
||||
{ attribute: 'hierarchy.lvl0', label: 'Section' },
|
||||
{ attribute: 'type', label: 'Type' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Each facet is a disjunctive (OR) refinement list. Active refinements are reflected in the URL so filtered searches are shareable.
|
||||
|
||||
### Analytics
|
||||
|
||||
When `insights: true` is set (the same flag used by the modal), the search page enables Algolia click analytics and sends `clickedObjectIDsAfterSearch` events when a result is opened, so full-page searches feed relevance tuning just like the modal.
|
||||
|
||||
## Customizing Search UI (SearchBar/SearchPage)
|
||||
|
||||
If you want to customize search behavior or UI, customize the adapter theme components (`@theme/SearchBar` and `@theme/SearchPage`) from the adapter integration path.
|
||||
|
||||
This keeps your customization aligned with DocSearch feature updates and avoids coupling to the built-in Docusaurus Algolia theme implementation.
|
||||
- [Install the adapter](/docs/packages/docusaurus-adapter/getting-started).
|
||||
- [Configure the adapter](/docs/packages/docusaurus-adapter/configuration-reference).
|
||||
- [Migrate a v4 configuration](/docs/packages/docusaurus-adapter/migrating-from-v4).
|
||||
|
|
|
|||
|
|
@ -1,358 +1,14 @@
|
|||
---
|
||||
id: examples
|
||||
title: Examples and extensions
|
||||
description: live demos showing how to use and extend docsearch beyond documentation-only use cases.
|
||||
title: Examples
|
||||
description: Explore DocSearch implementation examples and common configurations.
|
||||
---
|
||||
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
import '@docsearch/css/dist/style.css';
|
||||
Use these guides for production-ready examples:
|
||||
|
||||
> These examples are interactive. click a button to open the modal and try a query.
|
||||
- [React examples](/docs/packages/react/examples)
|
||||
- [Composable API](/docs/composable-api)
|
||||
- [Hybrid Mode](/docs/hybrid-mode)
|
||||
- [Agent Studio tools](/docs/agent-studio/tools)
|
||||
- [Docusaurus adapter](/docs/packages/docusaurus-adapter/getting-started)
|
||||
|
||||
## Basic keyword search
|
||||
|
||||
Use the default experience with your index credentials. this works great for typical docs, blogs, and any site with a docsearch-compliant index.
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[{ name: 'docsearch' }]}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'keyword search (demo)' } }}
|
||||
/>
|
||||
```
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[{ name: 'docsearch' }]}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'keyword search (demo)' } }}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Ask AI: ai-assisted answers
|
||||
|
||||
Add algolia askai to get synthesized answers grounded in your indexed content. you can scope the llm context using `searchParameters` like `facetFilters`, `filters`, `attributesToRetrieve`,`restrictSearchableAttributes`, and `distinct`.
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[{ name: 'docsearch' }]}
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
}}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'search with askai (demo)' } }}
|
||||
/>
|
||||
```
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[{ name: 'docsearch' }]}
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
}}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'search with askai (demo)' } }}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Custom hit rendering (`hitComponent`)
|
||||
|
||||
Replace the default hit markup to match your brand and layout. below is a minimal example of a custom component.
|
||||
|
||||
```jsx
|
||||
function CustomHit({ hit }) {
|
||||
// render a compact, branded hit card
|
||||
return (
|
||||
<a
|
||||
href={hit.url}
|
||||
style={{ display: 'block', padding: '12px 16px', textDecoration: 'none' }}
|
||||
>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: '#e3f2fd',
|
||||
borderRadius: 6,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 600,
|
||||
color: '#1976d2',
|
||||
}}
|
||||
>
|
||||
{hit.type?.toUpperCase?.() || 'DOC'}
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{hit.hierarchy?.lvl1 || 'untitled'}
|
||||
</div>
|
||||
{hit.hierarchy?.lvl2 && (
|
||||
<div
|
||||
style={{
|
||||
color: '#666',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{hit.hierarchy.lvl2}
|
||||
</div>
|
||||
)}
|
||||
{hit.content && (
|
||||
<div style={{ color: '#888', marginTop: 4 }}>{hit.content}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[{ name: 'docsearch' }]}
|
||||
hitComponent={CustomHit}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'custom hits (demo)' } }}
|
||||
/>;
|
||||
```
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[{ name: 'docsearch' }]}
|
||||
hitComponent={({ hit }) => {
|
||||
// render a compact, branded hit card
|
||||
return (
|
||||
<a
|
||||
href={hit.url}
|
||||
style={{
|
||||
display: 'block',
|
||||
padding: '12px 16px',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: '#e3f2fd',
|
||||
borderRadius: 6,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 600,
|
||||
color: '#1976d2',
|
||||
}}
|
||||
>
|
||||
{hit.type?.toUpperCase?.() || 'DOC'}
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{hit.hierarchy?.lvl1 || 'untitled'}
|
||||
</div>
|
||||
{hit.hierarchy?.lvl2 && (
|
||||
<div
|
||||
style={{
|
||||
color: '#666',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{hit.hierarchy.lvl2}
|
||||
</div>
|
||||
)}
|
||||
{hit.content && (
|
||||
<div style={{ color: '#888', marginTop: 4 }}>{hit.content}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'custom hits (demo)' } }}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Opening links in new tabs
|
||||
|
||||
By default, DocSearch opens search result links in the current window. If you want results to open in new tabs, you need to use both a custom `hitComponent` and the `navigator` prop to handle both click and keyboard navigation consistently.
|
||||
|
||||
```jsx
|
||||
// Custom hit component with target="_blank"
|
||||
function HitWithNewTab({ hit, children }) {
|
||||
return (
|
||||
<a href={hit.url} target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// Navigator configuration to handle keyboard navigation
|
||||
const newTabNavigator = {
|
||||
navigate: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
||||
navigateNewTab: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
||||
navigateNewWindow: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
||||
};
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[{ name: 'docsearch' }]}
|
||||
hitComponent={HitWithNewTab}
|
||||
navigator={newTabNavigator}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'open in new tabs (demo)' } }}
|
||||
/>;
|
||||
```
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[{ name: 'docsearch' }]}
|
||||
hitComponent={({ hit, children }) => (
|
||||
<a href={hit.url} target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</a>
|
||||
)}
|
||||
navigator={{
|
||||
navigate: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
||||
navigateNewTab: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
||||
navigateNewWindow: ({ itemUrl }) => window.open(itemUrl, '_blank'),
|
||||
}}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'open in new tabs (demo)' } }}
|
||||
/>
|
||||
|
||||
<br></br>
|
||||
|
||||
:::warning
|
||||
**Note**: Using only `hitComponent` with `target="_blank"` will work for mouse clicks, but keyboard navigation (arrows + Enter) requires the `navigator` prop to consistently open links in new tabs.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Bring-your-own-data shape with `transformItems`
|
||||
|
||||
Docsearch is not limited to docsearch-like records. use `transformItems` to adapt any record shape into the internal structure docsearch expects. this lets you build search for apps, help centers, changelogs, or any custom content.
|
||||
|
||||
the snippet below maps a non-standard record to the internal format. try it live:
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[
|
||||
{
|
||||
name: 'crawler_doc',
|
||||
searchParameters: {
|
||||
attributesToRetrieve: ['*'],
|
||||
attributesToSnippet: ['*'],
|
||||
hitsPerPage: 20,
|
||||
},
|
||||
},
|
||||
]}
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
}}
|
||||
transformItems={(items) =>
|
||||
items.map((item) => ({
|
||||
objectID: item.objectID,
|
||||
content: item.content ?? '',
|
||||
url: item.domain + item.path,
|
||||
hierarchy: {
|
||||
lvl0: (item.breadcrumb || []).join(' > ') ?? '',
|
||||
lvl1: item.h1 ?? '',
|
||||
lvl2: item.h2 ?? '',
|
||||
lvl3: null,
|
||||
lvl4: null,
|
||||
lvl5: null,
|
||||
lvl6: null,
|
||||
},
|
||||
url_without_anchor: item.domain + item.path,
|
||||
type: 'content',
|
||||
anchor: null,
|
||||
_highlightResult: item._highlightResult,
|
||||
_snippetResult: item._snippetResult,
|
||||
}))
|
||||
}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'transform items (demo)' } }}
|
||||
/>
|
||||
```
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indices={[
|
||||
{
|
||||
name: 'crawler_doc',
|
||||
searchParameters: {
|
||||
attributesToRetrieve: ['*'],
|
||||
attributesToSnippet: ['*'],
|
||||
hitsPerPage: 20,
|
||||
},
|
||||
},
|
||||
]}
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
}}
|
||||
transformItems={(items) =>
|
||||
items.map((item) => ({
|
||||
objectID: item.objectID,
|
||||
content: item.content ?? '',
|
||||
url: item.domain + item.path,
|
||||
hierarchy: {
|
||||
lvl0: (item.breadcrumb || []).join(' > ') ?? '',
|
||||
lvl1: item.h1 ?? '',
|
||||
lvl2: item.h2 ?? '',
|
||||
lvl3: null,
|
||||
lvl4: null,
|
||||
lvl5: null,
|
||||
lvl6: null,
|
||||
},
|
||||
url_without_anchor: item.domain + item.path,
|
||||
type: 'content',
|
||||
anchor: null,
|
||||
_highlightResult: item._highlightResult,
|
||||
_snippetResult: item._snippetResult,
|
||||
}))
|
||||
}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'transform items (demo)' } }}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Tips
|
||||
|
||||
- **Instrumentation**: enable `insights` to send usage analytics and iterate on relevance.
|
||||
- **Ask AI scoping**: use `facetFilters`, `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct` to control ai context and improve answer quality.
|
||||
- **Customization**: use `hitComponent`, `transformItems`, and `translations` to make docsearch feel native to any product surface.
|
||||
For JavaScript setup examples, see [Get started with `@docsearch/js`](/docs/packages/js/getting-started).
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
---
|
||||
title: How does it work?
|
||||
description: Learn how DocSearch crawls, indexes, and serves documentation content.
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
Getting up and ready with DocSearch is a straightforward process that requires three steps: you apply, we configure the crawler and the Algolia app for you, and you integrate our UI in your frontend. You only need to copy and paste a JavaScript snippet.
|
||||
DocSearch setup has three parts: join the DocSearch program, index your content, and add a frontend package or framework integration.
|
||||
|
||||
<img
|
||||
src={useBaseUrl('img/assets/docsearch-how-it-works.png')}
|
||||
|
|
@ -13,39 +14,40 @@ Getting up and ready with DocSearch is a straightforward process that requires t
|
|||
|
||||
## You apply
|
||||
|
||||
The first thing you'll need to do is to apply for DocSearch by [filling out the form on this page][1] (double check first that [you qualify][2]). We are receiving a lot of requests, so this form makes sure we won't be forgetting anyone.
|
||||
First, confirm that [your website qualifies][2], then [apply for DocSearch][1]. The onboarding flow validates your domain against the program requirements.
|
||||
|
||||
We guarantee that we will answer every request, but as we receive a lot of applications, please give us a couple of days to get back to you :)
|
||||
If automated validation can't determine eligibility, the application might require a manual review.
|
||||
|
||||
## We create your Algolia application and a dedicated crawler
|
||||
## Index your content
|
||||
|
||||
Once we receive [your application][1], we'll have a look at your website, create an Algolia application and a dedicated [crawler][5] for it. Your crawler comes with [a configuration file][6] which defines which URLs we should crawl or ignore, as well as the specific CSS selectors to use for selecting headers, subheaders, etc.
|
||||
After approval, create a dedicated [crawler][5] in your Algolia application. Its [configuration file][6] defines which URLs to crawl, which URLs to ignore, and which selectors extract your content hierarchy.
|
||||
|
||||
This step still requires some manual work and human brain, but thanks to the +4,000 configs we already created, we're able to automate most of it. Once this creation finishes, we'll run a first indexing of your website and have it run automatically at a random time of the week.
|
||||
Run a test crawl, review the extracted records, and configure recurring crawls. Use the Crawler interface to adjust the configuration as your website changes.
|
||||
|
||||
**With the Crawler, comes [a dedicated interface][8] for you to:**
|
||||
|
||||
- Start, schedule and monitor your crawls
|
||||
- Edit and test your config file directly with [DocSearch v3][7]
|
||||
- Edit and test your crawler configuration
|
||||
|
||||
**With the Algolia application comes access to the dashboard for you to:**
|
||||
|
||||
- Browse your index and see how your content is indexed
|
||||
- Various analytics to understand how your search performs and ensure that your users are able to find what they’re searching for
|
||||
- Review analytics to understand how search performs
|
||||
- Trials for other Algolia features
|
||||
- Team management
|
||||
|
||||
## You update your website
|
||||
## Add DocSearch to your website
|
||||
|
||||
We'll then get back to you with the JavaScript snippet you'll need to add to your website. This will bind your [DocSearch component][7] to display results from your Algolia index on each keystroke in a pop-up modal.
|
||||
Choose a [DocSearch v5 package][7] or [supported framework integration][9]. Configure it with your application ID, Search API key, and index name. The frontend queries the Algolia index and displays the results.
|
||||
|
||||
Now that DocSearch is set, you don't have anything else to do. We'll keep crawling your website and update your search results automatically. All we ask is that you keep the "Search by Algolia" logo next to your search results.
|
||||
Crawler versions, record schema versions, and frontend package versions are separate. In particular, crawler `recordVersion: 'v3'` describes the indexed record schema, not the DocSearch UI version. You can upgrade the v5 frontend packages without recreating the crawler. Keep the "Search by Algolia" logo next to your search results.
|
||||
|
||||
[1]: https://dashboard.algolia.com/users/sign_up?selected_plan=docsearch&utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=docsearch&utm_content=apply
|
||||
[2]: /docs/who-can-apply
|
||||
[3]: https://github.com/algolia/docsearch-configs/tree/master/configs
|
||||
[4]: /docs/styling
|
||||
[4]: /docs/packages/css/styling
|
||||
[5]: https://www.algolia.com/products/search-and-discovery/crawler/
|
||||
[6]: https://www.algolia.com/doc/tools/crawler/apis/configuration/
|
||||
[7]: /docs/v3/docsearch
|
||||
[8]: https://crawler.algolia.com/
|
||||
[7]: /docs/packages/overview
|
||||
[8]: https://dashboard.algolia.com/crawler
|
||||
[9]: /docs/integrations
|
||||
|
|
|
|||
441
packages/website/docs/hybrid-mode.mdx
Normal file
441
packages/website/docs/hybrid-mode.mdx
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
---
|
||||
title: Hybrid mode
|
||||
description: Connect DocSearch keyword search and Ask AI Sidepanel experiences in React, JavaScript, and Docusaurus.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
Hybrid mode starts in the DocSearch modal, then moves an Ask AI request into the Sidepanel. Users can scan keyword results in a focused overlay and continue an AI conversation beside the page.
|
||||
|
||||
The handoff carries the prompt and its identifiers. It doesn't submit the same prompt in both interfaces.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you add hybrid mode:
|
||||
|
||||
- Use matching v5 DocSearch package versions.
|
||||
- Configure a searchable Algolia index and a public search-only API key.
|
||||
- Create an assistant in [Agent Studio](/docs/agent-studio/getting-started).
|
||||
- Decide whether you need React, JavaScript, or the DocSearch Docusaurus adapter.
|
||||
|
||||
Read the package references for [core](/docs/packages/core/overview), [modal](/docs/packages/modal/overview), [Sidepanel](/docs/packages/sidepanel/getting-started), [JavaScript](/docs/packages/js/getting-started), [Sidepanel JavaScript](/docs/packages/sidepanel-js/getting-started), and the [Docusaurus adapter](/docs/packages/docusaurus-adapter/getting-started) when you need options beyond this workflow.
|
||||
|
||||
## Understand the handoff
|
||||
|
||||
An Ask AI action can come from a typed prompt, a suggested question, a prompt suggestion in keyword results, or a saved conversation.
|
||||
|
||||
DocSearch represents the action as an `InitialAskAiMessage`:
|
||||
|
||||
```ts
|
||||
interface InitialAskAiMessage {
|
||||
query: string;
|
||||
messageId?: string;
|
||||
suggestedQuestionId?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Preserve the complete object during a manual handoff:
|
||||
|
||||
- `query` becomes the first prompt when the Sidepanel starts a conversation.
|
||||
- `suggestedQuestionId` is sent with the prompt so Agent Studio can identify the selected suggestion.
|
||||
- `messageId` lets the Sidepanel restore a locally stored conversation. If it can't find that conversation, it starts a new one with `query`.
|
||||
|
||||
## Add hybrid mode
|
||||
|
||||
<Tabs groupId="language" aria-label="Programming language">
|
||||
<TabItem value="react" label="React">
|
||||
|
||||
### React
|
||||
|
||||
Install the connected React packages and CSS:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/sidepanel@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/sidepanel@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/sidepanel@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/sidepanel@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Render `DocSearchAskAiModal` and `Sidepanel` under the same `DocSearch` provider. The Sidepanel registers itself with the provider after mount. From then on, desktop Ask AI actions move to the Sidepanel automatically.
|
||||
|
||||
```tsx title="HybridSearch.tsx"
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { DocSearchAskAiModal, DocSearchButton } from '@docsearch/modal';
|
||||
import { Sidepanel, SidepanelButton } from '@docsearch/sidepanel';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
import '@docsearch/css/dist/style.css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
|
||||
interface HybridSearchProps {
|
||||
appId: string;
|
||||
apiKey: string;
|
||||
keywordIndexName: string;
|
||||
askAiIndexName: string;
|
||||
assistantId: string;
|
||||
onReady?: () => void;
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
onSidepanelOpen?: () => void;
|
||||
onSidepanelClose?: () => void;
|
||||
}
|
||||
|
||||
export function HybridSearch({
|
||||
appId,
|
||||
apiKey,
|
||||
keywordIndexName,
|
||||
askAiIndexName,
|
||||
assistantId,
|
||||
onReady,
|
||||
onOpen,
|
||||
onClose,
|
||||
onSidepanelOpen,
|
||||
onSidepanelClose,
|
||||
}: HybridSearchProps): JSX.Element {
|
||||
return (
|
||||
<DocSearch
|
||||
onReady={onReady}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
onSidepanelOpen={onSidepanelOpen}
|
||||
onSidepanelClose={onSidepanelClose}
|
||||
>
|
||||
<DocSearchButton />
|
||||
<DocSearchAskAiModal
|
||||
appId={appId}
|
||||
apiKey={apiKey}
|
||||
indices={[keywordIndexName]}
|
||||
askAi={{ assistantId }}
|
||||
/>
|
||||
|
||||
<SidepanelButton />
|
||||
<Sidepanel
|
||||
appId={appId}
|
||||
apiKey={apiKey}
|
||||
indexName={askAiIndexName}
|
||||
assistantId={assistantId}
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`SidepanelButton` gives users a direct Ask AI entry point. You can omit it without disabling hybrid handoff. Keep `Sidepanel` mounted so it can register before a user submits an Ask AI request.
|
||||
|
||||
Use the same index name for `keywordIndexName` and `askAiIndexName` when one index serves both experiences. Pass different names when your Agent Studio setup uses a dedicated content index.
|
||||
|
||||
#### React lifecycle callbacks
|
||||
|
||||
Put lifecycle callbacks on `DocSearch` so they observe the shared state.
|
||||
|
||||
During the automatic React handoff, the state changes directly from `modal-askai` to `sidepanel`. That transition calls `onSidepanelOpen`; it doesn't call `onClose`.
|
||||
|
||||
##### `onReady`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when the provider mounts.
|
||||
|
||||
##### `onOpen`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when a modal opens from `ready` or `sidepanel`.
|
||||
|
||||
##### `onClose`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when a modal changes directly to `ready`.
|
||||
|
||||
##### `onSidepanelOpen`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when the state changes to `sidepanel`.
|
||||
|
||||
##### `onSidepanelClose`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when the state leaves `sidepanel`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
### JavaScript
|
||||
|
||||
The JavaScript packages create independent DocSearch and Sidepanel instances. Connect them with `interceptAskAiEvent`.
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/js@^5.0.0-beta @docsearch/sidepanel-js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/js@^5.0.0-beta @docsearch/sidepanel-js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/js@^5.0.0-beta @docsearch/sidepanel-js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/js@^5.0.0-beta @docsearch/sidepanel-js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Add one mount element for each instance:
|
||||
|
||||
```html title="index.html"
|
||||
<div id="docsearch"></div>
|
||||
<div id="docsearch-sidepanel"></div>
|
||||
```
|
||||
|
||||
Create the Sidepanel first so the interceptor always has a target. The mobile check in this example matches the React integration: mobile users stay in the Ask AI modal, while desktop users move to the Sidepanel.
|
||||
|
||||
```ts title="search.ts"
|
||||
import docsearch, { type DocSearchInstance } from '@docsearch/js';
|
||||
import sidepanel, { type SidepanelInstance } from '@docsearch/sidepanel-js';
|
||||
|
||||
import '@docsearch/css/dist/style.css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
|
||||
const appId = 'YOUR_APP_ID';
|
||||
const apiKey = 'YOUR_SEARCH_API_KEY';
|
||||
const keywordIndexName = 'YOUR_KEYWORD_INDEX_NAME';
|
||||
const askAiIndexName = 'YOUR_ASK_AI_INDEX_NAME';
|
||||
const assistantId = 'YOUR_ASSISTANT_ID';
|
||||
const mobileQuery = window.matchMedia('(max-width: 768px)');
|
||||
|
||||
let search: DocSearchInstance | undefined;
|
||||
let panel: SidepanelInstance | undefined;
|
||||
|
||||
panel = sidepanel({
|
||||
container: '#docsearch-sidepanel',
|
||||
appId,
|
||||
apiKey,
|
||||
indexName: askAiIndexName,
|
||||
assistantId,
|
||||
onReady: () => {
|
||||
document.body.setAttribute('data-sidepanel-ready', '');
|
||||
},
|
||||
onOpen: () => {
|
||||
search?.close();
|
||||
document.body.setAttribute('data-sidepanel-open', '');
|
||||
},
|
||||
onClose: () => {
|
||||
document.body.removeAttribute('data-sidepanel-open');
|
||||
},
|
||||
});
|
||||
|
||||
search = docsearch({
|
||||
container: '#docsearch',
|
||||
appId,
|
||||
apiKey,
|
||||
indices: [keywordIndexName],
|
||||
askAi: { assistantId },
|
||||
interceptAskAiEvent: (initialMessage) => {
|
||||
if (mobileQuery.matches) {
|
||||
return false;
|
||||
}
|
||||
|
||||
search?.close();
|
||||
panel?.open(initialMessage);
|
||||
return true;
|
||||
},
|
||||
onReady: () => {
|
||||
document.body.setAttribute('data-docsearch-ready', '');
|
||||
},
|
||||
onOpen: () => {
|
||||
panel?.close();
|
||||
document.body.setAttribute('data-search-open', '');
|
||||
},
|
||||
onClose: () => {
|
||||
document.body.removeAttribute('data-search-open');
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The root `@docsearch/js` export includes Ask AI. The keyword-only JavaScript entry point is `@docsearch/js/docsearch`; don't use that entry point for hybrid mode because it has no Ask AI action to intercept.
|
||||
|
||||
#### How `interceptAskAiEvent` works
|
||||
|
||||
`interceptAskAiEvent` runs before the modal changes to Ask AI or sends a message.
|
||||
|
||||
- Return `true` after another interface accepts the request. DocSearch clears the keyword query and skips all default Ask AI behavior.
|
||||
- Return `false` or `undefined` to keep the default Ask AI modal flow.
|
||||
- Pass `initialMessage` unchanged to `sidepanelInstance.open(initialMessage)` so conversation and suggestion IDs survive.
|
||||
|
||||
Keep the `onOpen` callbacks that close the other instance. They prevent two interfaces from staying open when application code calls `search.open()`, `search.openAskAi()`, or `panel.open()` directly.
|
||||
|
||||
The JavaScript instances expose these lifecycle callbacks and controls:
|
||||
|
||||
- The DocSearch instance accepts the `onReady`, `onOpen`, and `onClose` callbacks, and exposes `open()`, `close()`, `openAskAi()`, `destroy()`, `isReady`, and `isOpen`.
|
||||
- The Sidepanel instance accepts the `onReady`, `onOpen`, and `onClose` callbacks, and exposes `open()`, `close()`, `destroy()`, `isReady`, and `isOpen`.
|
||||
|
||||
Call both `destroy()` methods if your application removes the mount elements or tears down the page without a full navigation.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Add hybrid mode to Docusaurus
|
||||
|
||||
Hybrid mode is supported through `@docsearch/docusaurus-adapter`. The adapter loads the v5 modal and Sidepanel, connects their state, and applies the mobile behavior described in this guide.
|
||||
|
||||
Install the adapter and keep `@docusaurus/preset-classic`:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Configure DocSearch under `themeConfig.docsearch`. Set top-level `sidePanel` to `true` or to a Sidepanel options object.
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
export default {
|
||||
plugins: ['@docsearch/docusaurus-adapter'],
|
||||
themeConfig: {
|
||||
docsearch: {
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: [{ name: 'YOUR_INDEX_NAME' }],
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
},
|
||||
sidePanel: true,
|
||||
contextualSearch: true,
|
||||
searchPage: { path: 'search' },
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The adapter requires `askAi` to be an object with `assistantId`; it doesn't accept the string shorthand in Docusaurus configuration. It also requires `askAi` whenever `sidePanel` is set.
|
||||
|
||||
Don't put `sidePanel` inside `askAi`. Don't configure this adapter under `themeConfig.algolia`. If you need function-valued custom tools, pass them through a swizzled `@theme/SearchBar` component because Docusaurus removes functions while serializing theme configuration.
|
||||
|
||||
The built-in `@docusaurus/theme-search-algolia` integration doesn't provide this v5 Sidepanel configuration. Use the DocSearch adapter for this workflow.
|
||||
|
||||
## Mobile behavior
|
||||
|
||||
The React provider treats viewports that match `(max-width: 768px)` as mobile. On those viewports, an Ask AI action stays in the modal even when a Sidepanel is registered. This avoids moving the conversation into the desktop Sidepanel layout.
|
||||
|
||||
The provider updates this decision on window resize. The Sidepanel also avoids focusing its prompt automatically on mobile because opening the virtual keyboard can disrupt the layout.
|
||||
|
||||
The JavaScript packages don't share a provider, so they don't apply the hybrid mobile decision to your interceptor. Add the `matchMedia` branch shown in the JavaScript example when you want the same behavior.
|
||||
|
||||
## Load the styles
|
||||
|
||||
Hybrid mode needs both style bundles:
|
||||
|
||||
```ts title="search.ts"
|
||||
import '@docsearch/css/dist/style.css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
```
|
||||
|
||||
`style.css` includes shared variables, the button, keyword modal, and Ask AI modal. `sidepanel.css` adds the Sidepanel layout and imports the shared variables it needs.
|
||||
|
||||
If your bundler resolves the React style entry points, `@docsearch/react/style` and `@docsearch/react/style/sidepanel` load the same bundles. Import each bundle once.
|
||||
|
||||
## Troubleshoot hybrid mode
|
||||
|
||||
### Ask AI stays in the modal on desktop
|
||||
|
||||
Confirm that `Sidepanel` is mounted under the same `DocSearch` provider before the Ask AI action. The provider enables automatic handoff only after the Sidepanel registers. Also confirm that the viewport is wider than 768 pixels.
|
||||
|
||||
### Both the modal and Sidepanel submit the prompt
|
||||
|
||||
Return `true` from `interceptAskAiEvent` after calling `panel.open(initialMessage)`. Returning `false` or no value tells the modal to continue its default flow.
|
||||
|
||||
### Mobile users open the Sidepanel
|
||||
|
||||
In JavaScript integrations, check `(max-width: 768px)` in the interceptor and return `false` on a match. React and the Docusaurus adapter already make this decision.
|
||||
|
||||
### The Sidepanel loses a suggestion or saved conversation
|
||||
|
||||
Pass the complete `initialMessage` object to `open()`. Rebuilding it with only `query` discards `suggestedQuestionId` and `messageId`.
|
||||
|
||||
### A callback doesn't run during React handoff
|
||||
|
||||
Expect `onSidepanelOpen`, not `onClose`, when React changes directly from the modal to the Sidepanel. Use the shared provider callbacks rather than treating modal close as the handoff signal.
|
||||
|
||||
### Two JavaScript interfaces remain open
|
||||
|
||||
Close the search instance in the Sidepanel's `onOpen`, and close the Sidepanel in the search instance's `onOpen`. This also covers programmatic calls that bypass the interceptor.
|
||||
|
||||
### The interface is unstyled
|
||||
|
||||
Import both `style.css` and `sidepanel.css`. Check that your bundler includes CSS imports from dependencies.
|
||||
|
||||
### Docusaurus rejects the configuration
|
||||
|
||||
Use `themeConfig.docsearch`, pass `askAi` as an object, and put `sidePanel` beside `askAi`. Install `@docsearch/docusaurus-adapter@^5.0.0-beta` instead of configuring hybrid mode through the built-in Algolia theme.
|
||||
|
||||
### Server rendering fails with `window` or `document` errors
|
||||
|
||||
Mount the composable React components only in the browser. The Docusaurus adapter handles this boundary and lazy-loads the modal and Sidepanel client-side.
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
---
|
||||
title: Supported Integrations
|
||||
title: Supported integrations
|
||||
description: Find supported framework integrations for DocSearch.
|
||||
---
|
||||
|
||||
We worked with **documentation website generators** to have DocSearch directly embedded as a first class citizen in the websites they produce.
|
||||
Documentation frameworks can embed DocSearch in the websites they generate. A framework integration configures the frontend; your Algolia Crawler and index remain separate.
|
||||
|
||||
## Our great integrations
|
||||
## Framework integrations
|
||||
|
||||
So, if you're using one of the following tools, checkout their documentation to see how to enable DocSearch on your website:
|
||||
If you use one of the following tools, follow its documentation to enable DocSearch. Framework documentation can target a different DocSearch frontend release, so check its package compatibility before adopting v5.
|
||||
|
||||
- [Docusaurus v1][1] - [How to enable search][2]
|
||||
- [Docusaurus v2 & v3][3] - [DocSearch adapter (recommended)][23] / [Using Algolia DocSearch][4]
|
||||
- [Docusaurus v2 and v3][3] - [DocSearch v5 adapter][23] / [Using Algolia DocSearch][4]
|
||||
- [VuePress][5] - [Algolia Search][6]
|
||||
- [VitePress][21] - [Search][22]
|
||||
- [Starlight][7] - [Algolia Search][8]
|
||||
|
|
@ -20,7 +21,7 @@ So, if you're using one of the following tools, checkout their documentation to
|
|||
- [Lotus Docs][19] - [Enabling the DocSearch Plugin][20]
|
||||
- [Sphinx](https://www.sphinx-doc.org/en/master/) - [Algolia DocSearch for Sphinx](https://sphinx-docsearch.readthedocs.io/)
|
||||
|
||||
If you're maintaining a similar tool and want us to add you to the list, [feel free to make a pull request](https://github.com/algolia/docsearch/edit/main/packages/website/docs/integrations.md) and [contribute to Code Exchange](https://www.algolia.com/developers/code-exchange/contribute/). We're happy to help.
|
||||
If you maintain a similar tool and want us to add it to the list, [open a pull request](https://github.com/algolia/docsearch/edit/main/packages/website/docs/integrations.md) and [contribute to Code Exchange](https://www.algolia.com/developers/code-exchange/contribute/).
|
||||
|
||||
[1]: https://v1.docusaurus.io/
|
||||
[2]: https://v1.docusaurus.io/docs/en/search
|
||||
|
|
@ -42,4 +43,4 @@ If you're maintaining a similar tool and want us to add you to the list, [feel f
|
|||
[20]: https://lotusdocs.dev/docs/guides/features/docsearch/#enabling-the-docsearch-plugin
|
||||
[21]: https://vitepress.dev/
|
||||
[22]: https://vitepress.dev/reference/default-theme-search#algolia-search
|
||||
[23]: /docs/docusaurus-adapter
|
||||
[23]: /docs/packages/docusaurus-adapter/getting-started
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
---
|
||||
title: "[Pre-v4] Manage your crawls"
|
||||
title: Legacy crawler interface
|
||||
description: Manage legacy DocSearch crawler configurations in the Algolia dashboard.
|
||||
---
|
||||
|
||||
:::caution
|
||||
This UI is deprecated and no longer maintained. For the latest instructions, please use the new documentation: [Crawler Configuration Visual UI](./crawler-configuration-visual). You can find the new Crawler UI at [dashboard.algolia.com/crawler](https://dashboard.algolia.com/crawler).
|
||||
:::
|
||||
|
||||
This crawler interface is deprecated and no longer maintained. It isn't a DocSearch frontend version. For current instructions, use the [Crawler interface guide](./crawler-configuration-visual). Open the current interface at [dashboard.algolia.com/crawler](https://dashboard.algolia.com/crawler).
|
||||
|
||||
:::
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
|
|
@ -46,7 +48,7 @@ The live editor allows you to update your config file and test your URLs (`URL t
|
|||
|
||||
## Search preview
|
||||
|
||||
From the [`editor`](#update-your-config), you have access to a `Search preview` tab to browse search results with [`DocSearch v3`](/docs/v3/docsearch).
|
||||
The legacy editor's `Search preview` tab renders results with the historical [`DocSearch v3` frontend](/docs/v3/docsearch). This preview doesn't require your website to use that frontend version. For current integrations, see the [v5 package guides](/docs/packages/overview).
|
||||
|
||||
<div className="uil-ta-center">
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -1,32 +1,264 @@
|
|||
---
|
||||
title: Install DocSearch MCP
|
||||
description: Install and configure the DocSearch MCP server.
|
||||
sidebar_label: Installation
|
||||
---
|
||||
|
||||
import MCPInstall from '@site/src/components/mcp/MCPInstall';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
DocSearch MCP is a remote MCP server. Point any MCP-compatible client at this endpoint — no authentication required:
|
||||
DocSearch MCP is a hosted remote MCP server. It doesn't require authentication:
|
||||
|
||||
```text
|
||||
https://mcp.algolia.com/1/docsearch/mcp
|
||||
```
|
||||
|
||||
The fastest path is the **DocSearch CLI** — one command that configures your client for you. Prefer to set things up yourself? Install it as a **plugin** (ships the MCP server plus client guidance like rules, skills, and commands) or **manually** (just the MCP server config). Pick your client below.
|
||||
Use `@docsearch/cli` to configure a supported coding agent, or add the endpoint to another MCP client manually.
|
||||
|
||||
<MCPInstall />
|
||||
## Install with `@docsearch/cli`
|
||||
|
||||
## Verify the install
|
||||
The DocSearch CLI requires Node.js 20 or later. Run the interactive installer without installing the package globally:
|
||||
|
||||
Ask your MCP client a public documentation question, for example:
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```text
|
||||
Use DocSearch MCP to find the current Next.js middleware matcher docs.
|
||||
```sh
|
||||
npx @docsearch/cli@^0.0.2 setup
|
||||
```
|
||||
|
||||
The client should call the DocSearch tools and answer with content from the matching documentation, ideally with source links.
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
:::note
|
||||
```sh
|
||||
yarn dlx @docsearch/cli@^0.0.2 setup
|
||||
```
|
||||
|
||||
Algolia DocSearch MCP is a free service and is provided by Algolia "AS IS" and "AS AVAILABLE" without warranty of any kind, and may be suspended, modified, or discontinued by Algolia at any time in its sole discretion. Algolia disclaims all obligation and liability arising out of or in connection with Your use of DocSearch MCP. You shall comply with all laws and governmental regulations in Your use of the DocSearch MCP.
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
:::
|
||||
```sh
|
||||
pnpm dlx @docsearch/cli@^0.0.2 setup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```sh
|
||||
bunx @docsearch/cli@^0.0.2 setup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The installer asks whether to update the current project or your user-level settings. It detects and configures these clients:
|
||||
|
||||
- Cursor
|
||||
- Claude Code
|
||||
- Codex
|
||||
- OpenCode
|
||||
- Gemini CLI
|
||||
|
||||
For each selected client, the installer adds the `algolia-docsearch` server, a rule that describes when to search documentation, and a skill that describes the tool workflow. Project setup finds the repository root before writing files. You can run setup again without removing unrelated configuration.
|
||||
|
||||
Pass a scope and client flags to skip the prompts:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```sh
|
||||
npx @docsearch/cli@^0.0.2 setup --project --cursor --claude --yes
|
||||
npx @docsearch/cli@^0.0.2 setup --global --all --yes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```sh
|
||||
yarn dlx @docsearch/cli@^0.0.2 setup --project --cursor --claude --yes
|
||||
yarn dlx @docsearch/cli@^0.0.2 setup --global --all --yes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```sh
|
||||
pnpm dlx @docsearch/cli@^0.0.2 setup --project --cursor --claude --yes
|
||||
pnpm dlx @docsearch/cli@^0.0.2 setup --global --all --yes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```sh
|
||||
bunx @docsearch/cli@^0.0.2 setup --project --cursor --claude --yes
|
||||
bunx @docsearch/cli@^0.0.2 setup --global --all --yes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Use `--cursor`, `--claude`, `--codex`, `--opencode`, or `--gemini` to select clients. Use either `--project` or `--global`, not both.
|
||||
|
||||
To install the `docsearch` command globally, run:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```sh
|
||||
npm install --global @docsearch/cli@^0.0.2
|
||||
docsearch setup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```sh
|
||||
yarn global add @docsearch/cli@^0.0.2
|
||||
docsearch setup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```sh
|
||||
pnpm add --global @docsearch/cli@^0.0.2
|
||||
docsearch setup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```sh
|
||||
bun add --global @docsearch/cli@^0.0.2
|
||||
docsearch setup
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Install a client plugin
|
||||
|
||||
Plugins include the MCP connection and instructions that teach the client when and how to use DocSearch.
|
||||
|
||||
### Claude Code
|
||||
|
||||
Run these commands inside Claude Code:
|
||||
|
||||
```text
|
||||
/plugin marketplace add algolia/docsearch
|
||||
/plugin install algolia-docsearch@algolia-docsearch-marketplace
|
||||
```
|
||||
|
||||
The plugin adds the MCP server, a skill, and the `/algolia-docsearch:docs` command.
|
||||
|
||||
### Cursor
|
||||
|
||||
The Cursor plugin package is in the DocSearch repository at:
|
||||
|
||||
```text
|
||||
mcp/plugins/docsearch/cursor/algolia-docsearch
|
||||
```
|
||||
|
||||
It includes the MCP server configuration, a rule, and a skill.
|
||||
|
||||
## Configure a supported agent manually
|
||||
|
||||
Manual configuration adds the server but doesn't install the rules and skills supplied by the CLI or plugins.
|
||||
|
||||
<Tabs groupId="mcp-client" aria-label="MCP client">
|
||||
<TabItem value="cursor" label="Cursor">
|
||||
|
||||
Add this entry to `~/.cursor/mcp.json` for user-level configuration or `.cursor/mcp.json` for project configuration:
|
||||
|
||||
```json title=".cursor/mcp.json"
|
||||
{
|
||||
"mcpServers": {
|
||||
"algolia-docsearch": {
|
||||
"url": "https://mcp.algolia.com/1/docsearch/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="claude" label="Claude Code">
|
||||
|
||||
Add the user-level remote server with the Claude Code CLI:
|
||||
|
||||
```sh
|
||||
claude mcp add --scope user --transport http algolia-docsearch https://mcp.algolia.com/1/docsearch/mcp
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="codex" label="Codex">
|
||||
|
||||
Add the remote server with the Codex CLI:
|
||||
|
||||
```sh
|
||||
codex mcp add algolia-docsearch --url https://mcp.algolia.com/1/docsearch/mcp
|
||||
```
|
||||
|
||||
You can also add the URL under `[mcp_servers.algolia-docsearch]` in `~/.codex/config.toml`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="opencode" label="OpenCode">
|
||||
|
||||
Add this entry to `opencode.json` or `opencode.jsonc`:
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"algolia-docsearch": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.algolia.com/1/docsearch/mcp",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="gemini" label="Gemini CLI">
|
||||
|
||||
Add this entry to `.gemini/settings.json` for project configuration or `~/.gemini/settings.json` for user-level configuration:
|
||||
|
||||
```json title=".gemini/settings.json"
|
||||
{
|
||||
"mcpServers": {
|
||||
"algolia-docsearch": {
|
||||
"httpUrl": "https://mcp.algolia.com/1/docsearch/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Connect another MCP client
|
||||
|
||||
You can also connect clients that support remote HTTP MCP servers. Use these connection methods:
|
||||
|
||||
- **Claude Desktop**: Add a custom connector named `Algolia DocSearch` with the endpoint URL.
|
||||
- **ChatGPT**: Add a no-auth custom connector. Availability depends on the plan and workspace settings.
|
||||
- **Pi**: Install an MCP extension or adapter before adding the server.
|
||||
- **VS Code**: Add a remote server to the workspace or user MCP configuration. The tools are available in Copilot Chat agent mode.
|
||||
- **Windsurf**: Add the endpoint to the Cascade MCP configuration, then restart Windsurf.
|
||||
- **Zed**: Add the endpoint as a context server.
|
||||
- **Conductor**: Configure the server in the selected Claude Code, Codex, or Cursor host. Conductor doesn't use a separate MCP configuration for those sessions.
|
||||
- **Antigravity**: Add the endpoint through **Manage MCP Servers > View raw config**, then refresh the installed servers.
|
||||
|
||||
Configuration keys differ by client. Follow the client's MCP documentation and keep the server name `algolia-docsearch` and the endpoint URL unchanged.
|
||||
|
||||
## Verify the installation
|
||||
|
||||
Ask your client to search public documentation:
|
||||
|
||||
```text
|
||||
Use DocSearch MCP to find the Next.js middleware matcher documentation.
|
||||
```
|
||||
|
||||
Confirm that the client calls a tool whose name starts with `algolia_docsearch_` and returns source links from the selected documentation.
|
||||
|
||||
If verification fails, see [Troubleshooting](/docs/mcp/usage#troubleshooting).
|
||||
|
|
|
|||
|
|
@ -1,49 +1,66 @@
|
|||
---
|
||||
title: DocSearch MCP
|
||||
description: Understand the DocSearch Model Context Protocol server.
|
||||
sidebar_label: Overview
|
||||
---
|
||||
|
||||
DocSearch MCP lets AI clients search current public developer documentation from the DocSearch corpus.
|
||||
DocSearch MCP lets AI clients search public developer documentation in the DocSearch corpus.
|
||||
|
||||
Use it when you want an assistant to answer questions from public docs instead of relying only on model training data. The public endpoint does not require authentication:
|
||||
Use it when an assistant needs information from published documentation instead of relying only on its training data. Connect an MCP-compatible client to this endpoint:
|
||||
|
||||
```text
|
||||
https://mcp.algolia.com/1/docsearch/mcp
|
||||
```
|
||||
|
||||
## What it does
|
||||
The endpoint doesn't require authentication. You don't need an Algolia application ID, search API key, or DocSearch application.
|
||||
|
||||
DocSearch MCP exposes documentation search through the [Model Context Protocol](https://modelcontextprotocol.io/). MCP-compatible clients connect to the endpoint and call DocSearch tools while answering your questions.
|
||||
## What DocSearch MCP provides
|
||||
|
||||
The endpoint is focused on public developer documentation. You do not need an Algolia application ID, search API key, or DocSearch application to use it.
|
||||
DocSearch MCP exposes documentation search through the [Model Context Protocol](https://modelcontextprotocol.io/). Your client calls its tools, receives ranked documentation excerpts and source URLs, and uses those results to answer your question.
|
||||
|
||||
## How it works
|
||||
Use DocSearch MCP for public documentation about libraries, frameworks, SDKs, APIs, CLI tools, and cloud services. It doesn't provide access to:
|
||||
|
||||
Most lookups are a single call: name the product and ask your question, and DocSearch finds the right documentation set and returns the matching content together.
|
||||
- Private or unpublished documentation
|
||||
- Internal repositories
|
||||
- DocSearch crawler or administration operations
|
||||
- Algolia indices or credentials
|
||||
- General programming advice outside the indexed documentation
|
||||
|
||||
When a question spans several products, or you want to inspect and hand-pick documentation sets first, there is a two-step flow: resolve the documentation sets, then query the ones you choose.
|
||||
## How searches work
|
||||
|
||||
For most searches, provide a product name and a focused question. DocSearch resolves the matching documentation set and searches it in one call.
|
||||
|
||||
Use the two-step workflow when a question covers several products or when you need to select the documentation set yourself. First resolve candidate sets, then query the selected set IDs.
|
||||
|
||||
## Available tools
|
||||
|
||||
### `algolia_docsearch_search_docs`
|
||||
|
||||
The one-shot tool, and the right default for most lookups. Give it a `library` (the product, SDK, or platform) and a `query` (your question); it resolves the best matching documentation set and returns ranked content in a single call. If the library is ambiguous, it returns candidate documentation sets to choose from instead.
|
||||
Use this one-shot tool for most searches. Set `library` to the official product, SDK, or platform name. Set `query` to the documentation question.
|
||||
|
||||
The tool resolves the best matching documentation set and returns ranked content. If the product name is ambiguous, it returns candidate documentation sets instead.
|
||||
|
||||
### `algolia_docsearch_resolve_docset`
|
||||
|
||||
Step 1 of the manual flow. Finds the documentation sets that best match a product, library, or platform and returns candidates — each with a `docset_id`, title, description, and ranking signals to help pick the best match.
|
||||
Use this tool first in the manual workflow. It returns candidate documentation sets with a `docset_id`, title, description, and ranking signals.
|
||||
|
||||
### `algolia_docsearch_query_docs`
|
||||
|
||||
Step 2 of the manual flow. Retrieves documentation content for one or more `docset_id`s returned by `algolia_docsearch_resolve_docset`. Pass several at once when a question spans multiple products.
|
||||
Use this tool with one or more `docset_id` values returned by `algolia_docsearch_resolve_docset`. Pass several IDs when the question covers more than one product.
|
||||
|
||||
## Security and service limits
|
||||
|
||||
Connect only to the HTTPS endpoint shown on this page. Don't add Algolia credentials to the MCP configuration. Send documentation questions, not secrets or confidential data, to the hosted service.
|
||||
|
||||
DocSearch MCP searches public content and returns external documentation to your client. Review generated answers and commands before you use them.
|
||||
|
||||
:::note[Service terms]
|
||||
|
||||
Algolia DocSearch MCP is a free service and is provided by Algolia "AS IS" and "AS AVAILABLE" without warranty of any kind. Algolia may suspend, modify, or discontinue the service at any time at its sole discretion. Algolia disclaims all obligation and liability arising from or related to your use of DocSearch MCP. You must comply with all applicable laws and government regulations when you use the service.
|
||||
|
||||
:::
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Install DocSearch MCP](/docs/mcp/installation)
|
||||
- [Use DocSearch MCP](/docs/mcp/usage)
|
||||
|
||||
:::note
|
||||
|
||||
Algolia DocSearch MCP is a free service and is provided by Algolia "AS IS" and "AS AVAILABLE" without warranty of any kind, and may be suspended, modified, or discontinued by Algolia at any time in its sole discretion. Algolia disclaims all obligation and liability arising out of or in connection with Your use of DocSearch MCP. You shall comply with all laws and governmental regulations in Your use of the DocSearch MCP.
|
||||
|
||||
:::
|
||||
|
|
|
|||
|
|
@ -1,41 +1,40 @@
|
|||
---
|
||||
title: Use DocSearch MCP
|
||||
description: Query documentation with the DocSearch MCP server.
|
||||
sidebar_label: Usage
|
||||
---
|
||||
|
||||
DocSearch MCP works best when your client knows to search public documentation before answering library, framework, API, or SDK questions.
|
||||
|
||||
## Ask documentation questions
|
||||
|
||||
After installation, ask your client about public developer docs in natural language:
|
||||
Ask focused questions about public documentation. Include the official product name, topic, and version when the version affects the answer.
|
||||
|
||||
```text
|
||||
How do I configure middleware matchers in Next.js?
|
||||
```
|
||||
|
||||
```text
|
||||
Show me the current Stripe webhook signature verification docs.
|
||||
Show me the Stripe webhook signature verification documentation.
|
||||
```
|
||||
|
||||
```text
|
||||
What is the current setup for Algolia InstantSearch React?
|
||||
What is the setup for Algolia InstantSearch React?
|
||||
```
|
||||
|
||||
If your client does not automatically use MCP tools, mention DocSearch MCP explicitly:
|
||||
If your client doesn't select MCP tools on its own, name the server in your request:
|
||||
|
||||
```text
|
||||
Use DocSearch MCP to look up React Server Components data fetching.
|
||||
```
|
||||
|
||||
Ask the client to include source URLs when you need to inspect the supporting documentation.
|
||||
|
||||
## Use the Claude Code command
|
||||
|
||||
The Claude Code plugin includes a manual command:
|
||||
The Claude Code plugin provides this command:
|
||||
|
||||
```text
|
||||
/algolia-docsearch:docs <library-or-product> [topic]
|
||||
```
|
||||
|
||||
Examples:
|
||||
For example:
|
||||
|
||||
```text
|
||||
/algolia-docsearch:docs Next.js middleware matcher
|
||||
|
|
@ -43,15 +42,48 @@ Examples:
|
|||
/algolia-docsearch:docs Algolia InstantSearch React configure search client
|
||||
```
|
||||
|
||||
## Tool flow
|
||||
## Search from the terminal
|
||||
|
||||
DocSearch MCP exposes three tools. Most of the time the client only needs the one-shot tool; the two-step flow is for multi-product questions or when you want to hand-pick documentation sets.
|
||||
`@docsearch/cli` can call the hosted MCP tools without a separate MCP client. Use `docs` for most questions:
|
||||
|
||||
You can ask in natural language — full sentences and questions work well. For the one-shot tool, keep `library` to the product name and put the actual question in `query`.
|
||||
```sh
|
||||
docsearch docs Next.js "how do middleware matchers work"
|
||||
```
|
||||
|
||||
### One-shot: `algolia_docsearch_search_docs`
|
||||
Limit the number of results or documentation sets when needed:
|
||||
|
||||
The client names the product and asks the question in a single call:
|
||||
```sh
|
||||
docsearch docs Next.js "cache revalidation" --max-results 3 --max-docsets 2
|
||||
```
|
||||
|
||||
For explicit documentation-set selection, resolve a product and then query its returned ID:
|
||||
|
||||
```sh
|
||||
docsearch resolve "Algolia InstantSearch React"
|
||||
docsearch query repo/algolia/instantsearch "configure the React search client"
|
||||
```
|
||||
|
||||
Limit resolver candidates with `--top-n`:
|
||||
|
||||
```sh
|
||||
docsearch resolve Next.js --top-n 3
|
||||
```
|
||||
|
||||
Add `--json` to print the raw MCP result as JSON:
|
||||
|
||||
```sh
|
||||
docsearch docs Next.js "cache revalidation" --json
|
||||
```
|
||||
|
||||
Machine-readable results go to standard output. Progress and errors go to standard error.
|
||||
|
||||
## Choose a tool workflow
|
||||
|
||||
DocSearch MCP provides a one-shot search and a two-step search. Use natural-language questions rather than lists of keywords.
|
||||
|
||||
### Search in one call
|
||||
|
||||
Use `algolia_docsearch_search_docs` for most questions. Keep the product name in `library` and the question in `query`:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -60,54 +92,72 @@ The client names the product and asks the question in a single call:
|
|||
}
|
||||
```
|
||||
|
||||
It returns ranked documentation content for the best matching set. If the library is ambiguous, it returns candidate documentation sets instead so the client can pick one and fall back to `algolia_docsearch_query_docs`.
|
||||
The tool resolves the best documentation set and returns ranked content. If `library` doesn't identify a clear match, it returns candidate sets instead of searching the wrong one.
|
||||
|
||||
### Two-step: resolve, then query
|
||||
### Resolve and query documentation sets
|
||||
|
||||
For questions that span several products, or when the client wants to choose documentation sets explicitly:
|
||||
Use the two-step workflow when the product is ambiguous, you want to inspect the candidates, or the question covers multiple products.
|
||||
|
||||
1. `algolia_docsearch_resolve_docset` finds documentation sets:
|
||||
1. Call `algolia_docsearch_resolve_docset` with the product or platform name:
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "Next.js app router"
|
||||
}
|
||||
```
|
||||
```json
|
||||
{
|
||||
"query": "Next.js app router"
|
||||
}
|
||||
```
|
||||
|
||||
It returns candidates, each with a `docset_id`.
|
||||
2. Select the best `docset_id` from the response.
|
||||
|
||||
2. `algolia_docsearch_query_docs` retrieves content for the chosen `docset_id`(s):
|
||||
3. Call `algolia_docsearch_query_docs` with the selected ID and a focused question:
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "middleware matcher config",
|
||||
"docsetIds": ["nextjs"]
|
||||
}
|
||||
```
|
||||
```json
|
||||
{
|
||||
"query": "middleware matcher configuration",
|
||||
"docsetIds": ["nextjs"]
|
||||
}
|
||||
```
|
||||
|
||||
Pass multiple `docsetIds` when a question spans more than one product.
|
||||
|
||||
## Tips
|
||||
|
||||
- Be specific about the product and topic you want.
|
||||
- Include a version when it matters.
|
||||
- Ask for source URLs if you want the client to show where the answer came from.
|
||||
- If the first result is too broad, ask for a narrower topic.
|
||||
Pass more than one `docsetIds` value when the question covers multiple products. Don't guess IDs. Use only IDs returned by `algolia_docsearch_resolve_docset` or `algolia_docsearch_search_docs`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### The client does not call DocSearch MCP
|
||||
### The client doesn't call DocSearch MCP
|
||||
|
||||
Make sure the MCP server is enabled in your client and named `algolia-docsearch`. If you installed the plugin, check that the plugin is enabled too.
|
||||
Confirm that the server is enabled and named `algolia-docsearch`. If you installed a plugin, confirm that the plugin is enabled. Then ask the client to use DocSearch MCP explicitly.
|
||||
|
||||
### The result is about the wrong product
|
||||
### The client can't connect
|
||||
|
||||
Ask again with the official product name. For the one-shot tool, set `library` to the vendor's product name (for example, `Algolia InstantSearch` rather than `search`).
|
||||
|
||||
### The client cannot connect
|
||||
|
||||
Confirm that your client supports remote HTTP MCP servers and that the configured URL is:
|
||||
Confirm that the client supports remote HTTP MCP servers and uses this exact HTTPS URL:
|
||||
|
||||
```text
|
||||
https://mcp.algolia.com/1/docsearch/mcp
|
||||
```
|
||||
|
||||
The public endpoint doesn't require authentication. Remove application IDs, API keys, headers, or authentication settings that the client doesn't require.
|
||||
|
||||
### CLI setup doesn't detect an agent
|
||||
|
||||
Pass the agent flag directly, or use `--all` with the setup command from the [installation guide](/docs/mcp/installation).
|
||||
|
||||
```sh
|
||||
docsearch setup --project --cursor --yes
|
||||
docsearch setup --global --all --yes
|
||||
```
|
||||
|
||||
Supported flags are `--cursor`, `--claude`, `--codex`, `--opencode`, and `--gemini`.
|
||||
|
||||
### CLI setup reports an invalid configuration
|
||||
|
||||
Fix the reported JSON, JSONC, or TOML error before running setup again. The CLI stops instead of replacing an invalid configuration. It also rejects duplicate `[mcp_servers.algolia-docsearch]` sections in Codex configuration.
|
||||
|
||||
### The result covers the wrong product
|
||||
|
||||
Use the official product or vendor name. For example, use `Algolia InstantSearch` instead of `search`. If the name remains ambiguous, resolve the documentation set first and query the selected ID.
|
||||
|
||||
### The result is too broad or empty
|
||||
|
||||
Add the product version and narrow the question to one task. Run separate searches for unrelated topics. If a narrower search still returns no useful content, the requested documentation might not be in the public DocSearch corpus.
|
||||
|
||||
### Private documentation isn't returned
|
||||
|
||||
DocSearch MCP searches public developer documentation only. It doesn't search private company docs, internal repositories, or unpublished APIs.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Migrating from the legacy scraper
|
||||
description: Move a legacy DocSearch scraper configuration to the Algolia Crawler.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
|
@ -7,21 +8,23 @@ import TabItem from '@theme/TabItem';
|
|||
|
||||
## Introduction
|
||||
|
||||
With the new version of the [DocSearch UI][1], we wanted to go further and provide better tooling for you to create and maintain your config file, and some extra Algolia features that you all have been requesting for a long time!
|
||||
This guide covers the historical migration from the self-hosted DocSearch scraper to the Algolia Crawler. It is separate from upgrading the frontend UI. For the current frontend, choose a [DocSearch v5 package][33].
|
||||
|
||||
## What's new?
|
||||
|
||||
### Scraper
|
||||
### Indexing infrastructure
|
||||
|
||||
The DocSearch infrastructure now leverages the [Algolia Crawler][2]. We've teamed up with our friends and created a new [DocSearch helper][4], that extracts records as we were previously doing with our beloved [DocSearch scraper][3]!
|
||||
DocSearch indexing now uses the [Algolia Crawler][2]. The [DocSearch helper][4] extracts records in place of the legacy [DocSearch scraper][3].
|
||||
|
||||
The best part, is that you no longer need to install any tooling on your side if you want to maintain or update your index!
|
||||
You don't need to install local scraper tooling to maintain or update your index.
|
||||
|
||||
We now provide a web interface **[legacy][7]** or **[new](https://dashboard.algolia.com/crawler)** that will allow you to:
|
||||
Use the **[legacy interface][7]** or the **[current Crawler interface](https://dashboard.algolia.com/crawler)** to:
|
||||
|
||||
- Start, schedule and monitor your crawls
|
||||
- Edit your config file from our live editor
|
||||
- Test your results directly with [DocSearch v3][1] or [DocSearch v4][32]
|
||||
- Test results with the historical [DocSearch v3][1] or [DocSearch v4][32] previews
|
||||
|
||||
These preview versions don't set the crawler record schema or the frontend package installed on your website. Likewise, crawler `recordVersion: 'v3'` names a record schema, not DocSearch UI v3.
|
||||
|
||||
### Algolia application and credentials
|
||||
|
||||
|
|
@ -31,7 +34,7 @@ We've received a lot of requests asking for:
|
|||
- Browse and see how Algolia records are indexed
|
||||
- See and subscribe to other Algolia features
|
||||
|
||||
They are now all available, in **your own Algolia application**, for free :D
|
||||
These features are available in your Algolia application.
|
||||
|
||||
## FAQ
|
||||
|
||||
|
|
@ -44,7 +47,7 @@ You can find answers related to the DocSearch migration in our [Crawler FAQ page
|
|||
|
||||
## Config file key mapping
|
||||
|
||||
Below are the keys that can be found in the [`legacy` DocSearch configs][14] and their translation to an [Algolia Crawler config][16]. More detailed documentation of the Algolia Crawler can be found on the [the official documentation][15]
|
||||
Below are the keys that can be found in the [`legacy` DocSearch configs][14] and their translation to an [Algolia Crawler config][16]. For more detailed information on the Algolia Crawler, see [the official documentation][15].
|
||||
|
||||
| `legacy` | `current` | description |
|
||||
| --- | --- | --- |
|
||||
|
|
@ -84,4 +87,5 @@ Below are the keys that can be found in the [`legacy` DocSearch configs][14] and
|
|||
[29]: /docs/record-extractor
|
||||
[30]: /docs/record-extractor#introduction
|
||||
[31]: /docs/record-extractor#pagerank
|
||||
[32]: /docs/docsearch
|
||||
[32]: /docs/v4/docsearch
|
||||
[33]: /docs/packages/overview
|
||||
|
|
|
|||
293
packages/website/docs/migrating-from-v4.mdx
Normal file
293
packages/website/docs/migrating-from-v4.mdx
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
---
|
||||
title: Migrate from DocSearch v4
|
||||
description: Move a DocSearch v4 integration to the 5.0.0 beta packages and Agent Studio APIs.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
DocSearch v5 separates keyword search from the AI experience and uses Agent Studio for Ask AI. Plan the migration around the experience your site provides.
|
||||
|
||||
Review the complete [v5 breaking changes](./v5-breaking-changes) before releasing.
|
||||
|
||||
## 1. Upgrade the beta packages
|
||||
|
||||
Upgrade every DocSearch package in the integration together with the `^5.0.0-beta` range.
|
||||
|
||||
<Tabs groupId="language" aria-label="Programming language">
|
||||
<TabItem value="react" label="React">
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
If you use the Composable API or Sidepanel, also upgrade `@docsearch/core@^5.0.0-beta`, `@docsearch/modal@^5.0.0-beta`, `@docsearch/sidepanel@^5.0.0-beta`, or `@docsearch/sidepanel-js@^5.0.0-beta`.
|
||||
|
||||
## 2. Choose keyword-only or AI-capable search
|
||||
|
||||
### JavaScript without Ask AI
|
||||
|
||||
The v4 root export rendered the combined component. In v5, the root export is AI-capable and the `/docsearch` subpath is keyword-only.
|
||||
|
||||
Change the import:
|
||||
|
||||
```diff title="app.js"
|
||||
-import docsearch from '@docsearch/js';
|
||||
+import docsearch from '@docsearch/js/docsearch';
|
||||
```
|
||||
|
||||
Your keyword options can remain unchanged while you migrate deprecated options in a later step.
|
||||
|
||||
For a CDN integration, replace the root bundle with the keyword-only UMD file:
|
||||
|
||||
```diff title="index.html"
|
||||
-<script src="https://cdn.jsdelivr.net/npm/@docsearch/js@4"></script>
|
||||
+<script src="https://cdn.jsdelivr.net/npm/@docsearch/js@^5.0.0-beta/dist/umd/docsearch.js"></script>
|
||||
```
|
||||
|
||||
### JavaScript with Ask AI
|
||||
|
||||
Keep the root import. It now renders the AI-capable `DocSearchAI` component:
|
||||
|
||||
```js title="app.js"
|
||||
import docsearch from '@docsearch/js';
|
||||
|
||||
docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'],
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For a CDN integration, use `dist/umd/index.js`. It exposes `window.docsearch`, as v4 did.
|
||||
|
||||
### React without Ask AI
|
||||
|
||||
Keep `DocSearch`, but remove any AI props. In v5 it renders keyword search only:
|
||||
|
||||
```jsx title="Search.jsx"
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
/>;
|
||||
```
|
||||
|
||||
### React with Ask AI
|
||||
|
||||
Replace `DocSearch` with `DocSearchAI`:
|
||||
|
||||
```diff title="Search.jsx"
|
||||
-import { DocSearch } from '@docsearch/react';
|
||||
+import { DocSearchAI } from '@docsearch/react';
|
||||
|
||||
-<DocSearch
|
||||
+<DocSearchAI
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
askAi={{ assistantId: 'YOUR_ASSISTANT_ID' }}
|
||||
/>
|
||||
```
|
||||
|
||||
`DocSearchAI` requires `askAi`. The string shorthand remains supported:
|
||||
|
||||
```jsx title="Search.jsx"
|
||||
<DocSearchAI askAi="YOUR_ASSISTANT_ID" {...searchProps} />
|
||||
```
|
||||
|
||||
## 3. Move Ask AI to Agent Studio
|
||||
|
||||
V5 removes the legacy Ask AI token and chat transport. Create the assistant in [Agent Studio](/docs/agent-studio/getting-started) before switching production traffic.
|
||||
|
||||
Remove `agentStudio` and `useStagingEnv` from `askAi`. Agent Studio is the only Ask AI backend in v5.
|
||||
|
||||
```diff title="app.js"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
- agentStudio: true,
|
||||
- useStagingEnv: false,
|
||||
}
|
||||
```
|
||||
|
||||
The v4 Agent Studio form keyed search parameters by index name. V5 keeps that shape and removes the legacy flat form:
|
||||
|
||||
```diff title="app.js"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
- filters: 'language:en',
|
||||
- attributesToRetrieve: ['title', 'content', 'url'],
|
||||
+ docs: {
|
||||
+ filters: 'language:en',
|
||||
+ attributesToRetrieve: ['title', 'content', 'url'],
|
||||
+ },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Agent Studio search parameters support `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct`. They don't support `facetFilters` in this object. Put fixed facet conditions in `filters`, or configure dynamic Agent Studio indices.
|
||||
|
||||
V5 adds `askAi.indices`, `askAi.tools`, `askAi.memory`, and `askAi.promptSuggestions`. Keep all Agent Studio configuration inside `askAi`. Keep `interceptAskAiEvent` at the component or `docsearch()` root because it controls view routing.
|
||||
|
||||
## 4. Move keyword configuration to `indices`
|
||||
|
||||
`indexName` and root `searchParameters` still work in v5, but both are deprecated.
|
||||
|
||||
```diff title="app.js"
|
||||
docsearch({
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
- indexName: 'docs',
|
||||
- searchParameters: {
|
||||
- facetFilters: ['language:en'],
|
||||
- },
|
||||
+ indices: [
|
||||
+ {
|
||||
+ name: 'docs',
|
||||
+ searchParameters: {
|
||||
+ facetFilters: ['language:en'],
|
||||
+ },
|
||||
+ },
|
||||
+ ],
|
||||
});
|
||||
```
|
||||
|
||||
Use one item per index. DocSearch queries them in array order. If you temporarily pass both `indexName` and `indices`, it queries `indexName` first, so remove the old option to avoid duplicate requests.
|
||||
|
||||
## 5. Update result customization
|
||||
|
||||
Existing `transformItems`, `hitComponent`, `resultsFooterComponent`, `transformSearchClient`, navigation, Insights, and translation props remain available.
|
||||
|
||||
V5 changes the result markup and adds breadcrumbs, source sections, facets, and badges. Review custom CSS, DOM selectors, screenshots, and tests that depend on v4 markup.
|
||||
|
||||
To show a badge from a custom record property, add the property to the per-index `attributesToRetrieve`, then set `resultBadgeKey`:
|
||||
|
||||
```jsx title="Search.jsx"
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={[
|
||||
{
|
||||
name: 'docs',
|
||||
searchParameters: {
|
||||
attributesToRetrieve: [
|
||||
'hierarchy.lvl0',
|
||||
'hierarchy.lvl1',
|
||||
'hierarchy.lvl2',
|
||||
'hierarchy.lvl3',
|
||||
'hierarchy.lvl4',
|
||||
'hierarchy.lvl5',
|
||||
'hierarchy.lvl6',
|
||||
'content',
|
||||
'type',
|
||||
'url',
|
||||
'version',
|
||||
],
|
||||
},
|
||||
},
|
||||
]}
|
||||
resultBadgeKey="version"
|
||||
/>
|
||||
```
|
||||
|
||||
## 6. Verify styles and package exports
|
||||
|
||||
The full stylesheet remains available from `@docsearch/css` and the CDN `dist/style.css` file:
|
||||
|
||||
```js title="app.js"
|
||||
import '@docsearch/css';
|
||||
```
|
||||
|
||||
The React package adds split style entries for variables, button, modal, Ask AI, and Sidepanel. See [Styling](/docs/packages/css/styling).
|
||||
|
||||
`@docsearch/js` now defines an exports map. Replace unsupported deep imports with `@docsearch/js` or `@docsearch/js/docsearch`. Continue to use documented React subpaths such as `@docsearch/react/button`, `/modal`, `/sidepanel`, and `/version`; their generated file names changed, but the package subpaths remain the public API.
|
||||
|
||||
## 7. Verify programmatic controls
|
||||
|
||||
The JavaScript function returns a `DocSearchInstance` with `open`, `close`, `openAskAi`, `destroy`, `isReady`, and `isOpen`. Use `openAskAi` only with the default AI-capable entry.
|
||||
|
||||
React forwards `DocSearchRef`. It also exposes `openSidepanel`, `isSidepanelOpen`, and `isSidepanelSupported` for [hybrid mode](/docs/hybrid-mode).
|
||||
|
||||
Test these cases before release:
|
||||
|
||||
- Open and close by button, `Ctrl/Cmd+K`, `/`, and Escape.
|
||||
- Search every configured index and apply every facet.
|
||||
- Open Ask AI, send follow-up questions, submit feedback, and restore a conversation.
|
||||
- Exercise any custom tools and memory authentication.
|
||||
- Verify custom hit links, result footers, translations, badges, and Insights events.
|
||||
- Verify mobile behavior. Hybrid mode uses the modal instead of the Sidepanel on mobile.
|
||||
|
||||
## 8. Update framework integrations
|
||||
|
||||
If Docusaurus manages DocSearch configuration, follow the [Docusaurus adapter guide](/docs/packages/docusaurus-adapter/getting-started). For custom provider, button, and modal layouts, review the [Composable API](/docs/composable-api).
|
||||
284
packages/website/docs/packages/core/api.mdx
Normal file
284
packages/website/docs/packages/core/api.mdx
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
---
|
||||
title: Core API reference
|
||||
description: API reference for @docsearch/core v5 beta.
|
||||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## `DocSearch`
|
||||
|
||||
`DocSearch` is a ref-forwarding React provider.
|
||||
|
||||
### `children`
|
||||
|
||||
> `type: React.ReactNode` | **required**
|
||||
|
||||
Views and controls that consume DocSearch state.
|
||||
|
||||
### `theme`
|
||||
|
||||
> `type: 'dark' | 'light'` | **optional**
|
||||
|
||||
Sets `document.documentElement.dataset.theme` while mounted.
|
||||
|
||||
### `initialQuery`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Initial modal query and reset value after close. Defaults to `''`.
|
||||
|
||||
### `keyboardShortcuts`
|
||||
|
||||
> `type: KeyboardShortcuts` | **optional**
|
||||
|
||||
Overrides individual shortcut defaults. All shortcuts are enabled by default.
|
||||
|
||||
### `onReady`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs after the provider mounts.
|
||||
|
||||
### `onOpen`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when a modal opens.
|
||||
|
||||
### `onClose`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when a modal closes to the ready state.
|
||||
|
||||
### `onSidepanelOpen`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when the Sidepanel opens.
|
||||
|
||||
### `onSidepanelClose`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when the Sidepanel closes.
|
||||
|
||||
### `KeyboardShortcuts`
|
||||
|
||||
```ts
|
||||
interface KeyboardShortcuts {
|
||||
'Ctrl/Cmd+K'?: boolean;
|
||||
'/'?: boolean;
|
||||
'Ctrl/Cmd+I'?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
`DEFAULT_KEYBOARD_SHORTCUTS` sets all three fields to `true`.
|
||||
|
||||
## `DocSearchRef`
|
||||
|
||||
Attach this ref to `DocSearch`.
|
||||
|
||||
### `open`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Opens the search modal.
|
||||
|
||||
### `close`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Returns to the ready state and focuses the registered search button.
|
||||
|
||||
### `openAskAi`
|
||||
|
||||
> `type: (message?: InitialAskAiMessage) => void`
|
||||
|
||||
Opens Ask AI in a registered view. See [Hybrid Mode][1].
|
||||
|
||||
### `openSidepanel`
|
||||
|
||||
> `type: (message?: InitialAskAiMessage) => void`
|
||||
|
||||
Opens the Sidepanel; it does nothing until that view registers.
|
||||
|
||||
### `isReady`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether the provider is mounted.
|
||||
|
||||
### `isOpen`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether a modal is open.
|
||||
|
||||
### `isSidepanelOpen`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether the Sidepanel is open.
|
||||
|
||||
### `isSidepanelSupported`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether Sidepanel support is registered. See [Hybrid Mode][1].
|
||||
|
||||
```ts
|
||||
type InitialAskAiMessage = {
|
||||
query: string;
|
||||
messageId?: string;
|
||||
suggestedQuestionId?: string;
|
||||
};
|
||||
```
|
||||
|
||||
## `useDocSearch`
|
||||
|
||||
```ts
|
||||
function useDocSearch(): DocSearchContext;
|
||||
```
|
||||
|
||||
The hook returns the nearest provider value.
|
||||
|
||||
`DocSearchState` is `'modal-askai' | 'modal-search' | 'ready' | 'sidepanel'`.
|
||||
|
||||
`View` is `'modal' | 'sidepanel' | (Record<string, unknown> & string)`, so integrations can register another string view.
|
||||
|
||||
### `docsearchState`
|
||||
|
||||
> `type: DocSearchState`
|
||||
|
||||
Active state.
|
||||
|
||||
### `setDocsearchState`
|
||||
|
||||
> `type: (state: DocSearchState) => void`
|
||||
|
||||
Sets active state directly.
|
||||
|
||||
### `searchButtonRef`
|
||||
|
||||
> `type: React.RefObject<HTMLButtonElement | null>`
|
||||
|
||||
Ref used to restore focus after close.
|
||||
|
||||
### `initialQuery`
|
||||
|
||||
> `type: string`
|
||||
|
||||
Query for a modal opening.
|
||||
|
||||
### `keyboardShortcuts`
|
||||
|
||||
> `type: Required<KeyboardShortcuts>`
|
||||
|
||||
User options merged with defaults.
|
||||
|
||||
### `openModal`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Opens keyword search.
|
||||
|
||||
### `closeModal`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Closes the modal and resets its query.
|
||||
|
||||
### `isAskAiActive`
|
||||
|
||||
> `type: boolean`
|
||||
|
||||
Whether the modal is in Ask AI state.
|
||||
|
||||
### `isModalActive`
|
||||
|
||||
> `type: boolean`
|
||||
|
||||
Whether either modal state is active.
|
||||
|
||||
### `onAskAiToggle`
|
||||
|
||||
> `type: OnAskAiToggle`
|
||||
|
||||
Changes the active Ask AI state. See [Hybrid Mode][1].
|
||||
|
||||
### `initialAskAiMessage`
|
||||
|
||||
> `type: InitialAskAiMessage | undefined` | **optional**
|
||||
|
||||
Initial Sidepanel message.
|
||||
|
||||
### `registerView`
|
||||
|
||||
> `type: (view: View) => void`
|
||||
|
||||
Registers an available view.
|
||||
|
||||
### `isHybridModeSupported`
|
||||
|
||||
> `type: boolean`
|
||||
|
||||
Whether cross-view support is available. See [Hybrid Mode][1].
|
||||
|
||||
## `useKeyboardShortcuts`
|
||||
|
||||
```ts
|
||||
function useKeyboardShortcuts(
|
||||
userShortcuts?: KeyboardShortcuts
|
||||
): Required<KeyboardShortcuts>;
|
||||
```
|
||||
|
||||
Returns the defaults merged with the supplied fields.
|
||||
|
||||
## `useTheme`
|
||||
|
||||
```ts
|
||||
function useTheme(props: { theme?: 'dark' | 'light' }): void;
|
||||
```
|
||||
|
||||
When `theme` differs from the document's existing `data-theme`, the hook sets it and restores the previous value on cleanup. It makes no change when `theme` is omitted.
|
||||
|
||||
## `useDocSearchKeyboardEvents`
|
||||
|
||||
```ts
|
||||
interface UseDocSearchKeyboardEventsProps {
|
||||
isOpen: boolean;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
isAskAiActive: boolean;
|
||||
onAskAiToggle: (active: boolean) => void;
|
||||
keyboardShortcuts?: KeyboardShortcuts;
|
||||
onInput?: (event: KeyboardEvent) => void;
|
||||
searchButtonRef?: React.RefObject<HTMLButtonElement | null>;
|
||||
}
|
||||
```
|
||||
|
||||
The hook registers a window `keydown` listener. `Escape` leaves Ask AI first, then closes an open modal. `Control/Command+K` toggles the modal. `/` opens it when the user isn't editing content.
|
||||
|
||||
`onInput` and `searchButtonRef` remain in the type for compatibility but are deprecated and aren't used by the hook.
|
||||
|
||||
## Exports
|
||||
|
||||
The root `@docsearch/core` entry exports:
|
||||
|
||||
| Kind | Exports |
|
||||
| --- | --- |
|
||||
| Components and hooks | `DocSearch`, `useDocSearch`, `useDocSearchKeyboardEvents`, `useKeyboardShortcuts`, `useTheme` |
|
||||
| Constants | `DEFAULT_KEYBOARD_SHORTCUTS` |
|
||||
| Types | `DocSearchState`, `View`, `InitialAskAiMessage`, `OnAskAiToggle`, `DocSearchRef`, `DocSearchContext`, `DocSearchCallbacks`, `DocSearchProps`, `DocSearchTheme`, `UseThemeProps`, `UseDocSearchKeyboardEventsProps`, `DocSearchModalShortcuts`, `SidepanelShortcuts`, `KeyboardShortcuts` |
|
||||
|
||||
The package also exposes these subpaths:
|
||||
|
||||
| Import path | Exports |
|
||||
| --- | --- |
|
||||
| `@docsearch/core/useDocSearchKeyboardEvents` | `useDocSearchKeyboardEvents`, `UseDocSearchKeyboardEventsProps` |
|
||||
| `@docsearch/core/useKeyboardShortcuts` | `useKeyboardShortcuts`, `DEFAULT_KEYBOARD_SHORTCUTS`, shortcut types |
|
||||
| `@docsearch/core/useTheme` | `useTheme`, `DocSearchTheme`, `UseThemeProps` |
|
||||
|
||||
`useIsMobile` is internal and isn't a package export.
|
||||
|
||||
[1]: /docs/hybrid-mode
|
||||
124
packages/website/docs/packages/core/overview.mdx
Normal file
124
packages/website/docs/packages/core/overview.mdx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
---
|
||||
title: Core package
|
||||
description: Understand the shared state and controls in @docsearch/core v5 beta.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
`@docsearch/core` provides the React state shared by DocSearch views. It owns open and close state, keyboard events, theme selection, lifecycle callbacks, initial queries, and imperative controls. It doesn't render a search interface by itself.
|
||||
|
||||
:::info v5 beta
|
||||
|
||||
These instructions use the `^5.0.0-beta` range.
|
||||
|
||||
:::
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/core@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/core@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/core@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/core@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Packages such as `@docsearch/modal` and `@docsearch/sidepanel` consume this provider. For component composition instructions, see the [Composable API guide](/docs/composable-api).
|
||||
|
||||
## Configure shared behavior
|
||||
|
||||
```tsx title="SearchState.tsx"
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
|
||||
export function SearchState({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<DocSearch
|
||||
theme="dark"
|
||||
initialQuery="authentication"
|
||||
keyboardShortcuts={{
|
||||
'Ctrl/Cmd+K': true,
|
||||
'/': false,
|
||||
'Ctrl/Cmd+I': true,
|
||||
}}
|
||||
onReady={() => track('docsearch_ready')}
|
||||
onOpen={() => track('search_opened')}
|
||||
onClose={() => track('search_closed')}
|
||||
onSidepanelOpen={() => track('assistant_opened')}
|
||||
onSidepanelClose={() => track('assistant_closed')}
|
||||
>
|
||||
{children}
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The default shortcuts enable `Control/Command+K`, `/`, and `Control/Command+I`. Slash doesn't open the modal while focus is in an input, select, textarea, or editable element.
|
||||
|
||||
## Use a ref
|
||||
|
||||
```tsx title="SearchState.tsx"
|
||||
import { DocSearch, type DocSearchRef } from '@docsearch/core';
|
||||
import { useRef } from 'react';
|
||||
|
||||
export function SearchState({ children }: { children: React.ReactNode }) {
|
||||
const ref = useRef<DocSearchRef>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button type="button" onClick={() => ref.current?.open()}>
|
||||
Open search
|
||||
</button>
|
||||
<DocSearch ref={ref}>{children}</DocSearch>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The ref can open and close registered views and report their state. See the [core API reference](./api#docsearchref). For interactions between views, see [Hybrid Mode](/docs/hybrid-mode).
|
||||
|
||||
## Read provider state
|
||||
|
||||
Call `useDocSearch` only beneath `DocSearch`. The hook throws outside the provider.
|
||||
|
||||
```tsx title="SearchStatus.tsx"
|
||||
import { useDocSearch } from '@docsearch/core';
|
||||
|
||||
export function SearchStatus() {
|
||||
const { docsearchState, openModal, closeModal } = useDocSearch();
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={docsearchState === 'ready' ? openModal : closeModal}
|
||||
>
|
||||
{docsearchState === 'ready' ? 'Open search' : 'Close search'}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Use the higher-level modal and Sidepanel packages for their standard triggers. Read context directly when your interface needs custom controls.
|
||||
101
packages/website/docs/packages/css/bundle-exports.mdx
Normal file
101
packages/website/docs/packages/css/bundle-exports.mdx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
title: CSS bundles and exports
|
||||
description: Choose the @docsearch/css v5 beta bundle for each DocSearch surface.
|
||||
---
|
||||
|
||||
`@docsearch/css` publishes compiled CSS and SCSS-named assets in `dist`. The package doesn't define an `exports` map, so import the root entry or a listed `dist` file.
|
||||
|
||||
## Root entry
|
||||
|
||||
The package fields `main`, `unpkg`, and `jsdelivr` all point to `dist/style.css`.
|
||||
|
||||
These imports load the same full search-and-modal bundle:
|
||||
|
||||
```js title="app.js"
|
||||
import '@docsearch/css';
|
||||
import '@docsearch/css/dist/style.css';
|
||||
```
|
||||
|
||||
Use only one of them.
|
||||
|
||||
## Choose imports by surface
|
||||
|
||||
Each surface needs a different set of assets. See [Published assets](#published-assets) for the full file list.
|
||||
|
||||
### Search button only
|
||||
|
||||
```js title="app.js"
|
||||
import '@docsearch/css/dist/button.css';
|
||||
```
|
||||
|
||||
### Keyword-search modal
|
||||
|
||||
```js title="app.js"
|
||||
import '@docsearch/css/dist/button.css';
|
||||
import '@docsearch/css/dist/modal.css';
|
||||
```
|
||||
|
||||
Use the root bundle instead if your modal can show Agent Studio responses.
|
||||
|
||||
### Agent Studio modal
|
||||
|
||||
```js title="app.js"
|
||||
import '@docsearch/css';
|
||||
```
|
||||
|
||||
### Sidepanel
|
||||
|
||||
```js title="app.js"
|
||||
import '@docsearch/css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
```
|
||||
|
||||
The Sidepanel reuses Agent Studio response and Markdown classes from the root bundle, so `sidepanel.css` isn't the complete Sidepanel presentation by itself.
|
||||
|
||||
## Load from a CDN
|
||||
|
||||
```html title="index.html"
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@docsearch/css@^5.0.0-beta/dist/style.css"
|
||||
/>
|
||||
```
|
||||
|
||||
Add the Sidepanel asset when needed:
|
||||
|
||||
```html title="index.html"
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@docsearch/css@^5.0.0-beta/dist/sidepanel.css"
|
||||
/>
|
||||
```
|
||||
|
||||
Use the caret beta range so compatible beta updates remain available.
|
||||
|
||||
## Published assets
|
||||
|
||||
| Path | Contents |
|
||||
| --- | --- |
|
||||
| `@docsearch/css/dist/style.css` | Variables, search button, modal, and Agent Studio response styles. |
|
||||
| `@docsearch/css/dist/_variables.css` | Shared light and dark custom properties. |
|
||||
| `@docsearch/css/dist/button.css` | Variables and search-button styles. |
|
||||
| `@docsearch/css/dist/modal.css` | Variables and keyword-search modal styles. |
|
||||
| `@docsearch/css/dist/_askai.css` | Agent Studio modal response and Markdown styles. |
|
||||
| `@docsearch/css/dist/sidepanel.css` | Variables, Sidepanel button, panel layout, and Sidepanel-specific overrides. |
|
||||
|
||||
Each asset also has an equivalent `.scss` file: `style.scss`, `_variables.scss`, `button.scss`, `modal.scss`, `_askai.scss`, and `sidepanel.scss`. The build writes the compiled bundle to both extensions; these aren't Sass source partials.
|
||||
|
||||
## React style bridge exports
|
||||
|
||||
`@docsearch/react` v5 beta also exposes JavaScript style bridge entry points. They re-export files from `@docsearch/css`:
|
||||
|
||||
| Import path | CSS target |
|
||||
| ---------------------------------- | ------------------------------------ |
|
||||
| `@docsearch/react/style` | `@docsearch/css` |
|
||||
| `@docsearch/react/style/variables` | `@docsearch/css/dist/_variables.css` |
|
||||
| `@docsearch/react/style/button` | `@docsearch/css/dist/button.css` |
|
||||
| `@docsearch/react/style/modal` | `@docsearch/css/dist/modal.css` |
|
||||
| `@docsearch/react/style/askai` | `@docsearch/css/dist/_askai.css` |
|
||||
| `@docsearch/react/style/sidepanel` | `@docsearch/css/dist/sidepanel.css` |
|
||||
|
||||
Prefer direct `@docsearch/css` imports when your bundler supports CSS package imports. They show which assets enter your bundle and work across the DocSearch React packages.
|
||||
131
packages/website/docs/packages/css/styling.mdx
Normal file
131
packages/website/docs/packages/css/styling.mdx
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
title: Style DocSearch
|
||||
description: Load and customize the DocSearch v5 beta CSS.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
`@docsearch/css` contains the styles for the search button, modal, Agent Studio responses, and Sidepanel.
|
||||
|
||||
:::info v5 beta
|
||||
|
||||
These instructions use the `^5.0.0-beta` range.
|
||||
|
||||
:::
|
||||
|
||||
## Install and import
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
For the search button and modal, import the package root:
|
||||
|
||||
```js title="app.js"
|
||||
import '@docsearch/css';
|
||||
```
|
||||
|
||||
The root stylesheet includes variables, button styles, modal styles, and Agent Studio response styles. It doesn't include Sidepanel layout styles.
|
||||
|
||||
For a Sidepanel, add its stylesheet after the root stylesheet:
|
||||
|
||||
```js title="app.js"
|
||||
import '@docsearch/css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
```
|
||||
|
||||
## Override variables
|
||||
|
||||
Load your overrides after DocSearch CSS.
|
||||
|
||||
```css title="custom.css"
|
||||
:root {
|
||||
--docsearch-primary-color: #2457d6;
|
||||
--docsearch-primary-dark-color: #173b99;
|
||||
--docsearch-focus-color: #0b57d0;
|
||||
--docsearch-modal-width: 48rem;
|
||||
--docsearch-modal-radius: 0.75rem;
|
||||
--docsearch-hit-height: 3.75rem;
|
||||
--docsearch-border-radius: 0.375rem;
|
||||
}
|
||||
```
|
||||
|
||||
Prefer variables over selector overrides. Variables preserve focus, responsive, and state styles across package updates.
|
||||
|
||||
Frequently used variables include:
|
||||
|
||||
- **Brand and state**: `--docsearch-primary-color`, `--docsearch-primary-dark-color`, `--docsearch-highlight-color`, `--docsearch-focus-color`, `--docsearch-error-color`, `--docsearch-success-color`
|
||||
- **Text and surfaces**: `--docsearch-text-color`, `--docsearch-secondary-text-color`, `--docsearch-background-color`, `--docsearch-modal-background`, `--docsearch-subtle-color`, `--docsearch-muted-color`
|
||||
- **Modal**: `--docsearch-modal-width`, `--docsearch-modal-height`, `--docsearch-modal-variable-height`, `--docsearch-modal-radius`, `--docsearch-modal-shadow`
|
||||
- **Search box and hits**: `--docsearch-searchbox-height`, `--docsearch-searchbox-background`, `--docsearch-hit-height`, `--docsearch-hit-background`, `--docsearch-hit-focus-background`
|
||||
- **Button**: `--docsearch-search-button-background`, `--docsearch-search-button-text-color`
|
||||
- **Sidepanel**: `--docsearch-sidepanel-background`, `--docsearch-sidepanel-primary`, `--docsearch-sidepanel-border`, `--docsearch-sidepanel-text-base`, `--docsearch-sidepanel-text-muted`
|
||||
|
||||
## Set dark theme values
|
||||
|
||||
DocSearch applies dark variables under `html[data-theme='dark']`. The `theme="dark"` prop on a DocSearch component sets this attribute.
|
||||
|
||||
Override dark values with the same selector:
|
||||
|
||||
```css title="custom.css"
|
||||
html[data-theme='dark'] {
|
||||
--docsearch-modal-background: #111827;
|
||||
--docsearch-background-color: #1f2937;
|
||||
--docsearch-text-color: #f3f4f6;
|
||||
--docsearch-secondary-text-color: #d1d5db;
|
||||
--docsearch-hit-background: #111827;
|
||||
--docsearch-hit-focus-background: #263244;
|
||||
}
|
||||
```
|
||||
|
||||
## Add targeted overrides
|
||||
|
||||
All component classes use the `DocSearch-` prefix. Scope selector overrides to a component and preserve visible focus states.
|
||||
|
||||
```css title="custom.css"
|
||||
.DocSearch-Button {
|
||||
min-width: 15rem;
|
||||
}
|
||||
|
||||
.DocSearch-Sidepanel-Container.floating {
|
||||
box-shadow: 0 1rem 3rem rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
.DocSearch-Action:focus-visible,
|
||||
.DocSearch-Button:focus-visible {
|
||||
outline: 2px solid var(--docsearch-focus-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
|
||||
Don't remove the visually hidden helpers or reduced-motion rules. They support accessible labels, keyboard use, and user motion preferences.
|
||||
|
||||
See the [CSS bundle and export guide](./bundle-exports) when you need component-specific assets.
|
||||
|
|
@ -0,0 +1,618 @@
|
|||
---
|
||||
title: Docusaurus adapter configuration
|
||||
description: Configure DocSearch v5 search, Ask AI, the Sidepanel, and the search page in Docusaurus.
|
||||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
Add supported options under `themeConfig.docsearch`. The adapter doesn't read `themeConfig.algolia`.
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
export default {
|
||||
plugins: ['@docsearch/docusaurus-adapter'],
|
||||
themeConfig: {
|
||||
docsearch: {
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Compatibility
|
||||
|
||||
| Dependency | Supported version |
|
||||
| ------------------- | --------------------------------------- |
|
||||
| Node.js | 20 or later |
|
||||
| Docusaurus | 3.10.2 or later in the 3.x release line |
|
||||
| React and React DOM | 18 or 19 |
|
||||
|
||||
Keep the adapter, Docusaurus, React, and React DOM within these ranges. Don't run the adapter beside `@docusaurus/theme-search-algolia`.
|
||||
|
||||
## Root options
|
||||
|
||||
The validator rejects unknown root options. Pass runtime functions such as custom Agent Studio tools through a swizzled `SearchBar` instead of `themeConfig`.
|
||||
|
||||
### `appId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Algolia application ID.
|
||||
|
||||
### `apiKey`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Public API key with search permission.
|
||||
|
||||
### `indices`
|
||||
|
||||
> `type: Array<string | DocSearchIndex>` | **required**
|
||||
|
||||
Nonempty list of keyword indices, in query order. See [Indices and search parameters](#indices-and-search-parameters).
|
||||
|
||||
### `contextualSearch`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Whether to add the current Docusaurus locale, default tag, and docs-version tags to search. Defaults to `true`. See [Contextual search](#contextual-search).
|
||||
|
||||
### `externalUrlRegex`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Regular expression that identifies result URLs that require full-page navigation. See [URL processing](#url-processing).
|
||||
|
||||
### `replaceSearchResultPathname`
|
||||
|
||||
> `type: { from: string | RegExp, to: string }` | **optional**
|
||||
|
||||
Replace a result pathname before adding the Docusaurus base URL. See [URL processing](#url-processing).
|
||||
|
||||
### `facets`
|
||||
|
||||
> `type: Array<{ key: string, label?: string }>` | **optional**
|
||||
|
||||
Facet controls in the search modal. Defaults to `[]`. See [Modal facets](#modal-facets).
|
||||
|
||||
### `initialQuery`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Query placed in the modal when it opens. Defaults to `''`.
|
||||
|
||||
### `insights`
|
||||
|
||||
> `type: boolean | object` | **optional**
|
||||
|
||||
Whether to enable Algolia Insights, or an object that configures it. Defaults to `false`.
|
||||
|
||||
### `placeholder`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Search input placeholder. Defaults to the built-in translation.
|
||||
|
||||
### `translations`
|
||||
|
||||
> `type: DocSearchTranslations` | **optional**
|
||||
|
||||
Search button and modal text overrides. Defaults to the Docusaurus translations.
|
||||
|
||||
### `maxResultsPerGroup`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Maximum results shown in each modal result group.
|
||||
|
||||
### `disableUserPersonalization`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Whether to disable recent searches, favorites, and stored AI conversations. Defaults to `false`.
|
||||
|
||||
### `getMissingResultsUrl`
|
||||
|
||||
> `type: ({ query }) => string` | **optional**
|
||||
|
||||
Build the no-results report URL.
|
||||
|
||||
### `keyboardShortcuts`
|
||||
|
||||
> `type: object` | **optional**
|
||||
|
||||
Enable or disable `Ctrl/Cmd+K`, `/`, and `Ctrl/Cmd+I`. All shortcuts are enabled by default.
|
||||
|
||||
### `recentSearchesLimit`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Maximum recent searches when no favorites exist. Defaults to `7`.
|
||||
|
||||
### `recentSearchesWithFavoritesLimit`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Maximum recent searches when favorites exist. Defaults to `4`.
|
||||
|
||||
### `resultBadgeKey`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Hit property rendered as a badge in modal results. See [Result badges](#result-badges).
|
||||
|
||||
### `searchPage`
|
||||
|
||||
> `type: false | SearchPageConfig` | **optional**
|
||||
|
||||
Configure or disable the search results page. Defaults to `{ path: 'search' }`. See [Search page](#search-page).
|
||||
|
||||
### `askAi`
|
||||
|
||||
> `type: AskAiConfig` | **optional**
|
||||
|
||||
Configure Agent Studio for the modal and Sidepanel. See [Ask AI](#ask-ai).
|
||||
|
||||
### `sidePanel`
|
||||
|
||||
> `type: boolean | SidePanelConfig` | **optional**
|
||||
|
||||
Whether to enable the Ask AI Sidepanel, or an object that configures it. Defaults to `false`. See [Sidepanel](#sidepanel).
|
||||
|
||||
## Indices and search parameters
|
||||
|
||||
Define at least one keyword index. Use a string for its default settings or an object for per-index settings:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
indices: [
|
||||
'docs_en',
|
||||
{
|
||||
name: 'api_reference',
|
||||
searchParameters: {
|
||||
facetFilters: ['version:v5'],
|
||||
filters: 'visibility:public',
|
||||
attributesToRetrieve: [
|
||||
'content',
|
||||
'hierarchy',
|
||||
'type',
|
||||
'url',
|
||||
'version',
|
||||
],
|
||||
restrictSearchableAttributes: ['hierarchy', 'content'],
|
||||
distinct: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
`searchParameters` accepts Algolia search parameters. The validator explicitly accepts `facetFilters`, `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct`, and preserves other Algolia parameters.
|
||||
|
||||
The modal queries every index in array order. The search page queries only the first index.
|
||||
|
||||
## Contextual search
|
||||
|
||||
Keep `contextualSearch: true` to limit results to the current locale and Docusaurus docs versions. The adapter merges these conditions into each index's `facetFilters` instead of replacing your filters.
|
||||
|
||||
Contextual search also applies to each `askAi.indices` item. Define dynamic Agent Studio indices if Ask AI must receive these facet filters.
|
||||
|
||||
Set `contextualSearch: false` when your records don't contain Docusaurus `language` and `docusaurus_tag` attributes:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
docsearch: {
|
||||
// ...
|
||||
contextualSearch: false,
|
||||
},
|
||||
```
|
||||
|
||||
## Modal facets
|
||||
|
||||
Use root `facets` to add filter controls to the modal:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
docsearch: {
|
||||
// ...
|
||||
facets: [
|
||||
{ key: 'language', label: 'Language' },
|
||||
{ key: 'version', label: 'Version' },
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
Configure each attribute as an Algolia facet. The modal supports up to five unique, nonempty facet keys. It merges facet values from every keyword index and hides facets with no values.
|
||||
|
||||
These controls don't configure the search page sidebar. Use `searchPage.facets` for that page.
|
||||
|
||||
## Result badges
|
||||
|
||||
Set `resultBadgeKey` to a property path returned by each keyword index:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
docsearch: {
|
||||
// ...
|
||||
indices: [
|
||||
{
|
||||
name: 'docs',
|
||||
searchParameters: {
|
||||
attributesToRetrieve: [
|
||||
'content',
|
||||
'hierarchy',
|
||||
'type',
|
||||
'url',
|
||||
'version',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
resultBadgeKey: 'version',
|
||||
},
|
||||
```
|
||||
|
||||
Dot paths and array indices work, including `hierarchy.lvl1`, `tags[2]`, and `tags.2`. The adapter displays the badge in modal results. The built-in search page doesn't render result badges.
|
||||
|
||||
## Search page
|
||||
|
||||
The adapter creates `/search` by default. Change the path and sidebar facets with an object:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
searchPage: {
|
||||
path: 'find',
|
||||
facets: [
|
||||
{ attribute: 'hierarchy.lvl0', label: 'Section' },
|
||||
{ attribute: 'version', label: 'Version' },
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
Each facet requires `attribute`; `label` defaults to the attribute name. If `facets` is missing or empty, the page shows `hierarchy.lvl0` with the label `Section`.
|
||||
|
||||
The page uses the first `indices` item and its `searchParameters`. It shows 15 results per request, stores refinements in the URL, and adds docs-version controls when contextual search and Docusaurus versioning are active. Configure every sidebar attribute as an Algolia facet.
|
||||
|
||||
Disable the route, modal footer link, and OpenSearch metadata with:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
searchPage: false,
|
||||
```
|
||||
|
||||
## Ask AI
|
||||
|
||||
Create an assistant by following [Get started with Agent Studio](/docs/agent-studio/getting-started). Set `askAi` to an object:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
suggestedQuestions: true,
|
||||
searchParameters: {
|
||||
docs: {
|
||||
filters: 'visibility:public',
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
distinct: true,
|
||||
},
|
||||
},
|
||||
memory: {
|
||||
enabled: true,
|
||||
userToken: 'SERVER_GENERATED_JWT',
|
||||
},
|
||||
promptSuggestions: {
|
||||
indexName: 'docs_prompt_suggestions',
|
||||
hitsPerPage: 3,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
### `assistantId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Agent Studio assistant ID.
|
||||
|
||||
### `suggestedQuestions`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Whether to show published assistant questions on the new-conversation screen. Defaults to `false`.
|
||||
|
||||
### `searchParameters`
|
||||
|
||||
> `type: Record<string, AgentStudioSearchParameters>` | **optional**
|
||||
|
||||
Search parameters keyed by index name. Root `askAi.searchParameters` supports `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct`. Don't add `facetFilters` at this level.
|
||||
|
||||
### `indices`
|
||||
|
||||
> `type: AgentStudioIndex[]` | **optional**
|
||||
|
||||
Dynamic indices available to Agent Studio search tools. See [dynamic indices][1].
|
||||
|
||||
Use `askAi.indices` to describe dynamic indices:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
indices: [
|
||||
{
|
||||
index: 'docs_markdown',
|
||||
description: 'Product documentation.',
|
||||
enhancedDescription: 'Use for setup and API questions.',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
Each item requires `index` and `description`. It can also include `enhancedDescription`, `searchParameters`, and `searchControls`. Search controls can configure `query`, `hits_per_page`, `page`, `attributesToRetrieve`, `responseFields`, `facets`, and custom properties.
|
||||
|
||||
### `memory`
|
||||
|
||||
> `type: { enabled?: boolean, userToken?: string }` | **optional**
|
||||
|
||||
Show memory tool calls and send a user JWT. Memory is disabled by default. See [user-scoped memory][2].
|
||||
|
||||
### `promptSuggestions`
|
||||
|
||||
> `type: { indexName: string, hitsPerPage?: number }` | **optional**
|
||||
|
||||
Show prompt suggestions with keyword results. `hitsPerPage` defaults to `3`. See [Configure prompt suggestions](/docs/agent-studio/prompt-suggestions).
|
||||
|
||||
## Sidepanel
|
||||
|
||||
Set `sidePanel: true` for the default panel. `askAi` is required whenever the Sidepanel is enabled.
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
sidePanel: {
|
||||
variant: 'inline',
|
||||
side: 'right',
|
||||
width: 420,
|
||||
expandedWidth: '60vw',
|
||||
pushSelector: '#__docusaurus',
|
||||
suggestedQuestions: true,
|
||||
hideButton: false,
|
||||
keyboardShortcuts: {
|
||||
'Ctrl/Cmd+I': true,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Review [hybrid mode](/docs/hybrid-mode) before changing panel behavior.
|
||||
|
||||
### `variant`
|
||||
|
||||
> `type: 'floating' | 'inline'` | **optional**
|
||||
|
||||
Float above content or push selected content. Defaults to `'inline'`.
|
||||
|
||||
### `side`
|
||||
|
||||
> `type: 'left' | 'right'` | **optional**
|
||||
|
||||
Side where the panel opens. Defaults to `'right'`.
|
||||
|
||||
### `width`
|
||||
|
||||
> `type: number | string` | **optional**
|
||||
|
||||
Collapsed panel width. Numbers represent pixels. Defaults to `360px`.
|
||||
|
||||
### `expandedWidth`
|
||||
|
||||
> `type: number | string` | **optional**
|
||||
|
||||
Expanded panel width. Numbers represent pixels. Defaults to `580px`.
|
||||
|
||||
### `pushSelector`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Element pushed by the inline panel. Defaults to `#__docusaurus`.
|
||||
|
||||
### `suggestedQuestions`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Whether to show suggested questions in the panel. Defaults to `askAi.suggestedQuestions`.
|
||||
|
||||
### `translations`
|
||||
|
||||
> `type: SidepanelTranslations` | **optional**
|
||||
|
||||
Panel text overrides. Defaults to the built-in strings.
|
||||
|
||||
### `hideButton`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Whether to hide the Sidepanel button while keeping the panel registered. Defaults to `false`.
|
||||
|
||||
### `portalContainer`
|
||||
|
||||
> `type: DocumentFragment | Element | null` | **optional**
|
||||
|
||||
Element that receives the panel portal. Defaults to `document.body`. Pass this option through a swizzled `SearchBar` because a DOM element isn't available while Docusaurus evaluates its server configuration.
|
||||
|
||||
### `keyboardShortcuts`
|
||||
|
||||
> `type: { 'Ctrl/Cmd+I'?: boolean }` | **optional**
|
||||
|
||||
Enable or disable the panel shortcut. The shortcut is enabled by default.
|
||||
|
||||
### `indices`
|
||||
|
||||
> `type: AgentStudioIndex[]` | **optional**
|
||||
|
||||
Override Agent Studio indices for the panel. Defaults to `askAi.indices`. See [dynamic indices][1].
|
||||
|
||||
### `memory`
|
||||
|
||||
> `type: { enabled?: boolean, userToken?: string }` | **optional**
|
||||
|
||||
Override memory for the panel. Defaults to `askAi.memory`. See [user-scoped memory][2].
|
||||
|
||||
## Custom tools
|
||||
|
||||
Docusaurus can't serialize tool functions in `themeConfig`. The adapter rejects both `askAi.tools` and `sidePanel.tools` there.
|
||||
|
||||
Swizzle `SearchBar` from `@docsearch/docusaurus-adapter`, then pass tools as component props:
|
||||
|
||||
```bash
|
||||
npm run swizzle -- @docsearch/docusaurus-adapter SearchBar --eject
|
||||
```
|
||||
|
||||
Replace the generated component with one of these examples:
|
||||
|
||||
<Tabs groupId="language" aria-label="Programming language">
|
||||
<TabItem value="react" label="React">
|
||||
|
||||
```tsx title="src/theme/SearchBar/index.tsx"
|
||||
import type { DocusaurusSearchBarProps } from '@docsearch/docusaurus-adapter';
|
||||
import type { ToolCalls } from '@docsearch/react';
|
||||
import OriginalSearchBar from '@theme-original/SearchBar';
|
||||
|
||||
const tools: ToolCalls = {
|
||||
getReleaseStatus: {
|
||||
render: ({ message }) => JSON.stringify(message.output),
|
||||
},
|
||||
};
|
||||
|
||||
export default function SearchBar(props: DocusaurusSearchBarProps) {
|
||||
return (
|
||||
<OriginalSearchBar
|
||||
{...props}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
tools,
|
||||
}}
|
||||
sidePanel={{ tools }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/theme/SearchBar/index.js"
|
||||
import OriginalSearchBar from '@theme-original/SearchBar';
|
||||
|
||||
const tools = {
|
||||
getReleaseStatus: {
|
||||
render: ({ message }) => JSON.stringify(message.output),
|
||||
},
|
||||
};
|
||||
|
||||
export default function SearchBar(props) {
|
||||
return (
|
||||
<OriginalSearchBar
|
||||
{...props}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
tools,
|
||||
}}
|
||||
sidePanel={{ tools }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Passing `askAi` or `sidePanel` as a prop replaces that nested theme-config value. Include every nested option that you want to keep in the prop object. See [Agent Studio tools](/docs/agent-studio/tools) for tool renderers and client-side handlers.
|
||||
|
||||
## Translations
|
||||
|
||||
Override only the modal strings you need:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
translations: {
|
||||
button: {
|
||||
buttonText: 'Search API docs',
|
||||
buttonAriaLabel: 'Search API docs',
|
||||
},
|
||||
modal: {
|
||||
searchBox: {
|
||||
placeholderText: 'Search API docs',
|
||||
},
|
||||
facets: {
|
||||
clearAllLabel: 'Reset filters',
|
||||
},
|
||||
resultsScreen: {
|
||||
resultBadgeLabelText: 'Version',
|
||||
},
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
The adapter uses Docusaurus translation IDs for its default search button, modal, and search page strings. Run the Docusaurus `write-translations` command to localize built-in strings. Put Sidepanel overrides under `sidePanel.translations`.
|
||||
|
||||
## URL processing
|
||||
|
||||
The adapter parses each result URL. It leaves URLs matching `externalUrlRegex` unchanged and uses full-page navigation for them. For other URLs, it keeps the pathname, query, and hash, applies `replaceSearchResultPathname`, and adds the Docusaurus base URL.
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
docsearch: {
|
||||
// ...
|
||||
externalUrlRegex: '^https://external\\.example\\.com/',
|
||||
replaceSearchResultPathname: {
|
||||
from: '/legacy-docs/',
|
||||
to: '/docs/',
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
A string `from` value is treated literally. A `RegExp` is converted to its source during validation.
|
||||
|
||||
## Validation errors
|
||||
|
||||
The adapter reports dedicated errors for removed or unsupported v4 options. Use the replacement named in each message:
|
||||
|
||||
```text
|
||||
`themeConfig.algolia` is no longer supported by @docsearch/docusaurus-adapter v5. Move the configuration to `themeConfig.docsearch`.
|
||||
|
||||
`themeConfig.docsearch.indexName` was removed. Use `themeConfig.docsearch.indices` instead.
|
||||
|
||||
`themeConfig.docsearch.searchParameters` was removed. Configure `searchParameters` on each `themeConfig.docsearch.indices` entry instead.
|
||||
|
||||
`themeConfig.docsearch.searchPagePath` was removed. Use `themeConfig.docsearch.searchPage` instead.
|
||||
|
||||
`themeConfig.docsearch.askAi` must be an object with `assistantId`.
|
||||
|
||||
`themeConfig.docsearch.askAi.agentStudio` was removed. The adapter now only supports Agent Studio.
|
||||
|
||||
`themeConfig.docsearch.askAi.indexName`, `apiKey`, and `appId` were removed. Use the top-level DocSearch credentials instead.
|
||||
|
||||
`themeConfig.docsearch.askAi.sidePanel` was removed. Use `themeConfig.docsearch.sidePanel` instead.
|
||||
|
||||
`themeConfig.docsearch.sidePanel` requires `themeConfig.docsearch.askAi`.
|
||||
```
|
||||
|
||||
Tool definitions report these errors:
|
||||
|
||||
```text
|
||||
`themeConfig.docsearch.askAi.tools` is not supported because Docusaurus removes function values when serializing theme config. Pass custom tools through a swizzled `@theme/SearchBar` component instead: use `askAi` for the modal and `sidePanel` for the side panel.
|
||||
|
||||
`themeConfig.docsearch.sidePanel.tools` is not supported because Docusaurus removes function values when serializing theme config. Pass custom tools through a swizzled `@theme/SearchBar` component instead: use `askAi` for the modal and `sidePanel` for the side panel.
|
||||
```
|
||||
|
||||
The schema also reports these messages for missing or invalid required configuration:
|
||||
|
||||
```text
|
||||
"themeConfig.docsearch" is required
|
||||
"docsearch.appId" is required. If you haven't migrated to the new DocSearch infra, please refer to the blog post for instructions: https://docusaurus.io/blog/2021/11/21/algolia-docsearch-migration
|
||||
"docsearch.apiKey" is required
|
||||
"docsearch.indices" is required
|
||||
"docsearch.indices" must contain at least 1 items
|
||||
"docsearch.unknownKey" is not allowed
|
||||
"docsearch.askAi.indices" must contain at least 1 items
|
||||
"docsearch.askAi.indices[0].description" is required
|
||||
"docsearch.sidePanel.indices" must contain at least 1 items
|
||||
"docsearch.sidePanel.indices[0].description" is required
|
||||
```
|
||||
|
||||
Replace `unknownKey` with the rejected option name. Other invalid nested values use the same Joi path format.
|
||||
|
||||
[1]: /docs/agent-studio/dynamic-indices
|
||||
[2]: /docs/agent-studio/memory
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
---
|
||||
title: Docusaurus adapter
|
||||
description: Add DocSearch v5 keyword search and Agent Studio answers to a Docusaurus site.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
Use `@docsearch/docusaurus-adapter` to add keyword search, a search results page, Ask AI, and an Ask AI Sidepanel to Docusaurus.
|
||||
|
||||
## Before you start
|
||||
|
||||
Collect these values:
|
||||
|
||||
- An Algolia application ID.
|
||||
- A Search API key with access to your DocSearch indices.
|
||||
- At least one DocSearch index name.
|
||||
- An Agent Studio assistant ID if you want Ask AI or the Sidepanel. Follow [Get started with Agent Studio](/docs/agent-studio/getting-started) to create one.
|
||||
|
||||
Use Node.js 20 or later, Docusaurus 3.10.2 or later in the Docusaurus 3 release line, and React 18 or 19.
|
||||
|
||||
## Install the adapter
|
||||
|
||||
Install `@docsearch/docusaurus-adapter@^5.0.0-beta`:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Keep `@docusaurus/preset-classic`. Don't add `@docusaurus/theme-search-algolia` as another search integration.
|
||||
|
||||
## Configure keyword search
|
||||
|
||||
Add the adapter to `plugins`. Configure it under `themeConfig.docsearch`:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
export default {
|
||||
plugins: ['@docsearch/docusaurus-adapter'],
|
||||
themeConfig: {
|
||||
docsearch: {
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: [{ name: 'YOUR_INDEX_NAME' }],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
`indices` is required and must contain at least one index. Use an object to set search parameters for one index:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
indices: [
|
||||
{
|
||||
name: 'docs',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
attributesToRetrieve: [
|
||||
'content',
|
||||
'hierarchy',
|
||||
'type',
|
||||
'url',
|
||||
'version',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
Start Docusaurus and open the search modal. The adapter also creates `/search` and links to it from the modal. Change or disable that route with [`searchPage`](./configuration-reference#search-page).
|
||||
|
||||
## Add Ask AI
|
||||
|
||||
Set `askAi` to an object with your Agent Studio assistant ID:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
export default {
|
||||
plugins: ['@docsearch/docusaurus-adapter'],
|
||||
themeConfig: {
|
||||
docsearch: {
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: [{ name: 'YOUR_INDEX_NAME' }],
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The adapter uses Agent Studio for Ask AI. Pass an object, not an assistant ID string. It reuses the root `appId`, `apiKey`, and first keyword index unless you configure dynamic Agent Studio indices.
|
||||
|
||||
## Add the Sidepanel
|
||||
|
||||
Set `sidePanel` at the root of `docsearch`. You must also configure `askAi`:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
docsearch: {
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: [{ name: 'YOUR_INDEX_NAME' }],
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
},
|
||||
sidePanel: true,
|
||||
},
|
||||
```
|
||||
|
||||
Pass an object instead of `true` to configure its placement, width, questions, translations, memory, or indices. The modal and Sidepanel share the DocSearch provider. Review [hybrid mode](/docs/hybrid-mode) for their responsive behavior.
|
||||
|
||||
## Next steps
|
||||
|
||||
- Review every adapter option in the [configuration reference](./configuration-reference).
|
||||
- Follow [Migrate from v4](./migrating-from-v4) if your site uses `themeConfig.algolia`, `indexName`, root `searchParameters`, or `askAi.sidePanel`.
|
||||
- Review the package-level [DocSearch v4 migration guide](/docs/migrating-from-v4).
|
||||
|
|
@ -0,0 +1,388 @@
|
|||
---
|
||||
title: Migrate the Docusaurus adapter from v4
|
||||
description: Move a Docusaurus DocSearch adapter configuration from v4 to the v5 beta.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
DocSearch v5 requires `themeConfig.docsearch`, a nonempty `indices` array, and Agent Studio for Ask AI. Update the adapter and configuration together.
|
||||
|
||||
Review the broader [DocSearch v4 migration guide](/docs/migrating-from-v4) for changes outside the Docusaurus adapter.
|
||||
|
||||
## 1. Check compatibility
|
||||
|
||||
Upgrade the site to Node.js 20 or later and Docusaurus 3.10.2 or later in the Docusaurus 3 release line. Use React and React DOM 18 or 19.
|
||||
|
||||
## 2. Install the v5 adapter
|
||||
|
||||
Install `@docsearch/docusaurus-adapter@^5.0.0-beta`:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/docusaurus-adapter@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Keep the adapter in `plugins` and don't add `@docusaurus/theme-search-algolia` as another search integration.
|
||||
|
||||
## 3. Move to `themeConfig.docsearch`
|
||||
|
||||
V4 accepted `themeConfig.algolia` as an alias. V5 rejects it.
|
||||
|
||||
<Tabs aria-label="Comparison">
|
||||
<TabItem value="before" label="Before: v4">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
export default {
|
||||
plugins: ['@docsearch/docusaurus-adapter'],
|
||||
themeConfig: {
|
||||
algolia: {
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indexName: 'docs',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After: v5">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
export default {
|
||||
plugins: ['@docsearch/docusaurus-adapter'],
|
||||
themeConfig: {
|
||||
docsearch: {
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: [{ name: 'docs' }],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Don't define both keys during the migration. V5 accepts only `themeConfig.docsearch`.
|
||||
|
||||
## 4. Replace `indexName` and root search parameters
|
||||
|
||||
Move every keyword index into `indices`. Move root `searchParameters` onto the matching index:
|
||||
|
||||
<Tabs aria-label="Comparison">
|
||||
<TabItem value="before" label="Before: v4">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
docsearch: {
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indexName: 'docs',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
attributesToRetrieve: ['content', 'hierarchy', 'type', 'url'],
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After: v5">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
docsearch: {
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: [
|
||||
{
|
||||
name: 'docs',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
attributesToRetrieve: ['content', 'hierarchy', 'type', 'url'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
`indices` is required and must contain at least one item. The modal queries all items in order. The search page uses the first item.
|
||||
|
||||
## 5. Replace `searchPagePath`
|
||||
|
||||
V5 replaces the path value with an object that can also configure facets:
|
||||
|
||||
<Tabs aria-label="Comparison">
|
||||
<TabItem value="before" label="Before: v4">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
searchPagePath: 'find',
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After: v5">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
searchPage: {
|
||||
path: 'find',
|
||||
facets: [
|
||||
{ attribute: 'hierarchy.lvl0', label: 'Section' },
|
||||
{ attribute: 'version', label: 'Version' },
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Use `searchPage: false` to disable the route. An empty object uses the default `search` path and the default `hierarchy.lvl0` section facet.
|
||||
|
||||
## 6. Move Ask AI to Agent Studio
|
||||
|
||||
Create an assistant by following [Get started with Agent Studio](/docs/agent-studio/getting-started). V5 accepts only an `askAi` object and always uses Agent Studio.
|
||||
|
||||
<Tabs aria-label="Comparison">
|
||||
<TabItem value="before" label="Before: v4">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
appId: 'ASK_AI_APPLICATION_ID',
|
||||
apiKey: 'ASK_AI_SEARCH_API_KEY',
|
||||
indexName: 'docs_markdown',
|
||||
agentStudio: true,
|
||||
},
|
||||
```
|
||||
|
||||
V4 also accepted this shorthand:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
askAi: 'YOUR_ASSISTANT_ID',
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After: v5">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
indices: [
|
||||
{
|
||||
index: 'docs_markdown',
|
||||
description: 'Product documentation.',
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Remove `agentStudio`, `appId`, `apiKey`, and `indexName` from `askAi`. The adapter reuses the root credentials. Without `askAi.indices`, it uses the first keyword index as the Ask AI index.
|
||||
|
||||
V4 supported flat search parameters for the legacy Ask AI backend. V5 requires Agent Studio search parameters keyed by index name:
|
||||
|
||||
<Tabs aria-label="Comparison">
|
||||
<TabItem value="before" label="Before: v4">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
filters: 'language:en',
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After: v5">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
docs: {
|
||||
filters: 'language:en',
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
},
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Root `askAi.searchParameters` supports `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct`. Put `facetFilters` on a dynamic `askAi.indices` item's `searchParameters` instead.
|
||||
|
||||
## 7. Move the Sidepanel to the root
|
||||
|
||||
Move `sidePanel` out of `askAi`:
|
||||
|
||||
<Tabs aria-label="Comparison">
|
||||
<TabItem value="before" label="Before: v4">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
sidePanel: {
|
||||
variant: 'inline',
|
||||
side: 'right',
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After: v5">
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
},
|
||||
sidePanel: {
|
||||
variant: 'inline',
|
||||
side: 'right',
|
||||
},
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The root `sidePanel` requires `askAi`. It can override Agent Studio `indices`, `memory`, `suggestedQuestions`, and translations for the panel. Review [hybrid mode](/docs/hybrid-mode) for modal and panel behavior.
|
||||
|
||||
## 8. Move custom tools into `SearchBar`
|
||||
|
||||
V5 rejects `askAi.tools` and `sidePanel.tools` in `docusaurus.config` because Docusaurus removes functions while serializing theme config. Swizzle `@theme/SearchBar` and pass tools as component props.
|
||||
|
||||
```bash
|
||||
npm run swizzle -- @docsearch/docusaurus-adapter SearchBar --eject
|
||||
```
|
||||
|
||||
<Tabs groupId="language" aria-label="Programming language">
|
||||
<TabItem value="react" label="React">
|
||||
|
||||
```tsx title="src/theme/SearchBar/index.tsx"
|
||||
import type { DocusaurusSearchBarProps } from '@docsearch/docusaurus-adapter';
|
||||
import type { ToolCalls } from '@docsearch/react';
|
||||
import OriginalSearchBar from '@theme-original/SearchBar';
|
||||
|
||||
const tools: ToolCalls = {
|
||||
getReleaseStatus: {
|
||||
render: ({ message }) => JSON.stringify(message.output),
|
||||
},
|
||||
};
|
||||
|
||||
export default function SearchBar(props: DocusaurusSearchBarProps) {
|
||||
return (
|
||||
<OriginalSearchBar
|
||||
{...props}
|
||||
askAi={{ assistantId: 'YOUR_ASSISTANT_ID', tools }}
|
||||
sidePanel={{ tools }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/theme/SearchBar/index.js"
|
||||
import OriginalSearchBar from '@theme-original/SearchBar';
|
||||
|
||||
const tools = {
|
||||
getReleaseStatus: {
|
||||
render: ({ message }) => JSON.stringify(message.output),
|
||||
},
|
||||
};
|
||||
|
||||
export default function SearchBar(props) {
|
||||
return (
|
||||
<OriginalSearchBar
|
||||
{...props}
|
||||
askAi={{ assistantId: 'YOUR_ASSISTANT_ID', tools }}
|
||||
sidePanel={{ tools }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Include every nested `askAi` and `sidePanel` option that you want to keep because component props replace those nested theme-config values. See [Agent Studio tools](/docs/agent-studio/tools) for complete tool definitions.
|
||||
|
||||
## 9. Add v5 search features
|
||||
|
||||
Configure modal facets at the `docsearch` root:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
facets: [
|
||||
{ key: 'language', label: 'Language' },
|
||||
{ key: 'version', label: 'Version' },
|
||||
],
|
||||
```
|
||||
|
||||
Configure search-page facets separately under `searchPage.facets`. Configure every facet attribute in your Algolia index.
|
||||
|
||||
Show a custom hit property in modal results with `resultBadgeKey`. Add that property to each index's `attributesToRetrieve`:
|
||||
|
||||
```js title="docusaurus.config.mjs"
|
||||
indices: [
|
||||
{
|
||||
name: 'docs',
|
||||
searchParameters: {
|
||||
attributesToRetrieve: [
|
||||
'content',
|
||||
'hierarchy',
|
||||
'type',
|
||||
'url',
|
||||
'version',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
resultBadgeKey: 'version',
|
||||
```
|
||||
|
||||
## 10. Verify the migration
|
||||
|
||||
Run the production Docusaurus build, then verify these paths:
|
||||
|
||||
- Open keyword search with the button, `Ctrl/Cmd+K`, and `/`.
|
||||
- Search every configured index and apply each modal facet.
|
||||
- Open the search page, apply its facets, and check docs-version filtering.
|
||||
- Open Ask AI and submit a follow-up question.
|
||||
- Open the Sidepanel with its button and `Ctrl/Cmd+I`.
|
||||
- Check contextual results in every locale and docs version.
|
||||
- Follow internal, replaced, and external result URLs.
|
||||
- Check translated strings and result badges.
|
||||
- Test mobile behavior. Hybrid mode uses the modal instead of the Sidepanel on mobile.
|
||||
|
||||
Use the [configuration reference](./configuration-reference) to resolve validation messages and review every v5 option.
|
||||
487
packages/website/docs/packages/js/api-reference.mdx
Normal file
487
packages/website/docs/packages/js/api-reference.mdx
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
---
|
||||
title: JS package API reference
|
||||
description: Configure @docsearch/js 5.0.0-beta.0 and control its returned instance.
|
||||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## `docsearch(options)`
|
||||
|
||||
Renders DocSearch in `options.container` and returns a `DocSearchInstance`.
|
||||
|
||||
```ts title="load-docsearch.ts"
|
||||
import docsearch from '@docsearch/js';
|
||||
|
||||
const instance = docsearch(options);
|
||||
```
|
||||
|
||||
The default export from `@docsearch/js` is AI-capable and uses `DocSearchAI`. The default export from `@docsearch/js/docsearch` is keyword-only and uses `DocSearch`.
|
||||
|
||||
## JavaScript options
|
||||
|
||||
### `container`
|
||||
|
||||
> `type: HTMLElement | string` | **required**
|
||||
|
||||
Container element or CSS selector for the search button. A selector resolves against `environment.document` and must match an element.
|
||||
|
||||
### `environment`
|
||||
|
||||
> `type: typeof window` | **optional**
|
||||
|
||||
Browser-like environment used to resolve a string `container`. Defaults to `window`.
|
||||
|
||||
### `onReady`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Callback after DocSearch mounts.
|
||||
|
||||
### `onOpen`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Callback when the modal opens.
|
||||
|
||||
### `onClose`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Callback when the modal closes.
|
||||
|
||||
### `interceptAskAiEvent`
|
||||
|
||||
> `type: (initialMessage: InitialAskAiMessage) => boolean | void` | **optional**
|
||||
|
||||
Callback before DocSearch starts an Ask AI request. Return `true` to prevent the modal from toggling or sending the message. Use this option to route the request to another view, such as the Sidepanel.
|
||||
|
||||
`InitialAskAiMessage` contains `query` and can contain `messageId` or `suggestedQuestionId`.
|
||||
|
||||
## Search options
|
||||
|
||||
These options apply to both package entries.
|
||||
|
||||
### `appId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Algolia application ID.
|
||||
|
||||
### `apiKey`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Public API key with search permission.
|
||||
|
||||
### `indices`
|
||||
|
||||
> `type: Array<string | DocSearchIndex>` | **optional**
|
||||
|
||||
Indices used for keyword search, in display order. There's no default, and at least `indices` or `indexName` is required.
|
||||
|
||||
Use a string for default search parameters or a `DocSearchIndex` for per-index parameters:
|
||||
|
||||
```ts
|
||||
interface DocSearchIndex {
|
||||
name: string;
|
||||
searchParameters?: SearchParamsObject;
|
||||
}
|
||||
```
|
||||
|
||||
```js title="docsearch-options.js"
|
||||
indices: [
|
||||
{
|
||||
name: 'docs_en',
|
||||
searchParameters: {
|
||||
facetFilters: ['version:v5'],
|
||||
attributesToRetrieve: [
|
||||
'hierarchy.lvl0',
|
||||
'hierarchy.lvl1',
|
||||
'hierarchy.lvl2',
|
||||
'hierarchy.lvl3',
|
||||
'hierarchy.lvl4',
|
||||
'hierarchy.lvl5',
|
||||
'hierarchy.lvl6',
|
||||
'content',
|
||||
'type',
|
||||
'url',
|
||||
'version',
|
||||
],
|
||||
},
|
||||
},
|
||||
'docs_fr',
|
||||
];
|
||||
```
|
||||
|
||||
If you pass both `indexName` and `indices`, DocSearch queries `indexName` first, then each `indices` item. `indexName` and the root `searchParameters` remain available for v4 compatibility but are deprecated.
|
||||
|
||||
### `indexName`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Deprecated index name. There's no default. Use `indices`.
|
||||
|
||||
### `facets`
|
||||
|
||||
> `type: DocSearchFacet[]` | **optional**
|
||||
|
||||
Facet controls populated from the configured indices. Defaults to `[]`.
|
||||
|
||||
```ts
|
||||
interface DocSearchFacet {
|
||||
key: string;
|
||||
label?: string;
|
||||
}
|
||||
```
|
||||
|
||||
DocSearch supports up to five facets. It compares trimmed, lowercase keys to ignore duplicates and empty keys, fetches values from all configured indices, and displays only facets that have values. Selecting a value adds a `facetFilters` entry to every index query while retaining that index's configured filters.
|
||||
|
||||
```js title="docsearch-options.js"
|
||||
facets: [
|
||||
{ key: 'language', label: 'Language' },
|
||||
{ key: 'version', label: 'Version' },
|
||||
];
|
||||
```
|
||||
|
||||
Configure each attribute for faceting in the Algolia index before exposing it here.
|
||||
|
||||
### `theme`
|
||||
|
||||
> `type: 'light' | 'dark'` | **optional**
|
||||
|
||||
Theme written to `document.documentElement.dataset.theme`. By default, DocSearch leaves the current theme unchanged.
|
||||
|
||||
### `placeholder`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Search input placeholder. The default is experience-specific.
|
||||
|
||||
### `searchParameters`
|
||||
|
||||
> `type: SearchParamsObject` | **optional**
|
||||
|
||||
Deprecated search parameters for `indexName`. There's no default. Put them on an `indices` item.
|
||||
|
||||
### `maxResultsPerGroup`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Maximum results in each result group. There's no default.
|
||||
|
||||
### `transformItems`
|
||||
|
||||
> `type: (items: DocSearchHit[]) => DocSearchHit[]` | **optional**
|
||||
|
||||
Function that transforms hits before grouping and rendering. Defaults to the identity function.
|
||||
|
||||
### `hitComponent`
|
||||
|
||||
> `type: template function` | **optional**
|
||||
|
||||
Template for a result link. Defaults to the built-in hit template. See [Templates](#templates).
|
||||
|
||||
### `resultsFooterComponent`
|
||||
|
||||
> `type: template function` | **optional**
|
||||
|
||||
Template below the result collections. There's no default. See [Templates](#templates).
|
||||
|
||||
### `transformSearchClient`
|
||||
|
||||
> `type: (client) => client` | **optional**
|
||||
|
||||
Function that wraps or replaces the Algolia search client. Defaults to the identity function.
|
||||
|
||||
### `disableUserPersonalization`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Whether to disable recent searches, favorites, and stored AI conversations. Defaults to `false`.
|
||||
|
||||
### `initialQuery`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Query placed in the input when the modal opens. Defaults to `''`.
|
||||
|
||||
### `navigator`
|
||||
|
||||
> `type: AutocompleteOptions['navigator']` | **optional**
|
||||
|
||||
Autocomplete navigation implementation. Defaults to the default navigator.
|
||||
|
||||
### `translations`
|
||||
|
||||
> `type: DocSearchTranslations` | **optional**
|
||||
|
||||
Button and modal text overrides. Defaults to the English strings.
|
||||
|
||||
### `getMissingResultsUrl`
|
||||
|
||||
> `type: ({ query }) => string` | **optional**
|
||||
|
||||
Function that builds the no-results report URL. There's no default.
|
||||
|
||||
### `insights`
|
||||
|
||||
> `type: AutocompleteOptions['insights']` | **optional**
|
||||
|
||||
Algolia Insights integration options. Defaults to `false`.
|
||||
|
||||
### `portalContainer`
|
||||
|
||||
> `type: DocumentFragment | Element` | **optional**
|
||||
|
||||
Element that receives the modal portal. Defaults to `document.body`.
|
||||
|
||||
### `recentSearchesLimit`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Maximum recent searches without favorites. Defaults to `7`.
|
||||
|
||||
### `recentSearchesWithFavoritesLimit`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Maximum recent searches when favorites exist. Defaults to `4`.
|
||||
|
||||
### `keyboardShortcuts`
|
||||
|
||||
> `type: DocSearchModalShortcuts` | **optional**
|
||||
|
||||
Whether `Ctrl/Cmd+K` and `/` open the modal. Both shortcuts are enabled by default. Escape always closes it.
|
||||
|
||||
### `resultBadgeKey`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Hit property rendered as a result badge. There's no default.
|
||||
|
||||
The key supports property paths such as `version`, `hierarchy.lvl1`, `tags[2]`, and `tags.2`. Primitive values render as text. Arrays of primitive values render as a comma-separated list.
|
||||
|
||||
Add custom badge data to `attributesToRetrieve`. Otherwise the property isn't present in the hit:
|
||||
|
||||
```js title="docsearch-options.js"
|
||||
indices: [
|
||||
{
|
||||
name: 'docs',
|
||||
searchParameters: {
|
||||
attributesToRetrieve: [
|
||||
'hierarchy.lvl0',
|
||||
'hierarchy.lvl1',
|
||||
'hierarchy.lvl2',
|
||||
'hierarchy.lvl3',
|
||||
'hierarchy.lvl4',
|
||||
'hierarchy.lvl5',
|
||||
'hierarchy.lvl6',
|
||||
'content',
|
||||
'type',
|
||||
'url',
|
||||
'version',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
resultBadgeKey: 'version',
|
||||
```
|
||||
|
||||
Set `translations.modal.resultsScreen.resultBadgeLabelText` to describe the badge to screen-reader users. Its default is `Category`.
|
||||
|
||||
### Templates
|
||||
|
||||
`hitComponent` receives `{ hit, children }`. `resultsFooterComponent` receives `{ state }`. JavaScript templates can return a Preact element, a string, or a component function. The optional second argument provides an `html` tagged-template helper.
|
||||
|
||||
```js title="docsearch-options.js"
|
||||
hitComponent({ hit, children }, { html }) {
|
||||
return html`<a href=${hit.url} data-result-type=${hit.type}>${children}</a>`;
|
||||
}
|
||||
```
|
||||
|
||||
String returns render as text, not HTML.
|
||||
|
||||
## Ask AI options
|
||||
|
||||
Ask AI is available from the default `@docsearch/js` entry. Its `askAi` option is required by that entry's `DocSearchProps` type.
|
||||
|
||||
### `askAi`
|
||||
|
||||
> `type: string | DocSearchAskAi` | **required**
|
||||
|
||||
Assistant ID or Agent Studio configuration. The default entry requires it.
|
||||
|
||||
```js title="docsearch-options.js"
|
||||
askAi: 'YOUR_ASSISTANT_ID';
|
||||
```
|
||||
|
||||
Use an object for additional controls.
|
||||
|
||||
Follow [Get started with Agent Studio](/docs/agent-studio/getting-started) to create the assistant and [hybrid mode](/docs/hybrid-mode) to route AI requests to a Sidepanel.
|
||||
|
||||
#### `assistantId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Agent Studio assistant ID.
|
||||
|
||||
#### `appId`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Application ID used by Ask AI. Defaults to the root `appId`.
|
||||
|
||||
#### `apiKey`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
API key used by Ask AI. Defaults to the root `apiKey`.
|
||||
|
||||
#### `indexName`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Index used by Ask AI. Defaults to the first normalized keyword index.
|
||||
|
||||
#### `suggestedQuestions`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Whether to show published assistant questions on the new-conversation screen. Defaults to `false`.
|
||||
|
||||
#### `searchParameters`
|
||||
|
||||
> `type: AgentStudioSearchParameters` | **optional**
|
||||
|
||||
Search parameters keyed by index name. There's no default.
|
||||
|
||||
Each value supports `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct`. This Agent Studio shape doesn't accept `facetFilters`.
|
||||
|
||||
```js title="docsearch-options.js"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
docs: {
|
||||
filters: 'language:en',
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
distinct: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### `indices`
|
||||
|
||||
> `type: AgentStudioIndices[]` | **optional**
|
||||
|
||||
Dynamic indices available to the Agent Studio search tool. There's no default.
|
||||
|
||||
Each `AgentStudioIndices` item requires `index` and `description`. It can also define `enhancedDescription`, `searchParameters`, and `searchControls`.
|
||||
|
||||
`searchControls` supports `query`, `hits_per_page`, `page`, `attributesToRetrieve`, `responseFields`, `facets`, and `custom`. The `exposed` flag controls whether the model can set a value. Constraints set allowed numeric ranges or string-array values.
|
||||
|
||||
```js title="docsearch-options.js"
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
indices: [
|
||||
{
|
||||
index: 'api_reference',
|
||||
description: 'API symbols and parameter reference',
|
||||
enhancedDescription: 'Use for questions about methods and options.',
|
||||
searchControls: {
|
||||
hits_per_page: {
|
||||
exposed: true,
|
||||
default: 7,
|
||||
constraint: { min: 1, max: 10 },
|
||||
},
|
||||
facets: {
|
||||
exposed: false,
|
||||
default: ['version'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
#### `tools`
|
||||
|
||||
> `type: ToolCalls` | **optional**
|
||||
|
||||
Custom Agent Studio tool renderers and handlers. Defaults to `{}`.
|
||||
|
||||
`ToolCalls` maps Agent Studio tool names to `ToolDefinition` objects. `render` converts the tool input and output to displayed text. `onToolCall` handles a client-side tool and must call `addToolOutput` when it produces a result. `translations.callingToolText` changes the pending label.
|
||||
|
||||
#### `memory`
|
||||
|
||||
> `type: Memory` | **optional**
|
||||
|
||||
Agent Studio memory display and user token. Memory is disabled by default.
|
||||
|
||||
```js title="docsearch-options.js"
|
||||
memory: {
|
||||
enabled: true,
|
||||
userToken: 'SERVER_GENERATED_JWT_TOKEN',
|
||||
}
|
||||
```
|
||||
|
||||
`enabled` controls whether memory tool calls appear. `userToken` is the JWT sent in the `x-algolia-secure-user-token` header.
|
||||
|
||||
#### `promptSuggestions`
|
||||
|
||||
> `type: PromptSuggestions` | **optional**
|
||||
|
||||
Prompt suggestions displayed with keyword results. There's no default.
|
||||
|
||||
```js title="docsearch-options.js"
|
||||
promptSuggestions: {
|
||||
indexName: 'docsearch_prompt_suggestions',
|
||||
hitsPerPage: 3,
|
||||
}
|
||||
```
|
||||
|
||||
The index records must contain a `prompt` attribute. `hitsPerPage` defaults to `3`.
|
||||
|
||||
## `DocSearchInstance`
|
||||
|
||||
### `isReady`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether the instance is mounted.
|
||||
|
||||
### `isOpen`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether the modal is open.
|
||||
|
||||
### `open`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Opens keyword search.
|
||||
|
||||
### `close`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Closes the modal.
|
||||
|
||||
### `openAskAi`
|
||||
|
||||
> `type: (initialMessage?: InitialAskAiMessage) => void`
|
||||
|
||||
Opens Ask AI with an optional query. Use with the AI-capable entry.
|
||||
|
||||
### `destroy`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Unmounts DocSearch and marks the instance not ready.
|
||||
|
||||
## Compatibility and deprecations
|
||||
|
||||
- The bundles target ES2017 browsers.
|
||||
- `indexName` and root `searchParameters` remain supported but are deprecated. Move to `indices`.
|
||||
- The default entry uses Agent Studio. The v4 Ask AI transport and `askAi.agentStudio` switch aren't available.
|
||||
- The keyword-only `/docsearch` export is the smaller choice when you don't configure Ask AI.
|
||||
168
packages/website/docs/packages/js/getting-started.mdx
Normal file
168
packages/website/docs/packages/js/getting-started.mdx
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
title: JS package
|
||||
description: Add DocSearch v5 keyword search or Agent Studio answers to a JavaScript site.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
Use `@docsearch/js` to render DocSearch into an HTML element. The default entry includes keyword search and Ask AI. A separate entry includes keyword search only.
|
||||
|
||||
## Before you start
|
||||
|
||||
Collect your Algolia application ID, Search API key, and index name. To add Ask AI, also create an assistant in [Agent Studio](/docs/agent-studio/getting-started) and copy its assistant ID.
|
||||
|
||||
## Install the packages
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Add a container where you want the search button:
|
||||
|
||||
```html title="index.html"
|
||||
<div id="docsearch"></div>
|
||||
```
|
||||
|
||||
Pass a container element or a selector that matches one. Don't pass an `input`; DocSearch renders the search button and input.
|
||||
|
||||
## Add keyword search and Ask AI
|
||||
|
||||
Import the default entry and provide an `askAi` configuration:
|
||||
|
||||
```js title="app.js"
|
||||
import docsearch from '@docsearch/js';
|
||||
|
||||
import '@docsearch/css';
|
||||
|
||||
const search = docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'],
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The default entry bundles both experiences. `askAi` accepts an assistant ID string or a configuration object. See the [JavaScript API reference](./api-reference#ask-ai-options).
|
||||
|
||||
## Add keyword search only
|
||||
|
||||
Import the `/docsearch` entry to exclude Ask AI code:
|
||||
|
||||
```js title="app.js"
|
||||
import docsearch from '@docsearch/js/docsearch';
|
||||
|
||||
import '@docsearch/css';
|
||||
|
||||
const search = docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'],
|
||||
});
|
||||
```
|
||||
|
||||
## Load DocSearch from a CDN
|
||||
|
||||
Load the complete stylesheet and one JavaScript bundle. Both UMD bundles expose `window.docsearch`.
|
||||
|
||||
### Keyword search and Ask AI
|
||||
|
||||
```html title="index.html"
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@docsearch/css@^5.0.0-beta/dist/style.css"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@docsearch/js@^5.0.0-beta/dist/umd/index.js"></script>
|
||||
<script>
|
||||
window.docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'],
|
||||
askAi: {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Keyword search only
|
||||
|
||||
```html title="index.html"
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@docsearch/css@^5.0.0-beta/dist/style.css"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@docsearch/js@^5.0.0-beta/dist/umd/docsearch.js"></script>
|
||||
<script>
|
||||
window.docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'],
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Don't load both UMD bundles on the same page. The second bundle replaces the same `window.docsearch` global.
|
||||
|
||||
## Control the instance
|
||||
|
||||
`docsearch()` returns a `DocSearchInstance`:
|
||||
|
||||
```js title="app.js"
|
||||
const search = docsearch({
|
||||
container: '#docsearch',
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
indices: ['YOUR_INDEX_NAME'],
|
||||
askAi: 'YOUR_ASSISTANT_ID',
|
||||
});
|
||||
|
||||
search.open();
|
||||
search.close();
|
||||
search.openAskAi({ query: 'How do I configure search?' });
|
||||
|
||||
// Unmount DocSearch when your application removes this view.
|
||||
search.destroy();
|
||||
```
|
||||
|
||||
Use `openAskAi()` only with the default AI-capable entry.
|
||||
|
||||
## Next steps
|
||||
|
||||
- Review every option in the [JavaScript API reference](./api-reference).
|
||||
- Add multiple indices, facets, or result badges in the [React examples](../react/examples). The configuration options also apply to JavaScript.
|
||||
- Customize the design in [Styling](/docs/packages/css/styling).
|
||||
- Combine the modal and Sidepanel in [hybrid mode](/docs/hybrid-mode).
|
||||
343
packages/website/docs/packages/modal/api.mdx
Normal file
343
packages/website/docs/packages/modal/api.mdx
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
---
|
||||
title: Modal API reference
|
||||
description: API reference for @docsearch/modal v5 beta.
|
||||
---
|
||||
|
||||
## `DocSearchButton`
|
||||
|
||||
> `type: React.ComponentType<DocSearchButtonProps>`
|
||||
|
||||
Renders the search trigger. It runs your `onClick` handler, then opens the modal through the nearest `DocSearch` provider.
|
||||
|
||||
`DocSearchButtonProps` accepts standard React `button` props and the following prop. Configure `theme` and `keyboardShortcuts` on `DocSearch`, not on this component.
|
||||
|
||||
### `translations`
|
||||
|
||||
> `type: { buttonText?: string; buttonAriaLabel?: string }` | **optional**
|
||||
|
||||
Sets the visible and accessible button text. Both values default to English labels.
|
||||
|
||||
## `DocSearchModal`
|
||||
|
||||
> `type: React.ComponentType<DocSearchModalProps>`
|
||||
|
||||
Renders a keyword-search modal when its provider is active. It renders in `document.body` unless you set [`portalContainer`](#portalcontainer).
|
||||
|
||||
### Required props
|
||||
|
||||
#### `appId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Algolia application ID.
|
||||
|
||||
#### `apiKey`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Public API key with search permission.
|
||||
|
||||
#### `indices`
|
||||
|
||||
> `type: Array<string | DocSearchIndex>` | **required**
|
||||
|
||||
Indices to search. You can omit this prop only if you use the deprecated [`indexName`](#indexname).
|
||||
|
||||
### Search props
|
||||
|
||||
#### `indexName`
|
||||
|
||||
> `type: string` | **optional** | **deprecated**
|
||||
|
||||
Sets one index to search. Use [`indices`](#indices) instead.
|
||||
|
||||
#### `facets`
|
||||
|
||||
> `type: Array<{ key: string; label?: string }>` | **optional**
|
||||
|
||||
Adds filter controls that use facet values from the configured indices. Defaults to `[]`.
|
||||
|
||||
#### `placeholder`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Sets the search input placeholder. Defaults to `Search docs`. A translation can override this value.
|
||||
|
||||
#### `searchParameters`
|
||||
|
||||
> `type: SearchParamsObject` | **optional** | **deprecated**
|
||||
|
||||
Merges parameters into search requests. Set parameters on each [`indices`](#indices) entry instead.
|
||||
|
||||
#### `maxResultsPerGroup`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Limits the displayed hits in each result group.
|
||||
|
||||
#### `transformItems`
|
||||
|
||||
> `type: (items: DocSearchHit[]) => DocSearchHit[]` | **optional**
|
||||
|
||||
Changes hits before rendering. Defaults to the identity function.
|
||||
|
||||
#### `transformSearchClient`
|
||||
|
||||
> `type: (client: DocSearchTransformClient) => DocSearchTransformClient` | **optional**
|
||||
|
||||
Wraps or changes the search client. Defaults to the identity function.
|
||||
|
||||
#### `navigator`
|
||||
|
||||
> `type: AutocompleteOptions['navigator']` | **optional**
|
||||
|
||||
Controls link navigation.
|
||||
|
||||
#### `insights`
|
||||
|
||||
> `type: AutocompleteOptions['insights']` | **optional**
|
||||
|
||||
Configures Algolia Insights. Defaults to `false`.
|
||||
|
||||
#### `resultBadgeKey`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Reads a hit property and displays it as a result badge. Include the property in `attributesToRetrieve`.
|
||||
|
||||
### Rendering and behavior props
|
||||
|
||||
#### `initialQuery`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Prefills the search input. This value takes precedence over the provider value and defaults to `''` if neither value is set.
|
||||
|
||||
#### `portalContainer`
|
||||
|
||||
> `type: DocumentFragment | Element` | **optional**
|
||||
|
||||
Receives the modal portal. Defaults to `document.body`.
|
||||
|
||||
#### `hitComponent`
|
||||
|
||||
> `type: (props, helpers?) => JSX.Element` | **optional**
|
||||
|
||||
Renders an individual result. Defaults to the built-in hit component.
|
||||
|
||||
#### `resultsFooterComponent`
|
||||
|
||||
> `type: (props, helpers?) => JSX.Element | null` | **optional**
|
||||
|
||||
Renders content below the results. Defaults to `null`.
|
||||
|
||||
#### `disableUserPersonalization`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Disables storing and using recent and favorite searches. Defaults to `false`.
|
||||
|
||||
#### `recentSearchesLimit`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Limits saved and displayed recent searches. Defaults to `7`.
|
||||
|
||||
#### `recentSearchesWithFavoritesLimit`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Limits recent searches when favorites exist. Defaults to `4`.
|
||||
|
||||
#### `getMissingResultsUrl`
|
||||
|
||||
> `type: ({ query }: { query: string }) => string` | **optional**
|
||||
|
||||
Builds the missing-results report URL.
|
||||
|
||||
#### `translations`
|
||||
|
||||
> `type: ModalTranslations` | **optional**
|
||||
|
||||
Supplies partial strings for `searchBox`, `footer`, `facets`, `errorScreen`, `startScreen`, `noResultsScreen`, and `resultsScreen`. Defaults to `{}`.
|
||||
|
||||
`DocSearchModal` receives `theme`, `keyboardShortcuts`, and close behavior from the provider. It doesn't accept these props directly.
|
||||
|
||||
### `DocSearchIndex`
|
||||
|
||||
#### `name`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Index to search.
|
||||
|
||||
#### `searchParameters`
|
||||
|
||||
> `type: SearchParamsObject` | **optional**
|
||||
|
||||
Sets search parameters for this index.
|
||||
|
||||
## `DocSearchAskAiModal`
|
||||
|
||||
> `type: React.ComponentType<DocSearchAskAiModalProps>`
|
||||
|
||||
Renders the Agent Studio-enabled modal. It accepts every `DocSearchModal` prop, requires [`askAi`](#askai), and adds Agent Studio translations.
|
||||
|
||||
### `askAi`
|
||||
|
||||
> `type: string | DocSearchAskAi` | **required**
|
||||
|
||||
Sets the Agent Studio agent ID or the complete Agent Studio configuration.
|
||||
|
||||
### `interceptAskAiEvent`
|
||||
|
||||
> `type: (message: InitialAskAiMessage) => boolean | void` | **optional**
|
||||
|
||||
Runs before the default Ask AI action. Return `true` to prevent the action.
|
||||
|
||||
### `translations`
|
||||
|
||||
> `type: DocSearchAskAiModalTranslations` | **optional**
|
||||
|
||||
Adds `askAiScreen`, `newConversation`, and Agent Studio search-box strings to the search translations. Defaults to `{}`.
|
||||
|
||||
The component receives its active state and close behavior from `DocSearch`. It doesn't accept `isAskAiActive`, `isHybridModeSupported`, `onAskAiToggle`, `onClose`, `theme`, or `keyboardShortcuts`. See [Hybrid Mode][1] for cross-view behavior.
|
||||
|
||||
## `DocSearchAskAi`
|
||||
|
||||
The object form of [`askAi`](#askai). It accepts the following fields.
|
||||
|
||||
### `assistantId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Agent Studio agent ID.
|
||||
|
||||
### `appId`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Overrides the root `appId` for Agent Studio.
|
||||
|
||||
### `apiKey`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Overrides the root `apiKey` for Agent Studio.
|
||||
|
||||
### `indexName`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Sets the index for conversation storage and Agent Studio requests. Defaults to the first root index.
|
||||
|
||||
### `suggestedQuestions`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Shows configured suggested questions on the new-conversation screen. Defaults to `false`.
|
||||
|
||||
### `searchParameters`
|
||||
|
||||
> `type: Record<string, AgentStudioIndexSearchParameters>` | **optional**
|
||||
|
||||
Sends search parameters keyed by index name. `AgentStudioIndexSearchParameters` supports `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct`. It doesn't support `facetFilters`.
|
||||
|
||||
### `indices`
|
||||
|
||||
> `type: AgentStudioIndices[]` | **optional**
|
||||
|
||||
Defines dynamic indices available to Agent Studio search tools.
|
||||
|
||||
### `tools`
|
||||
|
||||
> `type: Record<string, ToolDefinition>` | **optional**
|
||||
|
||||
Handles and renders custom Agent Studio tools. Defaults to `{}`.
|
||||
|
||||
### `memory`
|
||||
|
||||
> `type: { enabled?: boolean; userToken?: string }` | **optional**
|
||||
|
||||
Displays memory tool activity and sends a secure user token.
|
||||
|
||||
### `promptSuggestions`
|
||||
|
||||
> `type: { indexName: string; hitsPerPage?: number }` | **optional**
|
||||
|
||||
Configures prompt suggestions in keyword search. `hitsPerPage` defaults to `3`.
|
||||
|
||||
## Dynamic indices
|
||||
|
||||
Each dynamic index accepts the following fields:
|
||||
|
||||
### `index`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Index available to Agent Studio search tools.
|
||||
|
||||
### `description`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Describes the index contents to Agent Studio.
|
||||
|
||||
### `enhancedDescription`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Provides more context about the index contents.
|
||||
|
||||
### `searchParameters`
|
||||
|
||||
> `type: SearchParamsObject` | **optional**
|
||||
|
||||
Sets search parameters for the dynamic index.
|
||||
|
||||
### `searchControls`
|
||||
|
||||
> `type: AgentStudioSearchControls` | **optional**
|
||||
|
||||
Configures `query`, `hits_per_page`, `page`, `attributesToRetrieve`, `responseFields`, `facets`, and `custom` controls. Text, number, and string-array controls use an `exposed` flag and can define defaults or constraints.
|
||||
|
||||
## Custom tools
|
||||
|
||||
Each `ToolDefinition` accepts the following fields:
|
||||
|
||||
### `render`
|
||||
|
||||
> `type: (params: { message: { input: unknown; output: unknown } }) => string` | **required**
|
||||
|
||||
Renders the tool input and output.
|
||||
|
||||
### `onToolCall`
|
||||
|
||||
> `type: (params: ToolCallParameters) => Promise<void> | void` | **optional**
|
||||
|
||||
Handles a client-side tool call. `ToolCallParameters` includes `input`, `addToolOutput`, `toolCallId`, `toolName`, and the optional `dynamic` flag.
|
||||
|
||||
### `translations`
|
||||
|
||||
> `type: { callingToolText?: string }` | **optional**
|
||||
|
||||
Overrides the text shown while the tool runs.
|
||||
|
||||
When `onToolCall` handles a client-side tool, call `addToolOutput` with the result. Define the tools object outside your component or memoize it to keep its identity stable.
|
||||
|
||||
## Memory
|
||||
|
||||
Set `memory.enabled` to render Agent Studio memory tool calls. Set `memory.userToken` to send the JSON Web Token (JWT) as the `x-algolia-secure-user-token` request header.
|
||||
|
||||
Generate the JWT on your server. Don't put signing secrets in browser code.
|
||||
|
||||
## Exports
|
||||
|
||||
| Import path | Value exports | Type exports |
|
||||
| --- | --- | --- |
|
||||
| `@docsearch/modal` | `DocSearchButton`, `DocSearchModal`, `DocSearchAskAiModal` | `DocSearchButtonProps`, `DocSearchModalProps`, `DocSearchAskAiModalProps` |
|
||||
| `@docsearch/modal/button` | `DocSearchButton` | `DocSearchButtonProps` |
|
||||
| `@docsearch/modal/modal` | `DocSearchModal` | `DocSearchModalProps` |
|
||||
| `@docsearch/modal/askai` | `DocSearchAskAiModal` | `DocSearchAskAiModalProps` |
|
||||
|
||||
[1]: /docs/hybrid-mode
|
||||
139
packages/website/docs/packages/modal/overview.mdx
Normal file
139
packages/website/docs/packages/modal/overview.mdx
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
---
|
||||
title: Modal package
|
||||
description: Add the composable DocSearch modal to a React application.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
`@docsearch/modal` provides the DocSearch search button, keyword-search modal, and Agent Studio-enabled modal for React. Render these components inside the `DocSearch` provider from `@docsearch/core`.
|
||||
|
||||
:::info v5 beta
|
||||
|
||||
These instructions use the `^5.0.0-beta` range. Use the same range for every DocSearch package.
|
||||
|
||||
:::
|
||||
|
||||
For the complete component workflow, see the [Composable API guide](/docs/composable-api).
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/core@^5.0.0-beta @docsearch/modal@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Add keyword search
|
||||
|
||||
Import the main stylesheet once, near your application entry point.
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { DocSearchButton, DocSearchModal } from '@docsearch/modal';
|
||||
|
||||
import '@docsearch/css';
|
||||
|
||||
export function Search() {
|
||||
return (
|
||||
<DocSearch>
|
||||
<DocSearchButton />
|
||||
<DocSearchModal
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Use a search-only API key. Don't expose an Algolia Admin API key in browser code.
|
||||
|
||||
## Add Agent Studio
|
||||
|
||||
In v5, Ask AI uses Agent Studio. Pass an Agent Studio agent ID through `askAi.assistantId`. There's no `agentStudio` boolean in the v5 API.
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { DocSearchAskAiModal, DocSearchButton } from '@docsearch/modal';
|
||||
|
||||
import '@docsearch/css';
|
||||
|
||||
export function Search() {
|
||||
return (
|
||||
<DocSearch>
|
||||
<DocSearchButton />
|
||||
<DocSearchAskAiModal
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
}}
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The `askAi` object also supports custom tools, conversation memory, and dynamic indices. See the [`DocSearchAskAiModal` API](./api#docsearchaskaimodal).
|
||||
|
||||
## Set provider options
|
||||
|
||||
The modal package reads theme, keyboard shortcut, and lifecycle state from `DocSearch`.
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
<DocSearch
|
||||
theme="dark"
|
||||
keyboardShortcuts={{ 'Ctrl/Cmd+K': true, '/': false }}
|
||||
onOpen={() => track('search_opened')}
|
||||
onClose={() => track('search_closed')}
|
||||
>
|
||||
<DocSearchButton />
|
||||
<DocSearchModal
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
/>
|
||||
</DocSearch>
|
||||
```
|
||||
|
||||
See the [`@docsearch/core` API](../core/api) for provider props and refs.
|
||||
|
||||
## Load smaller entry points
|
||||
|
||||
The package exposes component-specific entry points for code splitting:
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
import { DocSearchAskAiModal } from '@docsearch/modal/askai';
|
||||
import { DocSearchButton } from '@docsearch/modal/button';
|
||||
import { DocSearchModal } from '@docsearch/modal/modal';
|
||||
```
|
||||
|
||||
See the [modal API reference](./api) for all exports and props.
|
||||
84
packages/website/docs/packages/overview.mdx
Normal file
84
packages/website/docs/packages/overview.mdx
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
title: DocSearch packages
|
||||
description: Choose the DocSearch v5 package that matches your framework and search experience.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
DocSearch v5 provides keyword search, AI answers powered by Agent Studio, and lower-level packages for custom integrations.
|
||||
|
||||
Start with `@docsearch/js` for a browser integration or `@docsearch/react` for a React application. Both packages support the same keyword search options.
|
||||
|
||||
## Choose a package
|
||||
|
||||
| Package | Use it for |
|
||||
| --- | --- |
|
||||
| [`@docsearch/js`](./js/getting-started) | JavaScript sites, including sites that don't use React. The default entry includes keyword search and Ask AI. |
|
||||
| [`@docsearch/react`](./react/getting-started) | React applications. Choose `DocSearch` for keyword search or `DocSearchAI` for keyword search and Ask AI. |
|
||||
| [`@docsearch/css`](./css/styling) | The complete button and modal stylesheet. See [Styling](/docs/packages/css/styling). |
|
||||
| [`@docsearch/core`](./core/overview) | Shared state, keyboard handling, and the provider for the [Composable API](/docs/composable-api). |
|
||||
| [`@docsearch/modal`](./modal/overview) | Search button, keyword modal, and Ask AI modal components for the Composable API. |
|
||||
| [`@docsearch/sidepanel`](./sidepanel/getting-started) | React Sidepanel components for persistent AI conversations. |
|
||||
| [`@docsearch/sidepanel-js`](./sidepanel-js/getting-started) | JavaScript wrapper for the AI Sidepanel. |
|
||||
|
||||
If your Docusaurus site uses the DocSearch adapter, follow the [Docusaurus adapter guide](/docs/packages/docusaurus-adapter/getting-started).
|
||||
|
||||
## Choose a search experience
|
||||
|
||||
### Keyword search
|
||||
|
||||
Use keyword search when you only need results from Algolia indices. It has no Ask AI runtime.
|
||||
|
||||
- JavaScript: import `@docsearch/js/docsearch`.
|
||||
- React: render `DocSearch` from `@docsearch/react`.
|
||||
|
||||
### Keyword search and Ask AI
|
||||
|
||||
Use the AI-capable integration when you have an Agent Studio assistant.
|
||||
|
||||
- JavaScript: use the default `@docsearch/js` export.
|
||||
- React: render `DocSearchAI` from `@docsearch/react`.
|
||||
|
||||
Create and configure the assistant before adding it to DocSearch. See [Get started with Agent Studio](/docs/agent-studio/getting-started).
|
||||
|
||||
### Composable and hybrid experiences
|
||||
|
||||
Use the [Composable API](/docs/composable-api) to control the provider, button, and modal separately. Use [hybrid mode](/docs/hybrid-mode) to combine the modal with the AI Sidepanel.
|
||||
|
||||
## Beta version
|
||||
|
||||
Install the v5 beta with the caret range:
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
For an existing v4 integration, read [Migrate from v4](/docs/migrating-from-v4) and [v5 breaking changes](/docs/v5-breaking-changes).
|
||||
418
packages/website/docs/packages/react/api-reference.mdx
Normal file
418
packages/website/docs/packages/react/api-reference.mdx
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
---
|
||||
title: React API reference
|
||||
description: Reference for the components, props, ref, and exports in @docsearch/react 5.0.0-beta.0.
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
### `DocSearch`
|
||||
|
||||
> `type: React.ForwardRefExoticComponent<DocSearchProps>`
|
||||
|
||||
Renders keyword search. It accepts the [common props](#common-props) and forwards a [`DocSearchRef`](#docsearchref).
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
```
|
||||
|
||||
### `DocSearchAI`
|
||||
|
||||
> `type: React.ForwardRefExoticComponent<DocSearchAIProps>`
|
||||
|
||||
Renders keyword search and Ask AI. It accepts the [common props](#common-props), requires [`askAi`](#askai), and forwards a [`DocSearchRef`](#docsearchref).
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
import { DocSearchAI } from '@docsearch/react';
|
||||
```
|
||||
|
||||
## Common props
|
||||
|
||||
### `appId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Algolia application ID.
|
||||
|
||||
### `apiKey`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Public API key with search permission.
|
||||
|
||||
### `indices`
|
||||
|
||||
> `type: Array<string | DocSearchIndex>` | **optional**
|
||||
|
||||
Indices to search in display order. Provide `indices` or the deprecated [`indexName`](#indexname).
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={[
|
||||
{
|
||||
name: 'docs_en',
|
||||
searchParameters: {
|
||||
facetFilters: ['version:v5'],
|
||||
},
|
||||
},
|
||||
'docs_fr',
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### `indexName`
|
||||
|
||||
> `type: string` | **optional** | **deprecated**
|
||||
|
||||
Single index to search. Use [`indices`](#indices) instead. If you pass both options, DocSearch queries `indexName` first.
|
||||
|
||||
### `facets`
|
||||
|
||||
> `type: DocSearchFacet[]` | **optional**
|
||||
|
||||
Facet controls populated from the configured indices. Defaults to `[]`.
|
||||
|
||||
DocSearch supports up to five facets. It ignores empty and duplicate keys, merges sorted values from all configured indices, and hides facets with no values. Configure each attribute for faceting in the Algolia index.
|
||||
|
||||
### `theme`
|
||||
|
||||
> `type: 'light' | 'dark'` | **optional**
|
||||
|
||||
Theme written to `document.documentElement.dataset.theme`. By default, DocSearch doesn't change the current theme.
|
||||
|
||||
### `placeholder`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Search input placeholder. The active experience supplies the default.
|
||||
|
||||
### `searchParameters`
|
||||
|
||||
> `type: SearchParamsObject` | **optional** | **deprecated**
|
||||
|
||||
Search parameters for `indexName`. Set `searchParameters` on an [`indices`](#indices) item instead.
|
||||
|
||||
### `maxResultsPerGroup`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Maximum results to show in each result group.
|
||||
|
||||
### `transformItems`
|
||||
|
||||
> `type: (items: DocSearchHit[]) => DocSearchHit[]` | **optional**
|
||||
|
||||
Transforms hits before DocSearch groups and renders them. Defaults to the identity function.
|
||||
|
||||
### `hitComponent`
|
||||
|
||||
> `type: React.ComponentType<HitProps>` | **optional**
|
||||
|
||||
Renders one result link. The default component renders the standard result content.
|
||||
|
||||
```tsx title="Hit.tsx"
|
||||
function Hit({ hit, children }: HitProps): JSX.Element {
|
||||
return (
|
||||
<a href={hit.url} data-result-type={hit.type}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Preserve `children` to retain the default hit content. `hit` is an `InternalDocSearchHit | StoredDocSearchHit`.
|
||||
|
||||
### `resultsFooterComponent`
|
||||
|
||||
> `type: React.ComponentType<ResultsFooterProps>` | **optional**
|
||||
|
||||
Renders below result collections. It receives the current Autocomplete state. By default, DocSearch doesn't render a footer.
|
||||
|
||||
### `transformSearchClient`
|
||||
|
||||
> `type: (client: SearchClient) => SearchClient` | **optional**
|
||||
|
||||
Wraps or replaces the Algolia search client. Defaults to the identity function.
|
||||
|
||||
### `disableUserPersonalization`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Disables recent searches, favorites, and stored AI conversations. Defaults to `false`.
|
||||
|
||||
### `initialQuery`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Query to place in the input when the modal opens. Defaults to an empty string.
|
||||
|
||||
### `navigator`
|
||||
|
||||
> `type: AutocompleteOptions['navigator']` | **optional**
|
||||
|
||||
Autocomplete navigation implementation. Defaults to the Autocomplete navigator.
|
||||
|
||||
### `translations`
|
||||
|
||||
> `type: DocSearchTranslations` | **optional**
|
||||
|
||||
Overrides button and modal text. Defaults to English strings.
|
||||
|
||||
`DocSearchTranslations` groups overrides under `button` and `modal`. Modal groups include `searchBox`, `footer`, `facets`, `errorScreen`, `startScreen`, `noResultsScreen`, and `resultsScreen`. `DocSearchAI` also supports `askAiScreen` and `newConversation`.
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
<DocSearch
|
||||
translations={{
|
||||
button: {
|
||||
buttonText: 'Search API docs',
|
||||
buttonAriaLabel: 'Search API docs',
|
||||
},
|
||||
modal: {
|
||||
searchBox: {
|
||||
placeholderText: 'Search API docs',
|
||||
},
|
||||
facets: {
|
||||
clearAllLabel: 'Reset filters',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### `getMissingResultsUrl`
|
||||
|
||||
> `type: ({ query }: { query: string }) => string` | **optional**
|
||||
|
||||
Builds the no-results report URL.
|
||||
|
||||
### `insights`
|
||||
|
||||
> `type: AutocompleteOptions['insights']` | **optional**
|
||||
|
||||
Configures Algolia Insights. Defaults to `false`.
|
||||
|
||||
### `portalContainer`
|
||||
|
||||
> `type: DocumentFragment | Element` | **optional**
|
||||
|
||||
Element that receives the modal portal. Defaults to `document.body`.
|
||||
|
||||
### `recentSearchesLimit`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Maximum recent searches when the user has no favorites. Defaults to `7`.
|
||||
|
||||
### `recentSearchesWithFavoritesLimit`
|
||||
|
||||
> `type: number` | **optional**
|
||||
|
||||
Maximum recent searches when the user has favorites. Defaults to `4`.
|
||||
|
||||
### `keyboardShortcuts`
|
||||
|
||||
> `type: DocSearchModalShortcuts` | **optional**
|
||||
|
||||
Enables `Ctrl/Cmd+K` and `/` to open the modal. Both shortcuts are enabled by default. Escape always closes the modal.
|
||||
|
||||
### `resultBadgeKey`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Property path for a value on each hit. Paths support dot and array-index notation, such as `hierarchy.lvl1`, `tags[2]`, and `tags.2`.
|
||||
|
||||
Add custom properties to `attributesToRetrieve`:
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={[
|
||||
{
|
||||
name: 'docs',
|
||||
searchParameters: {
|
||||
attributesToRetrieve: ['hierarchy.lvl0', 'content', 'url', 'version'],
|
||||
},
|
||||
},
|
||||
]}
|
||||
resultBadgeKey="version"
|
||||
/>
|
||||
```
|
||||
|
||||
Primitive values render as text and arrays of primitives render as a comma-separated list. Set `translations.modal.resultsScreen.resultBadgeLabelText` to replace the default screen-reader label, `Category`.
|
||||
|
||||
## Ask AI props
|
||||
|
||||
### `askAi`
|
||||
|
||||
> `type: string | DocSearchAskAi` | **required**
|
||||
|
||||
Agent Studio assistant ID or configuration. This prop is required by `DocSearchAI`.
|
||||
|
||||
#### `assistantId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Agent Studio assistant ID.
|
||||
|
||||
#### `appId`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Application ID for Agent Studio. Defaults to the root `appId`.
|
||||
|
||||
#### `apiKey`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
API key for Agent Studio. Defaults to the root `apiKey`.
|
||||
|
||||
#### `indexName`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Index for Agent Studio. Defaults to the first normalized keyword index.
|
||||
|
||||
#### `suggestedQuestions`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Shows published assistant questions on the new-conversation screen. Defaults to `false`.
|
||||
|
||||
#### `searchParameters`
|
||||
|
||||
> `type: AgentStudioSearchParameters` | **optional**
|
||||
|
||||
Search parameters keyed by index name. Each value supports `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct`.
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
<DocSearchAI
|
||||
askAi={{
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
docs: {
|
||||
filters: 'language:en',
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
distinct: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
#### `indices`
|
||||
|
||||
> `type: AgentStudioIndices[]` | **optional**
|
||||
|
||||
Dynamic indices for Agent Studio. Each item requires `index` and `description`. It can also define `enhancedDescription`, `searchParameters`, and `searchControls`.
|
||||
|
||||
`searchControls` supports `query`, `hits_per_page`, `page`, `attributesToRetrieve`, `responseFields`, `facets`, and `custom`.
|
||||
|
||||
#### `tools`
|
||||
|
||||
> `type: ToolCalls` | **optional**
|
||||
|
||||
Custom Agent Studio tool renderers and handlers. Defaults to `{}`. Each `ToolDefinition` requires `render({ message })`; `onToolCall` can handle client-side work and call `addToolOutput({ output })`.
|
||||
|
||||
#### `memory`
|
||||
|
||||
> `type: Memory` | **optional**
|
||||
|
||||
Agent Studio memory display and user token. Memory is disabled by default. `Memory.userToken` is the user JWT sent to Agent Studio.
|
||||
|
||||
#### `promptSuggestions`
|
||||
|
||||
> `type: PromptSuggestions` | **optional**
|
||||
|
||||
Prompt suggestions displayed with keyword results. `indexName` selects an index whose records contain a `prompt` attribute. `hitsPerPage` defaults to `3`.
|
||||
|
||||
### `interceptAskAiEvent`
|
||||
|
||||
> `type: (initialMessage: InitialAskAiMessage) => boolean | void` | **optional**
|
||||
|
||||
Runs before DocSearch starts an Ask AI request. Return `true` to prevent the modal from toggling or sending the message. Use it to route Ask AI to another view.
|
||||
|
||||
## `DocSearchRef`
|
||||
|
||||
Ref interface for `DocSearch` and `DocSearchAI`.
|
||||
|
||||
### `isReady`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether the provider is mounted.
|
||||
|
||||
### `isOpen`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether the modal is open.
|
||||
|
||||
### `isSidepanelOpen`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether the registered Sidepanel is open.
|
||||
|
||||
### `isSidepanelSupported`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether a Sidepanel is registered and the viewport isn't mobile.
|
||||
|
||||
### `open`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Opens keyword search.
|
||||
|
||||
### `close`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Closes the modal.
|
||||
|
||||
### `openAskAi`
|
||||
|
||||
> `type: (initialMessage?: InitialAskAiMessage) => void`
|
||||
|
||||
Opens Ask AI in the Sidepanel when hybrid mode is available, or in the modal otherwise.
|
||||
|
||||
### `openSidepanel`
|
||||
|
||||
> `type: (initialMessage?: InitialAskAiMessage) => void`
|
||||
|
||||
Opens a registered Sidepanel. Does nothing when no Sidepanel is registered.
|
||||
|
||||
`InitialAskAiMessage` requires `query` and can include `messageId` or `suggestedQuestionId`.
|
||||
|
||||
## Compatibility and deprecations
|
||||
|
||||
- `react`, `react-dom`, and `@types/react` support versions `>=16.8.0 <20.0.0`.
|
||||
- `search-insights` supports versions `>=1 <3` and is optional.
|
||||
- The browser build targets ES2017.
|
||||
- `indexName` and root `searchParameters` remain supported but are deprecated. Move to `indices`.
|
||||
- `UseDocSearchKeyboardEventsProps.onInput` and `searchButtonRef` remain accepted for compatibility but are deprecated.
|
||||
- `DocSearch` is keyword-only in v5. Use `DocSearchAI` for Ask AI.
|
||||
|
||||
## Package exports
|
||||
|
||||
The root package exports `DocSearch`, `DocSearchAI`, `DocSearchAskAiModal`, `DocSearchButton`, `DocSearchModal`, `useDocSearchKeyboardEvents`, `version`, and public types.
|
||||
|
||||
| Import | Export |
|
||||
| --- | --- |
|
||||
| `@docsearch/react/askaiModal` | `DocSearchAskAiModal` |
|
||||
| `@docsearch/react/button` | `DocSearchButton` |
|
||||
| `@docsearch/react/docsearchAi` | `DocSearchAI` and AI types |
|
||||
| `@docsearch/react/modal` | `DocSearchModal` |
|
||||
| `@docsearch/react/sidepanel` | `DocSearchSidepanel` and Sidepanel components |
|
||||
| `@docsearch/react/useDocSearchKeyboardEvents` | `useDocSearchKeyboardEvents` |
|
||||
| `@docsearch/react/useTheme` | `useTheme` |
|
||||
| `@docsearch/react/version` | `version` |
|
||||
|
||||
For provider and modal composition, see [Composable API](/docs/composable-api). For modal and Sidepanel composition, see [hybrid mode](/docs/hybrid-mode).
|
||||
|
||||
## Style exports
|
||||
|
||||
Import `@docsearch/css` for the complete modal stylesheet. The React package also exports `@docsearch/react/style` and split `style/variables`, `style/button`, `style/modal`, `style/askai`, and `style/sidepanel` entries for component-level builds. See [Styling](/docs/packages/css/styling).
|
||||
271
packages/website/docs/packages/react/examples.mdx
Normal file
271
packages/website/docs/packages/react/examples.mdx
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
---
|
||||
title: React examples
|
||||
description: Configure DocSearch v5 indices, facets, result badges, templates, Agent Studio, and refs.
|
||||
---
|
||||
|
||||
These examples build on the [React getting started guide](./getting-started). Replace all placeholder credentials before using them.
|
||||
|
||||
## Search multiple indices
|
||||
|
||||
Pass strings or per-index objects. Results follow the array order.
|
||||
|
||||
```jsx title="MultiIndexSearch.jsx"
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={[
|
||||
{
|
||||
name: 'product_docs',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
},
|
||||
},
|
||||
'api_reference',
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Add facet filters
|
||||
|
||||
Configure the attributes for faceting in Algolia, then expose up to five controls:
|
||||
|
||||
```jsx title="FacetedSearch.jsx"
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['product_docs']}
|
||||
facets={[
|
||||
{ key: 'language', label: 'Language' },
|
||||
{ key: 'version', label: 'Version' },
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
DocSearch loads available values from all configured indices and appends selected values to each index's `facetFilters`.
|
||||
|
||||
## Show a result badge
|
||||
|
||||
Retrieve the property and pass its path to `resultBadgeKey`:
|
||||
|
||||
```jsx title="ResultBadgeSearch.jsx"
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={[
|
||||
{
|
||||
name: 'product_docs',
|
||||
searchParameters: {
|
||||
attributesToRetrieve: [
|
||||
'hierarchy.lvl0',
|
||||
'hierarchy.lvl1',
|
||||
'hierarchy.lvl2',
|
||||
'hierarchy.lvl3',
|
||||
'hierarchy.lvl4',
|
||||
'hierarchy.lvl5',
|
||||
'hierarchy.lvl6',
|
||||
'content',
|
||||
'type',
|
||||
'url',
|
||||
'version',
|
||||
],
|
||||
},
|
||||
},
|
||||
]}
|
||||
resultBadgeKey="version"
|
||||
translations={{
|
||||
modal: {
|
||||
resultsScreen: {
|
||||
resultBadgeLabelText: 'Version',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Nested paths such as `hierarchy.lvl1` and `tags[2]` also work.
|
||||
|
||||
## Customize result links
|
||||
|
||||
Use `hitComponent` to change the link while preserving DocSearch's result content:
|
||||
|
||||
```jsx title="SearchHit.jsx"
|
||||
function SearchHit({ hit, children }) {
|
||||
return (
|
||||
<a
|
||||
href={hit.url}
|
||||
data-index={hit.__autocomplete_indexName}
|
||||
onClick={() => console.info('Opened result', hit.objectID)}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['product_docs']}
|
||||
hitComponent={SearchHit}
|
||||
/>;
|
||||
```
|
||||
|
||||
## Add a results footer
|
||||
|
||||
Use the Autocomplete state to show query-level information:
|
||||
|
||||
```jsx title="ResultsFooter.jsx"
|
||||
function ResultsFooter({ state }) {
|
||||
const count = state.context.nbHits ?? 0;
|
||||
|
||||
return <p>{count} matching records</p>;
|
||||
}
|
||||
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['product_docs']}
|
||||
resultsFooterComponent={ResultsFooter}
|
||||
/>;
|
||||
```
|
||||
|
||||
## Transform results
|
||||
|
||||
Return the hits in the order you want DocSearch to group and render them:
|
||||
|
||||
```jsx title="TransformedSearch.jsx"
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['product_docs']}
|
||||
transformItems={(items) =>
|
||||
items.filter((item) => item.hierarchy.lvl0 !== 'Archived')
|
||||
}
|
||||
/>
|
||||
```
|
||||
|
||||
## Open search from another control
|
||||
|
||||
```tsx title="SearchTrigger.tsx"
|
||||
import { useRef } from 'react';
|
||||
import { DocSearch, type DocSearchRef } from '@docsearch/react';
|
||||
|
||||
function Search() {
|
||||
const searchRef = useRef<DocSearchRef>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button type="button" onClick={() => searchRef.current?.open()}>
|
||||
Search documentation
|
||||
</button>
|
||||
<DocSearch
|
||||
ref={searchRef}
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['product_docs']}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Add Agent Studio search parameters
|
||||
|
||||
Use `DocSearchAI`. Key Ask AI search parameters by index name:
|
||||
|
||||
```jsx title="AskAiSearch.jsx"
|
||||
<DocSearchAI
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['product_docs']}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
searchParameters: {
|
||||
product_docs: {
|
||||
filters: 'language:en AND version:v5',
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
restrictSearchableAttributes: ['title', 'content'],
|
||||
distinct: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
See [Get started with Agent Studio](/docs/agent-studio/getting-started) before configuring the component.
|
||||
|
||||
## Add dynamic Agent Studio indices
|
||||
|
||||
Describe each index so the agent can select the right source:
|
||||
|
||||
```jsx title="DynamicIndicesSearch.jsx"
|
||||
const askAi = {
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
indices: [
|
||||
{
|
||||
index: 'product_docs',
|
||||
description: 'Product guides and conceptual documentation',
|
||||
searchControls: {
|
||||
hits_per_page: {
|
||||
exposed: true,
|
||||
default: 7,
|
||||
constraint: { min: 1, max: 10 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
index: 'api_reference',
|
||||
description: 'API symbols, options, and return values',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
<DocSearchAI
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['product_docs', 'api_reference']}
|
||||
askAi={askAi}
|
||||
/>;
|
||||
```
|
||||
|
||||
Define stable objects outside the component, or memoize them, when they contain `tools`.
|
||||
|
||||
## Add prompt suggestions
|
||||
|
||||
Show matching prompts next to keyword results:
|
||||
|
||||
```jsx title="PromptSuggestionsSearch.jsx"
|
||||
<DocSearchAI
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['product_docs']}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
suggestedQuestions: true,
|
||||
promptSuggestions: {
|
||||
indexName: 'docsearch_prompt_suggestions',
|
||||
hitsPerPage: 3,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
`suggestedQuestions` shows published assistant questions on a new conversation. `promptSuggestions` searches your prompt index while the user types a keyword query.
|
||||
|
||||
## Route Ask AI to another view
|
||||
|
||||
Return `true` from `interceptAskAiEvent` after handling the message:
|
||||
|
||||
```jsx title="InterceptAskAi.jsx"
|
||||
<DocSearchAI
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['product_docs']}
|
||||
askAi="YOUR_ASSISTANT_ID"
|
||||
interceptAskAiEvent={(initialMessage) => {
|
||||
openCustomAssistant(initialMessage);
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
For the supported modal and Sidepanel integration, use [hybrid mode](/docs/hybrid-mode).
|
||||
140
packages/website/docs/packages/react/getting-started.mdx
Normal file
140
packages/website/docs/packages/react/getting-started.mdx
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
---
|
||||
title: React package
|
||||
description: Add DocSearch v5 keyword search or Agent Studio answers to a React application.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
`@docsearch/react` exports separate components for keyword search and Ask AI. Choose the component that matches your application.
|
||||
|
||||
## Before you start
|
||||
|
||||
Collect your Algolia application ID, Search API key, and index name. To add Ask AI, create an assistant in [Agent Studio](/docs/agent-studio/getting-started) and copy its assistant ID.
|
||||
|
||||
## Install the packages
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/react@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The package supports React and React DOM versions from 16.8 through 19.
|
||||
|
||||
## Add keyword search
|
||||
|
||||
Render `DocSearch` when your site only needs keyword results:
|
||||
|
||||
```jsx title="Search.jsx"
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
|
||||
import '@docsearch/css';
|
||||
|
||||
export function Search() {
|
||||
return (
|
||||
<DocSearch
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`DocSearch` doesn't include the Ask AI experience.
|
||||
|
||||
## Add keyword search and Ask AI
|
||||
|
||||
Render `DocSearchAI` and pass the Agent Studio assistant:
|
||||
|
||||
```jsx title="Search.jsx"
|
||||
import { DocSearchAI } from '@docsearch/react';
|
||||
|
||||
import '@docsearch/css';
|
||||
|
||||
export function Search() {
|
||||
return (
|
||||
<DocSearchAI
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
askAi={{
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`askAi` also accepts the assistant ID as a string. Use the object form for Agent Studio search parameters, dynamic indices, tools, memory, and prompt suggestions.
|
||||
|
||||
## Control DocSearch with a ref
|
||||
|
||||
Both components forward a `DocSearchRef`:
|
||||
|
||||
```tsx title="Search.tsx"
|
||||
import { useRef } from 'react';
|
||||
import { DocSearchAI, type DocSearchRef } from '@docsearch/react';
|
||||
|
||||
export function Search() {
|
||||
const searchRef = useRef<DocSearchRef>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
searchRef.current?.openAskAi({
|
||||
query: 'How do I configure search?',
|
||||
})
|
||||
}
|
||||
>
|
||||
Ask about setup
|
||||
</button>
|
||||
<DocSearchAI
|
||||
ref={searchRef}
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
askAi="YOUR_ASSISTANT_ID"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Use `open()` and `close()` for keyword search. Sidepanel methods become useful when you register a Sidepanel through [hybrid mode](/docs/hybrid-mode).
|
||||
|
||||
## Next steps
|
||||
|
||||
- Review every component option in the [React API reference](./api-reference).
|
||||
- Start from the [React examples](./examples) for indices, facets, badges, templates, and refs.
|
||||
- Use the [Composable API](/docs/composable-api) for separate provider, button, and modal components.
|
||||
- Customize the design in [Styling](/docs/packages/css/styling).
|
||||
- For Docusaurus, follow the [Docusaurus adapter guide](/docs/packages/docusaurus-adapter/getting-started).
|
||||
230
packages/website/docs/packages/sidepanel-js/api.mdx
Normal file
230
packages/website/docs/packages/sidepanel-js/api.mdx
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
---
|
||||
title: Sidepanel JS API reference
|
||||
description: API reference for @docsearch/sidepanel-js v5 beta.
|
||||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## `sidepanel`
|
||||
|
||||
```ts
|
||||
function sidepanel(props: SidepanelProps): SidepanelInstance;
|
||||
```
|
||||
|
||||
The function resolves `container`, mounts the component, and returns immediately. Use `onReady` or `instance.isReady` to detect the mounted state.
|
||||
|
||||
### Mounting props
|
||||
|
||||
#### `container`
|
||||
|
||||
> `type: HTMLElement | string` | **required**
|
||||
|
||||
Element or CSS selector that receives the Sidepanel application.
|
||||
|
||||
#### `environment`
|
||||
|
||||
> `type: typeof window` | **optional**
|
||||
|
||||
Environment used to resolve a selector. Defaults to the browser `window`. A selector without a browser environment throws.
|
||||
|
||||
The function throws if a selector doesn't match an element.
|
||||
|
||||
### Connection props
|
||||
|
||||
#### `appId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Algolia application ID.
|
||||
|
||||
#### `apiKey`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Public API key with search permission.
|
||||
|
||||
#### `assistantId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Agent Studio agent ID.
|
||||
|
||||
#### `indexName`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Primary index for Agent Studio and conversation storage.
|
||||
|
||||
The public `SidepanelProps` type in `5.0.0-beta.0` doesn't include the React Sidepanel's top-level `searchParameters` field.
|
||||
|
||||
### UI props
|
||||
|
||||
#### `theme`
|
||||
|
||||
> `type: 'light' | 'dark'` | **optional**
|
||||
|
||||
Sets `data-theme` on the document root while mounted.
|
||||
|
||||
#### `keyboardShortcuts`
|
||||
|
||||
> `type: { 'Ctrl/Cmd+I'?: boolean }` | **optional**
|
||||
|
||||
Enables or disables the Sidepanel shortcut. The shortcut is enabled by default.
|
||||
|
||||
#### `button`
|
||||
|
||||
> `type: SidepanelButtonProps` | **optional**
|
||||
|
||||
Configures the built-in button. Defaults to `{}`.
|
||||
|
||||
`button` accepts `variant: 'floating' | 'inline'` and `translations.buttonText` or `translations.buttonAriaLabel`.
|
||||
|
||||
#### `panel`
|
||||
|
||||
> `type: SidepanelPanelProps` | **optional**
|
||||
|
||||
Configures layout, suggested questions, and translations. Defaults to `{}`.
|
||||
|
||||
`panel` accepts `variant`, `side`, `pushSelector`, `width`, `expandedWidth`, `portalContainer`, `suggestedQuestions`, `translations`, `tools`, `memory`, and `indices`. Panel-level `tools`, `memory`, and `indices` override their root values. Don't set `panel.keyboardShortcuts`; configure shortcuts at the root.
|
||||
|
||||
### Callbacks
|
||||
|
||||
#### `onReady`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs once after the component mounts.
|
||||
|
||||
#### `onOpen`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when the Sidepanel opens.
|
||||
|
||||
#### `onClose`
|
||||
|
||||
> `type: () => void` | **optional**
|
||||
|
||||
Runs when the Sidepanel closes.
|
||||
|
||||
Use these Sidepanel-specific callbacks rather than the inherited React provider callback names.
|
||||
|
||||
## Agent Studio options
|
||||
|
||||
### `tools`
|
||||
|
||||
> `type: Record<string, ToolDefinition>` | **optional**
|
||||
|
||||
Handles and renders custom Agent Studio tools. Defaults to `{}`.
|
||||
|
||||
### `memory`
|
||||
|
||||
> `type: { enabled?: boolean; userToken?: string }` | **optional**
|
||||
|
||||
Configures memory-tool rendering and authentication.
|
||||
|
||||
### `indices`
|
||||
|
||||
> `type: AgentStudioIndices[]` | **optional**
|
||||
|
||||
Defines dynamic indices available to Agent Studio.
|
||||
|
||||
These fields are root props:
|
||||
|
||||
```js title="load-sidepanel.js"
|
||||
const assistant = sidepanel({
|
||||
container: '#docsearch-sidepanel',
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
indexName: 'docs',
|
||||
memory: {
|
||||
enabled: true,
|
||||
userToken: userMemoryToken,
|
||||
},
|
||||
indices: [
|
||||
{
|
||||
index: 'docs',
|
||||
description: 'Product documentation and API references.',
|
||||
},
|
||||
{
|
||||
index: 'support_articles',
|
||||
description: 'Troubleshooting and support articles.',
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
getReleaseChannel: {
|
||||
async onToolCall({ input, addToolOutput }) {
|
||||
const channel = await readReleaseChannel(input);
|
||||
await addToolOutput({ output: { channel } });
|
||||
},
|
||||
render({ message }) {
|
||||
return `Release channel: ${message.output.channel}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`AgentStudioIndices` requires `index` and `description`. It also accepts `enhancedDescription`, `searchParameters`, and `searchControls`.
|
||||
|
||||
`ToolDefinition` requires `render({ message: { input, output } })`, which returns a string. It can also define `onToolCall` and `translations.callingToolText`. If you handle a client-side tool, call `addToolOutput`.
|
||||
|
||||
Generate `memory.userToken` on your server. The package sends it as `x-algolia-secure-user-token`.
|
||||
|
||||
## `SidepanelInstance`
|
||||
|
||||
Calls to `open` and `close` before the internal ref is ready are no-ops.
|
||||
|
||||
`InitialAskAiMessage` is:
|
||||
|
||||
```ts title="types.ts"
|
||||
type InitialAskAiMessage = {
|
||||
query: string;
|
||||
messageId?: string;
|
||||
suggestedQuestionId?: string;
|
||||
};
|
||||
```
|
||||
|
||||
### `isReady`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether the component is mounted.
|
||||
|
||||
### `isOpen`
|
||||
|
||||
> `type: readonly boolean`
|
||||
|
||||
Whether the panel is open.
|
||||
|
||||
### `open`
|
||||
|
||||
> `type: (initialMessage?: InitialAskAiMessage) => void`
|
||||
|
||||
Opens the panel, optionally with a question or stored-message IDs.
|
||||
|
||||
### `close`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Closes the panel.
|
||||
|
||||
### `destroy`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Unmounts the component and marks the instance not ready.
|
||||
|
||||
## Exports
|
||||
|
||||
`@docsearch/sidepanel-js` has one value export: the default `sidepanel` function.
|
||||
|
||||
It also exports the `SidepanelProps`, `SidepanelInstance`, and `SidepanelCallbacks` TypeScript types. It doesn't provide a named `sidepanel` export or package subpaths.
|
||||
|
||||
```ts title="load-sidepanel.ts"
|
||||
import sidepanel, {
|
||||
type SidepanelCallbacks,
|
||||
type SidepanelInstance,
|
||||
type SidepanelProps,
|
||||
} from '@docsearch/sidepanel-js';
|
||||
```
|
||||
120
packages/website/docs/packages/sidepanel-js/getting-started.mdx
Normal file
120
packages/website/docs/packages/sidepanel-js/getting-started.mdx
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
title: Sidepanel JS package
|
||||
description: Mount and control the v5 beta Agent Studio Sidepanel without React application code.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
`@docsearch/sidepanel-js` mounts the Agent Studio Sidepanel into an existing HTML element. It bundles its rendering dependencies and returns an imperative instance.
|
||||
|
||||
:::info v5 beta
|
||||
|
||||
These instructions use the `^5.0.0-beta` range.
|
||||
|
||||
:::
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/sidepanel-js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/sidepanel-js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/sidepanel-js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/sidepanel-js@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Add a container to your page:
|
||||
|
||||
```html title="index.html"
|
||||
<div id="docsearch-sidepanel"></div>
|
||||
```
|
||||
|
||||
Mount the Sidepanel and retain the returned instance:
|
||||
|
||||
```js title="load-sidepanel.js"
|
||||
import sidepanel from '@docsearch/sidepanel-js';
|
||||
|
||||
import '@docsearch/css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
|
||||
const assistant = sidepanel({
|
||||
container: '#docsearch-sidepanel',
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
indexName: 'YOUR_INDEX_NAME',
|
||||
onReady() {
|
||||
console.info('DocSearch sidepanel is ready');
|
||||
},
|
||||
});
|
||||
|
||||
document.querySelector('#ask-docs').addEventListener('click', () => {
|
||||
assistant.open();
|
||||
});
|
||||
```
|
||||
|
||||
Use a search-only API key. The `assistantId` is an Agent Studio agent ID. V5 doesn't provide an `agentStudio` switch.
|
||||
|
||||
## Open with a question
|
||||
|
||||
Pass an initial message to `open`:
|
||||
|
||||
```js title="load-sidepanel.js"
|
||||
assistant.open({
|
||||
query: 'How do I configure a search-only API key?',
|
||||
});
|
||||
```
|
||||
|
||||
Call `assistant.destroy()` before removing the container or remounting another Sidepanel in it.
|
||||
|
||||
## Load from a CDN
|
||||
|
||||
```html title="index.html"
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@docsearch/css@^5.0.0-beta/dist/style.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@docsearch/css@^5.0.0-beta/dist/sidepanel.css"
|
||||
/>
|
||||
|
||||
<div id="docsearch-sidepanel"></div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@docsearch/sidepanel-js@^5.0.0-beta/dist/umd/index.js"></script>
|
||||
<script>
|
||||
const assistant = docsearchSidepanel({
|
||||
container: '#docsearch-sidepanel',
|
||||
appId: 'YOUR_APPLICATION_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
assistantId: 'YOUR_AGENT_ID',
|
||||
indexName: 'YOUR_INDEX_NAME',
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
See the [JavaScript Sidepanel API](./api) for configuration, Agent Studio options, callbacks, and instance methods.
|
||||
162
packages/website/docs/packages/sidepanel/advanced-use-cases.mdx
Normal file
162
packages/website/docs/packages/sidepanel/advanced-use-cases.mdx
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
---
|
||||
title: React Sidepanel advanced use cases
|
||||
description: Control Sidepanel layout, refs, Agent Studio tools, memory, and indices.
|
||||
---
|
||||
|
||||
## Push page content
|
||||
|
||||
Use the `inline` panel variant to add a margin to the first element that matches `pushSelector`. On screens up to 768 pixels wide, the panel overlays the page instead.
|
||||
|
||||
```tsx title="Docs.tsx"
|
||||
<SidepanelButton variant="inline" />
|
||||
<Sidepanel
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_AGENT_ID"
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
variant="inline"
|
||||
side="left"
|
||||
pushSelector="#documentation"
|
||||
width="24rem"
|
||||
expandedWidth="36rem"
|
||||
/>
|
||||
```
|
||||
|
||||
Keep `pushSelector` narrow enough to identify one layout container. The implementation uses the first matching element and restores its inline margin and transition when it unmounts.
|
||||
|
||||
## Render into another container
|
||||
|
||||
Both the floating button and panel default to `document.body`. Set `portalContainer` on the panel and `portalcontainer` on the button to use another element. The button prop is lower-case in the v5 beta API.
|
||||
|
||||
```tsx title="AssistantPortal.tsx"
|
||||
const assistantRoot = document.getElementById('assistant-root');
|
||||
|
||||
<DocSearch>
|
||||
<SidepanelButton portalcontainer={assistantRoot} />
|
||||
<Sidepanel
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_AGENT_ID"
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
portalContainer={assistantRoot}
|
||||
/>
|
||||
</DocSearch>;
|
||||
```
|
||||
|
||||
An inline `SidepanelButton` renders where it appears in the React tree and doesn't use its portal container.
|
||||
|
||||
## Control the panel with a ref
|
||||
|
||||
Attach a `DocSearchRef` to the provider. `openSidepanel` accepts an optional initial message.
|
||||
|
||||
```tsx title="Support.tsx"
|
||||
import { DocSearch, type DocSearchRef } from '@docsearch/core';
|
||||
import { Sidepanel } from '@docsearch/sidepanel';
|
||||
import { useRef } from 'react';
|
||||
|
||||
export function Support() {
|
||||
const docsearchRef = useRef<DocSearchRef>(null);
|
||||
|
||||
return (
|
||||
<DocSearch ref={docsearchRef}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
docsearchRef.current?.openSidepanel({
|
||||
query: 'How do I rotate an API key?',
|
||||
})
|
||||
}
|
||||
>
|
||||
Ask about API keys
|
||||
</button>
|
||||
<Sidepanel
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_AGENT_ID"
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Call the method after the Sidepanel has mounted and registered its view. Check `isSidepanelOpen` to read its open state. See [Hybrid Mode](/docs/hybrid-mode) for cross-view ref behavior.
|
||||
|
||||
## Handle Agent Studio tools
|
||||
|
||||
Key each client tool by the name emitted by your agent. Always return tool output through `addToolOutput` when you provide `onToolCall`.
|
||||
|
||||
```tsx title="tools.tsx"
|
||||
const tools = {
|
||||
getReleaseChannel: {
|
||||
translations: { callingToolText: 'Reading release settings' },
|
||||
async onToolCall({ input, addToolOutput }) {
|
||||
const channel = await readReleaseChannel(input);
|
||||
await addToolOutput({ output: { channel } });
|
||||
},
|
||||
render({ message }) {
|
||||
const output = message.output as { channel: string };
|
||||
return `Release channel: ${output.channel}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
<Sidepanel
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_AGENT_ID"
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
tools={tools}
|
||||
/>;
|
||||
```
|
||||
|
||||
Define `tools` outside the component or memoize it. Tool errors don't render a tool result.
|
||||
|
||||
## Enable memory
|
||||
|
||||
Pass a server-generated Agent Studio user JWT. `enabled` controls whether memory tool activity appears in the conversation; `userToken` authenticates the user's memory requests.
|
||||
|
||||
```tsx title="Memory.tsx"
|
||||
<Sidepanel
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_AGENT_ID"
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
memory={{
|
||||
enabled: true,
|
||||
userToken: userMemoryToken,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Don't generate or sign the token in the browser.
|
||||
|
||||
## Provide dynamic indices
|
||||
|
||||
Use `indices` to describe the indices Agent Studio can select at request time. This list is separate from the required `indexName`, which remains the Sidepanel's primary index.
|
||||
|
||||
```tsx title="DynamicIndices.tsx"
|
||||
<Sidepanel
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_AGENT_ID"
|
||||
indexName="docs"
|
||||
searchParameters={{
|
||||
docs: { filters: 'version:v5' },
|
||||
support_articles: { filters: 'visibility:public' },
|
||||
}}
|
||||
indices={[
|
||||
{
|
||||
index: 'docs',
|
||||
description: 'Product documentation and API references.',
|
||||
},
|
||||
{
|
||||
index: 'support_articles',
|
||||
description: 'Troubleshooting and support articles.',
|
||||
enhancedDescription: 'Use for errors and operational incidents.',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
Search parameters are keyed by index name. See the [Sidepanel API](./api#agent-studio-options) for the supported fields.
|
||||
229
packages/website/docs/packages/sidepanel/api.mdx
Normal file
229
packages/website/docs/packages/sidepanel/api.mdx
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
---
|
||||
title: React Sidepanel API reference
|
||||
description: API reference for @docsearch/sidepanel v5 beta.
|
||||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## `Sidepanel`
|
||||
|
||||
Render `Sidepanel` inside `DocSearch` from `@docsearch/core`.
|
||||
|
||||
Set `theme` on `DocSearch`; `SidepanelProps` omits it. Set lifecycle callbacks such as `onSidepanelOpen` and `onSidepanelClose` on the provider. Don't pass inherited `onOpen` or `onClose` callbacks to `Sidepanel`, because they replace the wrapper's state handlers in this beta.
|
||||
|
||||
### Connection props
|
||||
|
||||
#### `appId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Algolia application ID.
|
||||
|
||||
#### `apiKey`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Public API key with search permission.
|
||||
|
||||
#### `assistantId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Agent Studio agent ID.
|
||||
|
||||
#### `indexName`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Primary index for Agent Studio and conversation storage.
|
||||
|
||||
#### `searchParameters`
|
||||
|
||||
> `type: Record<string, AgentStudioIndexSearchParameters>` | **optional**
|
||||
|
||||
Search parameters keyed by index name.
|
||||
|
||||
`AgentStudioIndexSearchParameters` supports `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct`.
|
||||
|
||||
### Layout props
|
||||
|
||||
#### `variant`
|
||||
|
||||
> `type: 'floating' | 'inline'` | **optional**
|
||||
|
||||
Overlays the page or pushes matching page content on desktop. Defaults to `'floating'`.
|
||||
|
||||
#### `side`
|
||||
|
||||
> `type: 'left' | 'right'` | **optional**
|
||||
|
||||
Sets the side from which the panel opens. Defaults to `'right'`.
|
||||
|
||||
#### `pushSelector`
|
||||
|
||||
> `type: string` | **optional**
|
||||
|
||||
Selects the first element pushed by the inline panel. Defaults to `'#root, main, .app, body'`.
|
||||
|
||||
#### `width`
|
||||
|
||||
> `type: number | string` | **optional**
|
||||
|
||||
Sets the normal width. Numbers use pixels. Defaults to `360`.
|
||||
|
||||
#### `expandedWidth`
|
||||
|
||||
> `type: number | string` | **optional**
|
||||
|
||||
Sets the expanded width. Numbers use pixels. Defaults to `580`.
|
||||
|
||||
#### `portalContainer`
|
||||
|
||||
> `type: DocumentFragment | Element | null` | **optional**
|
||||
|
||||
Receives the panel portal. Defaults to `document.body`.
|
||||
|
||||
### Behavior props
|
||||
|
||||
#### `keyboardShortcuts`
|
||||
|
||||
> `type: { 'Ctrl/Cmd+I'?: boolean }` | **optional**
|
||||
|
||||
Enables or disables the Sidepanel shortcut. The shortcut is enabled by default.
|
||||
|
||||
#### `suggestedQuestions`
|
||||
|
||||
> `type: boolean` | **optional**
|
||||
|
||||
Whether to show Agent Studio suggested questions on the new-conversation screen. Defaults to `false`.
|
||||
|
||||
#### `translations`
|
||||
|
||||
> `type: SidepanelTranslations` | **optional**
|
||||
|
||||
Supplies partial strings for `header`, `promptForm`, `conversationScreen`, `newConversationScreen`, and `logo`. Defaults to `{}`.
|
||||
|
||||
#### `tools`
|
||||
|
||||
> `type: Record<string, ToolDefinition>` | **optional**
|
||||
|
||||
Handles and renders custom Agent Studio tools. Defaults to `{}`.
|
||||
|
||||
#### `memory`
|
||||
|
||||
> `type: { enabled?: boolean; userToken?: string }` | **optional**
|
||||
|
||||
Configures Agent Studio memory rendering and authentication.
|
||||
|
||||
#### `indices`
|
||||
|
||||
> `type: AgentStudioIndices[]` | **optional**
|
||||
|
||||
Defines dynamic indices available to Agent Studio.
|
||||
|
||||
### Translation groups
|
||||
|
||||
| Group | Keys |
|
||||
| --- | --- |
|
||||
| `header` | `title`, `conversationHistoryTitle`, `newConversationText`, `viewConversationHistoryText` |
|
||||
| `promptForm` | `promptPlaceholderText`, `promptAnsweringText`, `promptAskAnotherQuestionText`, `promptDisclaimerText`, `promptLabelText`, `promptAriaLabelText` |
|
||||
| `newConversationScreen` | `titleText`, `introductionText` |
|
||||
| `logo` | `poweredByText` |
|
||||
| `conversationScreen` | Tool status, source, copy, feedback, error, disclaimer, and suggested-prompt strings exposed by `ConversationScreenTranslations` |
|
||||
|
||||
## Agent Studio options
|
||||
|
||||
V5 uses Agent Studio for Sidepanel conversations. It doesn't expose an `agentStudio` prop.
|
||||
|
||||
### `AgentStudioIndices`
|
||||
|
||||
```ts
|
||||
interface AgentStudioIndices {
|
||||
index: string;
|
||||
description: string;
|
||||
enhancedDescription?: string;
|
||||
searchParameters?: SearchParamsObject;
|
||||
searchControls?: AgentStudioSearchControls;
|
||||
}
|
||||
```
|
||||
|
||||
`searchControls` accepts `query`, `hits_per_page`, `page`, `attributesToRetrieve`, `responseFields`, `facets`, and `custom`. Controls specify whether Agent Studio may expose a value to the model and can include defaults and constraints.
|
||||
|
||||
### `ToolDefinition`
|
||||
|
||||
```ts
|
||||
type ToolDefinition = {
|
||||
render: (params: { message: { input: unknown; output: unknown } }) => string;
|
||||
onToolCall?: (params: {
|
||||
input: unknown;
|
||||
addToolOutput: (props: { output: unknown }) => Promise<void>;
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
dynamic?: boolean;
|
||||
}) => Promise<void> | void;
|
||||
translations?: { callingToolText?: string };
|
||||
};
|
||||
```
|
||||
|
||||
### `memory`
|
||||
|
||||
`enabled` defaults to `false` and controls memory-tool status rendering. `userToken` is sent as the `x-algolia-secure-user-token` header.
|
||||
|
||||
## `SidepanelButton`
|
||||
|
||||
### `variant`
|
||||
|
||||
> `type: 'floating' | 'inline'` | **optional**
|
||||
|
||||
Renders an icon-only floating button or an inline button with text. Defaults to `'floating'`.
|
||||
|
||||
### `translations`
|
||||
|
||||
> `type: { buttonText?: string; buttonAriaLabel?: string }` | **optional**
|
||||
|
||||
Changes visible and accessible text. `buttonText` applies to the inline variant. Defaults to the English labels.
|
||||
|
||||
### `portalcontainer`
|
||||
|
||||
> `type: DocumentFragment | Element | null` | **optional**
|
||||
|
||||
Receives the floating button portal. Defaults to `document.body`. This beta prop uses a lower-case `c`.
|
||||
|
||||
The wrapper supplies its click handler and keyboard shortcut configuration from `DocSearch`. An inline button renders at its position in the React tree.
|
||||
|
||||
## Programmatic control
|
||||
|
||||
`Sidepanel` doesn't expose its lower-level `SidepanelRef` directly. Attach a `DocSearchRef` to the provider and use:
|
||||
|
||||
### `openSidepanel`
|
||||
|
||||
> `type: (initialMessage?: InitialAskAiMessage) => void`
|
||||
|
||||
Opens a registered Sidepanel.
|
||||
|
||||
### `close`
|
||||
|
||||
> `type: () => void`
|
||||
|
||||
Returns the provider to its ready state.
|
||||
|
||||
### `isSidepanelOpen`
|
||||
|
||||
> `type: boolean`
|
||||
|
||||
Whether the Sidepanel is open.
|
||||
|
||||
### `isReady`
|
||||
|
||||
> `type: boolean`
|
||||
|
||||
Whether the provider is mounted.
|
||||
|
||||
`InitialAskAiMessage` is `{ query: string; messageId?: string; suggestedQuestionId?: string }`. See the [`@docsearch/core` API](../core/api) for the full ref.
|
||||
|
||||
## Exports
|
||||
|
||||
| Import path | Value exports | Type exports |
|
||||
| --- | --- | --- |
|
||||
| `@docsearch/sidepanel` | `Sidepanel`, `SidepanelButton` | `SidepanelProps`, `SidepanelButtonProps` |
|
||||
| `@docsearch/sidepanel/sidepanel` | `Sidepanel` | `SidepanelProps` |
|
||||
| `@docsearch/sidepanel/button` | `SidepanelButton` | `SidepanelButtonProps` |
|
||||
101
packages/website/docs/packages/sidepanel/getting-started.mdx
Normal file
101
packages/website/docs/packages/sidepanel/getting-started.mdx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
title: Sidepanel package
|
||||
description: Add the v5 beta Agent Studio Sidepanel to a React application.
|
||||
---
|
||||
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
`@docsearch/sidepanel` provides composable React components for an Agent Studio chat panel.
|
||||
|
||||
:::info v5 beta
|
||||
|
||||
These instructions use the `^5.0.0-beta` range. Use the same range for every DocSearch package.
|
||||
|
||||
:::
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs groupId="package-manager" aria-label="Package manager">
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/core@^5.0.0-beta @docsearch/sidepanel@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/core@^5.0.0-beta @docsearch/sidepanel@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/core@^5.0.0-beta @docsearch/sidepanel@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="bun" label="Bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/core@^5.0.0-beta @docsearch/sidepanel@^5.0.0-beta @docsearch/css@^5.0.0-beta
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Render the Sidepanel
|
||||
|
||||
Import both the main DocSearch stylesheet and the Sidepanel stylesheet.
|
||||
|
||||
```tsx title="HelpAssistant.tsx"
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { Sidepanel, SidepanelButton } from '@docsearch/sidepanel';
|
||||
|
||||
import '@docsearch/css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
|
||||
export function HelpAssistant() {
|
||||
return (
|
||||
<DocSearch>
|
||||
<SidepanelButton />
|
||||
<Sidepanel
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_AGENT_ID"
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Use a search-only API key. The `assistantId` is an Agent Studio agent ID. V5 doesn't provide the legacy Ask AI backend or an `agentStudio` switch.
|
||||
|
||||
The default button and panel float over the page. Press `Control+I` or `Command+I` to toggle the panel, and press `Escape` to close it.
|
||||
|
||||
## Configure the provider
|
||||
|
||||
Set the theme and lifecycle callbacks on `DocSearch`.
|
||||
|
||||
```tsx title="HelpAssistant.tsx"
|
||||
<DocSearch
|
||||
theme="dark"
|
||||
keyboardShortcuts={{ 'Ctrl/Cmd+I': true }}
|
||||
onSidepanelOpen={() => track('assistant_opened')}
|
||||
onSidepanelClose={() => track('assistant_closed')}
|
||||
>
|
||||
<SidepanelButton variant="inline" />
|
||||
<Sidepanel
|
||||
appId="YOUR_APPLICATION_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_AGENT_ID"
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
/>
|
||||
</DocSearch>
|
||||
```
|
||||
|
||||
Continue with [advanced use cases](./advanced-use-cases) or review the [Sidepanel API](./api). For a combined search and assistant experience, see [Hybrid Mode](/docs/hybrid-mode).
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
---
|
||||
title: Record Extractor
|
||||
description: Configure the DocSearch record extractor for Algolia Crawler records.
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
:::info
|
||||
|
||||
This documentation will only contain information regarding the **helpers.docsearch** method, see **[Algolia Crawler Documentation][7]** for more information on the **[Algolia Crawler][8]**.
|
||||
This page documents the **helpers.docsearch** method. See the **[Algolia Crawler documentation][7]** for information about the **[Algolia Crawler][8]**.
|
||||
|
||||
:::
|
||||
|
||||
Pages are extracted by a [`recordExtractor`][9]. These extractors are assigned to [`actions`][12] via the [`recordExtractor`][9] parameter. This parameter links to a function that returns the data you want to index, organized in an array of JSON objects.
|
||||
Set the [`recordExtractor`][9] parameter on an [`action`][12] to extract each page. Its function returns the data to index as an array of JSON objects.
|
||||
|
||||
_The helpers are a collection of functions to help you extract content and generate Algolia records._
|
||||
The helpers are functions for extracting content and generating Algolia records.
|
||||
|
||||
### Useful links
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ _The helpers are a collection of functions to help you extract content and gener
|
|||
|
||||
## Usage
|
||||
|
||||
The most common way to use the DocSearch helper, is to return its result to the [`recordExtractor`][9] function.
|
||||
The most common way to use the DocSearch helper is to return its result to the [`recordExtractor`][9] function.
|
||||
|
||||
```js
|
||||
recordExtractor: ({ helpers }) => {
|
||||
|
|
@ -77,7 +78,7 @@ recordExtractor: ({ $, helpers }) => {
|
|||
// `.exists h1` will be selected if `.exists-probably h1` does not exists.
|
||||
lvl0: {
|
||||
selectors: [".exists-probably h1", ".exists h1"],
|
||||
}
|
||||
},
|
||||
lvl1: "article h2",
|
||||
lvl2: "article h3",
|
||||
lvl3: "article h4",
|
||||
|
|
@ -131,7 +132,7 @@ recordExtractor: ({ $, helpers }) => {
|
|||
|
||||
_These selectors also support [`defaultValue`](#provide-raw-text-defaultvalue) and [fallback selectors](#provide-fallback-selectors)_
|
||||
|
||||
You might want to index content that will be used as filters in your frontend (e.g. `version` or `lang`), you can defined any custom variable to the `recordProps` object to add them to your Algolia records:
|
||||
To index content for frontend filters, such as `version` or `language`, define custom variables in `recordProps`. The helper adds them to each matching Algolia record:
|
||||
|
||||
```js
|
||||
recordExtractor: ({ helpers }) => {
|
||||
|
|
@ -164,15 +165,19 @@ recordExtractor: ({ helpers }) => {
|
|||
},
|
||||
```
|
||||
|
||||
The following `version`, `lang` and `foo` attributes will be available in your records:
|
||||
The `version`, `language`, and `foo` attributes are then available in your records:
|
||||
|
||||
```json
|
||||
foo: "valueFromBarSelector",
|
||||
language: ["en", "en-US"],
|
||||
version: ["latest", "stable"]
|
||||
{
|
||||
"foo": "valueFromBarSelector",
|
||||
"language": ["en", "en-US"],
|
||||
"version": ["latest", "stable"]
|
||||
}
|
||||
```
|
||||
|
||||
You can now use them to [filter your search in the frontend][16]
|
||||
Add every filter attribute to the index's `attributesForFaceting`, then expose up to five of them with the v5 [`facets` option][16]. If you display one with `resultBadgeKey`, also add that attribute to `attributesToRetrieve`; see the [`resultBadgeKey` reference][17].
|
||||
|
||||
V5 result breadcrumbs use the `hierarchy.lvl0` through `hierarchy.lvl6` values generated from your selectors. Keep the heading levels ordered and include the hierarchy attributes in `attributesToRetrieve`.
|
||||
|
||||
### Boost search results with `pageRank`
|
||||
|
||||
|
|
@ -209,7 +214,7 @@ You can view the computed weight directly from the Algolia dashboard (dashboard.
|
|||
|
||||
### Reduce the number of records
|
||||
|
||||
If you encounter the `Extractors returned too many records` error when your page outputs more than 750 records. The [`aggregateContent`](#aggregatecontent) option helps you reducing the number of records at the `content` level of the extractor.
|
||||
If you encounter the `Extractors returned too many records` error when your page outputs more than 750 records, the [`aggregateContent`](#aggregatecontent) option helps you reduce the number of records at the `content` level of the extractor.
|
||||
|
||||
```js
|
||||
{
|
||||
|
|
@ -236,7 +241,7 @@ If you encounter the `Extractors returned too many records` error when your page
|
|||
|
||||
### Reduce the record size
|
||||
|
||||
If you encounter the `Records extracted are too big` error when crawling your website, it's mostly because there was too many informations in your records, or that your page is too big. The [`recordVersion`](#recordversion) option helps you reducing the records size by removing informations that are only used with [DocSearch v2](/docs/legacy/dropdown).
|
||||
If you encounter the `Records extracted are too big` error, your records or source page might contain too much information. The [`recordVersion`](#recordversion) option reduces record size by removing fields used only by the [DocSearch v2 UI](/docs/legacy/dropdown).
|
||||
|
||||
```js
|
||||
{
|
||||
|
|
@ -303,7 +308,7 @@ type CustomVariable =
|
|||
};
|
||||
```
|
||||
|
||||
Custom variables are used to [`filter your search`](/docs/v3/docsearch#filtering-your-search), you can define them in the [`recordProps`](#indexing-content-for-faceting)
|
||||
Define custom variables in [`recordProps`](#indexing-content-for-faceting). You can use them with v5 [`facets` and per-index filters][16].
|
||||
|
||||
## `helpers.docsearch` API Reference
|
||||
|
||||
|
|
@ -317,7 +322,7 @@ Custom variables are used to [`filter your search`](/docs/v3/docsearch#filtering
|
|||
|
||||
> `type: 'v3' | 'v2'` | default: `v2` | **optional**
|
||||
|
||||
This option remove content from the Algolia records that are only used for [DocSearch v2](/docs/legacy/dropdown). If you are using [the latest version of DocSearch](/docs/v3/docsearch), you can [set it to `v3`](#reduce-the-record-size).
|
||||
This option selects the crawler record schema. It doesn't select the DocSearch UI package version. Set it to `v3` to remove fields used only by the [DocSearch v2 UI](/docs/legacy/dropdown). The `v3` value is also the current record schema for DocSearch v5 frontends.
|
||||
|
||||
### `indexHeadings`
|
||||
|
||||
|
|
@ -328,7 +333,6 @@ This option tells the crawler if the `headings` (`lvlX`) should be indexed.
|
|||
- When `false`, only records for the `content` level will be created.
|
||||
- When `from, to` is provided, only records for the `lvlX` to `lvlY` will be created.
|
||||
|
||||
[1]: /docs/v3/docsearch
|
||||
[2]: https://github.com/algolia/docsearch/
|
||||
[3]: https://github.com/algolia/docsearch/tree/master
|
||||
[4]: /docs/legacy/dropdown
|
||||
|
|
@ -342,4 +346,5 @@ This option tells the crawler if the `headings` (`lvlX`) should be indexed.
|
|||
[12]: https://www.algolia.com/doc/tools/crawler/apis/configuration/actions/
|
||||
[13]: /docs/record-extractor#indexing-content-for-faceting
|
||||
[15]: https://www.algolia.com/doc/guides/managing-results/refine-results/faceting/
|
||||
[16]: /docs/v3/docsearch/#filtering-your-search
|
||||
[16]: /docs/packages/js/api-reference#facets
|
||||
[17]: /docs/packages/js/api-reference#resultbadgekey
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
---
|
||||
title: Required configuration
|
||||
description: Structure website content and crawler settings for DocSearch indexing.
|
||||
---
|
||||
|
||||
This section, gives you the best practices to optimize our crawl. Adopting this following specification is required to let our crawler build the best experience from your website. You will need to update your website and follow these rules.
|
||||
Follow these requirements so the crawler can extract structured records from your website. Update your markup and crawler configuration where needed.
|
||||
|
||||
:::info
|
||||
|
||||
If your website is generated, thanks to one of [our supported tools][1], you do not need to change your website as it is already compliant with our requirements.
|
||||
If your website uses one of [the supported framework integrations][1], its generated markup might already meet these requirements. Confirm that the crawler selectors match your framework and version.
|
||||
|
||||
:::
|
||||
|
||||
## The generic configuration example
|
||||
## Generic configuration example
|
||||
|
||||
You can find the default DocSearch config template below and tweak it with some examples from our [`complex extractors` section][12].
|
||||
Start with the default DocSearch configuration template. For more customization options, see [complex record extractors][12].
|
||||
|
||||
If you are using one of [our integrations][13], please see [the templates page][11].
|
||||
If you use a [framework integration][13], see the [configuration templates][11].
|
||||
|
||||
<details>
|
||||
<summary>docsearch-default.js</summary>
|
||||
|
|
@ -53,7 +54,7 @@ new Crawler({
|
|||
],
|
||||
initialIndexSettings: {
|
||||
YOUR_INDEX_NAME: {
|
||||
attributesForFaceting: ['type', 'lang'],
|
||||
attributesForFaceting: ['type', 'lang', 'language', 'version'],
|
||||
attributesToRetrieve: [
|
||||
'hierarchy',
|
||||
'content',
|
||||
|
|
@ -61,6 +62,9 @@ new Crawler({
|
|||
'url',
|
||||
'url_without_anchor',
|
||||
'type',
|
||||
'lang',
|
||||
'language',
|
||||
'version',
|
||||
],
|
||||
attributesToHighlight: ['hierarchy', 'content'],
|
||||
attributesToSnippet: ['content:10'],
|
||||
|
|
@ -110,67 +114,73 @@ new Crawler({
|
|||
</div>
|
||||
</details>
|
||||
|
||||
`recordVersion: 'v3'` selects the crawler record schema. It isn't the DocSearch UI version and works with the v5 frontend packages. If you expose `lang`, `language`, or `version` as v5 facets, add those attributes to `attributesForFaceting`. Keep a badge attribute in `attributesToRetrieve` when you pass it to [`resultBadgeKey`][15].
|
||||
|
||||
### Overview of a clear layout
|
||||
|
||||
A website implementing these good practises will look simple and crystal clear. It can have this following aspect:
|
||||
Use a page layout that separates documentation content from navigation and other page elements:
|
||||
|
||||
<img
|
||||
src="https://docsearch.algolia.com/img/assets/recommended-layout.png"
|
||||
alt="Recommended layout for your page"
|
||||
/>
|
||||
|
||||
The main blue element will be your `.DocSearch-content` container. More details in the following guidelines.
|
||||
Use the main blue element as your `.DocSearch-content` container. Follow the next guidelines to structure its contents.
|
||||
|
||||
### Use the right classes as [`recordProps`][2]
|
||||
|
||||
You can add some specific static classes to help us find your content role. These classes can not involve any style changes. These dedicated classes will help us to create a great learn-as-you-type experience from your documentation.
|
||||
Add static classes to identify each content role. These classes don't need to change the page's appearance. The crawler uses them to extract structured records.
|
||||
|
||||
- Add a static class `DocSearch-content` to the main container of your textual content. Most of the time, this tag is a `<main>` or an `<article>` HTML element.
|
||||
- Add a static `DocSearch-content` class to the main container for your text. This container is usually a `<main>` or `<article>` element.
|
||||
|
||||
- Every searchable `lvl` element outside this main documentation container (for instance in a sidebar) must be a `global` selector. They will be globally picked up and injected to every record built from your page. Be careful, the level value matters and every matching element must have an increasing level along the HTML flow. A level `X` (for `lvlX`) should appear after a level `Y` while `X > Y`.
|
||||
- Configure every searchable `lvl` element outside the main documentation container, such as a sidebar item, as a `global` selector. The crawler adds these elements to every record from the page. Keep levels in increasing order in the document flow: `lvlX` should follow `lvlY` when `X > Y`.
|
||||
|
||||
- `lvlX` selectors should use the standard title tags like `h1`, `h2`, `h3`, etc. You can also use static classes. Set a unique `id` or `name` attribute to these elements as detailed below.
|
||||
- Use standard heading elements, such as `h1`, `h2`, and `h3`, for `lvlX` selectors. You can also use static classes. Add a unique `id` or `name` attribute to each matching element.
|
||||
|
||||
- Every DOM element matching the `lvlX` selectors must have a unique `id` or `name` attribute. This will help the redirection to directly scroll down to the exact place of the matching elements. These attributes define the right anchor to use.
|
||||
- Give every element that matches an `lvlX` selector a unique `id` or `name` attribute. DocSearch uses this anchor to open the page at the matching element.
|
||||
|
||||
- Every textual element (recordProps `content`) must be wrapped in a `<p>` or `<li>` tag. This content must be atomic and split into small entities. Be careful to never nest one matching element into another one as it will create duplicates.
|
||||
- V5 builds result breadcrumbs from the populated `hierarchy.lvl0` through `hierarchy.lvl6` attributes. Keep heading levels ordered, avoid gaps where possible, and retrieve the full `hierarchy` object.
|
||||
|
||||
- Stay consistent and do not forget that we need to have some consistency along the HTML flow.
|
||||
- Wrap every element that matches the `recordProps.content` selector in a `<p>` or `<li>` element. Split text into focused blocks, and don't nest matching elements because this creates duplicate records.
|
||||
|
||||
- Keep the content structure consistent throughout the document.
|
||||
|
||||
## Introduce global information as meta tags
|
||||
|
||||
Our crawler automatically extracts information from our DocSearch specific meta tags:
|
||||
The crawler automatically extracts information from DocSearch-specific meta tags:
|
||||
|
||||
```html
|
||||
```html title="index.html"
|
||||
<meta name="docsearch:language" content="en" />
|
||||
<meta name="docsearch:version" content="1.0.0" />
|
||||
```
|
||||
|
||||
The crawl adds the `content` value of these `meta` tags to all records extracted from the page. The meta tags `name` must follow the `docsearch:$NAME` pattern. `$NAME` is the name of the attribute set to all records.
|
||||
The crawler adds the `content` value of these `meta` tags to every record extracted from the page. Each tag's `name` attribute must follow the `docsearch:$NAME` pattern, where `$NAME` is the record attribute to set.
|
||||
|
||||
The `docsearch:version` meta tag can be a set [of comma-separated tokens][5], each of which is a version relevant to the page. These tokens must be compliant with [the SemVer specification][6] or only contain alphanumeric characters (e.g. `latest`, `next`, etc.). As facet filters, these version tokens are case-insensitive.
|
||||
|
||||
For example, all records extracted from a page with the following meta tag:
|
||||
For example, add the following meta tag to assign two versions to every record on a page:
|
||||
|
||||
```html
|
||||
```html title="index.html"
|
||||
<meta name="docsearch:version" content="2.0.0-alpha.62,latest" />
|
||||
```
|
||||
|
||||
The `version` attribute of these records will be :
|
||||
The crawler adds the following `version` attribute to each record:
|
||||
|
||||
```json
|
||||
version:["2.0.0-alpha.62", "latest"]
|
||||
```json title="record.json"
|
||||
{
|
||||
"version": ["2.0.0-alpha.62", "latest"]
|
||||
}
|
||||
```
|
||||
|
||||
You can then [transform these attributes as `facetFilters`][3] to [filter over them from the UI][10].
|
||||
Add these attributes to `attributesForFaceting`. You can then use them in per-index `facetFilters` or expose up to five controls with the v5 [`facets` option][10]. To show `version` in each result, retrieve it and set [`resultBadgeKey`][15] to `version`.
|
||||
|
||||
## Nice to have
|
||||
|
||||
- Your website should have [an updated sitemap][7]. This is key to let our crawler know what should be updated. Do not worry, we will still crawl your website and discover embedded hyperlinks to find your great content.
|
||||
- Keep your [sitemap][7] up to date so the crawler can identify changed pages. The crawler also discovers eligible links from crawled pages.
|
||||
|
||||
- Every page needs to have their full context available. Using global elements might help (see above).
|
||||
- Ensure that every page provides its full context. Use global elements where appropriate.
|
||||
|
||||
- Make sure your documentation content is also available without JavaScript rendering on the client-side. If you absolutely need JavaScript turned on, you need to [set `renderJavaScript: true` in your configuration][8].
|
||||
- Make your documentation content available without client-side JavaScript rendering. If your website requires JavaScript rendering, [set `renderJavaScript: true` in your configuration][8].
|
||||
|
||||
Any questions? Connect with us on [Discord][14] or [support][9].
|
||||
|
||||
|
|
@ -182,8 +192,9 @@ Any questions? Connect with us on [Discord][14] or [support][9].
|
|||
[7]: https://www.sitemaps.org/
|
||||
[8]: https://www.algolia.com/doc/tools/crawler/apis/configuration/render-java-script/
|
||||
[9]: https://support.algolia.com/
|
||||
[10]: /docs/v3/docsearch#filtering-your-search
|
||||
[10]: /docs/packages/js/api-reference#facets
|
||||
[11]: /docs/templates
|
||||
[12]: /docs/record-extractor#introduction
|
||||
[13]: /docs/integrations
|
||||
[14]: https://alg.li/discord
|
||||
[15]: /docs/packages/js/api-reference#resultbadgekey
|
||||
|
|
|
|||
|
|
@ -1,144 +1,8 @@
|
|||
---
|
||||
title: Advanced use cases
|
||||
title: Advanced sidepanel use cases
|
||||
description: Configure advanced DocSearch Agent Studio sidepanel behavior.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
See [Advanced `@docsearch/sidepanel` use cases](/docs/packages/sidepanel/advanced-use-cases) for dynamic indices, tools, memory, layout options, and programmatic control.
|
||||
|
||||
## Introduction
|
||||
|
||||
This guide will cover some advanced implementations/use cases for the Sidepanel. The examples below assume you're using the Sidepanel React package,
|
||||
available from `@docsearch/sidepanel`. The `@docsearch/sidepanel` package can be installed as follows:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/sidepanel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/sidepanel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/sidepanel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/sidepanel
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
> Or using your package manager of choice
|
||||
|
||||
## Complex implementation
|
||||
|
||||
Below is an example of a more complex implementation with `searchParameters`, a different `variant`, and some translations.
|
||||
|
||||
```tsx
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { SidepanelButton, Sidepanel } from '@docsearch/sidepanel'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<DocSearch>
|
||||
<SidepanelButton
|
||||
translations={{
|
||||
buttonAriaLabel: 'Open Ask AI Sidepanel',
|
||||
}}
|
||||
/>
|
||||
<Sidepanel
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_ASSISTANT_ID"
|
||||
variant="inline"
|
||||
searchParameters={{
|
||||
facetFilters: ['language:en'],
|
||||
distinct: false,
|
||||
}}
|
||||
translations={{
|
||||
header: {
|
||||
title: 'Ask our assistant',
|
||||
},
|
||||
promptForm: {
|
||||
promptPlaceholderText: 'e.g. How do I migrate my DB?',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic importing
|
||||
|
||||
Sidepanel is built in a way that allows for dynamic importing of its components to help reduce bundle size. Below is a brief example of how to do so:
|
||||
|
||||
```tsx
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { SidepanelButton } from '@docsearch/sidepanel/button';
|
||||
import type { Sidepanel as SidepanelType} from '@docsearch/sidepanel/sidepanel';
|
||||
import { useState } from 'react';
|
||||
|
||||
let Sidepanel: typeof SidepanelType | null = null;
|
||||
|
||||
async function importSidepanelIfNeeded() {
|
||||
if (Sidepanel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { Sidepanel: Panel } = await import('@docsearch/sidepanel/sidepanel');
|
||||
|
||||
Sidepanel = Panel;
|
||||
}
|
||||
|
||||
export default function DynamicSidepanel() {
|
||||
const [sidepanelLoaded, setSidepanelLoaded] = useState(false);
|
||||
|
||||
const loadSidepanel = () => {
|
||||
importSidepanelIfNeeded().then(() => {
|
||||
setSidepanelLoaded(true);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DocSearch>
|
||||
<SidepanelButton onClick={loadSidepanel} />
|
||||
{sidepanelLoaded && Sidepanel && (
|
||||
<Sidepanel
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_ASSISTANT_ID"
|
||||
/>
|
||||
)}
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Hybrid Mode
|
||||
|
||||
Hybrid Mode allows you to combine the Sidepanel and the original DocSearch Modal in one integrated experience.
|
||||
|
||||
You can trigger the Modal for search and the Sidepanel for AI-powered assistance.
|
||||
|
||||
Learn more in the [Hybrid Mode guide][1].
|
||||
|
||||
[1]: /docs/sidepanel/hybrid
|
||||
For JavaScript, see the [`@docsearch/sidepanel-js` API](/docs/packages/sidepanel-js/api).
|
||||
|
|
|
|||
|
|
@ -1,148 +1,9 @@
|
|||
---
|
||||
title: Sidepanel API Reference
|
||||
title: Sidepanel API references
|
||||
description: Reference the DocSearch sidepanel APIs and configuration options.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
Choose the API reference for your application:
|
||||
|
||||
## `appId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Your Algolia application ID.
|
||||
|
||||
## `apiKey`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
Your Algolia Search API key.
|
||||
|
||||
## `assistantId`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
The ID for which Ask AI assistant to use.
|
||||
|
||||
## `indexName`
|
||||
|
||||
> `type: string` | **required**
|
||||
|
||||
The name of the index to be used with the Ask AI service.
|
||||
|
||||
## `agentStudio`
|
||||
|
||||
> `type: boolean` | **optional** | **experimental**
|
||||
|
||||
:::warning[Experimental]
|
||||
|
||||
`agentStudio` is currently an experimental property. It is targeted to be stable in release `5.0.0`.
|
||||
|
||||
:::
|
||||
|
||||
If `agentStudio` is true, the Ask AI chat will use Algolia's [Agent Studio][2] as the chat backend instead of the Ask AI backend. More can be learned about setting up Agent Studio on their dedicated [documentation page][3].
|
||||
|
||||
## `searchParameters`
|
||||
|
||||
## `variant`
|
||||
|
||||
> `type: 'floating' | 'inline'` | default: `'floating'` | **optional**
|
||||
|
||||
Variant of the Sidepanel positioning.
|
||||
|
||||
- `inline` pushes page content when opened.
|
||||
- `floating` is positioned above all other content on the page.
|
||||
|
||||
## `side`
|
||||
|
||||
> `type: 'right' | 'left'` | default: `'right'` | **optional**
|
||||
|
||||
The side of the page which the panel will originate from.
|
||||
|
||||
## `width`
|
||||
|
||||
> `type: number | string` | default: `'360px'` | **optional**
|
||||
|
||||
Width of the Sidepanel (px or any CSS width) while in it's default state.
|
||||
|
||||
## `expandedWidth`
|
||||
|
||||
> `type: number | string` | default: `'580px'` | **optional**
|
||||
|
||||
Width of the Sidepanel (px or any CSS width) while in it's expanded state.
|
||||
|
||||
## `suggestedQuestions`
|
||||
|
||||
> `type: boolean` | default: `false` | **optional**
|
||||
|
||||
Enables displaying suggested questions on new conversation screen.
|
||||
|
||||
More information on setting up Suggested Questions can be found on [Algolia Docs][1]
|
||||
|
||||
## `keyboardShortcuts`
|
||||
|
||||
> `type: { 'Ctrl/Cmd+I': boolean }` | **optional**
|
||||
|
||||
Configuration for keyboard shortcuts. Allows enabling/disabling specific shortcuts.
|
||||
|
||||
### Default behavior:
|
||||
|
||||
- `Ctrl/Cmd+I` - Opens and closes the Sidepanel
|
||||
|
||||
### Interface:
|
||||
|
||||
```ts
|
||||
interface SidepanelShortcuts {
|
||||
'Ctrl/Cmd+I'?: boolean; // default: true
|
||||
}
|
||||
```
|
||||
|
||||
## `theme`
|
||||
|
||||
> `type: 'light' | 'dark'` | default: `'light'` | **optional**
|
||||
|
||||
## `portalContainer` (React only)
|
||||
|
||||
> `type: Element | DocumentFragment` | default: `document.body` | **optional**
|
||||
|
||||
The container element where the panel should be portaled to. Use this when you need the Sidepanel to render in a custom DOM node.
|
||||
|
||||
:::warning
|
||||
This prop only exists in the React based versions of Sidepanel. If you are using the `@docsearch/sidepanel-js` package, use the `container` option instead.
|
||||
:::
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="React">
|
||||
```tsx
|
||||
// assume you have a dedicated DOM node in your HTML
|
||||
<div id="sidepanel-root" />
|
||||
|
||||
const portalEl = document.getElementById('sidepanel-root');
|
||||
|
||||
<Sidepanel
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_ASSISTANT_ID"
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
// Render the Sidepanel inside of #sidepanel-root instead of document.body
|
||||
portalContainer={portalEl}
|
||||
/>
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="JavaScript">
|
||||
```js
|
||||
sidepanel({
|
||||
// The element that will contain the Sidepanel Button and Sidepanel
|
||||
container: '#sidepanel-root',
|
||||
indexName: 'YOUR_INDEX_NAME',
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
})
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
[1]: https://www.algolia.com/doc/guides/algolia-ai/askai/guides/suggested-questions
|
||||
[2]: https://www.algolia.com/products/ai/agent-studio
|
||||
[3]: https://www.algolia.com/doc/guides/algolia-ai/agent-studio
|
||||
- [`@docsearch/sidepanel` API](/docs/packages/sidepanel/api)
|
||||
- [`@docsearch/sidepanel-js` API](/docs/packages/sidepanel-js/api)
|
||||
|
|
|
|||
|
|
@ -1,136 +1,11 @@
|
|||
---
|
||||
title: Get started with Sidepanel
|
||||
title: Get started with the sidepanel
|
||||
description: Add the DocSearch Agent Studio sidepanel to your site.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
Choose the sidepanel package for your application:
|
||||
|
||||
:::info
|
||||
Sidepanel is available from version `>= 4.4`
|
||||
:::
|
||||
- Use [`@docsearch/sidepanel`](/docs/packages/sidepanel/getting-started) with React.
|
||||
- Use [`@docsearch/sidepanel-js`](/docs/packages/sidepanel-js/getting-started) with JavaScript.
|
||||
|
||||
## Introduction
|
||||
|
||||
DocSearch Sidepanel is a new experience separate from the DocSearch Modal experience. Sidepanel is built entirely for usage with Ask AI and can be used completely standalone or in [Hybrid mode][1] with the Modal.
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Sidepanel, first you will need to install the needed packages:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/react @docsearch/css
|
||||
|
||||
# Or if using JS based package
|
||||
|
||||
npm install @docsearch/sidepanel-js @docsearch/css
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/react @docsearch/css
|
||||
|
||||
# Or if using JS based package
|
||||
|
||||
yarn add @docsearch/sidepanel-js @docsearch/css
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/react @docsearch/css
|
||||
|
||||
# Or if using JS based package
|
||||
|
||||
pnpm add @docsearch/sidepanel-js @docsearch/css
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/react @docsearch/css
|
||||
|
||||
# Or if using JS based package
|
||||
|
||||
bun add @docsearch/sidepanel-js @docsearch/css
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
> Or using your package manager of choice
|
||||
|
||||
### Without package manager
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css/dist/style.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css/dist/sidepanel.css" />
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@docsearch/sidepanel-js"></script>
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
The simplest implementation of Sidepanel would be as follows:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="React">
|
||||
```tsx
|
||||
import { DocSearchSidepanel } from '@docsearch/react/sidepanel';
|
||||
|
||||
import '@docsearch/css/dist/style.css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<DocSearchSidepanel
|
||||
appId='YOUR_APP_ID'
|
||||
apiKey='YOUR_SEARCH_API_KEY'
|
||||
assistantId='YOUR_ASSISTANT_ID'
|
||||
indexName='YOUR_INDEX_NAME'
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="JavaScript">
|
||||
You will need a `container` DOM node to render the Sidepanel into:
|
||||
|
||||
```html
|
||||
<div id="docsearch-sidepanel"></div>
|
||||
```
|
||||
|
||||
```js
|
||||
import sidepanel from '@docsearch/sidepanel-js';
|
||||
|
||||
import '@docsearch/css/dist/style.css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
|
||||
sidepanel({
|
||||
container: '#docsearch-sidepanel',
|
||||
indexName: 'YOUR_INDEX_NAME',
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||
assistantId: 'YOUR_ASSISTANT_ID',
|
||||
});
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This is just the most basic form of implementation. To learn about other implementation methods, you can read our [Advanced use cases][2].
|
||||
|
||||
To learn more about the different configuration options for Sidepanel, you can read our [Sidepanel API References][3]
|
||||
|
||||
[1]: /docs/sidepanel/hybrid
|
||||
[2]: /docs/sidepanel/advanced-use-cases
|
||||
[3]: /docs/sidepanel/api-reference
|
||||
To combine the sidepanel with keyword search, follow the [Hybrid Mode guide](/docs/hybrid-mode).
|
||||
|
|
|
|||
|
|
@ -1,167 +1,8 @@
|
|||
---
|
||||
title: Hybrid Mode
|
||||
description: Combine DocSearch keyword search with the Agent Studio sidepanel.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
Use [Hybrid Mode](/docs/hybrid-mode) to combine the keyword-search modal with an Agent Studio sidepanel.
|
||||
|
||||
## Introduction
|
||||
|
||||
Sidepanel can run alongside the DocSearch Modal through what we call "Hybrid Mode." When a user initiates an Ask AI action from within
|
||||
the DocSearch Modal, such as submitting a prompt or selecting an AI-related suggestion, the interface automatically transitions into the Sidepanel for
|
||||
the continuation of the conversation.
|
||||
|
||||
## Set up
|
||||
|
||||
To set up the Hybrid Mode experience, you will need the following:
|
||||
|
||||
- [DocSearch Modal][1] packages installed
|
||||
- [Sidepanel Component][2] package installed
|
||||
|
||||
The needed packages can be installed as follows:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npm install @docsearch/css @docsearch/modal @docsearch/sidepanel
|
||||
|
||||
# Or if using JS based packages
|
||||
|
||||
npm install @docsearch/css @docsearch/js @docsearch/sidepanel-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn add @docsearch/css @docsearch/modal @docsearch/sidepanel
|
||||
|
||||
# Or if using JS based packages
|
||||
|
||||
yarn add @docsearch/css @docsearch/js @docsearch/sidepanel-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @docsearch/css @docsearch/modal @docsearch/sidepanel
|
||||
|
||||
# Or if using JS based packages
|
||||
|
||||
pnpm add @docsearch/css @docsearch/js @docsearch/sidepanel-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="bun">
|
||||
|
||||
```bash
|
||||
bun add @docsearch/css @docsearch/modal @docsearch/sidepanel
|
||||
|
||||
# Or if using JS based packages
|
||||
|
||||
bun add @docsearch/css @docsearch/js @docsearch/sidepanel-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
> Or using your package manager of choice
|
||||
|
||||
## Implementation
|
||||
|
||||
Once everything is installed, you can set up Hybrid Mode as such:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="React">
|
||||
|
||||
```tsx
|
||||
import { DocSearch } from '@docsearch/core';
|
||||
import { DocSearchButton, DocSearchModal } from '@docsearch/modal';
|
||||
import { SidepanelButton, Sidepanel } from '@docsearch/sidepanel';
|
||||
|
||||
import '@docsearch/css/dist/style.css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
|
||||
function HybridMode() {
|
||||
return (
|
||||
<DocSearch>
|
||||
<DocSearchButton />
|
||||
<DocSearchModal
|
||||
indices={['YOUR_INDEX_NAME']}
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
askAi="YOUR_ASSISTANT_ID" // Or configuration object
|
||||
/>
|
||||
|
||||
<SidepanelButton />
|
||||
<Sidepanel
|
||||
indexName="YOUR_INDEX_NAME"
|
||||
appId="YOUR_APP_ID"
|
||||
apiKey="YOUR_SEARCH_API_KEY"
|
||||
assistantId="YOUR_ASSISTANT_ID"
|
||||
/>
|
||||
</DocSearch>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
There is no manual opt-in for Hybrid Mode to work. When both the Modal and Sidepanel are rendered inside the same `<DocSearch>` context, Hybrid Mode is enabled automatically. No additional configuration is required.
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="JavaScript">
|
||||
|
||||
```html
|
||||
<div id="docsearch"></div>
|
||||
<div id="sidepanel"></div>
|
||||
```
|
||||
|
||||
```js
|
||||
import docsearch from '@docsearch/js';
|
||||
import sidepanel from '@docsearch/sidepanel-js';
|
||||
|
||||
import '@docsearch/css/dist/style.css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
|
||||
let docsearchInstance = undefined;
|
||||
let sidepanelInstance = undefined;
|
||||
|
||||
sidepanelInstance = sidepanel({
|
||||
container: "#sidepanel",
|
||||
indexName: "YOUR_INDEX_NAME",
|
||||
appId: "YOUR_APP_ID",
|
||||
apiKey: "YOUR_SEARCH_API_KEY",
|
||||
assistantId: "YOUR_ASSISTANT_ID",
|
||||
onOpen: () => {
|
||||
docsearchInstance?.close();
|
||||
}
|
||||
});
|
||||
|
||||
docsearchInstance = docsearch({
|
||||
container: "#docsearch",
|
||||
indices: ["YOUR_INDEX_NAME"],
|
||||
appId: "YOUR_APP_ID",
|
||||
apiKey: "YOUR_SEARCH_API_KEY",
|
||||
askAi: "YOUR_ASSISTANT_ID", // Or configuration object
|
||||
interceptAskAiEvent: (initialMessage) => {
|
||||
docsearchInstance?.close();
|
||||
sidepanelInstance.open(initialMessage);
|
||||
return true;
|
||||
},
|
||||
onOpen: () => {
|
||||
sidepanelInstance?.close();
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
[1]: /docs/docsearch#installation
|
||||
[2]: /docs/sidepanel/getting-started#installation
|
||||
The guide covers React, JavaScript, and Docusaurus integrations.
|
||||
|
|
|
|||
|
|
@ -1,46 +1,11 @@
|
|||
---
|
||||
title: Styling
|
||||
title: Styling DocSearch
|
||||
description: Customize the appearance of DocSearch v5 with CSS packages and bundles.
|
||||
---
|
||||
|
||||
:::info
|
||||
Use [`@docsearch/css`][1] to style DocSearch v5.
|
||||
|
||||
The following content is for **[DocSearch v4][2]**. If you are using **[DocSearch v3][3]**, see the **[legacy][4]** documentation.
|
||||
The package provides a complete stylesheet and separate bundles for the button, keyword modal, Ask AI interface, and sidepanel. See [CSS bundles and exports][2] to choose the files for your integration.
|
||||
|
||||
:::
|
||||
|
||||
## Introduction
|
||||
|
||||
DocSearch v4 comes with a theme package called `@docsearch/css`, which offers a sleek out of the box theme!
|
||||
|
||||
:::note
|
||||
|
||||
This package is a dependency of [`@docsearch/js`][1] and [`@docsearch/react`][1], you don't need to install it if you are using a package manager!
|
||||
|
||||
:::
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @docsearch/css@4
|
||||
```
|
||||
|
||||
If you don’t want to use a package manager, you can use a standalone endpoint:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@4" />
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
@docsearch/css
|
||||
├── dist/style.css # all styles
|
||||
├── dist/_variables.css # CSS variables
|
||||
├── dist/button.css # CSS for the button
|
||||
└── dist/modal.css # CSS for the modal
|
||||
```
|
||||
|
||||
[1]: /docs/docsearch
|
||||
[2]: https://github.com/algolia/docsearch/
|
||||
[3]: https://github.com/algolia/docsearch/tree/master
|
||||
[4]: /docs/v3/docsearch
|
||||
[1]: /docs/packages/css/styling
|
||||
[2]: /docs/packages/css/bundle-exports
|
||||
|
|
|
|||
|
|
@ -1,22 +1,27 @@
|
|||
---
|
||||
title: Config Templates
|
||||
description: Start a DocSearch crawler configuration from a framework template.
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
To help you create the best search experience for your users, we provide out-of-the-box crawler config templates for multiple websites generators. If you'd like to add a new template to our list, or believe we should update an existing one, please [let us know on Discord][1] or [open a pull request][2].
|
||||
Use these crawler configuration templates as a starting point for supported website generators. To add or update a template, [contact us on Discord][1] or [open a pull request][2].
|
||||
|
||||
> If you want to better understand the default parameters of the configs below, take a look at the [Crawler documentation](https://www.algolia.com/doc/tools/crawler/apis/configuration/).
|
||||
|
||||
## Getting Started
|
||||
## Getting started
|
||||
|
||||
Once approved for DocSearch, we will automatically create a Crawler on your behalf, include your URL, and the Algolia creditials for your appId, apiKey, and indexName. If we detected that you are using any of the predefined generators, we'll attempt to automatically assign the proper template that matches your generator. However, this is not gauranteed. If no specific generator is detected, we will apply the default template seen below.
|
||||
After approval, the onboarding flow creates a crawler with your URL and Algolia credentials. If it detects one of the listed generators, it can assign the matching template. Otherwise, it applies the default template.
|
||||
|
||||
## Updating the Template
|
||||
These templates configure indexing, not the installed DocSearch frontend package. In a template, `recordVersion: 'v3'` means the crawler's v3 record schema; it doesn't mean DocSearch UI v3.
|
||||
|
||||
You can manually update the crawler template by going to dashboard.algolia.com, click "Data sources", select your crawler, and go to the editor page. From there you can edit the Javascript directly. Note that you can make draft changes without saving, test the changes using the "URL Tester", and then "Save" once you're happy with your changes.
|
||||
For v5 facets, add each custom filter attribute to `attributesForFaceting` and expose no more than five keys in the frontend. For `resultBadgeKey`, add the selected property to `attributesToRetrieve`. Keep `hierarchy.lvl0` through `hierarchy.lvl6` populated and retrievable so v5 can render result breadcrumbs. See the [v5 JavaScript API reference](/docs/packages/js/api-reference#facets).
|
||||
|
||||
## Default Template
|
||||
## Update a template
|
||||
|
||||
You can manually update the crawler template by going to dashboard.algolia.com, click "Data sources", select your crawler, and go to the editor page. From there you can edit the JavaScript directly. Note that you can make draft changes without saving, test the changes using the "URL Tester", and then "Save" once you're happy with your changes.
|
||||
|
||||
## Default template
|
||||
|
||||
<details>
|
||||
<summary>default.js</summary>
|
||||
|
|
@ -117,7 +122,7 @@ new Crawler({
|
|||
</div>
|
||||
</details>
|
||||
|
||||
## Docusaurus v1 Template
|
||||
## Docusaurus v1 template
|
||||
|
||||
<details>
|
||||
<summary>docusaurus-v1.js</summary>
|
||||
|
|
@ -274,7 +279,7 @@ new Crawler({
|
|||
</div>
|
||||
</details>
|
||||
|
||||
## Docusaurus v2 & v3 Template
|
||||
## Docusaurus v2 and later template
|
||||
|
||||
<details>
|
||||
<summary>docusaurus-v2.js</summary>
|
||||
|
|
@ -296,12 +301,14 @@ new Crawler({
|
|||
pathsToMatch: ['https://YOUR_WEBSITE_URL/**'],
|
||||
recordExtractor: ({ $, helpers }) => {
|
||||
// priority order: deepest active sub list header -> navbar active item -> 'Documentation'
|
||||
// Extracting the breadcrumb titles for better accessibility.
|
||||
const navbarTitle = $('.navbar__item.navbar__link--active').text();
|
||||
const pageBreadcrumbTitles = $('.breadcrumbs__link')
|
||||
.toArray()
|
||||
.map((item) => $(item).text().trim())
|
||||
.filter(Boolean);
|
||||
const lvl0 =
|
||||
$(
|
||||
'.menu__link.menu__link--sublist.menu__link--active, .navbar__item.navbar__link--active'
|
||||
)
|
||||
.last()
|
||||
.text() || 'Documentation';
|
||||
[navbarTitle, ...pageBreadcrumbTitles].join(' / ') || 'Documentation';
|
||||
|
||||
return helpers.docsearch({
|
||||
recordProps: {
|
||||
|
|
@ -413,23 +420,23 @@ new Crawler({
|
|||
// Get the top level menu item
|
||||
const lvl0 =
|
||||
$('details:has(a[aria-current="page"])')
|
||||
.find("summary")
|
||||
.find("span")
|
||||
.text() || "Documentation";
|
||||
.find('summary')
|
||||
.find('span')
|
||||
.text() || 'Documentation';
|
||||
|
||||
return helpers.docsearch({
|
||||
recordProps: {
|
||||
lvl0: {
|
||||
selectors: "",
|
||||
selectors: '',
|
||||
defaultValue: lvl0,
|
||||
},
|
||||
lvl1: "main h1",
|
||||
lvl2: "main h2",
|
||||
lvl3: "main h3",
|
||||
lvl4: "main h4",
|
||||
lvl5: "main h5",
|
||||
lvl6: "main h6",
|
||||
content: "main p, main li",
|
||||
lvl1: 'main h1',
|
||||
lvl2: 'main h2',
|
||||
lvl3: 'main h3',
|
||||
lvl4: 'main h4',
|
||||
lvl5: 'main h5',
|
||||
lvl6: 'main h6',
|
||||
content: 'main p, main li',
|
||||
},
|
||||
indexHeadings: true,
|
||||
aggregateContent: true,
|
||||
|
|
@ -439,16 +446,8 @@ new Crawler({
|
|||
],
|
||||
initialIndexSettings: {
|
||||
YOUR_INDEX_NAME: {
|
||||
attributesForFaceting: [
|
||||
'type',
|
||||
'lang',
|
||||
],
|
||||
attributesToRetrieve: [
|
||||
'hierarchy',
|
||||
'content',
|
||||
'anchor',
|
||||
'url',
|
||||
],
|
||||
attributesForFaceting: ['type', 'lang'],
|
||||
attributesToRetrieve: ['hierarchy', 'content', 'anchor', 'url'],
|
||||
attributesToHighlight: ['hierarchy', 'content'],
|
||||
attributesToSnippet: ['content:10'],
|
||||
camelCaseAttributes: ['hierarchy', 'content'],
|
||||
|
|
@ -813,37 +812,34 @@ new Crawler({
|
|||
pathsToMatch: ['https://YOUR_WEBSITE_URL/**'],
|
||||
recordExtractor: ({ $, helpers }) => {
|
||||
const lvl0 =
|
||||
$(".rspress-nav-menu-item.rspress-nav-menu-item-active")
|
||||
$('.rspress-nav-menu-item.rspress-nav-menu-item-active')
|
||||
.first()
|
||||
.text() || "Documentation";
|
||||
.text() || 'Documentation';
|
||||
|
||||
return helpers.docsearch({
|
||||
recordProps: {
|
||||
lvl0: {
|
||||
selectors: "",
|
||||
selectors: '',
|
||||
defaultValue: lvl0,
|
||||
},
|
||||
lvl1: ".rspress-doc h1",
|
||||
lvl2: ".rspress-doc h2",
|
||||
lvl3: ".rspress-doc h3",
|
||||
lvl4: ".rspress-doc h4",
|
||||
lvl5: ".rspress-doc h5",
|
||||
lvl6: ".rspress-doc pre > code", // if you want to search code blocks, add this line
|
||||
content: ".rspress-doc p, .rspress-doc li",
|
||||
lvl1: '.rspress-doc h1',
|
||||
lvl2: '.rspress-doc h2',
|
||||
lvl3: '.rspress-doc h3',
|
||||
lvl4: '.rspress-doc h4',
|
||||
lvl5: '.rspress-doc h5',
|
||||
lvl6: '.rspress-doc pre > code', // if you want to search code blocks, add this line
|
||||
content: '.rspress-doc p, .rspress-doc li',
|
||||
},
|
||||
indexHeadings: true,
|
||||
aggregateContent: true,
|
||||
recordVersion: "v3",
|
||||
recordVersion: 'v3',
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
initialIndexSettings: {
|
||||
YOUR_INDEX_NAME: {
|
||||
attributesForFaceting: [
|
||||
'type',
|
||||
'lang',
|
||||
],
|
||||
attributesForFaceting: ['type', 'lang'],
|
||||
attributesToRetrieve: [
|
||||
'hierarchy',
|
||||
'content',
|
||||
|
|
|
|||
|
|
@ -1,71 +1,76 @@
|
|||
---
|
||||
title: Tips for a good search
|
||||
description: Improve DocSearch relevance with clear content structure and crawler selectors.
|
||||
---
|
||||
|
||||
DocSearch can work with almost any website, but we've found that some site structures yield more relevant results or faster indexing time. On this page we'll share some tips on how to make the most out of DocSearch.
|
||||
DocSearch works with many website structures, but consistent structure can improve relevance and indexing time. Follow these recommendations to improve your DocSearch results.
|
||||
|
||||
## Use a `sitemap.xml`
|
||||
|
||||
If you provide a sitemap in your configuration, DocSearch will use it to directly browse the pages to index. Pages are still crawled which means we extract every compliant link.
|
||||
If you provide a sitemap in your crawler configuration, DocSearch uses it to find pages to index. The crawler also follows eligible links on those pages.
|
||||
|
||||
We highly recommend you add a `sitemap.xml` to your website if you don't have one already. This will not only make the indexing faster, but also provide you more control over which pages to index.
|
||||
Add a `sitemap.xml` to your website if you don't have one. A sitemap can reduce indexing time and gives you more control over which pages are indexed.
|
||||
|
||||
Sitemaps are also considered good practice for other aspects, including SEO ([more information on sitemaps][1]).
|
||||
Sitemaps can also improve search engine optimization. For more information, see the [sitemaps specification][1].
|
||||
|
||||
## Structure the hierarchy of information
|
||||
|
||||
DocSearch works better on structured documentation. Relevance of results is based on the structural hierarchy of content. In simpler terms, it means that we read the `<h1>`, ..., `<h6>` headings of your page to guess the hierarchy of information. This hierarchy brings contextual information to your records.
|
||||
DocSearch works better on structured documentation. Result relevance uses the structural hierarchy of your content. The crawler reads the `<h1>` through `<h6>` headings or equivalent selectors to build `hierarchy.lvl0` through `hierarchy.lvl6`.
|
||||
|
||||
Documentation starts by explaining generic concepts first and then goes deeper into specifics. This is represented in your HTML markup by the hierarchy of headings you're using. For example, concepts discussed under a `<h4>` are more specific than concepts discussed under a `<h2>` in the same page. The sooner the information comes up within the page, the higher is it ranked.
|
||||
Documentation usually introduces general concepts before covering details. Represent this structure with an ordered heading hierarchy. For example, content under an `<h4>` is more specific than content under an `<h2>` on the same page. Content that appears earlier on the page ranks higher.
|
||||
|
||||
DocSearch uses this structure to fine-tune the relevance of results as well as to provide potential filtering. Documentations that follow this pattern often have better relevance in their search results.
|
||||
DocSearch uses this structure to improve relevance. V5 also uses the populated hierarchy levels to render result breadcrumbs. Keep headings in order and avoid skipping levels where possible so each result retains its page context.
|
||||
|
||||
Finding the right depth of your documentation tree and how to split up your content are two of the most complex tasks. For large pages, we recommend having 4 levels (from `lvl0` to `lvl3`). We recommend at least three different levels.
|
||||
Choose a documentation depth that gives each result enough context. For large pages, use four levels, from `lvl0` to `lvl3`. Use at least three levels.
|
||||
|
||||
_Note that you don't have to use `<hX>` tags and can use classes instead (e.g., `<span class="title-X">` )._
|
||||
You can use classes, such as `<span class="title-X">`, instead of `<hX>` elements.
|
||||
|
||||
## Set a unique class to the element holding the content
|
||||
|
||||
DocSearch extracts content based on the HTML structure. We recommend that you add a custom `class` to the HTML element wrapping all your textual content. This will help narrow selectors to the relevant content.
|
||||
|
||||
Having such a unique identifier will make your configuration more robust as it will make sure indexed content is relevant content. We found that this is the most reliable way to exclude content in headers, sidebars, and footers that are not relevant to the search.
|
||||
A unique identifier makes your configuration more robust and limits indexing to relevant content. Use it to exclude unrelated headers, sidebars, and footers.
|
||||
|
||||
## Add anchors to headings
|
||||
|
||||
When using headings (as mentioned above), you should also try to add a custom anchor to each of them. Anchors are specified by HTML attributes (`name` or `id`) added to headers that allow browsers to directly scroll to the right position in the page. They're accessible by clicking a link with `#` followed by the anchor.
|
||||
Add a custom anchor to each heading. Define anchors with an `id` or `name` HTML attribute so browsers can scroll directly to the corresponding position. Links can target an anchor with `#` followed by its value.
|
||||
|
||||
DocSearch will honor such anchors and automatically bring your users to the anchor closest to the search result they selected.
|
||||
DocSearch uses these anchors to send users to the location of the selected result.
|
||||
|
||||
## Marking the active page(s) in the navigation
|
||||
## Mark active pages in the navigation
|
||||
|
||||
If you're using a multi-level navigation, we recommend that you mark each active level with a custom CSS class. This will make it easier for DocSearch to know _where_ the current page fits in the website hierarchy.
|
||||
If you use multi-level navigation, mark each active level with a custom CSS class. The crawler can use this class to determine where the current page fits in the website hierarchy.
|
||||
|
||||
For example, if your `troubleshooting.html` page is located under the "Installation" menu in your sidebar, we recommend that you add a custom CSS class to the "Installation" and "Troubleshooting" links in your sidebar.
|
||||
|
||||
The name of the CSS class does not matter, as long as it's something that can be used as part of a CSS selector.
|
||||
Use any valid CSS class name that can be part of a CSS selector.
|
||||
|
||||
## Consistency of your content
|
||||
|
||||
Consistency is a pillar of meaningful documentation. It increases the **intelligibility** of a document and shortens the time required for a user to find the coveted information. The document **topic** should be **identifiable** and its **outline** should be demarcated.
|
||||
Use the same heading structure across documentation pages. Make each page topic and outline clear, and avoid selectors that create records without enough context, such as standalone introductions or asides.
|
||||
|
||||
The hierarchy should always have the same size. Try to **avoid orphan records** such as the introduction/conclusion, or asides. The selectors must be efficient for **every document** and highlight the proper hierarchy. They need to match the coveted elements depending on their level. Be careful to avoid the **edge effect** by matching unexpected **superfluous elements**.
|
||||
Write selectors that match documentation pages but exclude landing pages, tables of contents, and other unrelated content. Add a dedicated class, such as `.DocSearch-content`, to the main documentation container.
|
||||
|
||||
Selectors should match information from **real document web pages** and stay ineffective for others ones (e.g., landing page, table of content, etc.). We urge the maintainer to define a **dedicated class** for the **main DOM container** that includes the actual document content such as `.DocSearch-content`
|
||||
Use consistent terms for the same concepts. You can also configure [synonyms][5] for terms your users search interchangeably.
|
||||
|
||||
Since documentation should be **interactive**, it is a key point to **verbalize concepts with standardized words**. This **redundancy**, empowered with the **search experience** (dropdown), will even enable the **learn-as-you-type experience**. The **way to find the information** plays a key role in **leading** the user to the **retrieved knowledge**. You can also use the **synonym feature**.
|
||||
## Avoid duplicate content
|
||||
|
||||
## Avoid duplicates by promoting unicity
|
||||
Split broad topics into focused pages. Avoid catch-all pages that make it difficult to identify the relevant result.
|
||||
|
||||
The more time-consuming reading documentation is, the more painful and reluctant its use will be. You must avoid hazy points or catch-all. With being unhelpful, the catch-all document may be **confusing** and **counterproductive**.
|
||||
Duplicate content adds noise and can mislead users. Don't repeat all documentation content on a landing or summary page. If you need duplicate records for separate datasets, such as different versions, use [facets][3] to distinguish them.
|
||||
|
||||
Duplicates introduce noise and mislead users. This is why you should always focus on the relevant content and avoid duplicating content within your site (for example landing page which contains all information, summing up, etc.). If duplicates are expected because they belong to multiple datasets (for example a different version), you should use [facets][3].
|
||||
## Index metadata for v5
|
||||
|
||||
Add each attribute used by the v5 `facets` option to `attributesForFaceting`. DocSearch supports up to five facet controls. For a result badge, index a short value such as `version`, include it in `attributesToRetrieve`, and pass its property path to `resultBadgeKey`. See the [v5 JavaScript API reference][4].
|
||||
|
||||
## Conciseness
|
||||
|
||||
What is clearly thought out is clearly and concisely expressed.
|
||||
Keep content focused on one task or concept, and use short headings and paragraphs.
|
||||
|
||||
We highly recommend that you read this blog post about [how to build a helpful search for technical documentation][2].
|
||||
For more guidance, read [How to build a helpful search for technical documentation][2].
|
||||
|
||||
[1]: https://www.sitemaps.org/index.html
|
||||
[2]: https://blog.algolia.com/how-to-build-a-helpful-search-for-technical-documentation-the-laravel-example/
|
||||
[3]: https://www.algolia.com/doc/guides/searching/faceting/
|
||||
[4]: /docs/packages/js/api-reference#facets
|
||||
[5]: https://www.algolia.com/doc/guides/managing-results/must-do/searchable-attributes/#synonyms
|
||||
|
|
|
|||
236
packages/website/docs/v5-breaking-changes.mdx
Normal file
236
packages/website/docs/v5-breaking-changes.mdx
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
---
|
||||
title: v5 breaking changes
|
||||
description: Complete user-facing breaking changes and compatibility notes for DocSearch v5.
|
||||
---
|
||||
|
||||
This page lists the user-facing changes between the v4.6.0 package source and `5.0.0-beta.0`. Use it with the [v4 migration guide](./migrating-from-v4).
|
||||
|
||||
## JavaScript entry points
|
||||
|
||||
### The root export is AI-capable
|
||||
|
||||
In v4, the root `@docsearch/js` export rendered the combined component and allowed Ask AI to be omitted. In v5, it renders `DocSearchAI`, and its `DocSearchProps` type requires `askAi`.
|
||||
|
||||
Use the root entry when you configure Agent Studio:
|
||||
|
||||
```js title="app.js"
|
||||
import docsearch from '@docsearch/js';
|
||||
```
|
||||
|
||||
### Keyword-only search moved to `/docsearch`
|
||||
|
||||
Use the new subpath when you don't need Ask AI:
|
||||
|
||||
```js title="app.js"
|
||||
import docsearch from '@docsearch/js/docsearch';
|
||||
```
|
||||
|
||||
This entry excludes Ask AI code.
|
||||
|
||||
### The UMD bundle is split
|
||||
|
||||
- `dist/umd/index.js` includes keyword search and Ask AI.
|
||||
- `dist/umd/docsearch.js` includes keyword search only.
|
||||
- Both bundles expose `window.docsearch`.
|
||||
- Loading both bundles causes the later script to replace the same global.
|
||||
|
||||
### An exports map restricts JavaScript imports
|
||||
|
||||
`@docsearch/js` now exports only `.` and `./docsearch`. Replace imports of internal distribution files with one of these public entry points. Direct CDN URLs to the two documented UMD files remain supported by the package layout.
|
||||
|
||||
## React components
|
||||
|
||||
### `DocSearch` is keyword-only
|
||||
|
||||
V4's `DocSearch` accepted `askAi` and `interceptAskAiEvent`. V5's `DocSearch` contains keyword search only and no longer declares those props.
|
||||
|
||||
### `DocSearchAI` owns the AI experience
|
||||
|
||||
Use `DocSearchAI` for keyword search and Ask AI:
|
||||
|
||||
```jsx title="Search.jsx"
|
||||
import { DocSearchAI } from '@docsearch/react';
|
||||
```
|
||||
|
||||
`DocSearchAIProps` extends `DocSearchProps`, requires `askAi`, and adds `interceptAskAiEvent`.
|
||||
|
||||
The package also adds `@docsearch/react/docsearchAi` and `@docsearch/react/askaiModal` subpaths.
|
||||
|
||||
### The Ask AI modal is separate
|
||||
|
||||
`DocSearchModal` is keyword-only. `DocSearchAskAiModal` contains the combined keyword and AI modal. Composable integrations that rendered `DocSearchModal` with `askAi` must switch to `DocSearchAskAiModal` and its required provider callbacks. Review the [Composable API](/docs/composable-api) instead of constructing these props without the provider.
|
||||
|
||||
`@docsearch/modal` exports the AI modal from its root and from `@docsearch/modal/askai`.
|
||||
|
||||
## Ask AI and Agent Studio
|
||||
|
||||
### The legacy transport is removed
|
||||
|
||||
V5 no longer requests a legacy Ask AI token or sends chat requests to the v4 Ask AI endpoint. All Ask AI conversations use the Agent Studio completions endpoint.
|
||||
|
||||
Create and configure an assistant in [Agent Studio](/docs/agent-studio/getting-started) before upgrading.
|
||||
|
||||
### `askAi.agentStudio` is removed
|
||||
|
||||
The backend switch is no longer needed because Agent Studio is the only backend. Remove both `agentStudio: true` and `agentStudio: false`.
|
||||
|
||||
### `askAi.useStagingEnv` is removed
|
||||
|
||||
The staging endpoint switch isn't part of `DocSearchAskAi` in v5.
|
||||
|
||||
### Flat Ask AI search parameters are removed
|
||||
|
||||
`DocSearchAskAi.searchParameters` now always uses `AgentStudioSearchParameters`: an object keyed by index name.
|
||||
|
||||
```js title="app.js"
|
||||
searchParameters: {
|
||||
docs: {
|
||||
filters: 'language:en',
|
||||
attributesToRetrieve: ['title', 'content', 'url'],
|
||||
distinct: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Each value supports `filters`, `attributesToRetrieve`, `restrictSearchableAttributes`, and `distinct`. The Agent Studio type omits `facetFilters`.
|
||||
|
||||
### Agent Studio credentials are sent directly
|
||||
|
||||
Ask AI requests use the configured application ID and API key in `x-algolia-application-id` and `x-algolia-api-key` headers. Memory authentication adds `x-algolia-secure-user-token`. Check the permissions and domain restrictions of keys that were issued for the legacy transport.
|
||||
|
||||
### Feedback uses Agent Studio
|
||||
|
||||
Feedback now posts to Agent Studio and supports negative-feedback reason tags and notes. Stored conversation messages can contain `feedbackTags` and `feedbackNotes` in addition to the like or dislike value.
|
||||
|
||||
### Agent Studio configuration is nested under `askAi`
|
||||
|
||||
Dynamic `indices`, custom `tools`, `memory`, and keyword `promptSuggestions` belong inside the `askAi` object. `interceptAskAiEvent` remains a top-level integration callback.
|
||||
|
||||
### Suggested questions have two sources
|
||||
|
||||
- `askAi.suggestedQuestions` determines whether DocSearch loads published questions for the assistant from `algolia_ask_ai_suggested_questions` on the new-conversation screen.
|
||||
- `askAi.promptSuggestions` searches a configured index containing a `prompt` attribute and displays those prompts with keyword results.
|
||||
|
||||
These options aren't interchangeable.
|
||||
|
||||
## Search configuration
|
||||
|
||||
### The Docusaurus adapter configuration changed
|
||||
|
||||
The v5 adapter reads `themeConfig.docsearch` and rejects the former `themeConfig.algolia` key. It also requires `indices` and rejects `indexName` and root `searchParameters`.
|
||||
|
||||
Replace `searchPagePath` with `searchPage`. Move `askAi.sidePanel` to the root `sidePanel` option. Remove legacy Ask AI credentials and the `askAi.agentStudio` switch. Follow [Migrate the Docusaurus adapter from v4](/docs/packages/docusaurus-adapter/migrating-from-v4) for before-and-after configurations.
|
||||
|
||||
### At least one index is required at runtime
|
||||
|
||||
Pass `indices` or `indexName`. V5 throws this error when neither produces an index:
|
||||
|
||||
```text
|
||||
Must supply either `indexName` or `indices` for DocSearch to work
|
||||
```
|
||||
|
||||
### `indexName` remains deprecated
|
||||
|
||||
`indexName` still works; it isn't removed in v5. If present, DocSearch places it before all `indices` entries. Passing the same index through both options sends duplicate requests.
|
||||
|
||||
### Root `searchParameters` remains deprecated
|
||||
|
||||
The root option applies only to `indexName`. Move search parameters to each `DocSearchIndex` in `indices`.
|
||||
|
||||
### Multiple indices share one result flow
|
||||
|
||||
V5 creates one source for each index response and combines hit totals across responses. Result order follows the normalized index order. Review code that assumes one index or source identifier.
|
||||
|
||||
## New keyword search behavior
|
||||
|
||||
### Facets add requests and filters
|
||||
|
||||
The new `facets` option fetches facet values with a zero-hit query for every configured index. DocSearch merges and sorts values, supports at most five keys after trimmed, lowercase duplicate checks, and displays only facets with values.
|
||||
|
||||
A selected value is appended to that index's existing `facetFilters`. Account for the additional facet-value request in analytics, rate estimates, and search-client mocks.
|
||||
|
||||
### Result badges require retrieved attributes
|
||||
|
||||
The new `resultBadgeKey` reads a property path from each hit. The default `attributesToRetrieve` list doesn't include custom badge properties. Add them to each relevant index's `searchParameters.attributesToRetrieve`.
|
||||
|
||||
### Result markup and grouping changed
|
||||
|
||||
V5 refreshes the modal and result markup, renders breadcrumbs, introduces source panels, and adds facet and badge elements. CSS selectors, DOM tests, snapshots, and custom overrides that target v4 internals can break.
|
||||
|
||||
Use public component props for behavior and review [Styling](/docs/packages/css/styling) for visual changes.
|
||||
|
||||
## Styles and builds
|
||||
|
||||
### Ask AI styles have a separate source bundle
|
||||
|
||||
The complete `@docsearch/css` stylesheet still imports button, modal, and Ask AI rules. React also exposes split style entries:
|
||||
|
||||
- `@docsearch/react/style/variables`
|
||||
- `@docsearch/react/style/button`
|
||||
- `@docsearch/react/style/modal`
|
||||
- `@docsearch/react/style/askai`
|
||||
- `@docsearch/react/style/sidepanel`
|
||||
|
||||
If you assemble styles by component, add `style/askai` for `DocSearchAI` or `DocSearchAskAiModal`.
|
||||
|
||||
### Generated React file names changed
|
||||
|
||||
The documented package subpaths remain stable, but their targets changed from names such as `dist/esm/DocSearchModal.js` to generated entry files such as `dist/esm/modal.js`. Imports that bypassed the package exports can break.
|
||||
|
||||
### The React `main` field now points to ESM
|
||||
|
||||
`@docsearch/react` changes `main` from `dist/umd/index.js` to `dist/esm/index.js`. Consumers that resolve `main` instead of the package exports need an ESM-compatible build pipeline. The explicit `unpkg` and `jsdelivr` fields continue to point to `dist/umd/index.js`.
|
||||
|
||||
### The browser target is ES2017
|
||||
|
||||
V5's tsdown builds target ES2017. Provide transpilation or polyfills if your browser support policy extends below that target.
|
||||
|
||||
## Public controls
|
||||
|
||||
### JavaScript instances don't expose Sidepanel state
|
||||
|
||||
`DocSearchInstance` exposes `open`, `close`, `openAskAi`, `destroy`, `isReady`, and `isOpen`. It doesn't expose `openSidepanel`, `isSidepanelOpen`, or `isSidepanelSupported`.
|
||||
|
||||
### React refs include Sidepanel controls
|
||||
|
||||
`DocSearchRef` exposes the JavaScript-style modal controls plus `openSidepanel`, `isSidepanelOpen`, and `isSidepanelSupported`. `openSidepanel` does nothing until a Sidepanel view registers. On mobile, `openAskAi` and standard Ask AI actions fall back to the modal.
|
||||
|
||||
See [hybrid mode](/docs/hybrid-mode) for the supported integration.
|
||||
|
||||
### Deprecated keyboard hook fields remain
|
||||
|
||||
`UseDocSearchKeyboardEventsProps.onInput` and `searchButtonRef` are accepted for compatibility but are deprecated and aren't used by the v5 React hook implementation.
|
||||
|
||||
## Compatibility
|
||||
|
||||
### React peer range
|
||||
|
||||
`@docsearch/react`, `@docsearch/core`, `@docsearch/modal`, and `@docsearch/sidepanel` declare these optional peers:
|
||||
|
||||
- `react`: `>=16.8.0 <20.0.0`
|
||||
- `react-dom`: `>=16.8.0 <20.0.0`
|
||||
- `@types/react`: `>=16.8.0 <20.0.0`
|
||||
|
||||
`@docsearch/react` also accepts optional `search-insights` versions `>=1 <3`.
|
||||
|
||||
### Package versions must match
|
||||
|
||||
The `5.0.0-beta.0` packages depend on matching beta versions of the other DocSearch packages. Don't mix v4 and v5 packages in a Composable API or Sidepanel tree.
|
||||
|
||||
### CSS remains a separate install for top-level integrations
|
||||
|
||||
Install `@docsearch/css@^5.0.0-beta`, then import `@docsearch/css`. For a CDN integration, load `dist/style.css` from the same caret beta range.
|
||||
|
||||
## Additive v5 APIs
|
||||
|
||||
These additions aren't breaking by themselves, but they replace common v4 custom implementations:
|
||||
|
||||
- `facets` and `DocSearchFacet` for keyword filters.
|
||||
- `resultBadgeKey` for hit metadata.
|
||||
- `DocSearchAI` and `DocSearchAskAiModal` for AI-capable React views.
|
||||
- `AgentStudioIndices` and `AgentStudioSearchControls` for dynamic search tools.
|
||||
- `ToolCalls` and `ToolDefinition` for custom Agent Studio tools.
|
||||
- `Memory` for user-scoped Agent Studio memory.
|
||||
- `PromptSuggestions` for keyword-query prompt suggestions.
|
||||
- Ask AI feedback tags and notes.
|
||||
- Split JavaScript, React, and style entries for smaller keyword-only builds.
|
||||
|
|
@ -1,24 +1,27 @@
|
|||
---
|
||||
title: What is DocSearch?
|
||||
description: Understand how DocSearch provides search for technical documentation.
|
||||
sidebar_label: What is DocSearch?
|
||||
---
|
||||
|
||||
## Why?
|
||||
|
||||
We created DocSearch because we are scratching our own itch. As developers, we spend a lot of time reading documentation, and it can be hard to find relevant information in large documentations. We're not blaming anyone here: building good search is a challenge.
|
||||
We created DocSearch because developers spend a lot of time reading documentation, and finding relevant information in large documentation sites can be difficult. Building good search is a challenge.
|
||||
|
||||
It happens that we are a search company and we actually have a lot of experience building search interfaces. We wanted to use those skills to help others. That's why we created a way to automatically extract content from tech documentation and make it available to everyone from the first keystroke.
|
||||
Algolia has extensive experience building search interfaces. We use that experience to extract content from technical documentation and make it searchable from the first keystroke.
|
||||
|
||||
## Quick description
|
||||
## Overview
|
||||
|
||||
We split DocSearch into a crawler and a frontend library.
|
||||
DocSearch has two independent parts: indexing and the frontend search experience.
|
||||
|
||||
- Crawls are handled by the [Algolia Crawler][4] and scheduled to run once a week by default, you can then trigger new crawls yourself and monitor them directly from the [Crawler interface][5], which also offers a live editor where you can maintain your config.
|
||||
- The frontend library is built on top of [Algolia Autocomplete][6] and provides an immersive search experience through its modal.
|
||||
- The [Algolia Crawler][4] extracts your documentation into an Algolia index. Use the [Crawler interface][5] to edit the crawler configuration, monitor crawls, and trigger new crawls.
|
||||
- The [DocSearch v5 packages][7] query that index and render keyword search or Ask AI in your frontend. They are built on [Algolia Autocomplete][6].
|
||||
|
||||
Crawler configuration and record schema versions don't select the installed DocSearch frontend package version. You can update the frontend package without changing how the crawler is scheduled.
|
||||
|
||||
## How to feature DocSearch?
|
||||
|
||||
DocSearch is entirely free and automated. The one thing we'll need from you is to read [our checklist][2] and apply! After that, we'll share with you the snippet needed to add DocSearch to your website. We ask that you keep the "Search by Algolia" link displayed.
|
||||
DocSearch is free for eligible documentation sites. Read [the eligibility requirements][2] and apply. After approval and indexing, add a [DocSearch v5 package][7] or a supported framework integration to your website. Keep the "Search by Algolia" link displayed.
|
||||
|
||||
DocSearch is [one of our ways][1] to give back to the open source community for everything it did for us already.
|
||||
|
||||
|
|
@ -30,3 +33,4 @@ You can now [apply to the program][3]
|
|||
[4]: https://www.algolia.com/products/search-and-discovery/crawler/
|
||||
[5]: https://dashboard.algolia.com/crawler
|
||||
[6]: https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/
|
||||
[7]: /docs/packages/overview
|
||||
|
|
|
|||
|
|
@ -1,30 +1,32 @@
|
|||
---
|
||||
title: Who can apply?
|
||||
description: Check whether your documentation project is eligible for DocSearch.
|
||||
---
|
||||
|
||||
**Open for all developer documentation and technical blogs.**
|
||||
**Open to developer documentation and technical blogs.**
|
||||
|
||||
We built DocSearch from the ground up with the idea of improving search on large technical documentations. For this reason, we are offering a free hosting version to all online technical documentations and technical blogs.
|
||||
We built DocSearch to improve search on large technical documentation sites. We offer the free DocSearch program to public technical documentation and technical blogs.
|
||||
|
||||
We usually turn down applications when they are not production ready or have non-technical content on the website.
|
||||
|
||||
## Application process
|
||||
|
||||
To [apply][1] to the DocSearch program, follow the DocSearch onboarding process in the Algolia dashboard where you'll submit your domain for an automated validation check against our requirements. If your domain meets all criteria, you'll be quickly approved to proceed with creating your DocSearch crawler.
|
||||
To [apply][1] to the DocSearch program, follow the onboarding process in the Algolia dashboard. Submit your domain for validation against the program requirements. If your domain meets the criteria, you can create your DocSearch crawler.
|
||||
|
||||
- ✅ Using one of our official integrations will streamline your implementation process after data ingestion.
|
||||
- Use one of our [supported integrations][3] or a [DocSearch v5 package][5] after your content is indexed.
|
||||
|
||||
- ✅ You must verify your domain ownership within 7 days of approval to continue using the crawler.
|
||||
- Verify your domain ownership within 7 days of approval to continue using the crawler.
|
||||
|
||||
- ✅ Please review [DocSearch Plan Terms and Conditions][2].
|
||||
- Review the [DocSearch Plan Terms and Conditions][2].
|
||||
|
||||
## Process duration
|
||||
|
||||
DocSearch application process includes automated validation for faster processing. However, if we can't automatically determine your eligibility, we'll conduct a manual review that may take 1-2 business days.
|
||||
The application process includes automated validation. If we can't determine your eligibility automatically, we'll conduct a manual review that may take one to two business days.
|
||||
|
||||
Once approved, you can continue the onboarding process to create your DocSearch crawler. After your data is ingested into Algolia, you'll need to implement the search UI using either our provided code snippet or one of our [integrations][3].
|
||||
Once approved, continue the onboarding process to create your DocSearch crawler. After the crawler indexes your data, choose the frontend package or framework integration separately. Updating the frontend doesn't change your crawler or index format.
|
||||
|
||||
[1]: https://dashboard.algolia.com/users/sign_up?selected_plan=docsearch&utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=docsearch&utm_content=apply
|
||||
[2]: https://www.algolia.com/policies/docsearch-plan-specific-terms
|
||||
[3]: integrations.md
|
||||
[4]: https://alg.li/discord
|
||||
[5]: /docs/packages/overview
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ import tailwindLoader from './plugins/tailwind-loader.mjs';
|
|||
const SIGNUP_LINK =
|
||||
'https://dashboard.algolia.com/users/sign_up?selected_plan=docsearch&utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=docsearch&utm_content=apply';
|
||||
|
||||
// The MCP app is served at the same origin (/mcp) by hosting, not by
|
||||
// Docusaurus. Linking to the absolute URL makes it an external link so the
|
||||
// build's broken-link check doesn't flag a route Docusaurus doesn't own.
|
||||
const MCP_URL = 'https://docsearch.algolia.com/mcp';
|
||||
|
||||
const currentDate = new Date();
|
||||
const currentYear = currentDate.getFullYear();
|
||||
|
||||
|
|
@ -21,7 +26,7 @@ export default {
|
|||
favicon: 'img/favicon.ico',
|
||||
organizationName: 'Algolia',
|
||||
projectName: 'DocSearch',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenLinks: 'warn',
|
||||
markdown: {
|
||||
hooks: {
|
||||
onBrokenMarkdownLinks: 'throw',
|
||||
|
|
@ -45,6 +50,9 @@ export default {
|
|||
'https://github.com/algolia/docsearch/edit/main/packages/website/',
|
||||
versions: {
|
||||
current: {
|
||||
label: 'Beta (v5.0.0-beta.x)',
|
||||
},
|
||||
v4: {
|
||||
label: 'Stable (v4.x)',
|
||||
},
|
||||
v3: {
|
||||
|
|
@ -75,9 +83,6 @@ export default {
|
|||
indices: [{ name: 'docsearch' }],
|
||||
askAi: {
|
||||
assistantId: 'ccdec697-e3fe-465b-a1c3-657e7bf18aef',
|
||||
promptSuggestions: {
|
||||
indexName: 'docsearch-markdown_prompt_suggestions',
|
||||
},
|
||||
},
|
||||
sidePanel: true,
|
||||
contextualSearch: true,
|
||||
|
|
@ -96,12 +101,8 @@ export default {
|
|||
],
|
||||
navbar: {
|
||||
hideOnScroll: true,
|
||||
logo: {
|
||||
alt: 'DocSearch x Algolia',
|
||||
src: 'img/docsearch-x-algolia-logo-light-mode.png',
|
||||
srcDark: 'img/docsearch-x-algolia-logo-dark-mode.png',
|
||||
className: 'docsearch-nav-logo',
|
||||
},
|
||||
// Brand lockup is rendered by the swizzled Navbar/Logo (MCP vibe);
|
||||
// no image `logo` config needed.
|
||||
items: [
|
||||
// left
|
||||
{
|
||||
|
|
@ -111,19 +112,10 @@ export default {
|
|||
},
|
||||
{
|
||||
label: 'MCP',
|
||||
// Sibling SPA at /mcp — pathname:// bypasses Docusaurus route/link checks.
|
||||
to: 'pathname:///mcp',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Playground',
|
||||
to: 'https://community.algolia.com/docsearch-playground/',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Sign up',
|
||||
to: SIGNUP_LINK,
|
||||
href: MCP_URL,
|
||||
target: '_self',
|
||||
position: 'left',
|
||||
className: 'navbar-mcp-link',
|
||||
},
|
||||
// right
|
||||
{
|
||||
|
|
@ -135,12 +127,18 @@ export default {
|
|||
position: 'right',
|
||||
className: 'header-github-link',
|
||||
},
|
||||
{
|
||||
label: 'Sign up',
|
||||
to: SIGNUP_LINK,
|
||||
position: 'right',
|
||||
className: 'navbar-cta',
|
||||
},
|
||||
],
|
||||
},
|
||||
announcementBar: {
|
||||
id: 'announcement-bar',
|
||||
id: 'docsearch-v5-beta',
|
||||
content:
|
||||
'🚀 Get Ask AI now! Turn your docs site search into an AI-powered assistant – faster answers, fewer tickets, better self-serve. <a target="_blank" rel="noopener noreferrer" href="https://dashboard.algolia.com/ask-ai">Get Started Now</a>',
|
||||
'DocSearch 5.0.0-beta is available. <a href="/docs/migrating-from-v4">Migrate from v4</a> or <a href="/docs/packages/overview">choose a package</a>.',
|
||||
},
|
||||
colorMode: {
|
||||
defaultMode: 'light',
|
||||
|
|
@ -165,8 +163,12 @@ export default {
|
|||
to: 'docs/v3/docsearch',
|
||||
},
|
||||
{
|
||||
label: 'DocSearch v4 - Beta',
|
||||
to: 'docs/docsearch',
|
||||
label: 'DocSearch v4',
|
||||
to: 'docs/v4/docsearch',
|
||||
},
|
||||
{
|
||||
label: 'DocSearch v5 beta',
|
||||
to: 'docs/packages/overview',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -214,13 +216,8 @@ export default {
|
|||
],
|
||||
},
|
||||
],
|
||||
logo: {
|
||||
alt: 'Algolia',
|
||||
src: 'img/docsearch-x-algolia-logo-light-mode.png',
|
||||
srcDark: 'img/docsearch-x-algolia-logo-dark-mode.png',
|
||||
width: 200,
|
||||
},
|
||||
copyright: `2015-${currentYear} – Built with 💙 by Algolia`,
|
||||
// Brand lockup rendered by the swizzled Footer (MCP vibe).
|
||||
copyright: `2015–${currentYear} — Built with <span style="color:var(--accent)">♥</span> by Algolia`,
|
||||
},
|
||||
image: 'img/og_image.png',
|
||||
prism: {
|
||||
|
|
|
|||
|
|
@ -16,20 +16,21 @@
|
|||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docsearch/core": "workspace:*",
|
||||
"@docsearch/css": "workspace:*",
|
||||
"@docsearch/docusaurus-adapter": "workspace:*",
|
||||
"@docsearch/modal": "workspace:*",
|
||||
"@docsearch/react": "workspace:*",
|
||||
"@docusaurus/core": "3.10.2",
|
||||
"@docusaurus/preset-classic": "3.10.2",
|
||||
"@docusaurus/theme-common": "3.10.2",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@segment/analytics-next": "^1.84.0",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cobe": "^0.6.4",
|
||||
"file-loader": "6.2.0",
|
||||
"lucide-react": "^0.525.0",
|
||||
"iconoir-react": "^7.11.1",
|
||||
"mini-svg-data-uri": "^1.4.4",
|
||||
"motion": "^12.23.3",
|
||||
"postcss": "8.5.6",
|
||||
|
|
|
|||
|
|
@ -13,19 +13,87 @@ export default {
|
|||
{
|
||||
type: 'category',
|
||||
label: 'Introduction',
|
||||
items: ['what-is-docsearch', 'who-can-apply'],
|
||||
items: [
|
||||
'what-is-docsearch',
|
||||
'who-can-apply',
|
||||
'migrating-from-v4',
|
||||
'v5-breaking-changes',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'DocSearch v4',
|
||||
label: 'Packages',
|
||||
items: [
|
||||
'docsearch',
|
||||
'docusaurus-adapter',
|
||||
'packages/overview',
|
||||
{
|
||||
type: 'category',
|
||||
label: '@docsearch/js',
|
||||
items: ['packages/js/getting-started', 'packages/js/api-reference'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: '@docsearch/react',
|
||||
items: [
|
||||
'packages/react/getting-started',
|
||||
'packages/react/api-reference',
|
||||
'packages/react/examples',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: '@docsearch/modal',
|
||||
items: ['packages/modal/overview', 'packages/modal/api'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: '@docsearch/sidepanel',
|
||||
items: [
|
||||
'packages/sidepanel/getting-started',
|
||||
'packages/sidepanel/advanced-use-cases',
|
||||
'packages/sidepanel/api',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: '@docsearch/sidepanel-js',
|
||||
items: [
|
||||
'packages/sidepanel-js/getting-started',
|
||||
'packages/sidepanel-js/api',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: '@docsearch/css',
|
||||
items: ['packages/css/styling', 'packages/css/bundle-exports'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: '@docsearch/core',
|
||||
items: ['packages/core/overview', 'packages/core/api'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: '@docsearch/docusaurus-adapter',
|
||||
items: [
|
||||
'packages/docusaurus-adapter/getting-started',
|
||||
'packages/docusaurus-adapter/configuration-reference',
|
||||
'packages/docusaurus-adapter/migrating-from-v4',
|
||||
],
|
||||
},
|
||||
'composable-api',
|
||||
'styling',
|
||||
'api',
|
||||
'examples',
|
||||
'migrating-from-v3',
|
||||
'hybrid-mode',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Agent Studio',
|
||||
items: [
|
||||
'agent-studio/getting-started',
|
||||
'agent-studio/dynamic-indices',
|
||||
'agent-studio/tools',
|
||||
'agent-studio/memory',
|
||||
'agent-studio/prompt-suggestions',
|
||||
'agent-studio/feedback',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -33,34 +101,6 @@ export default {
|
|||
label: 'MCP',
|
||||
items: ['mcp/overview', 'mcp/installation', 'mcp/usage'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Algolia Ask AI',
|
||||
items: [
|
||||
'v4/askai',
|
||||
'v4/askai-api',
|
||||
'v4/askai-prompts',
|
||||
'v4/askai-whitelisted-domains',
|
||||
'v4/askai-models',
|
||||
'v4/askai-markdown-indexing',
|
||||
'v4/askai-errors',
|
||||
{
|
||||
type: 'link',
|
||||
label: 'Full Documentation',
|
||||
href: 'https://www.algolia.com/doc/guides/algolia-ai/askai',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Sidepanel',
|
||||
items: [
|
||||
'sidepanel/getting-started',
|
||||
'sidepanel/advanced-use-cases',
|
||||
'sidepanel/hybrid',
|
||||
'sidepanel/api-reference',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Algolia Crawler',
|
||||
|
|
|
|||
565
packages/website/src/components/AsciiBackdrop.jsx
Normal file
565
packages/website/src/components/AsciiBackdrop.jsx
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
import { useColorMode } from '@docusaurus/theme-common';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* Ambient "cursor wake" backdrop (ported from the DocSearch MCP app): a faint
|
||||
* field of source-code characters inside the page gradient. Moving the mouse
|
||||
* stirs the field — glyphs along the pointer path flicker and a few resolve
|
||||
* into accent-colored characters (the crawler parsing raw text into docs). A
|
||||
* DocSearch logomark is stamped as cell-art on the right.
|
||||
*
|
||||
* Zero-dependency canvas 2D engine. Reads --font-mono and --backdrop-glyph*
|
||||
* tokens. Respects prefers-reduced-motion (renders a single static frame) and
|
||||
* pauses when hidden or scrolled out of view.
|
||||
*/
|
||||
|
||||
const CELL_W = 14;
|
||||
const CELL_H = 18;
|
||||
const FONT_SIZE = 11;
|
||||
const SPRITE_PAD = 3;
|
||||
const MAX_DPR = 2;
|
||||
const MAX_CELLS = 6000;
|
||||
const FRAME_MS = 1000 / 24;
|
||||
const MAX_STEP_MS = 100;
|
||||
const ALPHA_CULL = 0.015;
|
||||
|
||||
const FIELD_DENSITY = 0.45;
|
||||
const FIELD_SEED = 0x0c5eed;
|
||||
const ROLL_SEED = 0xa11a9e;
|
||||
const FLICKER_MS = 320;
|
||||
const ACCENT_MS = 1200;
|
||||
const ACCENT_CHANCE = 0.08;
|
||||
const HOT_BOOST = 0.7;
|
||||
const POINTER_RADIUS = 8;
|
||||
const POINTER_STIR_FULL = 3;
|
||||
const Y_ASPECT = CELL_H / CELL_W;
|
||||
|
||||
const FIELD_GLYPHS =
|
||||
'abcdefghijklmnopqrstuvwxyz0123456789{}[]()<>/=;:.+-*&|#$%_?!{}[]()<>/=;:.+-*&|#$%_?!~^\'"`\\@,λ';
|
||||
const SAFE_GLYPHS = '{}<>/;=*&#';
|
||||
const DECODE_GLYPHS = 'docsearchmp';
|
||||
|
||||
const LOGO_ART = [
|
||||
'.................',
|
||||
' .....',
|
||||
' ----------- ...',
|
||||
'-------------- ..',
|
||||
' ..',
|
||||
' --------------- ..',
|
||||
' --------------- ..',
|
||||
' .',
|
||||
' --------------- ..',
|
||||
' --------------- ..',
|
||||
' ..',
|
||||
' ------------- ..',
|
||||
' ----------- ...',
|
||||
' ....',
|
||||
' .................',
|
||||
];
|
||||
const LOGO_MARGIN_MIN = 4;
|
||||
const LOGO_MARGIN_FRAC = 0.12;
|
||||
const LOGO_BASE = 0.34;
|
||||
const LOGO_GLOW_RADIUS = 12;
|
||||
const LOGO_GLOW = 0.55;
|
||||
const NO_HOME = 0xffff;
|
||||
|
||||
/* Mulberry32 PRNG — bitwise ops are intentional. */
|
||||
/* eslint-disable no-bitwise */
|
||||
function mulberry32(seed) {
|
||||
let a = seed;
|
||||
return () => {
|
||||
a |= 0;
|
||||
a = (a + 0x6d2b79f5) | 0;
|
||||
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
/* eslint-enable no-bitwise */
|
||||
|
||||
function auditionGlyphs(candidates, font) {
|
||||
const probe = document.createElement('canvas');
|
||||
probe.width = 24;
|
||||
probe.height = 24;
|
||||
const ctx = probe.getContext('2d', { willReadFrequently: true });
|
||||
if (!ctx) return candidates;
|
||||
const signature = (ch) => {
|
||||
ctx.clearRect(0, 0, 24, 24);
|
||||
ctx.font = font;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillText(ch, 12, 12);
|
||||
return ctx.getImageData(0, 0, 24, 24).data.join();
|
||||
};
|
||||
const tofu = signature('');
|
||||
const blank = signature(' ');
|
||||
const kept = candidates.filter((ch) => {
|
||||
const sig = signature(ch);
|
||||
return sig !== tofu && sig !== blank;
|
||||
});
|
||||
return kept.length > 0 ? kept : [...SAFE_GLYPHS];
|
||||
}
|
||||
|
||||
function buildAtlas(glyphs, font, colors, dpr) {
|
||||
const cw = CELL_W + SPRITE_PAD * 2;
|
||||
const ch = CELL_H + SPRITE_PAD * 2;
|
||||
const sprites = document.createElement('canvas');
|
||||
sprites.width = Math.ceil(cw * dpr) * glyphs.length;
|
||||
sprites.height = Math.ceil(ch * dpr) * 2;
|
||||
const ctx = sprites.getContext('2d');
|
||||
if (!ctx) return null;
|
||||
const spriteW = sprites.width / glyphs.length;
|
||||
const spriteH = sprites.height / 2;
|
||||
ctx.setTransform(spriteW / cw, 0, 0, spriteH / ch, 0, 0);
|
||||
ctx.font = font;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
for (let row = 0; row < 2; row++) {
|
||||
ctx.fillStyle = colors[row];
|
||||
for (let i = 0; i < glyphs.length; i++) {
|
||||
ctx.fillText(glyphs[i], i * cw + cw / 2, row * ch + ch / 2);
|
||||
}
|
||||
}
|
||||
return { sprites, spriteW, spriteH };
|
||||
}
|
||||
|
||||
function createCursorWakeScene(fieldCount, decodeCount, logoGlyph) {
|
||||
const roll = mulberry32(ROLL_SEED);
|
||||
let time = 0;
|
||||
let pointerOn = false;
|
||||
let px = 0;
|
||||
let py = 0;
|
||||
let prevX = 0;
|
||||
let prevY = 0;
|
||||
|
||||
let base = new Float32Array(0);
|
||||
let phase = new Float32Array(0);
|
||||
let speed = new Float32Array(0);
|
||||
let cx = new Float32Array(0);
|
||||
let cy = new Float32Array(0);
|
||||
let hot = new Float32Array(0);
|
||||
let flicker = new Float32Array(0);
|
||||
let accentT = new Float32Array(0);
|
||||
let home = new Uint16Array(0);
|
||||
|
||||
const scene = {
|
||||
cols: 0,
|
||||
rows: 0,
|
||||
glyph: new Uint16Array(0),
|
||||
alpha: new Float32Array(0),
|
||||
accent: new Uint8Array(0),
|
||||
reinit,
|
||||
step,
|
||||
setPointer,
|
||||
clearPointer,
|
||||
};
|
||||
|
||||
function setPointer(x, y) {
|
||||
if (!pointerOn) {
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
}
|
||||
pointerOn = true;
|
||||
px = x;
|
||||
py = y;
|
||||
}
|
||||
|
||||
function clearPointer() {
|
||||
pointerOn = false;
|
||||
}
|
||||
|
||||
function reinit(cols, rows) {
|
||||
const rng = mulberry32(FIELD_SEED);
|
||||
const count = cols * rows;
|
||||
scene.cols = cols;
|
||||
scene.rows = rows;
|
||||
scene.glyph = new Uint16Array(count);
|
||||
scene.alpha = new Float32Array(count);
|
||||
scene.accent = new Uint8Array(count);
|
||||
base = new Float32Array(count);
|
||||
phase = new Float32Array(count);
|
||||
speed = new Float32Array(count);
|
||||
cx = new Float32Array(count);
|
||||
cy = new Float32Array(count);
|
||||
hot = new Float32Array(count);
|
||||
flicker = new Float32Array(count);
|
||||
accentT = new Float32Array(count);
|
||||
home = new Uint16Array(count).fill(NO_HOME);
|
||||
for (let i = 0; i < count; i++) {
|
||||
base[i] = rng() < FIELD_DENSITY ? 0.12 + rng() * 0.18 : 0;
|
||||
scene.glyph[i] = Math.floor(rng() * fieldCount);
|
||||
phase[i] = rng() * Math.PI * 2;
|
||||
speed[i] = (Math.PI * 2) / (9000 + rng() * 9000);
|
||||
cx[i] = (i % cols) + 0.5;
|
||||
cy[i] = (Math.floor(i / cols) + 0.5) * Y_ASPECT;
|
||||
scene.alpha[i] = base[i];
|
||||
}
|
||||
const logoW = Math.max(...LOGO_ART.map((line) => line.length));
|
||||
const logoH = LOGO_ART.length;
|
||||
const margin = Math.max(
|
||||
LOGO_MARGIN_MIN,
|
||||
Math.round(cols * LOGO_MARGIN_FRAC)
|
||||
);
|
||||
const x0 = cols - logoW - margin;
|
||||
if (x0 >= 2 && rows >= logoH + 2) {
|
||||
const y0 = Math.round((rows - logoH) / 2);
|
||||
for (let r = 0; r < logoH; r++) {
|
||||
const line = LOGO_ART[r];
|
||||
for (let c = 0; c < line.length; c++) {
|
||||
if (line[c] !== ' ') {
|
||||
const i = (y0 + r) * cols + (x0 + c);
|
||||
const g = logoGlyph(line[c]);
|
||||
base[i] = LOGO_BASE + rng() * 0.08;
|
||||
home[i] = g;
|
||||
scene.glyph[i] = g;
|
||||
scene.alpha[i] = base[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function step(dt) {
|
||||
time += dt;
|
||||
const { cols, rows, glyph, alpha, accent } = scene;
|
||||
const count = cols * rows;
|
||||
const sx = prevX;
|
||||
const sy = prevY * Y_ASPECT;
|
||||
const dx = px - prevX;
|
||||
const dy = (py - prevY) * Y_ASPECT;
|
||||
const segLen2 = dx * dx + dy * dy;
|
||||
const strength = pointerOn
|
||||
? Math.min(1, Math.sqrt(segLen2) / POINTER_STIR_FULL)
|
||||
: 0;
|
||||
const pxa = px;
|
||||
const pya = py * Y_ASPECT;
|
||||
/* eslint-disable no-continue -- hot path: skip empty / culled cells */
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (base[i] === 0) continue;
|
||||
let a = base[i] * (0.85 + 0.15 * Math.sin(phase[i] + time * speed[i]));
|
||||
if (pointerOn && home[i] !== NO_HOME) {
|
||||
const gx = cx[i] - pxa;
|
||||
const gy = cy[i] - pya;
|
||||
const g2 = gx * gx + gy * gy;
|
||||
if (g2 < LOGO_GLOW_RADIUS * LOGO_GLOW_RADIUS) {
|
||||
a += (1 - Math.sqrt(g2) / LOGO_GLOW_RADIUS) * LOGO_GLOW;
|
||||
}
|
||||
}
|
||||
if (strength > 0) {
|
||||
let t =
|
||||
segLen2 > 0 ? ((cx[i] - sx) * dx + (cy[i] - sy) * dy) / segLen2 : 0;
|
||||
t = Math.min(1, Math.max(0, t));
|
||||
const qx = cx[i] - (sx + t * dx);
|
||||
const qy = cy[i] - (sy + t * dy);
|
||||
const d = Math.sqrt(qx * qx + qy * qy);
|
||||
if (d < POINTER_RADIUS) {
|
||||
const heat = (1 - d / POINTER_RADIUS) * strength;
|
||||
if (heat > hot[i]) hot[i] = heat;
|
||||
if (heat > 0.25 && flicker[i] <= 0) flicker[i] = FLICKER_MS;
|
||||
if (accent[i] === 0 && roll() < ACCENT_CHANCE * heat) {
|
||||
accent[i] = 1;
|
||||
accentT[i] = ACCENT_MS;
|
||||
glyph[i] = fieldCount + Math.floor(roll() * decodeCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flicker[i] > 0) {
|
||||
flicker[i] -= dt;
|
||||
if (accent[i] === 0) {
|
||||
glyph[i] =
|
||||
flicker[i] <= 0 && home[i] !== NO_HOME
|
||||
? home[i]
|
||||
: Math.floor(roll() * fieldCount);
|
||||
}
|
||||
}
|
||||
if (hot[i] > 0) {
|
||||
a += hot[i] * HOT_BOOST;
|
||||
hot[i] = Math.max(0, hot[i] - dt / 900);
|
||||
}
|
||||
if (accent[i] === 1) {
|
||||
accentT[i] -= dt;
|
||||
if (accentT[i] <= 0) {
|
||||
accent[i] = 0;
|
||||
glyph[i] =
|
||||
home[i] !== NO_HOME ? home[i] : Math.floor(roll() * fieldCount);
|
||||
} else {
|
||||
const p = 1 - accentT[i] / ACCENT_MS;
|
||||
a = Math.max(a, Math.sin(Math.PI * p) * 0.65);
|
||||
}
|
||||
}
|
||||
alpha[i] = a > 1 ? 1 : a;
|
||||
}
|
||||
/* eslint-enable no-continue */
|
||||
prevX = px;
|
||||
prevY = py;
|
||||
}
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
function createBackdropEngine(canvas) {
|
||||
const rawCtx = canvas.getContext('2d');
|
||||
if (!rawCtx) return { setThemeDirty: () => {}, destroy: () => {} };
|
||||
const ctx = rawCtx;
|
||||
|
||||
const root = document.documentElement;
|
||||
const monoStack =
|
||||
getComputedStyle(root).getPropertyValue('--font-mono').trim() ||
|
||||
'monospace';
|
||||
const font = `400 ${FONT_SIZE}px ${monoStack}`;
|
||||
|
||||
const fieldGlyphs = auditionGlyphs([...FIELD_GLYPHS], font);
|
||||
const glyphs = [...fieldGlyphs, ...DECODE_GLYPHS];
|
||||
const scene = createCursorWakeScene(
|
||||
fieldGlyphs.length,
|
||||
DECODE_GLYPHS.length,
|
||||
(ch) => {
|
||||
const i = fieldGlyphs.indexOf(ch);
|
||||
return i >= 0 ? i : Math.max(0, fieldGlyphs.indexOf('.'));
|
||||
}
|
||||
);
|
||||
|
||||
let dpr = 1;
|
||||
let cssW = 0;
|
||||
let cssH = 0;
|
||||
let cellW = CELL_W;
|
||||
let cellH = CELL_H;
|
||||
let atlas = null;
|
||||
|
||||
let rafId = null;
|
||||
let lastTs = -1;
|
||||
let pointerClientX = 0;
|
||||
let pointerClientY = 0;
|
||||
let pointerDirty = false;
|
||||
let atlasDirty = true;
|
||||
let disposed = false;
|
||||
let contextLost = false;
|
||||
let hidden = document.hidden;
|
||||
let intersecting = false;
|
||||
const reducedQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
let reduced = reducedQuery.matches;
|
||||
|
||||
const running = () =>
|
||||
!disposed && !reduced && !hidden && intersecting && !contextLost;
|
||||
|
||||
function scheduleFrame() {
|
||||
if (rafId === null && !disposed && !contextLost) {
|
||||
rafId = requestAnimationFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
function wake() {
|
||||
lastTs = -1;
|
||||
scheduleFrame();
|
||||
}
|
||||
|
||||
function frame(ts) {
|
||||
rafId = null;
|
||||
if (disposed || contextLost) return;
|
||||
const active = running();
|
||||
if (active) scheduleFrame();
|
||||
if (active && lastTs >= 0 && ts - lastTs < FRAME_MS && !atlasDirty) return;
|
||||
if (atlasDirty) {
|
||||
const styles = getComputedStyle(root);
|
||||
const colors = [
|
||||
styles.getPropertyValue('--backdrop-glyph').trim() ||
|
||||
'rgb(128 128 128 / 0.4)',
|
||||
styles.getPropertyValue('--backdrop-glyph-accent').trim() ||
|
||||
'rgb(84 104 255 / 0.6)',
|
||||
];
|
||||
atlas = buildAtlas(glyphs, font, colors, dpr);
|
||||
atlasDirty = false;
|
||||
}
|
||||
if (active) {
|
||||
if (pointerDirty) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
scene.setPointer(
|
||||
(pointerClientX - rect.left) / cellW,
|
||||
(pointerClientY - rect.top) / cellH
|
||||
);
|
||||
pointerDirty = false;
|
||||
}
|
||||
const dt = lastTs < 0 ? 0 : Math.min(ts - lastTs, MAX_STEP_MS);
|
||||
lastTs = lastTs < 0 ? ts : ts - ((ts - lastTs) % FRAME_MS);
|
||||
scene.step(dt);
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!atlas || ctx.isContextLost?.()) return;
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
ctx.clearRect(0, 0, cssW, cssH);
|
||||
const { glyph, alpha, accent, cols, rows } = scene;
|
||||
const { sprites, spriteW, spriteH } = atlas;
|
||||
const dw = CELL_W + SPRITE_PAD * 2;
|
||||
const dh = CELL_H + SPRITE_PAD * 2;
|
||||
const count = cols * rows;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const a = alpha[i];
|
||||
if (a > ALPHA_CULL) {
|
||||
ctx.globalAlpha = a > 1 ? 1 : a;
|
||||
ctx.drawImage(
|
||||
sprites,
|
||||
glyph[i] * spriteW,
|
||||
accent[i] * spriteH,
|
||||
spriteW,
|
||||
spriteH,
|
||||
(i % cols) * cellW - SPRITE_PAD,
|
||||
Math.floor(i / cols) * cellH - SPRITE_PAD,
|
||||
dw,
|
||||
dh
|
||||
);
|
||||
}
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
function resize() {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
if (rect.width === 0 || rect.height === 0) return;
|
||||
cssW = rect.width;
|
||||
cssH = rect.height;
|
||||
const nextDpr = Math.min(window.devicePixelRatio || 1, MAX_DPR);
|
||||
if (nextDpr !== dpr) {
|
||||
dpr = nextDpr;
|
||||
atlasDirty = true;
|
||||
}
|
||||
const bufW = Math.round(cssW * dpr);
|
||||
const bufH = Math.round(cssH * dpr);
|
||||
if (canvas.width !== bufW) canvas.width = bufW;
|
||||
if (canvas.height !== bufH) canvas.height = bufH;
|
||||
cellW = CELL_W;
|
||||
cellH = CELL_H;
|
||||
let cols = Math.ceil(cssW / cellW);
|
||||
let rows = Math.ceil(cssH / cellH);
|
||||
if (cols * rows > MAX_CELLS) {
|
||||
const s = Math.sqrt((cols * rows) / MAX_CELLS);
|
||||
cellW *= s;
|
||||
cellH *= s;
|
||||
cols = Math.ceil(cssW / cellW);
|
||||
rows = Math.ceil(cssH / cellH);
|
||||
}
|
||||
if (cols !== scene.cols || rows !== scene.rows) scene.reinit(cols, rows);
|
||||
wake();
|
||||
}
|
||||
|
||||
const resizeObserver = new ResizeObserver(resize);
|
||||
resizeObserver.observe(canvas);
|
||||
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
intersecting = entries[entries.length - 1]?.isIntersecting ?? true;
|
||||
if (running()) wake();
|
||||
});
|
||||
io.observe(canvas);
|
||||
|
||||
const onVisibility = () => {
|
||||
hidden = document.hidden;
|
||||
if (running()) wake();
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisibility);
|
||||
|
||||
const onPointerMove = (event) => {
|
||||
pointerClientX = event.clientX;
|
||||
pointerClientY = event.clientY;
|
||||
pointerDirty = true;
|
||||
};
|
||||
const onPointerGone = () => {
|
||||
pointerDirty = false;
|
||||
scene.clearPointer();
|
||||
};
|
||||
window.addEventListener('pointermove', onPointerMove, { passive: true });
|
||||
document.documentElement.addEventListener('pointerleave', onPointerGone);
|
||||
window.addEventListener('blur', onPointerGone);
|
||||
|
||||
const onReducedChange = (event) => {
|
||||
reduced = event.matches;
|
||||
wake();
|
||||
};
|
||||
reducedQuery.addEventListener('change', onReducedChange);
|
||||
|
||||
const onContextLost = (event) => {
|
||||
event.preventDefault();
|
||||
contextLost = true;
|
||||
};
|
||||
const onContextRestored = () => {
|
||||
contextLost = false;
|
||||
atlasDirty = true;
|
||||
wake();
|
||||
};
|
||||
canvas.addEventListener('contextlost', onContextLost);
|
||||
canvas.addEventListener('contextrestored', onContextRestored);
|
||||
|
||||
document.fonts
|
||||
?.load(font)
|
||||
.then(() => {
|
||||
if (!disposed) {
|
||||
atlasDirty = true;
|
||||
scheduleFrame();
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
return {
|
||||
setThemeDirty: () => {
|
||||
atlasDirty = true;
|
||||
scheduleFrame();
|
||||
},
|
||||
destroy: () => {
|
||||
disposed = true;
|
||||
if (rafId !== null) cancelAnimationFrame(rafId);
|
||||
rafId = null;
|
||||
resizeObserver.disconnect();
|
||||
io.disconnect();
|
||||
document.removeEventListener('visibilitychange', onVisibility);
|
||||
window.removeEventListener('pointermove', onPointerMove);
|
||||
document.documentElement.removeEventListener(
|
||||
'pointerleave',
|
||||
onPointerGone
|
||||
);
|
||||
window.removeEventListener('blur', onPointerGone);
|
||||
reducedQuery.removeEventListener('change', onReducedChange);
|
||||
canvas.removeEventListener('contextlost', onContextLost);
|
||||
canvas.removeEventListener('contextrestored', onContextRestored);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function AsciiBackdrop({ className }) {
|
||||
const canvasRef = useRef(null);
|
||||
const engineRef = useRef(null);
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return undefined;
|
||||
const engine = createBackdropEngine(canvas);
|
||||
engineRef.current = engine;
|
||||
return () => {
|
||||
engine.destroy();
|
||||
engineRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
engineRef.current?.setThemeDirty();
|
||||
}, [colorMode]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
aria-hidden="true"
|
||||
className={
|
||||
'pointer-events-none absolute inset-x-0 top-0 -z-10 h-[min(48vh,560px)] w-full forced-colors:hidden print:hidden' +
|
||||
(className ? ` ${className}` : '')
|
||||
}
|
||||
style={{
|
||||
maskImage: 'linear-gradient(to bottom, black 55%, transparent)',
|
||||
WebkitMaskImage: 'linear-gradient(to bottom, black 55%, transparent)',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default AsciiBackdrop;
|
||||
107
packages/website/src/components/DemoShowcase.jsx
Normal file
107
packages/website/src/components/DemoShowcase.jsx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { useColorMode } from '@docusaurus/theme-common';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
// Homepage showcase: a same-origin iframe running a live, self-driving DocSearch
|
||||
// widget. The iframe is what contains the modal/side panel (which attach to
|
||||
// their document's body) and keeps them from taking over the whole page.
|
||||
//
|
||||
// This host component:
|
||||
// - lazy-loads the iframe when it scrolls near the viewport,
|
||||
// - tells the iframe when it is on/off screen (so the tour pauses when hidden),
|
||||
// - keeps the iframe's theme in sync with the site's light/dark mode.
|
||||
export default function DemoShowcase() {
|
||||
const { colorMode } = useColorMode();
|
||||
// Resolve the embed URL through Docusaurus so it respects `baseUrl`.
|
||||
const baseSrc = useBaseUrl('/demo-embed');
|
||||
const wrapperRef = useRef(null);
|
||||
const iframeRef = useRef(null);
|
||||
const colorModeRef = useRef(colorMode);
|
||||
const baseSrcRef = useRef(baseSrc);
|
||||
baseSrcRef.current = baseSrc;
|
||||
const loadedRef = useRef(false);
|
||||
const [src, setSrc] = useState(null);
|
||||
|
||||
const post = useCallback((message) => {
|
||||
iframeRef.current?.contentWindow?.postMessage(message, '*');
|
||||
}, []);
|
||||
|
||||
// Lazy-load + visibility tracking.
|
||||
useEffect(() => {
|
||||
const el = wrapperRef.current;
|
||||
if (!el || typeof IntersectionObserver === 'undefined') return undefined;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const entry = entries[0];
|
||||
if (entry.isIntersecting && !loadedRef.current) {
|
||||
loadedRef.current = true;
|
||||
setSrc(`${baseSrcRef.current}?theme=${colorModeRef.current}`);
|
||||
}
|
||||
post({
|
||||
type: 'docsearch-demo-visibility',
|
||||
visible: entry.intersectionRatio >= 0.35,
|
||||
});
|
||||
},
|
||||
{ threshold: [0, 0.35, 0.7, 1], rootMargin: '300px 0px' }
|
||||
);
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, [post]);
|
||||
|
||||
// Keep the iframe theme in sync with the site.
|
||||
useEffect(() => {
|
||||
colorModeRef.current = colorMode;
|
||||
post({ type: 'docsearch-demo-theme', theme: colorMode });
|
||||
}, [colorMode, post]);
|
||||
|
||||
// When the iframe reports it's ready, push the current theme + visibility.
|
||||
useEffect(() => {
|
||||
const onMessage = (event) => {
|
||||
if (event.data?.type !== 'docsearch-demo-ready') return;
|
||||
post({ type: 'docsearch-demo-theme', theme: colorModeRef.current });
|
||||
const el = wrapperRef.current;
|
||||
if (!el) return;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const visible =
|
||||
rect.top < window.innerHeight * 0.65 &&
|
||||
rect.bottom > window.innerHeight * 0.35;
|
||||
post({ type: 'docsearch-demo-visibility', visible });
|
||||
};
|
||||
window.addEventListener('message', onMessage);
|
||||
return () => window.removeEventListener('message', onMessage);
|
||||
}, [post]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="mx-auto w-full max-w-6xl overflow-hidden rounded-2xl border border-[var(--border)] bg-[var(--surface)] shadow-sm"
|
||||
>
|
||||
<div className="flex items-center gap-3 border-b border-[var(--border)] px-4 py-2.5">
|
||||
<span className="hidden gap-1.5 sm:flex" aria-hidden="true">
|
||||
<span className="h-3 w-3 rounded-full bg-[var(--border-strong,#d0d3d7)]" />
|
||||
<span className="h-3 w-3 rounded-full bg-[var(--border-strong,#d0d3d7)]" />
|
||||
<span className="h-3 w-3 rounded-full bg-[var(--border-strong,#d0d3d7)]" />
|
||||
</span>
|
||||
<div className="mx-auto w-full max-w-sm truncate rounded-md border border-[var(--border)] bg-[var(--bg,transparent)] px-3 py-1 text-center text-[12px] text-[var(--text-tertiary)]">
|
||||
docs.your-project.com
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative aspect-[4/5] w-full bg-[var(--surface)] sm:aspect-[16/10]">
|
||||
{src ? (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={src}
|
||||
title="DocSearch live demo"
|
||||
loading="lazy"
|
||||
className="absolute inset-0 h-full w-full border-0"
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center text-[13px] text-[var(--text-tertiary)]">
|
||||
Loading live demo…
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
packages/website/src/components/DocSearchMark.jsx
Normal file
28
packages/website/src/components/DocSearchMark.jsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
|
||||
/**
|
||||
* DocSearch logomark — concentric-arc swirl (Algolia blue). Ported from the
|
||||
* DocSearch MCP app (docsearch-logo.tsx). Sized via className.
|
||||
*/
|
||||
export function DocSearchMark({ className }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 76 78"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fillRule="nonzero"
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M36.493 77.289H3.074C1.376 77.289 0 75.919 0 74.228h36.523c12.769.062 24.6-6.663 31.038-17.642 6.438-10.979 6.504-24.543.174-35.584C61.404 9.962 49.639 3.122 36.87 3.061H0C0 1.37 1.376 0 3.074 0h33.796c10.31.01 20.193 4.1 27.473 11.369 7.279 7.268 11.36 17.12 11.341 27.384-.06 21.366-17.741 38.536-39.19 38.536z"
|
||||
fill="#5468FF"
|
||||
/>
|
||||
<path
|
||||
d="M0 69.045h23.711c2.931.01 5.761-1.071 7.933-3.031H0v3.031zM0 60.8h36.374c.734-.987 1.418-1.975 2.053-3.07H0v3.07zM0 52.546h41.025c.407-.987.774-1.975 1.091-3.06H0v3.06zM0 44.302h43.306c.149-.987.268-2.034.337-3.061H0v3.061zM0 36.058h43.633a29.95 29.95 0 0 1-.337-3.071H0v3.071zM0 27.804h42.116c-.317-1.037-.684-2.064-1.09-3.061H0v3.061zM0 19.559h38.427a32.68 32.68 0 0 1-2.053-3.06H0v3.06zM0 8.244v3.071h31.674A19.04 19.04 0 0 0 23.74 8.284L0 8.244z"
|
||||
fill="#5468FF"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default DocSearchMark;
|
||||
|
|
@ -1,305 +1,232 @@
|
|||
import { useColorMode } from '@docusaurus/theme-common';
|
||||
import { useBaseUrlUtils } from '@docusaurus/useBaseUrl';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Github } from 'iconoir-react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { AsciiBackdrop } from './AsciiBackdrop';
|
||||
import DemoShowcase from './DemoShowcase';
|
||||
import { Button, PrimaryButton } from './ui/button';
|
||||
import { IntroducingSection } from './ui/features';
|
||||
import { FeaturesBento } from './ui/features-bento';
|
||||
import { FlipWords } from './ui/flip-words';
|
||||
import Keyboard from './ui/keyboard';
|
||||
import { Logos } from './ui/logos';
|
||||
import { Spotlight } from './ui/spotlight';
|
||||
import { Reveal } from './ui/reveal';
|
||||
|
||||
function formatTime(sec) {
|
||||
if (!sec || isNaN(sec)) return '0:00';
|
||||
const m = Math.floor(sec / 60);
|
||||
const s = Math.floor(sec % 60);
|
||||
return `${m}:${s.toString().padStart(2, '0')}`;
|
||||
const SIGNUP_LINK =
|
||||
'https://dashboard.algolia.com/users/sign_up?selected_plan=docsearch&utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=docsearch&utm_content=apply';
|
||||
|
||||
function useGithubStars(repo) {
|
||||
const [stars, setStars] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setIsLoading(true);
|
||||
|
||||
fetch(`https://api.github.com/repos/${repo}`)
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((data) => {
|
||||
if (cancelled || !data || typeof data.stargazers_count !== 'number')
|
||||
return;
|
||||
const n = data.stargazers_count;
|
||||
setStars(n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n));
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
if (!cancelled) setIsLoading(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [repo]);
|
||||
|
||||
return { isLoading, stars };
|
||||
}
|
||||
|
||||
function VideoPlayer({ chapters }) {
|
||||
const videoRef = useRef(null);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(1);
|
||||
function Hero() {
|
||||
const { isLoading, stars } = useGithubStars('algolia/docsearch');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<video
|
||||
loop={true}
|
||||
muted={true}
|
||||
playsInline={true}
|
||||
autoPlay={true}
|
||||
ref={videoRef}
|
||||
className="bg-blue-100 w-full md:w-4xl mx-auto h-auto rounded-md"
|
||||
preload="auto"
|
||||
poster="/img/resources/hero-video-poster.png"
|
||||
onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}
|
||||
onLoadedMetadata={(e) => setDuration(e.target.duration)}
|
||||
>
|
||||
<source src="/img/resources/askai720p.mp4" type="video/mp4" />
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
{/* Video chapter controls below video */}
|
||||
<div className="relative w-full max-w-2xl mx-auto mt-4">
|
||||
{/* Time labels */}
|
||||
<div className="flex justify-between text-xs text-slate-500 mt-8">
|
||||
<span>{formatTime(currentTime)}</span>
|
||||
<span>{formatTime(duration)}</span>
|
||||
</div>
|
||||
{/* Progress bar */}
|
||||
<div
|
||||
className="h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden relative cursor-pointer"
|
||||
role="slider"
|
||||
tabIndex={0}
|
||||
aria-valuenow={currentTime}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={duration}
|
||||
onClick={(e) => {
|
||||
const bar = e.currentTarget;
|
||||
const rect = bar.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const percent = x / rect.width;
|
||||
if (videoRef.current && duration) {
|
||||
videoRef.current.currentTime = percent * duration;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="h-2 bg-blue-500 transition-all absolute top-0 left-0 rounded-full pointer-events-none"
|
||||
style={{ width: `${(currentTime / duration) * 100}%` }}
|
||||
/>
|
||||
{/* Chapter markers... */}
|
||||
</div>
|
||||
{/* Chapter buttons below the bar */}
|
||||
<div className="absolute left-0 w-full" style={{ top: '1.5rem' }}>
|
||||
{chapters.map((chapter) => (
|
||||
<div
|
||||
key={chapter.label}
|
||||
className="absolute flex flex-col items-center"
|
||||
style={{
|
||||
left: `${(chapter.time / duration) * 100}%`,
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
<section className="relative isolate">
|
||||
<AsciiBackdrop />
|
||||
<div className="mx-auto w-full max-w-6xl px-4 pb-10 pt-16 text-left md:px-0 md:pt-24">
|
||||
<Reveal variant="mask" delay={40}>
|
||||
<p className="max-w-3xl font-display text-[34px] font-semibold leading-[1.08] tracking-[-0.03em] text-[var(--text)] sm:text-[40px]">
|
||||
Search made
|
||||
<br />
|
||||
for <span className="text-[var(--accent)]">documentation</span>
|
||||
</p>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={200}>
|
||||
<p className="mt-5 max-w-xl text-[14px] leading-relaxed text-[var(--text-secondary)]">
|
||||
DocSearch by Algolia makes your docs and blogs instantly searchable
|
||||
— fast, relevant, and AI-ready. Free for open-source and technical
|
||||
docs.
|
||||
</p>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={300}>
|
||||
<div className="mt-7 flex flex-wrap items-stretch gap-2.5">
|
||||
<PrimaryButton href={SIGNUP_LINK}>
|
||||
Sign up — it's free
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={400}>
|
||||
<div className="mt-5 flex flex-wrap items-center gap-x-5 gap-y-1.5 text-[12px] text-[var(--text-tertiary)]">
|
||||
<a
|
||||
href="https://github.com/algolia/docsearch"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1.5 text-[var(--text-tertiary)] no-underline! transition-colors hover:text-[var(--text)]"
|
||||
>
|
||||
{/* Arrow/triangle */}
|
||||
<div
|
||||
style={{
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderInlineStart: '8px solid transparent',
|
||||
borderInlineEnd: '8px solid transparent',
|
||||
borderBlockEnd: '8px solid #2563eb', // blue-600
|
||||
marginBlockEnd: '-2px',
|
||||
}}
|
||||
/>
|
||||
{/* Button */}
|
||||
<button
|
||||
className="px-3 py-0.5 rounded bg-blue-600 text-white text-xs font-semibold shadow hover:bg-blue-800 transition max-w-[120px] min-w-[60px] whitespace-normal break-words text-center"
|
||||
style={{ minWidth: 0 }}
|
||||
type="button"
|
||||
title={chapter.label}
|
||||
onClick={() => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.currentTime = chapter.time;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{chapter.label}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Github width={14} height={14} aria-hidden={true} />
|
||||
{isLoading ? (
|
||||
<output className="inline-flex items-center">
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="size-3 animate-spin rounded-full border-2 border-[var(--text-tertiary)] border-t-transparent"
|
||||
/>
|
||||
<span className="sr-only">Loading GitHub star count</span>
|
||||
</output>
|
||||
) : (
|
||||
<span className="tabular text-[var(--text-secondary)]">
|
||||
{stars ?? '—'} stars on GitHub
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionHeading({ eyebrow, title, subtitle }) {
|
||||
return (
|
||||
<div className="mx-auto mb-12 max-w-3xl text-center">
|
||||
{eyebrow ? (
|
||||
<p className="mb-3 font-mono text-[11px] uppercase tracking-[0.16em] text-[var(--accent)] !mb-0">
|
||||
{eyebrow}
|
||||
</p>
|
||||
) : null}
|
||||
<h2 className="font-display text-[28px] font-semibold leading-tight tracking-[-0.02em] text-[var(--text)] md:text-[36px]">
|
||||
{title}
|
||||
</h2>
|
||||
{subtitle ? (
|
||||
<p className="mt-3 text-[15px] text-[var(--text-secondary)] md:text-[17px]">
|
||||
{subtitle}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileSearchDemo() {
|
||||
const openSearch = useCallback(() => {
|
||||
document
|
||||
.querySelector("[class*='navbarSearchContainer'] .DocSearch-Button")
|
||||
?.click();
|
||||
}, []);
|
||||
const openAskAi = useCallback(() => {
|
||||
document.querySelector('.DocSearch-SidepanelButton')?.click();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md rounded-2xl border border-[var(--border)] bg-[var(--surface)] p-5 shadow-sm md:hidden">
|
||||
<p className="mb-5 text-center text-[14px] text-[var(--text-secondary)]">
|
||||
Search the docs or ask AI a question.
|
||||
</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Button className="h-12 w-full text-[15px]" onClick={openSearch}>
|
||||
Search documentation
|
||||
</Button>
|
||||
<PrimaryButton className="h-12 w-full text-[15px]" onClick={openAskAi}>
|
||||
Ask AI
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Home() {
|
||||
const { withBaseUrl } = useBaseUrlUtils();
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const videoChapters = [
|
||||
{ label: 'Keyword', time: 9 },
|
||||
{ label: 'Ask AI', time: 37 },
|
||||
{ label: 'Conversations', time: 65 },
|
||||
{ label: 'Dark Mode', time: 103 },
|
||||
];
|
||||
|
||||
React.useEffect(() => {
|
||||
if (colorMode === 'dark') {
|
||||
document.querySelector('html').classList.add('dark');
|
||||
} else {
|
||||
document.querySelector('html').classList.remove('dark');
|
||||
}
|
||||
}, [colorMode]);
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
<div className="mt-20 mb-10 snap-start">
|
||||
<Spotlight
|
||||
className="-top-40 left-0 md:-top-20 md:left-60 z-[10]"
|
||||
fill="white"
|
||||
/>
|
||||
<div className="flex flex-col items-center rounded-md p-10 pb-0">
|
||||
<div className="text-center font-[Sora] text-black dark:text-white">
|
||||
<div className="flex items-center justify-center mb-2">
|
||||
<span
|
||||
role="img"
|
||||
aria-label="sparkles"
|
||||
className="mr-2 animate-pulse"
|
||||
>
|
||||
✨
|
||||
</span>
|
||||
<span className="text-blue-600 font-semibold text-lg md:text-xl shimmer-effect mb-2">
|
||||
Celebrating 10 Years of DocSearch
|
||||
</span>
|
||||
<span
|
||||
role="img"
|
||||
aria-label="sparkles"
|
||||
className="ml-2 animate-pulse"
|
||||
>
|
||||
✨
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-center text-4xl font-bold bg-gradient-to-tl from-neutral-900 to-neutral-600 md:text-8xl dark:bg-gradient-to-b dark:from-neutral-50 dark:to-neutral-400 bg-clip-text text-transparent">
|
||||
Search Made For Documentation
|
||||
</p>
|
||||
<p className="text-base md:text-2xl">
|
||||
DocSearch by Algolia makes your docs and blogs instantly
|
||||
searchable—
|
||||
<span className="font-black">for free</span>.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex my-12 gap-8">
|
||||
<Button href={withBaseUrl('docs/what-is-docsearch')}>
|
||||
Find out more
|
||||
</Button>
|
||||
<PrimaryButton
|
||||
href={
|
||||
'https://dashboard.algolia.com/users/sign_up?selected_plan=docsearch&utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=docsearch&utm_content=apply'
|
||||
}
|
||||
>
|
||||
Sign up
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
<VideoPlayer chapters={videoChapters} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Description() {
|
||||
return (
|
||||
<>
|
||||
{/* Showcase */}
|
||||
<div className="py-16 overflow-hidden snap-start">
|
||||
<div className="relative max-w-xl mx-auto px-4 md:px-6 lg:px-8 lg:max-w-screen-xl">
|
||||
<div className="max-w-screen-xl mx-auto mb-16 px-4 md:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<p className="text-3xl text-black dark:text-white font-bold leading-9 font-[Sora] md:text-4xl md:leading-10">
|
||||
Already trusted by your favorite docs
|
||||
</p>
|
||||
<p className="text-lg md:text-2xl text-slate-400 dark:text-slate-500">
|
||||
Join 7,000+ projects finding answers in milliseconds
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Logos />
|
||||
|
||||
<div className="w-full flex justify-center">
|
||||
<a
|
||||
href="https://github.com/algolia/docsearch/network/dependents"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className="text-center text-lg text-slate-400 dark:text-slate-500"
|
||||
>
|
||||
...And <span className="font-bold">much more!</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="py-16 overflow-hidden snap-start">
|
||||
<div className="relative max-w-xl mx-auto px-4 md:px-6 lg:px-8 lg:max-w-screen-xl">
|
||||
<div className="max-w-screen-xl mx-auto mb-16 px-4 md:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<p className="text-3xl text-black dark:text-white font-bold leading-9 font-[Sora] md:text-4xl md:leading-10">
|
||||
Solve docs challenges with a search engine
|
||||
</p>
|
||||
<p className="text-lg md:text-2xl text-slate-400 dark:text-slate-500">
|
||||
Docs are only helpful when your users can find answers easily.
|
||||
Enter DocSearch.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<FeaturesBento />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Introducing Section */}
|
||||
<IntroducingSection />
|
||||
|
||||
<div className="py-16 overflow-hidden snap-start">
|
||||
<div className="relative max-w-xl mx-auto px-4 md:px-6 lg:px-8 lg:max-w-screen-xl">
|
||||
<div className="max-w-screen-xl mx-auto mb-16 px-4 md:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto text-center flex flex-col items-center gap-4">
|
||||
<div className="mb-12 text-center">
|
||||
<span className="inline-block px-4 py-2 rounded-full bg-blue-50 dark:bg-blue-900 text-blue-700 dark:text-blue-200 font-semibold text-lg">
|
||||
Over 10 years of
|
||||
<span className="mx-1">
|
||||
<kbd className="inline-block px-1 py-0.5 mx-1 bg-white dark:bg-blue-800 border rounded text-base md:text-lg font-mono align-middle">
|
||||
{typeof navigator !== 'undefined' &&
|
||||
/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)
|
||||
? '⌘'
|
||||
: 'Ctrl'}
|
||||
</kbd>
|
||||
<kbd className="inline-block px-1 py-0.5 bg-white dark:bg-blue-800 border rounded text-base md:text-lg font-mono align-middle">
|
||||
K
|
||||
</kbd>
|
||||
</span>
|
||||
– the OG search shortcut, still going strong
|
||||
</span>
|
||||
</div>
|
||||
<Keyboard />
|
||||
<div className="text-3xl mb-4 mt-12 text-black dark:text-white font-bold leading-9 font-[Sora] md:text-4xl md:leading-10">
|
||||
Build{' '}
|
||||
<FlipWords
|
||||
className="text-blue-600"
|
||||
words={[
|
||||
'faster',
|
||||
'smarter',
|
||||
'freely',
|
||||
'simpler',
|
||||
'better',
|
||||
'everything',
|
||||
'NOW!',
|
||||
]}
|
||||
/>{' '}
|
||||
<br />
|
||||
with DocSearch
|
||||
</div>
|
||||
<PrimaryButton
|
||||
key="apply"
|
||||
href={
|
||||
'https://dashboard.algolia.com/users/sign_up?selected_plan=docsearch&utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=docsearch&utm_content=apply'
|
||||
}
|
||||
>
|
||||
Sign up for free
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
document.body.classList.add('homepage');
|
||||
return () => document.body.classList.remove('homepage');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="container snap-y z-[10] snap-proximity">
|
||||
<Header />
|
||||
<Description />
|
||||
<Hero />
|
||||
|
||||
<section className="px-4 pt-16 pb-10 md:px-0">
|
||||
<SectionHeading
|
||||
eyebrow="Interactive demo"
|
||||
title="See DocSearch in action"
|
||||
/>
|
||||
<MobileSearchDemo />
|
||||
<div className="hidden md:block">
|
||||
<DemoShowcase />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Trusted by */}
|
||||
<div className="overflow-hidden py-16">
|
||||
<div className="mx-auto w-full max-w-6xl px-4 md:px-0">
|
||||
<SectionHeading
|
||||
title="Already trusted by your favorite docs"
|
||||
subtitle="Join 9,000+ projects finding answers in milliseconds"
|
||||
/>
|
||||
<Logos />
|
||||
<div className="mt-8 flex w-full justify-center">
|
||||
<a
|
||||
href="https://github.com/algolia/docsearch/network/dependents"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className="link-underline text-[14px] text-[var(--text-tertiary)] no-underline!"
|
||||
>
|
||||
…And much more!
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="overflow-hidden py-16">
|
||||
<div className="mx-auto w-full max-w-6xl px-4 md:px-0">
|
||||
<SectionHeading
|
||||
eyebrow="Built for developers"
|
||||
title="Solve docs challenges with a search engine"
|
||||
subtitle="Docs are only helpful when your users can find answers easily. Enter DocSearch."
|
||||
/>
|
||||
<FeaturesBento />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cmd+K */}
|
||||
<div className="overflow-hidden py-16">
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-col items-center gap-6 px-4 text-center md:px-0">
|
||||
<Keyboard />
|
||||
<h2 className="mt-6 font-display text-[28px] font-semibold leading-tight tracking-[-0.02em] text-[var(--text)] md:text-[36px]">
|
||||
Build{' '}
|
||||
<FlipWords
|
||||
className="text-[var(--accent)]"
|
||||
words={[
|
||||
'faster',
|
||||
'smarter',
|
||||
'freely',
|
||||
'simpler',
|
||||
'better',
|
||||
'everything',
|
||||
'NOW!',
|
||||
]}
|
||||
/>{' '}
|
||||
<br />
|
||||
with DocSearch
|
||||
</h2>
|
||||
<PrimaryButton href={SIGNUP_LINK}>Sign up for free</PrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
205
packages/website/src/components/demo/DemoApp.jsx
Normal file
205
packages/website/src/components/demo/DemoApp.jsx
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
import { DocSearch } from '@docsearch/core';
|
||||
import { DocSearchAskAiModal, DocSearchButton } from '@docsearch/modal';
|
||||
import { DocSearchSidepanel } from '@docsearch/react/sidepanel';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { installAskAiMock } from './askai-fixture';
|
||||
import { createAutopilot } from './autopilot';
|
||||
|
||||
// Public credentials — the same ones baked into the live site and the shipped
|
||||
// demos (docusaurus.config.mjs / examples/*).
|
||||
const APP_ID = 'PMZUYBQDAK';
|
||||
const API_KEY = '24b09689d5b4223813d9b8e48563c8f6';
|
||||
const INDEX_NAME = 'docsearch';
|
||||
const ASSISTANT_ID = 'ccdec697-e3fe-465b-a1c3-657e7bf18aef';
|
||||
|
||||
const NAV_LINKS = ['Guides', 'API', 'Integrations'];
|
||||
const POPULAR = ['installation', 'react', 'ask ai', 'crawler'];
|
||||
|
||||
function getInitialTheme() {
|
||||
if (typeof window === 'undefined') return 'light';
|
||||
const param = new URLSearchParams(window.location.search).get('theme');
|
||||
return param === 'dark' ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
export default function DemoApp() {
|
||||
const modalRef = useRef(null);
|
||||
const sidepanelRef = useRef(null);
|
||||
const [theme, setTheme] = useState(getInitialTheme);
|
||||
|
||||
// Open documentation links in a new tab so clicking a result never navigates
|
||||
// the iframe away from the demo.
|
||||
const navigator = useMemo(() => {
|
||||
const openInNewTab = (url) => {
|
||||
if (url) window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
return {
|
||||
navigate: ({ itemUrl, item }) => openInNewTab(itemUrl ?? item?.url),
|
||||
navigateNewTab: ({ itemUrl, item }) => openInNewTab(itemUrl ?? item?.url),
|
||||
navigateNewWindow: ({ itemUrl, item }) =>
|
||||
openInNewTab(itemUrl ?? item?.url),
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Keep the site design tokens (colours + fonts from custom.css, which are
|
||||
// theme-scoped on <html data-theme>) in sync with the demo theme.
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}, [theme]);
|
||||
|
||||
// Keep scrolling — and the keyboard — contained inside the iframe. Focusing an
|
||||
// element (the search input / prompt on open) or `scrollIntoView` (streaming
|
||||
// answers) otherwise scrolls ancestors across the iframe boundary, which yanks
|
||||
// the parent page back to the demo. So `focus` defaults to `preventScroll` and
|
||||
// is gated on real user intent (see below), and `scrollIntoView` only moves
|
||||
// the nearest scrollable ancestor within the frame.
|
||||
useEffect(() => {
|
||||
const nativeFocus = HTMLElement.prototype.focus;
|
||||
const nativeScrollIntoView = Element.prototype.scrollIntoView;
|
||||
|
||||
// The widget focuses its input when the modal opens. Inside an iframe that
|
||||
// pulls document focus off the host page, so the site's own ⌘K handler
|
||||
// never sees the keystroke and this demo answers it instead — the demo
|
||||
// looks like it has trapped the keyboard. So programmatic focus only lands
|
||||
// once the visitor has actually reached into the demo; the autopilot drives
|
||||
// the widget with synthetic `input` events, which don't require focus.
|
||||
let userEngaged = false;
|
||||
const engage = (event) => {
|
||||
if (event.isTrusted) userEngaged = true;
|
||||
};
|
||||
// Focus left the frame (the visitor clicked back out onto the page), so the
|
||||
// demo has to earn it again rather than grabbing it on the next tour loop.
|
||||
const disengage = () => {
|
||||
userEngaged = false;
|
||||
};
|
||||
const engageEvents = ['pointerdown', 'keydown', 'touchstart'];
|
||||
engageEvents.forEach((name) =>
|
||||
document.addEventListener(name, engage, { capture: true, passive: true })
|
||||
);
|
||||
window.addEventListener('blur', disengage);
|
||||
|
||||
HTMLElement.prototype.focus = function focusNoScroll(options) {
|
||||
if (!userEngaged) return undefined;
|
||||
return nativeFocus.call(this, { preventScroll: true, ...(options ?? {}) });
|
||||
};
|
||||
|
||||
Element.prototype.scrollIntoView = function containedScrollIntoView(arg) {
|
||||
const behavior = typeof arg === 'object' && arg?.behavior ? arg.behavior : 'auto';
|
||||
let ancestor = this.parentElement;
|
||||
while (ancestor) {
|
||||
const overflowY = getComputedStyle(ancestor).overflowY;
|
||||
if ((overflowY === 'auto' || overflowY === 'scroll') && ancestor.scrollHeight > ancestor.clientHeight) {
|
||||
const top =
|
||||
this.getBoundingClientRect().top - ancestor.getBoundingClientRect().top + ancestor.scrollTop;
|
||||
ancestor.scrollTo({ top, behavior });
|
||||
return;
|
||||
}
|
||||
ancestor = ancestor.parentElement;
|
||||
}
|
||||
// No scrollable ancestor inside the frame → intentionally do nothing so we
|
||||
// never scroll the parent document.
|
||||
};
|
||||
|
||||
return () => {
|
||||
HTMLElement.prototype.focus = nativeFocus;
|
||||
Element.prototype.scrollIntoView = nativeScrollIntoView;
|
||||
engageEvents.forEach((name) =>
|
||||
document.removeEventListener(name, engage, { capture: true })
|
||||
);
|
||||
window.removeEventListener('blur', disengage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const autopilot = createAutopilot({
|
||||
modalRef,
|
||||
sidepanelRef,
|
||||
indexName: INDEX_NAME,
|
||||
});
|
||||
const restoreFetch = installAskAiMock(() => autopilot.isAutopilotActive());
|
||||
autopilot.start();
|
||||
|
||||
const onMessage = (event) => {
|
||||
const data = event.data;
|
||||
if (!data || typeof data !== 'object') return;
|
||||
if (data.type === 'docsearch-demo-visibility')
|
||||
autopilot.setVisible(Boolean(data.visible));
|
||||
if (
|
||||
data.type === 'docsearch-demo-theme' &&
|
||||
(data.theme === 'light' || data.theme === 'dark')
|
||||
) {
|
||||
setTheme(data.theme);
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', onMessage);
|
||||
|
||||
// Tell the host we're ready so it can push the current visibility/theme.
|
||||
try {
|
||||
window.parent?.postMessage({ type: 'docsearch-demo-ready' }, '*');
|
||||
} catch {
|
||||
/* cross-origin host — visibility just falls back to the IntersectionObserver ping */
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', onMessage);
|
||||
autopilot.stop();
|
||||
restoreFetch();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="ds-demo" data-theme={theme}>
|
||||
<header className="ds-demo-topbar">
|
||||
<div className="ds-demo-brand" aria-hidden="true">
|
||||
<span className="ds-demo-brand-name">Acme Docs</span>
|
||||
</div>
|
||||
<nav className="ds-demo-nav" aria-label="Docs">
|
||||
{NAV_LINKS.map((link) => (
|
||||
<span key={link} className="ds-demo-nav-link">
|
||||
{link}
|
||||
</span>
|
||||
))}
|
||||
</nav>
|
||||
<div className="ds-demo-actions">
|
||||
<DocSearchSidepanel
|
||||
ref={sidepanelRef}
|
||||
theme={theme}
|
||||
indexName={INDEX_NAME}
|
||||
appId={APP_ID}
|
||||
apiKey={API_KEY}
|
||||
assistantId={ASSISTANT_ID}
|
||||
button={{ variant: 'inline' }}
|
||||
panel={{ suggestedQuestions: true }}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="ds-demo-hero">
|
||||
<p className="ds-demo-eyebrow">Acme documentation</p>
|
||||
<h1 className="ds-demo-title">What can we help you find?</h1>
|
||||
<div className="ds-demo-search">
|
||||
<DocSearch ref={modalRef} theme={theme}>
|
||||
<DocSearchButton
|
||||
translations={{ buttonText: 'Search or ask a question' }}
|
||||
/>
|
||||
<DocSearchAskAiModal
|
||||
indices={[INDEX_NAME]}
|
||||
appId={APP_ID}
|
||||
apiKey={API_KEY}
|
||||
askAi={{ assistantId: ASSISTANT_ID }}
|
||||
navigator={navigator}
|
||||
/>
|
||||
</DocSearch>
|
||||
</div>
|
||||
<div className="ds-demo-popular" aria-hidden="true">
|
||||
<span className="ds-demo-popular-label">Popular</span>
|
||||
{POPULAR.map((term) => (
|
||||
<span key={term} className="ds-demo-chip">
|
||||
{term}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
217
packages/website/src/components/demo/askai-fixture.js
Normal file
217
packages/website/src/components/demo/askai-fixture.js
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
// Ask AI mock for the homepage auto-demo.
|
||||
//
|
||||
// The autopilot runs against the real DocSearch widget, but we don't want to
|
||||
// fire a real Agent Studio completion for every homepage visitor. So while the
|
||||
// autopilot is driving, we intercept the completions request and replay a
|
||||
// canned Server-Sent-Events stream in the exact `ai-sdk-5` UI-message-stream
|
||||
// wire format the widget expects (verified against a real capture).
|
||||
//
|
||||
// The instant a real user takes over (`isAutopilotActive()` returns false),
|
||||
// requests fall through to the untouched `window.fetch`, so their questions hit
|
||||
// the real assistant.
|
||||
|
||||
// Matches the Agent Studio completions endpoint used by `getAgentStudioTransport`
|
||||
// in `@docsearch/react` (`.../agent-studio/1/agents/<id>/completions`).
|
||||
const COMPLETIONS_RE = /\/agent-studio\/1\/agents\/[^/]+\/completions/;
|
||||
|
||||
// Canned answers. We pick one based on the user's question so the demo reads as
|
||||
// if the assistant actually understood the prompt.
|
||||
const ANSWERS = [
|
||||
{
|
||||
match: /instal|setup|set up|add|start|docusaurus|react|vanilla/i,
|
||||
markdown: `To add DocSearch to your documentation site, pick the integration that matches your stack:
|
||||
|
||||
### React
|
||||
|
||||
\`\`\`bash
|
||||
npm install @docsearch/react@4 @docsearch/css@4
|
||||
\`\`\`
|
||||
|
||||
\`\`\`jsx
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
import '@docsearch/css';
|
||||
|
||||
<DocSearch appId="YOUR_APP_ID" indexName="YOUR_INDEX" apiKey="YOUR_SEARCH_KEY" />
|
||||
\`\`\`
|
||||
|
||||
### Docusaurus (recommended)
|
||||
|
||||
\`\`\`bash
|
||||
npm install @docsearch/docusaurus-adapter
|
||||
\`\`\`
|
||||
|
||||
\`\`\`js
|
||||
// docusaurus.config.js
|
||||
plugins: ['@docsearch/docusaurus-adapter'],
|
||||
themeConfig: {
|
||||
docsearch: {
|
||||
appId: 'YOUR_APP_ID',
|
||||
apiKey: 'YOUR_SEARCH_KEY',
|
||||
indexName: 'YOUR_INDEX',
|
||||
},
|
||||
},
|
||||
\`\`\`
|
||||
|
||||
That's it — the widget wires up \`Cmd\`+\`K\`, search-as-you-type, and Ask AI automatically. See the [Getting started guide](https://docsearch.algolia.com/docs/docsearch/) for the full walkthrough.`,
|
||||
suggestions: [
|
||||
'How do I get DocSearch API credentials?',
|
||||
'Can DocSearch work with static sites?',
|
||||
'How do I enable Ask AI?',
|
||||
],
|
||||
},
|
||||
{
|
||||
match: /.*/,
|
||||
markdown: `DocSearch pairs Algolia's search engine with an **Ask AI** assistant that answers questions in natural language, grounded in your own documentation.
|
||||
|
||||
- **Instant search** — millisecond, typo-tolerant results as you type.
|
||||
- **Ask AI** — conversational answers with citations back to your docs.
|
||||
- **Side panel** — keep the conversation open next to the page you're reading.
|
||||
|
||||
It's **free for open-source and technical docs**. You can [apply here](https://docsearch.algolia.com/docs/docsearch-program/) or wire it up yourself with your Algolia credentials.`,
|
||||
suggestions: [
|
||||
'How much does DocSearch cost?',
|
||||
'What is the Ask AI side panel?',
|
||||
'How do I keep results up to date?',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function pickAnswer(question) {
|
||||
const q = question || '';
|
||||
return ANSWERS.find((a) => a.match.test(q)) ?? ANSWERS[ANSWERS.length - 1];
|
||||
}
|
||||
|
||||
// Split into streamable tokens while preserving whitespace, so the mock streams
|
||||
// word-by-word like the real endpoint.
|
||||
function tokenize(text) {
|
||||
return text.match(/\s+|\S+/g) ?? [text];
|
||||
}
|
||||
|
||||
function extractQuestion(init) {
|
||||
try {
|
||||
const body = typeof init?.body === 'string' ? JSON.parse(init.body) : null;
|
||||
const messages = body?.messages ?? [];
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const message = messages[i];
|
||||
if (message?.role === 'user') {
|
||||
const textPart = (message.parts ?? []).find((p) => p.type === 'text');
|
||||
if (textPart?.text) return textPart.text;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore malformed bodies; fall back to the generic answer.
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function sse(obj) {
|
||||
return `data: ${JSON.stringify(obj)}\n\n`;
|
||||
}
|
||||
|
||||
function buildStream(question, { signal } = {}) {
|
||||
const { markdown, suggestions } = pickAnswer(question);
|
||||
const id = `demo_msg_${Math.random().toString(36).slice(2, 12)}`;
|
||||
const tokens = tokenize(markdown);
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
let closed = false;
|
||||
const close = () => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
try {
|
||||
controller.close();
|
||||
} catch {
|
||||
// Already closed.
|
||||
}
|
||||
};
|
||||
|
||||
if (signal) {
|
||||
if (signal.aborted) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
signal.addEventListener('abort', close, { once: true });
|
||||
}
|
||||
|
||||
const send = (obj) => {
|
||||
if (closed) return;
|
||||
controller.enqueue(encoder.encode(sse(obj)));
|
||||
};
|
||||
const wait = (ms) =>
|
||||
new Promise((resolve) => {
|
||||
const timer = setTimeout(resolve, ms);
|
||||
signal?.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
|
||||
send({ type: 'start', messageId: id, messageMetadata: {} });
|
||||
send({ type: 'start-step' });
|
||||
send({ type: 'text-start', id });
|
||||
|
||||
await wait(220);
|
||||
for (const token of tokens) {
|
||||
if (closed) return;
|
||||
send({ type: 'text-delta', id, delta: token });
|
||||
// Faster for whitespace, a touch slower for words → natural cadence.
|
||||
await wait(/\S/.test(token) ? 18 + Math.random() * 34 : 8);
|
||||
}
|
||||
|
||||
send({ type: 'data-suggestions', data: { suggestions } });
|
||||
send({ type: 'text-end', id });
|
||||
send({ type: 'finish-step' });
|
||||
send({ type: 'finish' });
|
||||
if (!closed) controller.enqueue(encoder.encode('data: [DONE]\n\n'));
|
||||
close();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function mockResponse(question, init) {
|
||||
const stream = buildStream(question, { signal: init?.signal });
|
||||
return new Response(stream, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'text/event-stream',
|
||||
'x-vercel-ai-ui-message-stream': 'v1',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches `window.fetch` so Agent Studio completion requests are served from
|
||||
* the canned fixture while `isAutopilotActive()` is true. Returns a cleanup
|
||||
* function that restores the original `fetch`.
|
||||
*
|
||||
* @param {() => boolean} isAutopilotActive - Whether the demo autopilot is
|
||||
* currently driving the UI.
|
||||
* @returns {() => void} Cleanup that restores the original `window.fetch`.
|
||||
*/
|
||||
export function installAskAiMock(isAutopilotActive) {
|
||||
if (typeof window === 'undefined') return () => {};
|
||||
|
||||
const originalFetch = window.fetch.bind(window);
|
||||
|
||||
window.fetch = function patchedFetch(input, init) {
|
||||
const url = typeof input === 'string' ? input : (input?.url ?? '');
|
||||
const isCompletions = COMPLETIONS_RE.test(url);
|
||||
|
||||
if (isCompletions && isAutopilotActive()) {
|
||||
const question = extractQuestion(init);
|
||||
return Promise.resolve(mockResponse(question, init));
|
||||
}
|
||||
|
||||
return originalFetch(input, init);
|
||||
};
|
||||
|
||||
return () => {
|
||||
window.fetch = originalFetch;
|
||||
};
|
||||
}
|
||||
367
packages/website/src/components/demo/autopilot.js
Normal file
367
packages/website/src/components/demo/autopilot.js
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
// Autopilot for the homepage DocSearch demo.
|
||||
//
|
||||
// Drives the *real* DocSearch widget (modal + side panel) through a scripted
|
||||
// tour: open, pin/unpin a saved search, search-as-you-type, Ask AI in the
|
||||
// modal, then open the side panel and ask a question there. Every action is a
|
||||
// real API call or a synthetic DOM event, so the widget stays fully live.
|
||||
//
|
||||
// The tour yields to the user instantly: any *trusted* pointer/keyboard/scroll
|
||||
// event pauses it (synthetic events dispatched here are `isTrusted === false`),
|
||||
// and it resumes from the top after a period of inactivity.
|
||||
|
||||
const RESUME_AFTER_IDLE_MS = 10_000;
|
||||
const MIN_AUTOPLAY_WIDTH = 640;
|
||||
|
||||
const SEARCH_QUERY = 'installation';
|
||||
const MODAL_AI_QUESTION = 'How do I install DocSearch in Docusaurus?';
|
||||
const SIDEPANEL_QUESTION = 'What can Ask AI do for my docs?';
|
||||
|
||||
// A believable set of "recently viewed docs" so the start screen has something
|
||||
// to pin/unpin. Shape matches `StoredDocSearchHit`.
|
||||
const SEED_RECENT_SEARCHES = [
|
||||
hit('getting-started', 'Getting started', 'Guides', 'installation'),
|
||||
hit('react', 'React', 'Integrations', 'react'),
|
||||
hit('api-reference', 'API reference', 'Reference', 'api'),
|
||||
];
|
||||
|
||||
function hit(id, lvl1, lvl0, anchor) {
|
||||
const url = `https://docsearch.algolia.com/docs/${id}`;
|
||||
return {
|
||||
objectID: `demo-${id}`,
|
||||
content: null,
|
||||
url: `${url}#${anchor}`,
|
||||
url_without_anchor: url,
|
||||
type: 'lvl1',
|
||||
anchor,
|
||||
hierarchy: {
|
||||
lvl0,
|
||||
lvl1,
|
||||
lvl2: null,
|
||||
lvl3: null,
|
||||
lvl4: null,
|
||||
lvl5: null,
|
||||
lvl6: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const rand = (min, max) => min + Math.random() * (max - min);
|
||||
|
||||
export function createAutopilot({ modalRef, sidepanelRef, indexName }) {
|
||||
const RECENT_KEY = `__DOCSEARCH_RECENT_SEARCHES__${indexName}`;
|
||||
const FAVORITE_KEY = `__DOCSEARCH_FAVORITE_SEARCHES__${indexName}`;
|
||||
|
||||
let enabled = true;
|
||||
let visible = false;
|
||||
let userActive = false;
|
||||
let running = false;
|
||||
let token = { cancelled: true };
|
||||
let idleTimer = null;
|
||||
let cursorEl = null;
|
||||
|
||||
// --- cancellation ------------------------------------------------------
|
||||
function newToken() {
|
||||
token.cancelled = true;
|
||||
token = { cancelled: false };
|
||||
return token;
|
||||
}
|
||||
const alive = (t) => !t.cancelled && visible && !userActive && enabled;
|
||||
|
||||
function sleep(ms, t) {
|
||||
return new Promise((resolve) => {
|
||||
const timer = setTimeout(resolve, ms);
|
||||
// Bail early if the run gets cancelled mid-wait.
|
||||
const check = setInterval(() => {
|
||||
if (t.cancelled) {
|
||||
clearTimeout(timer);
|
||||
clearInterval(check);
|
||||
resolve();
|
||||
}
|
||||
}, 60);
|
||||
setTimeout(() => clearInterval(check), ms + 10);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitFor(selector, t, timeout = 4000) {
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < timeout) {
|
||||
if (t.cancelled) return null;
|
||||
const el = document.querySelector(selector);
|
||||
if (el) return el;
|
||||
await sleep(80, t);
|
||||
}
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
|
||||
// --- ghost cursor ------------------------------------------------------
|
||||
function ensureCursor() {
|
||||
if (cursorEl && cursorEl.isConnected) return cursorEl;
|
||||
cursorEl = document.createElement('div');
|
||||
cursorEl.className = 'ds-demo-cursor';
|
||||
cursorEl.setAttribute('aria-hidden', 'true');
|
||||
document.body.appendChild(cursorEl);
|
||||
return cursorEl;
|
||||
}
|
||||
|
||||
function hideCursor() {
|
||||
if (cursorEl) cursorEl.classList.remove('is-visible', 'is-clicking');
|
||||
}
|
||||
|
||||
async function moveCursorTo(el, t) {
|
||||
if (!el) return;
|
||||
const cursor = ensureCursor();
|
||||
const rect = el.getBoundingClientRect();
|
||||
const x = rect.left + rect.width / 2;
|
||||
const y = rect.top + rect.height / 2;
|
||||
cursor.classList.add('is-visible');
|
||||
cursor.style.transform = `translate(${x}px, ${y}px)`;
|
||||
await sleep(rand(520, 720), t);
|
||||
}
|
||||
|
||||
function pulse() {
|
||||
if (!cursorEl) return;
|
||||
cursorEl.classList.add('is-clicking');
|
||||
setTimeout(() => cursorEl && cursorEl.classList.remove('is-clicking'), 240);
|
||||
}
|
||||
|
||||
// --- synthetic interactions -------------------------------------------
|
||||
async function moveAndClick(selector, t) {
|
||||
const el =
|
||||
typeof selector === 'string' ? await waitFor(selector, t) : selector;
|
||||
if (!alive(t) || !el) return null;
|
||||
await moveCursorTo(el, t);
|
||||
if (!alive(t)) return null;
|
||||
pulse();
|
||||
el.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true }));
|
||||
el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
||||
el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
||||
el.click();
|
||||
return el;
|
||||
}
|
||||
|
||||
function setNativeValue(el, value) {
|
||||
const proto =
|
||||
el instanceof HTMLTextAreaElement
|
||||
? HTMLTextAreaElement.prototype
|
||||
: HTMLInputElement.prototype;
|
||||
const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
||||
if (setter) setter.call(el, value);
|
||||
else el.value = value;
|
||||
}
|
||||
|
||||
async function typeInto(el, text, t) {
|
||||
if (!el) return;
|
||||
el.focus();
|
||||
let current = '';
|
||||
for (const char of text) {
|
||||
if (!alive(t)) return;
|
||||
current += char;
|
||||
setNativeValue(el, current);
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
await sleep(rand(45, 95), t);
|
||||
}
|
||||
}
|
||||
|
||||
// --- storage seeding ---------------------------------------------------
|
||||
function seedRecentSearches() {
|
||||
try {
|
||||
window.localStorage.setItem(
|
||||
RECENT_KEY,
|
||||
JSON.stringify(SEED_RECENT_SEARCHES)
|
||||
);
|
||||
window.localStorage.removeItem(FAVORITE_KEY);
|
||||
} catch {
|
||||
// localStorage may be unavailable; the tour still runs, just without the
|
||||
// pin/unpin beat.
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
try {
|
||||
modalRef.current?.close();
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
try {
|
||||
sidepanelRef.current?.close();
|
||||
} catch {
|
||||
/* noop */
|
||||
}
|
||||
hideCursor();
|
||||
}
|
||||
|
||||
// --- the tour ----------------------------------------------------------
|
||||
async function scenario(t) {
|
||||
reset();
|
||||
seedRecentSearches();
|
||||
await sleep(900, t);
|
||||
if (!alive(t)) return;
|
||||
|
||||
// 1. Open the search modal.
|
||||
await moveAndClick('.DocSearch-Button', t);
|
||||
const input = await waitFor('.DocSearch-Input', t);
|
||||
if (!alive(t) || !input) return;
|
||||
await sleep(700, t);
|
||||
|
||||
// 2. Pin a recently-viewed doc (recent -> favorite).
|
||||
const pin = document.querySelector('.DocSearch-Hit-action-button--pin');
|
||||
if (pin && alive(t)) {
|
||||
await moveAndClick(pin, t);
|
||||
await sleep(900, t);
|
||||
}
|
||||
|
||||
// 3. Unpin it again.
|
||||
const unpin =
|
||||
document.querySelector(
|
||||
'.DocSearch-Hit-action-button[title="Remove this saved search"]'
|
||||
) ||
|
||||
document.querySelector(
|
||||
'.DocSearch-Hits .DocSearch-Hit-action-button:not(.DocSearch-Hit-action-button--pin)'
|
||||
);
|
||||
if (unpin && alive(t)) {
|
||||
await moveAndClick(unpin, t);
|
||||
await sleep(900, t);
|
||||
}
|
||||
if (!alive(t)) return;
|
||||
|
||||
// 4. Search as you type.
|
||||
await moveCursorTo(input, t);
|
||||
await typeInto(input, SEARCH_QUERY, t);
|
||||
await waitFor('.DocSearch-Hit a', t, 3000);
|
||||
await sleep(1200, t);
|
||||
if (!alive(t)) return;
|
||||
|
||||
// 5. Ask AI, in the modal.
|
||||
const askAiCta = document.querySelector(
|
||||
'.DocSearch-Hit-AskAIButton, .DocSearch-Hit--AskAI'
|
||||
);
|
||||
if (askAiCta) {
|
||||
await moveAndClick(askAiCta, t);
|
||||
} else {
|
||||
modalRef.current?.openAskAi({ query: MODAL_AI_QUESTION });
|
||||
}
|
||||
await waitFor('.DocSearch-AskAiScreen', t, 3000);
|
||||
// Let the (mocked) answer stream in and be read.
|
||||
await sleep(6500, t);
|
||||
if (!alive(t)) return;
|
||||
|
||||
// 6. Close the modal.
|
||||
await moveAndClick('.DocSearch-Close', t);
|
||||
await sleep(1100, t);
|
||||
if (!alive(t)) return;
|
||||
|
||||
// 7. Open the side panel.
|
||||
await moveAndClick('.DocSearch-SidepanelButton', t);
|
||||
const sidepanelOpen = await waitFor(
|
||||
'.DocSearch-Sidepanel-Container.is-open',
|
||||
t,
|
||||
3000
|
||||
);
|
||||
if (!alive(t) || !sidepanelOpen) return;
|
||||
await sleep(900, t);
|
||||
|
||||
// 8. Ask a question in the side panel.
|
||||
const prompt = await waitFor(
|
||||
'.DocSearch-Sidepanel-Prompt--textarea',
|
||||
t,
|
||||
2000
|
||||
);
|
||||
if (prompt && alive(t)) {
|
||||
await moveCursorTo(prompt, t);
|
||||
await typeInto(prompt, SIDEPANEL_QUESTION, t);
|
||||
await sleep(400, t);
|
||||
await moveAndClick('.DocSearch-Sidepanel-Prompt--submit', t);
|
||||
await sleep(7000, t);
|
||||
}
|
||||
if (!alive(t)) return;
|
||||
|
||||
// 9. Close and breathe before looping.
|
||||
await moveAndClick('.DocSearch-Sidepanel-Action-close', t);
|
||||
sidepanelRef.current?.close();
|
||||
await sleep(2200, t);
|
||||
}
|
||||
|
||||
async function loop(t) {
|
||||
running = true;
|
||||
while (alive(t)) {
|
||||
try {
|
||||
await scenario(t);
|
||||
} catch {
|
||||
// A step failed (e.g. an element didn't appear). Reset and retry the
|
||||
// whole tour after a short pause rather than getting stuck.
|
||||
reset();
|
||||
await sleep(1500, t);
|
||||
}
|
||||
}
|
||||
running = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
function startRun() {
|
||||
if (!enabled || !visible || userActive) return;
|
||||
if (running) return;
|
||||
const t = newToken();
|
||||
loop(t);
|
||||
}
|
||||
|
||||
// --- user takeover -----------------------------------------------------
|
||||
function onUserActivity(event) {
|
||||
if (!event.isTrusted) return; // ignore our own synthetic events
|
||||
userActive = true;
|
||||
running = false;
|
||||
token.cancelled = true;
|
||||
hideCursor();
|
||||
if (idleTimer) clearTimeout(idleTimer);
|
||||
idleTimer = setTimeout(() => {
|
||||
userActive = false;
|
||||
idleTimer = null;
|
||||
startRun();
|
||||
}, RESUME_AFTER_IDLE_MS);
|
||||
}
|
||||
|
||||
const activityEvents = ['pointerdown', 'keydown', 'wheel', 'touchstart'];
|
||||
|
||||
// --- public API --------------------------------------------------------
|
||||
function start() {
|
||||
if (typeof window === 'undefined') return;
|
||||
const reducedMotion = window.matchMedia?.(
|
||||
'(prefers-reduced-motion: reduce)'
|
||||
).matches;
|
||||
if (reducedMotion || window.innerWidth < MIN_AUTOPLAY_WIDTH) {
|
||||
enabled = false;
|
||||
}
|
||||
activityEvents.forEach((name) =>
|
||||
document.addEventListener(name, onUserActivity, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function stop() {
|
||||
token.cancelled = true;
|
||||
running = false;
|
||||
if (idleTimer) clearTimeout(idleTimer);
|
||||
activityEvents.forEach((name) =>
|
||||
document.removeEventListener(name, onUserActivity, { capture: true })
|
||||
);
|
||||
if (cursorEl && cursorEl.isConnected) cursorEl.remove();
|
||||
cursorEl = null;
|
||||
}
|
||||
|
||||
function setVisible(next) {
|
||||
if (visible === next) return;
|
||||
visible = next;
|
||||
if (visible) {
|
||||
startRun();
|
||||
} else {
|
||||
token.cancelled = true;
|
||||
running = false;
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
const isAutopilotActive = () => running && visible && !userActive && enabled;
|
||||
|
||||
return { start, stop, setVisible, isAutopilotActive };
|
||||
}
|
||||
294
packages/website/src/components/demo/demo.css
Normal file
294
packages/website/src/components/demo/demo.css
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
/* Mini docs-site chrome for the homepage auto-demo (rendered inside the
|
||||
/demo-embed iframe). Widget styles come from @docsearch/css; everything here
|
||||
is the light "your docs" frame behind it. Colours + fonts reuse the site's
|
||||
own design tokens from custom.css (theme-scoped on <html data-theme>), so the
|
||||
demo always matches the surrounding page in both light and dark mode. */
|
||||
|
||||
.docsearch-demo-embed-html,
|
||||
.docsearch-demo-embed-body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ds-demo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--surface, #fff);
|
||||
color: var(--text, #1c1e21);
|
||||
font-family: var(--font-sans, ui-sans-serif, system-ui, -apple-system, sans-serif);
|
||||
}
|
||||
|
||||
/* Topbar ------------------------------------------------------------------ */
|
||||
.ds-demo-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 22px;
|
||||
padding: 0 22px;
|
||||
height: 54px;
|
||||
border-bottom: 1px solid var(--border, #e6e8eb);
|
||||
background: color-mix(in srgb, var(--surface, #fff) 80%, transparent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ds-demo-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: var(--font-display, var(--font-sans));
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-tertiary, #9aa0a6);
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ds-demo-nav {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.ds-demo-nav-link {
|
||||
font-size: 13.5px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
}
|
||||
|
||||
.ds-demo-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Hero -------------------------------------------------------------------- */
|
||||
.ds-demo-hero {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 14px;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ds-demo-eyebrow {
|
||||
margin: 0;
|
||||
font-family: var(--font-mono, ui-monospace, monospace);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent, #5468ff);
|
||||
}
|
||||
|
||||
.ds-demo-title {
|
||||
margin: 0 0 6px;
|
||||
font-family: var(--font-display, var(--font-sans));
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--text, #1c1e21);
|
||||
}
|
||||
|
||||
.ds-demo-search {
|
||||
width: min(560px, 100%);
|
||||
}
|
||||
|
||||
/* Make the search trigger read as a prominent hero search bar. */
|
||||
.ds-demo-search .DocSearch-Button {
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-strong, #d0d3d7);
|
||||
background: var(--surface-raised, #fafafa);
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 4%);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.ds-demo-search .DocSearch-Button:hover {
|
||||
border-color: var(--accent, #5468ff);
|
||||
box-shadow: 0 2px 10px
|
||||
color-mix(in srgb, var(--accent, #5468ff) 20%, transparent);
|
||||
}
|
||||
|
||||
.ds-demo-search .DocSearch-Button-Placeholder {
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
}
|
||||
|
||||
.ds-demo-popular {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.ds-demo-popular-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary, #9aa0a6);
|
||||
}
|
||||
|
||||
.ds-demo-chip {
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border, #e6e8eb);
|
||||
background: var(--surface-raised, #fafafa);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
}
|
||||
|
||||
/* Fix the Ask AI (inline sidepanel) button. The library rule sets `all: unset`
|
||||
after its own styling, which wipes it — so we restyle it here into a clean
|
||||
accent CTA. */
|
||||
.ds-demo .DocSearch-SidepanelButton.inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
background: var(--accent, #5468ff);
|
||||
color: #fff;
|
||||
font-family: var(--font-sans, system-ui, sans-serif);
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
filter 150ms ease,
|
||||
transform 150ms ease;
|
||||
}
|
||||
|
||||
.ds-demo .DocSearch-SidepanelButton.inline:hover {
|
||||
filter: brightness(1.05);
|
||||
background: var(--accent, #5468ff);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.ds-demo .DocSearch-SidepanelButton.inline svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Mobile ------------------------------------------------------------------ */
|
||||
|
||||
/* The iframe width equals the demo box width, so these fire on phones where
|
||||
the box is narrow — keep the chrome from overflowing and the hero readable. */
|
||||
@media (width <= 560px) {
|
||||
.ds-demo-topbar {
|
||||
gap: 10px;
|
||||
padding: 0 14px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
/* The middle nav is decorative; drop it so brand + Ask AI always fit. */
|
||||
.ds-demo-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ds-demo-hero {
|
||||
gap: 12px;
|
||||
padding: 18px 16px;
|
||||
}
|
||||
|
||||
.ds-demo-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.ds-demo-eyebrow {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Collapse the hero search bar into a compact icon button (the widget already
|
||||
hides the placeholder + keys below 768px; we just undo our full-width look). */
|
||||
.ds-demo-search {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.ds-demo-search .DocSearch-Button {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Trim the Ask AI CTA so the brand + button share the row comfortably. */
|
||||
.ds-demo .DocSearch-SidepanelButton.inline {
|
||||
height: 34px;
|
||||
padding: 0 11px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Very narrow: the Ask AI label alone is enough, drop the brand wordmark. */
|
||||
@media (width <= 380px) {
|
||||
.ds-demo-brand-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ghost cursor ------------------------------------------------------------ */
|
||||
.ds-demo-cursor {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2147483647;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transform: translate(-100px, -100px);
|
||||
transition:
|
||||
transform 0.55s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
opacity 0.2s ease;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M4 2l6.5 15.5 2.2-6 6-2.2z' fill='%23111' stroke='%23fff' stroke-width='1.2' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
filter: drop-shadow(0 2px 4px rgb(0 0 0 / 30%));
|
||||
}
|
||||
|
||||
.ds-demo-cursor.is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ds-demo-cursor.is-clicking::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--accent, #5468ff);
|
||||
transform: translate(-50%, -50%);
|
||||
animation: ds-demo-click 0.24s ease-out;
|
||||
}
|
||||
|
||||
@keyframes ds-demo-click {
|
||||
from {
|
||||
opacity: 0.9;
|
||||
transform: translate(-50%, -50%) scale(0.4);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(1.8);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ds-demo-cursor {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +1,12 @@
|
|||
import { NavArrowDown, NavArrowUp } from 'iconoir-react';
|
||||
import React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
export function ChevronDown({ className }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className={cn('size-4', className)}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M12.53 16.28a.75.75 0 0 1-1.06 0l-7.5-7.5a.75.75 0 0 1 1.06-1.06L12 14.69l6.97-6.97a.75.75 0 1 1 1.06 1.06l-7.5 7.5Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return <NavArrowDown className={cn('size-4', className)} />;
|
||||
}
|
||||
|
||||
export function ChevronUp({ className }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className={cn('size-4', className)}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M11.47 7.72a.75.75 0 0 1 1.06 0l7.5 7.5a.75.75 0 1 1-1.06 1.06L12 9.31l-6.97 6.97a.75.75 0 0 1-1.06-1.06l7.5-7.5Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
return <NavArrowUp className={cn('size-4', className)} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import { track } from '@site/src/lib/segment';
|
||||
import { Code2 } from 'lucide-react';
|
||||
import { Code } from 'iconoir-react';
|
||||
import { AnimatePresence, motion, useReducedMotion } from 'motion/react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
|
|
@ -91,12 +91,13 @@ const CLIENTS = [
|
|||
kind: 'code',
|
||||
caption: 'In Claude Code',
|
||||
code: `/plugin marketplace add algolia/docsearch
|
||||
/plugin install algolia-docsearch@algolia-docsearch-marketplace`,
|
||||
/plugin install algolia-docsearch@algolia-docsearch-marketplace
|
||||
/reload-plugins`,
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
content:
|
||||
'The plugin adds the MCP server, a skill, and the /algolia-docsearch:docs command. Plugins are a Claude Code feature — Claude Desktop connects via a custom connector (see Manual).',
|
||||
'The plugin adds the MCP server, a skill, and the /algolia-docsearch:docs command. /reload-plugins activates it in the current session. Plugins are a Claude Code feature — Claude Desktop connects via a custom connector (see Manual).',
|
||||
},
|
||||
],
|
||||
manual: [
|
||||
|
|
@ -633,7 +634,7 @@ function ClientLogo({ client, size = 'h-7 w-7' }) {
|
|||
<span
|
||||
className={`${size} flex items-center justify-center rounded-md bg-neutral-100 text-neutral-700 dark:bg-neutral-800 dark:text-neutral-200`}
|
||||
>
|
||||
<Code2 aria-label={`${client.name} icon`} className="h-[70%] w-[70%]" />
|
||||
<Code aria-label={`${client.name} icon`} className="h-[70%] w-[70%]" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
@ -687,7 +688,7 @@ export default function MCPInstall() {
|
|||
return (
|
||||
<motion.div
|
||||
id="tailwind"
|
||||
className="not-prose my-8 font-[Inter]"
|
||||
className="not-prose my-8 font-sans"
|
||||
initial={{ opacity: 0, y: rise }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.15 }}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,30 @@
|
|||
import React from 'react';
|
||||
|
||||
const BASE =
|
||||
'relative inline-flex h-10 cursor-pointer items-center justify-center gap-1.5 rounded-lg border px-4 text-sm font-medium no-underline! transition-all duration-150 active:scale-[0.97]';
|
||||
|
||||
// Outline / secondary
|
||||
export const Button = ({ children, href, className = '', ...props }) => {
|
||||
const Component = href ? 'a' : 'button';
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
{...(href ? { href } : { type: 'button' })}
|
||||
className={`relative cursor-pointer no-underline! h-12 text-sm overflow-hidden rounded flex items-center justify-center bg-white dark:bg-neutral-900 px-5 py-2.5 text-black! dark:text-white! transition-all! duration-300! ring-1 ring-neutral-300 dark:ring-neutral-500 ring-offset-2 dark:ring-offset-neutral-900 hover:bg-slate-100 hover:dark:bg-neutral-700 hover:ring-3 hover:ring-neutral-200 hover:dark:ring-neutral-700 font-[Sora] hover:ring-offset-6 w-[10rem] ${className}`}
|
||||
className={`${BASE} border-[var(--border-strong)] bg-[var(--surface)] text-[var(--text)]! hover:bg-[var(--surface-raised)] ${className}`}
|
||||
>
|
||||
<span className="relative">{children}</span>
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
// Primary — brand fill with inset highlight (MCP primary button)
|
||||
export const PrimaryButton = ({ children, href, className = '', ...props }) => {
|
||||
const Component = href ? 'a' : 'button';
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
{...(href ? { href } : { type: 'button' })}
|
||||
className={`relative cursor-pointer no-underline! h-12 overflow-hidden text-sm rounded flex items-center justify-center font-[Sora] bg-gradient-to-tl from-blue-500 via-blue-600 to-blue-700 dark:from-blue-400 dark:via-blue-500 dark:to-blue-600 ring-1 ring-blue-300 dark:ring-blue-400 ring-offset-3 px-5 py-2.5 text-white! dark:ring-offset-neutral-900 transition-all! duration-300! hover:bg-blue-800 hover:dark:bg-blue-700 hover:ring-3 hover:ring-blue-800 hover:dark:ring-blue-700 hover:ring-offset-6 w-[10rem] ${className}`}
|
||||
className={`${BASE} border-transparent bg-[var(--brand)] text-white! shadow-[0_1px_0_0_rgba(255,255,255,0.08)_inset,0_1px_2px_0_rgba(16,24,40,0.12)] hover:bg-[var(--brand-ink)] ${className}`}
|
||||
>
|
||||
<span className="relative">{children}</span>
|
||||
</Component>
|
||||
|
|
|
|||
115
packages/website/src/components/ui/features-ai-cells.jsx
Normal file
115
packages/website/src/components/ui/features-ai-cells.jsx
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import { ArrowRight, Sparks } from 'iconoir-react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { InstallCommand } from './install-command';
|
||||
|
||||
const mediaClass =
|
||||
'flex h-56 shrink-0 items-center justify-center overflow-hidden border-b border-[var(--border)] bg-[var(--surface-raised)]';
|
||||
const contentClass = 'flex flex-1 flex-col p-10 pt-4';
|
||||
const eyebrowClass =
|
||||
'font-mono text-[11px] uppercase tracking-[0.16em] text-[var(--accent)] !mb-0';
|
||||
const titleClass = 'text-xl font-semibold font-display text-[var(--text)] my-4';
|
||||
const bodyClass = 'mt-2 max-w-lg text-base text-[var(--text-secondary)]';
|
||||
const primaryButtonClass =
|
||||
'inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-[var(--brand)] px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-[var(--brand-ink)]';
|
||||
const linkClass =
|
||||
'inline-flex items-center gap-1.5 text-sm font-medium text-[var(--brand-ink)] no-underline! transition-colors hover:text-[var(--brand)]';
|
||||
|
||||
export function AskAICell() {
|
||||
const handleTryAskAI = useCallback(() => {
|
||||
const sidepanelButton = document.querySelector(
|
||||
'.DocSearch-SidepanelButton'
|
||||
);
|
||||
if (sidepanelButton) {
|
||||
sidepanelButton.click();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative lg:col-span-3">
|
||||
<div className="absolute inset-0 rounded-lg bg-[var(--surface)] lg:rounded-tl-4xl" />
|
||||
<div className="relative flex h-full flex-col overflow-hidden rounded-lg lg:rounded-tl-4xl">
|
||||
<div className={mediaClass}>
|
||||
<video
|
||||
autoPlay={true}
|
||||
muted={true}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
className="h-full w-full object-cover object-left"
|
||||
src="/img/resources/askai720p.mp4"
|
||||
preload="metadata"
|
||||
aria-label="Ask AI answering a question from the docs with cited sources"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={contentClass}>
|
||||
<p className={eyebrowClass}>Powered by Algolia Agent Studio</p>
|
||||
<p className={titleClass}>Ask AI</p>
|
||||
<p className={bodyClass}>
|
||||
Instant AI answers straight from your own docs. Users ask
|
||||
natural-language questions and get context-aware answers with real
|
||||
sources — with prompts and models you configure.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className={primaryButtonClass}
|
||||
onClick={handleTryAskAI}
|
||||
>
|
||||
Try now
|
||||
<Sparks width={16} height={16} />
|
||||
</button>
|
||||
<a
|
||||
href="/docs/v4/askai?utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=askai"
|
||||
className={linkClass}
|
||||
>
|
||||
Learn more
|
||||
<ArrowRight width={16} height={16} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg outline outline-[var(--border)] max-lg:rounded-t-4xl lg:rounded-tl-4xl" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MCPCell() {
|
||||
const { siteConfig } = useDocusaurusContext();
|
||||
// /mcp is served externally at the same origin (not a Docusaurus route).
|
||||
const mcpUrl = `${siteConfig.url}/mcp`;
|
||||
|
||||
return (
|
||||
<div className="relative lg:col-span-3">
|
||||
<div className="absolute inset-0 rounded-lg bg-[var(--surface)] lg:rounded-tr-4xl" />
|
||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] lg:rounded-tr-[calc(2rem+1px)]">
|
||||
<div className={`${mediaClass} px-8`}>
|
||||
<InstallCommand />
|
||||
</div>
|
||||
|
||||
<div className={contentClass}>
|
||||
<p className={eyebrowClass}>Model Context Protocol</p>
|
||||
<p className={titleClass}>MCP Server</p>
|
||||
<p className={bodyClass}>
|
||||
Make your docs neural-searchable by any AI agent — Claude, Cursor,
|
||||
Codex, and more — over the Model Context Protocol, powered by
|
||||
Algolia neural search.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex items-center gap-4">
|
||||
<a href={mcpUrl} className={linkClass}>
|
||||
Explore MCP
|
||||
<ArrowRight width={16} height={16} />
|
||||
</a>
|
||||
<a href="/docs/mcp/installation" className={linkClass}>
|
||||
Install guide
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg outline outline-[var(--border)] lg:rounded-tr-4xl" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,123 +1,108 @@
|
|||
import React from 'react';
|
||||
|
||||
import { AskAICell, MCPCell } from './features-ai-cells';
|
||||
|
||||
const surfaces = [
|
||||
{ label: 'Docusaurus' },
|
||||
{ label: 'VitePress' },
|
||||
{ label: 'React' },
|
||||
{ label: 'Vanilla JS' },
|
||||
{ label: 'Modal', accent: true },
|
||||
{ label: 'Sidepanel', accent: true },
|
||||
];
|
||||
|
||||
const algoliaStats = [
|
||||
{ value: '<20 ms', label: 'Search latency' },
|
||||
{ value: '99.99%', label: 'Uptime SLA' },
|
||||
{ value: '9,000+', label: 'Projects' },
|
||||
{ value: 'Free', label: 'For OSS & technical docs' },
|
||||
];
|
||||
|
||||
const badgeBase =
|
||||
'rounded-full border px-3 py-1.5 font-mono text-xs transition-colors';
|
||||
const badgeDefault =
|
||||
'border-[var(--border)] bg-[var(--surface)] text-[var(--text-secondary)]';
|
||||
const badgeAccent =
|
||||
'border-transparent bg-[var(--accent-light)] text-[var(--brand-ink)]';
|
||||
|
||||
export const FeaturesBento = () => {
|
||||
return (
|
||||
<div className="pb-8">
|
||||
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
|
||||
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2">
|
||||
<div className="relative lg:col-span-3">
|
||||
<div className="absolute inset-0 rounded-lg dark:bg-gray-400/7 bg-white max-lg:rounded-t-4xl lg:rounded-tl-4xl" />
|
||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] max-lg:rounded-t-[calc(2rem+1px)] lg:rounded-tl-[calc(2rem+1px)]">
|
||||
<img
|
||||
alt=""
|
||||
src="/img/resources/docs.png"
|
||||
className="h-80 object-cover object-left"
|
||||
/>
|
||||
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6">
|
||||
{/* Row 1 — AI-first hero cells (owned by features-ai-cells.jsx) */}
|
||||
<AskAICell />
|
||||
<MCPCell />
|
||||
|
||||
{/* Row 2 — Works everywhere / Made for docs / A11y */}
|
||||
<div className="relative lg:col-span-2">
|
||||
<div className="absolute inset-0 rounded-lg bg-[var(--surface)]" />
|
||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)]">
|
||||
<div className="flex h-56 flex-wrap content-center items-center justify-center gap-2 border-b border-[var(--border)] bg-[var(--surface-raised)] p-8">
|
||||
{surfaces.map((surface) => (
|
||||
<span
|
||||
key={surface.label}
|
||||
className={`${badgeBase} ${surface.accent ? badgeAccent : badgeDefault}`}
|
||||
>
|
||||
{surface.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="p-10 pt-4">
|
||||
<p className="text-xl font-semibold text-blue-600 font-[Sora] dark:text-white my-4">
|
||||
Made for docs
|
||||
<p className="text-xl font-semibold font-display text-[var(--text)] my-4">
|
||||
Works everywhere
|
||||
</p>
|
||||
<p className="mt-2 max-w-lg text-base text-gray-600 dark:text-slate-100">
|
||||
DocSearch is purpose-built to index and surface technical
|
||||
content, from API references to how-tos. It understands code
|
||||
snippets, tables, and markdown structures so your users get
|
||||
pinpoint answers every time.
|
||||
<p className="mt-2 max-w-lg text-base text-[var(--text-secondary)]">
|
||||
Drop DocSearch into any stack. First-class adapters for
|
||||
Docusaurus and VitePress, headless React components, and a
|
||||
vanilla-JS build for everything else — shipping both a
|
||||
command-palette modal and the Sidepanel.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 max-lg:rounded-t-4xl lg:rounded-tl-4xl" />
|
||||
</div>
|
||||
<div className="relative lg:col-span-3">
|
||||
<div className="absolute inset-0 rounded-lg dark:bg-gray-400/7 bg-white lg:rounded-tr-4xl" />
|
||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] lg:rounded-tr-[calc(2rem+1px)]">
|
||||
<img
|
||||
alt=""
|
||||
src="/img/resources/ai-powered.png"
|
||||
className="h-80 object-cover object-left lg:object-right"
|
||||
/>
|
||||
|
||||
<div className="p-10 pt-4">
|
||||
<p className="text-xl font-semibold text-blue-600 font-[Sora] dark:text-white my-4">
|
||||
AI-powered
|
||||
</p>
|
||||
<p className="mt-2 max-w-lg text-base text-gray-600 dark:text-slate-100">
|
||||
Leveraging Algolia Ask AI, DocSearch interprets
|
||||
natural-language queries, suggests synonyms, and ranks results
|
||||
by relevance. It turns even complex developer questions into
|
||||
instant, context-aware answers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 lg:rounded-tr-4xl" />
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg outline outline-[var(--border)]" />
|
||||
</div>
|
||||
|
||||
<div className="relative lg:col-span-6">
|
||||
<div className="absolute inset-0 rounded-lg dark:bg-gray-400/7 bg-white" />
|
||||
<div className="relative lg:col-span-2">
|
||||
<div className="absolute inset-0 rounded-lg bg-[var(--surface)]" />
|
||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)]">
|
||||
<img
|
||||
alt=""
|
||||
src="/img/resources/powered-by-algolia.png"
|
||||
className="h-80 object-cover"
|
||||
src="/img/resources/docs.png"
|
||||
className="h-56 object-cover object-left"
|
||||
/>
|
||||
|
||||
<div className="p-10 pt-4">
|
||||
<p className="text-xl font-semibold text-blue-600 font-[Sora] dark:text-white my-4">
|
||||
Powered by Algolia
|
||||
<p className="text-xl font-semibold font-display text-[var(--text)] my-4">
|
||||
Made for docs
|
||||
</p>
|
||||
<p className="mt-2 text-base text-gray-600 dark:text-slate-100">
|
||||
Built & deployed on Algolia’s global search infrastructure,
|
||||
DocSearch delivers sub-20 ms replies at any scale. Enjoy
|
||||
99.99% uptime and auto-scaled capacity without lifting a
|
||||
finger—your docs stay lightning-fast, always.
|
||||
<p className="mt-2 max-w-lg text-base text-[var(--text-secondary)]">
|
||||
DocSearch’s crawler automatically indexes your docs on a
|
||||
schedule — no manual reindexing, no stale results. A visual
|
||||
config editor lets you tune exactly what gets picked up, and
|
||||
it understands code snippets, tables, markdown structure, and
|
||||
API references so your users get pinpoint answers every time.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5" />
|
||||
</div>
|
||||
<div className="relative lg:col-span-3">
|
||||
<div className="absolute inset-0 rounded-lg dark:bg-gray-400/7 bg-white lg:rounded-bl-4xl" />
|
||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] lg:rounded-bl-[calc(2rem+1px)]">
|
||||
<img
|
||||
alt=""
|
||||
src="img/resources/customizable-light.png"
|
||||
className="h-80 object-cover object-left hidden dark:block"
|
||||
/>
|
||||
<img
|
||||
alt=""
|
||||
src="img/resources/customizable-dark.png"
|
||||
className="h-80 object-cover object-left dark:hidden"
|
||||
/>
|
||||
|
||||
<div className="p-10 pt-4">
|
||||
<p className="text-xl font-semibold text-blue-600 font-[Sora] dark:text-white my-4">
|
||||
Customizable
|
||||
</p>
|
||||
<p className="mt-2 max-w-lg text-base text-gray-600 dark:text-slate-100">
|
||||
Tailor DocSearch to match your brand and UX needs—colors,
|
||||
fonts, layouts, and even search behaviors are under your
|
||||
control. Drop-in CSS variables and simple JS hooks make it
|
||||
effortless to blend search seamlessly into any docs site.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 lg:rounded-bl-4xl" />
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg outline outline-[var(--border)]" />
|
||||
</div>
|
||||
|
||||
<div className="relative lg:col-span-3">
|
||||
<div className="absolute inset-0 rounded-lg dark:bg-gray-400/7 bg-white max-lg:rounded-b-4xl lg:rounded-br-4xl" />
|
||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-br-[calc(2rem+1px)]">
|
||||
<div className="relative lg:col-span-2">
|
||||
<div className="absolute inset-0 rounded-lg bg-[var(--surface)]" />
|
||||
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)]">
|
||||
<img
|
||||
alt=""
|
||||
src="/img/resources/accessible.png"
|
||||
className="h-80 object-cover"
|
||||
className="h-56 object-cover object-top"
|
||||
/>
|
||||
|
||||
<div className="p-10 pt-4">
|
||||
<p className="text-xl font-semibold text-blue-600 font-[Sora] dark:text-white my-4">
|
||||
<p className="text-xl font-semibold font-display text-[var(--text)] my-4">
|
||||
A11y
|
||||
</p>
|
||||
<p className="mt-2 max-w-lg text-base text-gray-600 dark:text-slate-100">
|
||||
<p className="mt-2 max-w-lg text-base text-[var(--text-secondary)]">
|
||||
DocSearch follows WAI-ARIA best practices to ensure full
|
||||
keyboard, screen-reader, and voice-control support. Delight
|
||||
every user with an inclusive search experience that’s tested
|
||||
|
|
@ -125,7 +110,36 @@ export const FeaturesBento = () => {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 max-lg:rounded-b-4xl lg:rounded-br-4xl" />
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg outline outline-[var(--border)]" />
|
||||
</div>
|
||||
|
||||
{/* Row 3 — Powered by Algolia (slim supporting stat strip) */}
|
||||
<div className="relative lg:col-span-6">
|
||||
<div className="absolute inset-0 rounded-lg bg-[var(--surface)] rounded-b-4xl" />
|
||||
<div className="relative flex h-full flex-col gap-6 overflow-hidden rounded-[calc(var(--radius-lg)+1px)] rounded-b-[calc(2rem+1px)] p-8 lg:flex-row lg:items-center lg:justify-between lg:gap-10">
|
||||
<div className="lg:max-w-xs">
|
||||
<p className="text-xl font-semibold font-display text-[var(--text)]">
|
||||
Search Powered by Algolia
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-[var(--text-secondary)]">
|
||||
Running on Algolia’s global search infrastructure — fast,
|
||||
reliable, and free for the docs that need it.
|
||||
</p>
|
||||
</div>
|
||||
<dl className="grid grid-cols-2 gap-x-8 gap-y-6 sm:grid-cols-4 lg:gap-x-10">
|
||||
{algoliaStats.map((stat) => (
|
||||
<div key={stat.label}>
|
||||
<dt className="font-display text-2xl font-semibold tabular text-[var(--text)] sm:text-3xl">
|
||||
{stat.value}
|
||||
</dt>
|
||||
<dd className="mt-1 text-xs text-[var(--text-tertiary)]">
|
||||
{stat.label}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-lg outline outline-[var(--border)] rounded-b-4xl" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,169 +0,0 @@
|
|||
import React, { useCallback } from 'react';
|
||||
|
||||
const mcpSteps = [
|
||||
{ id: 'signup', text: 'Sign up with Algolia or use an existing app' },
|
||||
{ id: 'navigate', text: 'Go to the MCP section under Generative AI' },
|
||||
{ id: 'create', text: 'Create a new MCP server with your index' },
|
||||
{ id: 'use', text: 'Use it anywhere' },
|
||||
];
|
||||
|
||||
export const IntroducingSection = () => {
|
||||
const handleTryAskAI = useCallback(() => {
|
||||
const sidepanelButton = document.querySelector(
|
||||
'.DocSearch-SidepanelButton'
|
||||
);
|
||||
if (sidepanelButton) {
|
||||
sidepanelButton.click();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="py-16 overflow-hidden snap-start">
|
||||
<div className="relative max-w-xl mx-auto px-4 md:px-6 lg:px-8 lg:max-w-screen-xl">
|
||||
{/* Section Header */}
|
||||
<div className="max-w-screen-xl mx-auto mb-12 px-4 md:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<p className="text-3xl text-black dark:text-white font-bold leading-9 font-[Sora] md:text-4xl md:leading-10">
|
||||
Expand your Docs beyond the search box
|
||||
</p>
|
||||
<p className="text-lg md:text-2xl text-slate-400 dark:text-slate-500">
|
||||
Power your documentation with AI
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cards Grid */}
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
{/* Ask AI Card */}
|
||||
<div className="relative rounded-2xl bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-gray-700 p-6 transition-all hover:shadow-lg hover:shadow-blue-500/10">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 rounded-lg bg-gradient-to-br from-blue-500 to-blue-700 text-white">
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-lg font-semibold text-gray-900 dark:text-white font-[Sora]">
|
||||
Ask AI
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Get instant, AI-powered answers from your documentation. Ask
|
||||
natural language questions and receive accurate, context-aware
|
||||
responses.
|
||||
</p>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg bg-gradient-to-r from-blue-500 to-blue-700 hover:from-blue-600 hover:to-blue-800 text-white text-sm font-medium transition-all cursor-pointer"
|
||||
onClick={handleTryAskAI}
|
||||
>
|
||||
Try now
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<a
|
||||
href="/docs/v4/askai?utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=askai"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium no-underline!"
|
||||
>
|
||||
Learn more
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* MCP Card */}
|
||||
<div className="relative rounded-2xl bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-gray-700 p-6 transition-all hover:shadow-lg hover:shadow-blue-500/10">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 rounded-lg bg-gradient-to-br from-blue-500 to-blue-700 text-white">
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Zm.75-12h9v9h-9v-9Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-lg font-semibold text-gray-900 dark:text-white font-[Sora]">
|
||||
MCP Server
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Connect your documentation to AI assistants like Claude and Cursor
|
||||
with the Model Context Protocol.
|
||||
</p>
|
||||
<div className="space-y-2 mb-4">
|
||||
{mcpSteps.map((step, index) => (
|
||||
<div key={step.id} className="flex items-start gap-2">
|
||||
<span className="flex-shrink-0 w-5 h-5 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400 text-xs font-bold flex items-center justify-center">
|
||||
{index + 1}
|
||||
</span>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-300 leading-tight">
|
||||
{step.text}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<a
|
||||
href="https://www.algolia.com/doc/guides/algolia-ai/mcp-server/overview?utm_source=docsearch.algolia.com&utm_medium=referral&utm_campaign=mcp"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium no-underline!"
|
||||
>
|
||||
Learn more
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
38
packages/website/src/components/ui/install-command.jsx
Normal file
38
packages/website/src/components/ui/install-command.jsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { Check, Copy } from 'iconoir-react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const COMMAND = 'npx @docsearch/cli setup';
|
||||
|
||||
export function InstallCommand() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(COMMAND);
|
||||
setCopied(true);
|
||||
window.setTimeout(() => setCopied(false), 1600);
|
||||
} catch {
|
||||
/* clipboard unavailable */
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 items-center gap-3 rounded-lg border border-[var(--term-border)] bg-[var(--term-bg)] py-2 pl-3.5 pr-2 text-[var(--term-fg)]">
|
||||
<code className="font-mono text-[12.5px] text-[var(--term-fg)] !border-none !px-2 !bg-none">
|
||||
{COMMAND}
|
||||
</code>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex shrink-0 cursor-pointer items-center gap-1.5 rounded-md border border-[var(--term-btn-border)] bg-[var(--term-btn-bg)] px-2.5 py-1 text-[12px] font-medium text-[var(--term-fg)] transition-colors hover:bg-[var(--term-btn-bg-hover)]"
|
||||
onClick={copy}
|
||||
>
|
||||
{copied ? (
|
||||
<Check width={14} height={14} />
|
||||
) : (
|
||||
<Copy width={14} height={14} />
|
||||
)}
|
||||
{copied ? 'Copied' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -115,6 +115,7 @@ export default function Keyboard() {
|
|||
<button
|
||||
type="button"
|
||||
key={k.id}
|
||||
aria-label={k.label}
|
||||
className={[
|
||||
'key',
|
||||
k.double ? 'keypad__double' : 'keypad__single',
|
||||
|
|
|
|||
|
|
@ -148,33 +148,53 @@ const logos = [
|
|||
},
|
||||
];
|
||||
|
||||
export const Logos = () => {
|
||||
// Split the wall into two rows that drift in opposite directions.
|
||||
const half = Math.ceil(logos.length / 2);
|
||||
const rowOne = logos.slice(0, half);
|
||||
const rowTwo = logos.slice(half);
|
||||
|
||||
function LogoLink({ alt, src, width, href, duplicate }) {
|
||||
return (
|
||||
<div className="py-16">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<div className="-mx-6 grid grid-cols-3 gap-0.5 overflow-hidden sm:mx-0 sm:rounded-2xl md:grid-cols-4">
|
||||
{logos.map(({ alt, src, width, href }) => (
|
||||
<a
|
||||
key={alt}
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="bg-gray-400/7 dark:bg-xenon-900 p-6 sm:p-10 flex flex-col items-center justify-center transition-all duration-200 cursor-pointer inset-shadow-none hover:inset-shadow-sm hover:bg-blue-400/40 !no-underline"
|
||||
>
|
||||
<img
|
||||
alt={alt}
|
||||
src={src}
|
||||
width={width}
|
||||
height={48}
|
||||
className="max-h-12 w-full object-contain"
|
||||
/>
|
||||
<span className="mt-4 text-sm font-medium text-zinc-400 dark:text-slate-200 !no-underline">
|
||||
{alt}
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={alt}
|
||||
tabIndex={duplicate ? -1 : undefined}
|
||||
className="logo-marquee-item no-underline!"
|
||||
>
|
||||
<img alt={alt} src={src} width={width} height={40} loading="lazy" />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function MarqueeRow({ items, duration, reverse }) {
|
||||
return (
|
||||
<div className="logo-marquee-row">
|
||||
<div
|
||||
className={`logo-marquee-track${reverse ? ' logo-marquee-track--reverse' : ''}`}
|
||||
style={{ '--marquee-duration': duration }}
|
||||
>
|
||||
<div className="logo-marquee-group">
|
||||
{items.map((logo) => (
|
||||
<LogoLink key={logo.alt} {...logo} />
|
||||
))}
|
||||
</div>
|
||||
<div className="logo-marquee-group" aria-hidden="true">
|
||||
{items.map((logo) => (
|
||||
<LogoLink key={`${logo.alt}-dup`} {...logo} duplicate={true} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Logos = () => {
|
||||
return (
|
||||
<div className="logo-marquee mx-auto max-w-6xl pt-4">
|
||||
<MarqueeRow items={rowOne} duration="40s" />
|
||||
<MarqueeRow items={rowTwo} duration="55s" reverse={true} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
28
packages/website/src/components/ui/reveal.jsx
Normal file
28
packages/website/src/components/ui/reveal.jsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Staggered entrance wrapper (ported from the DocSearch MCP app).
|
||||
* `variant="mask"` wipes in via clip-path; default slides up. `delay` in ms.
|
||||
* Motion collapses automatically under prefers-reduced-motion (see
|
||||
* custom.css).
|
||||
*/
|
||||
export function Reveal({
|
||||
children,
|
||||
as: Tag = 'div',
|
||||
delay = 0,
|
||||
variant = 'up',
|
||||
className,
|
||||
style,
|
||||
}) {
|
||||
const cls = variant === 'mask' ? 'reveal-mask' : 'reveal-up';
|
||||
return (
|
||||
<Tag
|
||||
className={className ? `${cls} ${className}` : cls}
|
||||
style={{ ...style, '--reveal-delay': `${delay}ms` }}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export default Reveal;
|
||||
File diff suppressed because it is too large
Load diff
28
packages/website/src/pages/demo-embed.jsx
Normal file
28
packages/website/src/pages/demo-embed.jsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
import Head from '@docusaurus/Head';
|
||||
import React from 'react';
|
||||
|
||||
import DemoApp from '../components/demo/DemoApp';
|
||||
|
||||
import '@docsearch/css/dist/style.css';
|
||||
import '@docsearch/css/dist/sidepanel.css';
|
||||
|
||||
import '../components/demo/demo.css';
|
||||
|
||||
// Bare, chrome-less page embedded in an iframe on the homepage. It hosts a live
|
||||
// DocSearch widget driven by the autopilot. No @theme/Layout on purpose: the
|
||||
// iframe's own document body is the containment box for the modal/side panel,
|
||||
// and it keeps the site navbar's real DocSearch instance out of the way.
|
||||
export default function DemoEmbedPage() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<html lang="en" className="docsearch-demo-embed-html" />
|
||||
<body className="docsearch-demo-embed-body" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<title>DocSearch live demo</title>
|
||||
</Head>
|
||||
<BrowserOnly>{() => <DemoApp />}</BrowserOnly>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue