@easyops-cn/docusaurus-search-local 0.52.3 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [0.53.0](https://github.com/easyops-cn/docusaurus-search-local/compare/v0.52.3...v0.53.0) (2026-02-05)
6
+
7
+
8
+ ### Features
9
+
10
+ * support Ask AI ([8a5d29e](https://github.com/easyops-cn/docusaurus-search-local/commit/8a5d29e2e5b3b1cb534b1bbf13075f6824307d2e))
11
+ * support Ask AI ([3b2e339](https://github.com/easyops-cn/docusaurus-search-local/commit/3b2e339a04ac9347e73fe39d63237ffa3e63242a))
12
+
5
13
  ## [0.52.3](https://github.com/easyops-cn/docusaurus-search-local/compare/v0.52.2...v0.52.3) (2026-01-29)
6
14
 
7
15
 
@@ -5,10 +5,13 @@ import useIsBrowser from "@docusaurus/useIsBrowser";
5
5
  import { useHistory, useLocation } from "@docusaurus/router";
6
6
  import { translate } from "@docusaurus/Translate";
7
7
  import { useActivePlugin, useActiveVersion, } from "@docusaurus/plugin-content-docs/client";
8
+ import { AskAIWidget } from "open-ask-ai";
9
+ import "open-ask-ai/styles.css";
8
10
  import { fetchIndexesByWorker, searchByWorker } from "../searchByWorker";
9
11
  import { SuggestionTemplate } from "./SuggestionTemplate";
10
12
  import { EmptyTemplate } from "./EmptyTemplate";
11
- import { Mark, searchBarShortcut, searchBarShortcutHint, searchBarShortcutKeymap, searchBarPosition, docsPluginIdForPreferredVersion, searchContextByPaths, hideSearchBarWithNoSearchContext, useAllContextsWithNoSearchContext, } from "../../utils/proxiedGenerated";
13
+ import { SearchDocumentType } from "../../../shared/interfaces";
14
+ import { Mark, searchBarShortcut, searchBarShortcutHint, searchBarShortcutKeymap, searchBarPosition, docsPluginIdForPreferredVersion, searchContextByPaths, hideSearchBarWithNoSearchContext, useAllContextsWithNoSearchContext, askAi, } from "../../utils/proxiedGenerated";
12
15
  import LoadingRing from "../LoadingRing/LoadingRing";
13
16
  import { normalizeContextByPath } from "../../utils/normalizeContextByPath";
14
17
  import { searchResultLimits } from "../../utils/proxiedGeneratedConstants";
@@ -49,6 +52,7 @@ export default function SearchBar({ handleSearchBarToggle, }) {
49
52
  const [inputChanged, setInputChanged] = useState(false);
50
53
  const [inputValue, setInputValue] = useState("");
51
54
  const search = useRef(null);
55
+ const askAIWidgetRef = useRef(null);
52
56
  const prevSearchContext = useRef("");
53
57
  const [searchContext, setSearchContext] = useState("");
54
58
  const prevVersionUrl = useRef(baseUrl);
@@ -176,7 +180,25 @@ export default function SearchBar({ handleSearchBarToggle, }) {
176
180
  {
177
181
  source: async (input, callback) => {
178
182
  const result = await searchByWorker(versionUrl, searchContext, input, searchResultLimits);
179
- callback(result);
183
+ if (input && askAi) {
184
+ callback([
185
+ {
186
+ document: {
187
+ i: -1,
188
+ t: "",
189
+ u: "",
190
+ },
191
+ type: SearchDocumentType.AskAI,
192
+ page: undefined,
193
+ metadata: {},
194
+ tokens: [input],
195
+ },
196
+ ...result,
197
+ ]);
198
+ }
199
+ else {
200
+ callback(result);
201
+ }
180
202
  },
181
203
  templates: {
182
204
  suggestion: SuggestionTemplate,
@@ -195,8 +217,12 @@ export default function SearchBar({ handleSearchBarToggle, }) {
195
217
  },
196
218
  },
197
219
  ])
198
- .on("autocomplete:selected", function (event, { document: { u, h }, tokens }) {
220
+ .on("autocomplete:selected", function (event, { document: { u, h }, type, tokens }) {
199
221
  searchBarRef.current?.blur();
222
+ if (type === SearchDocumentType.AskAI && askAi) {
223
+ askAIWidgetRef.current?.openWithNewSession(tokens.join(""));
224
+ return;
225
+ }
200
226
  let url = u;
201
227
  if (Mark && tokens.length > 0) {
202
228
  const params = new URLSearchParams();
@@ -333,6 +359,9 @@ export default function SearchBar({ handleSearchBarToggle, }) {
333
359
  message: "Search",
334
360
  description: "The ARIA label and placeholder for search button",
335
361
  })} aria-label="Search" className={`navbar__search-input ${styles.searchInput}`} onMouseEnter={onInputMouseEnter} onFocus={onInputFocus} onBlur={onInputBlur} onChange={onInputChange} ref={searchBarRef} value={inputValue}/>
362
+ {askAi && (<AskAIWidget ref={askAIWidgetRef} {...askAi}>
363
+ <span hidden></span>
364
+ </AskAIWidget>)}
336
365
  <LoadingRing className={styles.searchBarLoadingRing}/>
337
366
  {searchBarShortcut &&
338
367
  searchBarShortcutHint &&
@@ -17,10 +17,37 @@
17
17
  }
18
18
 
19
19
  .searchInput:focus {
20
- outline: 2px solid var(--search-local-input-active-border-color, var(--ifm-color-primary));
20
+ outline: 2px solid
21
+ var(--search-local-input-active-border-color, var(--ifm-color-primary));
21
22
  outline-offset: 0px;
22
23
  }
23
24
 
25
+ html[data-theme="dark"] div:global(.ask-ai),
26
+ div:global(.ask-ai) {
27
+ --ask-ai-primary: var(--ifm-color-primary);
28
+ --ask-ai-primary-hover: var(--ifm-color-primary-light);
29
+ --ask-ai-foreground: var(--ifm-color-content);
30
+ --ask-ai-border: var(--ifm-color-emphasis-300);
31
+ --ask-ai-error: var(--ifm-color-danger);
32
+ --ask-ai-button-bg: var(--ifm-color-emphasis-200);
33
+ }
34
+
35
+ :global(.ask-ai) {
36
+ --ask-ai-background: var(--search-local-modal-background, #f5f6f7);
37
+ --ask-ai-muted: var(--search-local-muted-color, #969faf);
38
+ }
39
+
40
+ html[data-theme="dark"] :global(.ask-ai) {
41
+ --ask-ai-background: var(
42
+ --search-local-modal-background,
43
+ var(--ifm-background-color)
44
+ );
45
+ --ask-ai-muted: var(
46
+ --search-local-muted-color,
47
+ var(--ifm-color-secondary-darkest)
48
+ );
49
+ }
50
+
24
51
  @media not (max-width: 996px) {
25
52
  .searchBar.searchBarLeft .dropdownMenu {
26
53
  left: 0 !important;
@@ -1,12 +1,21 @@
1
1
  import { SearchDocumentType, } from "../../../shared/interfaces";
2
2
  import { concatDocumentPath } from "../../utils/concatDocumentPath";
3
+ import { escapeHtml } from "../../utils/escapeHtml";
3
4
  import { getStemmedPositions } from "../../utils/getStemmedPositions";
4
5
  import { highlight } from "../../utils/highlight";
5
6
  import { highlightStemmed } from "../../utils/highlightStemmed";
6
7
  import { explicitSearchResultPath } from "../../utils/proxiedGenerated";
7
- import { iconAction, iconContent, iconHeading, iconTitle, iconTreeInter, iconTreeLast, } from "./icons";
8
+ import { iconAction, iconAskAI, iconContent, iconHeading, iconTitle, iconTreeInter, iconTreeLast, } from "./icons";
8
9
  import styles from "./SearchBar.module.css";
9
10
  export function SuggestionTemplate({ document, type, page, metadata, tokens, isInterOfTree, isLastOfTree, }) {
11
+ if (type === SearchDocumentType.AskAI) {
12
+ return [
13
+ `<span class="${styles.hitIcon}">${iconAskAI}</span>`,
14
+ `<span class="${styles.hitWrapper}">`,
15
+ `<span class="${styles.hitTitle}">Ask AI: <mark>${escapeHtml(tokens.join(" "))}</mark></span>`,
16
+ `</span>`,
17
+ ].join("");
18
+ }
10
19
  const isTitle = type === SearchDocumentType.Title;
11
20
  const isKeywords = type === SearchDocumentType.Keywords;
12
21
  const isTitleRelated = isTitle || isKeywords;
@@ -5,3 +5,4 @@ export const iconAction = '<svg width="20" height="20" viewBox="0 0 20 20"><g st
5
5
  export const iconNoResults = '<svg width="40" height="40" viewBox="0 0 20 20" fill="none" fill-rule="evenodd" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M15.5 4.8c2 3 1.7 7-1 9.7h0l4.3 4.3-4.3-4.3a7.8 7.8 0 01-9.8 1m-2.2-2.2A7.8 7.8 0 0113.2 2.4M2 18L18 2"></path></svg>';
6
6
  export const iconTreeInter = '<svg viewBox="0 0 24 54"><g stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path d="M8 6v42M20 27H8.3"></path></g></svg>';
7
7
  export const iconTreeLast = '<svg viewBox="0 0 24 54"><g stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path d="M8 6v21M20 27H8.3"></path></g></svg>';
8
+ export const iconAskAI = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles-icon lucide-sparkles"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg>';
@@ -5,4 +5,5 @@ export var SearchDocumentType;
5
5
  SearchDocumentType[SearchDocumentType["Description"] = 2] = "Description";
6
6
  SearchDocumentType[SearchDocumentType["Keywords"] = 3] = "Keywords";
7
7
  SearchDocumentType[SearchDocumentType["Content"] = 4] = "Content";
8
+ SearchDocumentType[SearchDocumentType["AskAI"] = 5] = "AskAI";
8
9
  })(SearchDocumentType || (SearchDocumentType = {}));
@@ -6,7 +6,7 @@ const fs_1 = tslib_1.__importDefault(require("fs"));
6
6
  const path_1 = tslib_1.__importDefault(require("path"));
7
7
  const getIndexHash_1 = require("./getIndexHash");
8
8
  function generate(config, dir) {
9
- const { language, removeDefaultStopWordFilter, removeDefaultStemmer, highlightSearchTermsOnTargetPage, searchResultLimits, searchResultContextMaxLength, explicitSearchResultPath, searchBarShortcut, searchBarShortcutHint, searchBarShortcutKeymap, searchBarPosition, docsPluginIdForPreferredVersion, indexDocs, searchContextByPaths, hideSearchBarWithNoSearchContext, useAllContextsWithNoSearchContext, fuzzyMatchingDistance, } = config;
9
+ const { language, removeDefaultStopWordFilter, removeDefaultStemmer, highlightSearchTermsOnTargetPage, searchResultLimits, searchResultContextMaxLength, explicitSearchResultPath, searchBarShortcut, searchBarShortcutHint, searchBarShortcutKeymap, searchBarPosition, docsPluginIdForPreferredVersion, indexDocs, searchContextByPaths, hideSearchBarWithNoSearchContext, useAllContextsWithNoSearchContext, fuzzyMatchingDistance, askAi, } = config;
10
10
  const indexHash = (0, getIndexHash_1.getIndexHash)(config);
11
11
  const contents = [];
12
12
  contents.push(`export const removeDefaultStemmer = ${JSON.stringify(removeDefaultStemmer)};`);
@@ -41,6 +41,7 @@ function generate(config, dir) {
41
41
  : null)};`);
42
42
  contents.push(`export const hideSearchBarWithNoSearchContext = ${JSON.stringify(!!hideSearchBarWithNoSearchContext)};`);
43
43
  contents.push(`export const useAllContextsWithNoSearchContext = ${JSON.stringify(!!useAllContextsWithNoSearchContext)};`);
44
+ contents.push(`export const askAi = ${JSON.stringify(askAi !== null && askAi !== void 0 ? askAi : null)};`);
44
45
  fs_1.default.writeFileSync(path_1.default.join(dir, "generated.js"), contents.join("\n"));
45
46
  const constantContents = [
46
47
  `import lunr from ${JSON.stringify(require.resolve("lunr"))};`,
@@ -40,6 +40,7 @@ const schema = utils_validation_1.Joi.object({
40
40
  useAllContextsWithNoSearchContext: utils_validation_1.Joi.boolean().default(false),
41
41
  forceIgnoreNoIndex: utils_validation_1.Joi.boolean().default(false),
42
42
  fuzzyMatchingDistance: utils_validation_1.Joi.number().default(1),
43
+ askAi: utils_validation_1.Joi.object().optional(),
43
44
  });
44
45
  function validateOptions({ options, validate, }) {
45
46
  return validate(schema, options || {});
@@ -8,4 +8,5 @@ var SearchDocumentType;
8
8
  SearchDocumentType[SearchDocumentType["Description"] = 2] = "Description";
9
9
  SearchDocumentType[SearchDocumentType["Keywords"] = 3] = "Keywords";
10
10
  SearchDocumentType[SearchDocumentType["Content"] = 4] = "Content";
11
+ SearchDocumentType[SearchDocumentType["AskAI"] = 5] = "AskAI";
11
12
  })(SearchDocumentType || (exports.SearchDocumentType = SearchDocumentType = {}));
@@ -0,0 +1,43 @@
1
+ declare module "ai" {
2
+ interface UIMessage {
3
+ }
4
+ interface UIMessageChunk {
5
+ }
6
+ }
7
+ declare module "@easyops-cn/autocomplete.js" {
8
+ const noConflict: () => void;
9
+ }
10
+ declare module "*/generated.js" {
11
+ const removeDefaultStemmer: string[];
12
+ class Mark {
13
+ constructor(root: HTMLElement);
14
+ mark: (terms: string[], options?: Record<string, unknown>) => void;
15
+ unmark: () => void;
16
+ }
17
+ const searchResultContextMaxLength: number;
18
+ const explicitSearchResultPath: boolean;
19
+ const searchBarShortcut: boolean;
20
+ const searchBarShortcutHint: boolean;
21
+ const searchBarShortcutKeymap: string;
22
+ const searchBarPosition: "left" | "right";
23
+ const docsPluginIdForPreferredVersion: string;
24
+ const indexDocs: boolean;
25
+ const searchContextByPaths: (string | {
26
+ label: string | Record<string, string>;
27
+ path: string;
28
+ })[];
29
+ const hideSearchBarWithNoSearchContext: boolean;
30
+ const useAllContextsWithNoSearchContext: boolean;
31
+ const forceIgnoreNoIndex: boolean;
32
+ const askAi: import("open-ask-ai").AskAIWidgetProps;
33
+ }
34
+ declare module "*/generated-constants.js" {
35
+ const removeDefaultStopWordFilter: string[];
36
+ const language: string[];
37
+ const searchIndexUrl: string;
38
+ const searchResultLimits: number;
39
+ const fuzzyMatchingDistance: number;
40
+ const __setLanguage: (value: string[]) => void;
41
+ const __setRemoveDefaultStopWordFilter: (value: string[]) => void;
42
+ }
43
+ declare module "@docusaurus/Head";
@@ -1,3 +1,4 @@
1
+ import { AskAIWidgetProps } from "open-ask-ai";
1
2
  export interface PluginOptions {
2
3
  /**
3
4
  * Whether to index docs.
@@ -193,4 +194,8 @@ export interface PluginOptions {
193
194
  * @default 1
194
195
  */
195
196
  fuzzyMatchingDistance?: number;
197
+ /**
198
+ * Configuration for Ask AI widget integration. When not set, the Ask AI feature will be disabled.
199
+ */
200
+ askAi?: AskAIWidgetProps;
196
201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easyops-cn/docusaurus-search-local",
3
- "version": "0.52.3",
3
+ "version": "0.53.0",
4
4
  "description": "An offline/local search plugin for Docusaurus v3",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,6 +46,7 @@
46
46
  "lunr": "^2.3.9",
47
47
  "lunr-languages": "^1.4.0",
48
48
  "mark.js": "^8.11.1",
49
+ "open-ask-ai": "^0.7.3",
49
50
  "tslib": "^2.4.0"
50
51
  },
51
52
  "devDependencies": {