Skip to main content

Docusaurus search with ChatGPT

· 5 min read
Roy Firestein

This guide will show you how to add a search bar to your Docusaurus site using ChatGPT. Users will be able to ask questions in natural language and get answers from your documentation.

Prerequisites

Setup

Overview of the steps:

  1. Create a new Docusaurus site
  2. Install Markprompt
  3. Configure GitHub Actions workflow

Create a new Docusaurus site

Create a new Docusaurus site using the Docusaurus Getting Started guide, or use an existing site.

npx create-docusaurus@latest my-website classic --typescript

Start a local development server to verify that the site is working.

npm run start
note

For the rest of this guide, we recommend you add your documentation Markdown or MDX files to the docs directory.

Install Markprompt

First, install the NPM package.

npm install --save @markprompt/react @markprompt/css

Next, we add the Markprompt component to the site's theme directory. This will be used to render the search bar.

Create the following file at src/theme/SearchBar/index.tsx:

import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import '@markprompt/css';
import {
Markprompt,
type MarkpromptProps,
openMarkprompt,
} from '@markprompt/react';
import React, {useEffect, type ReactElement, useState} from 'react';

export default function SearchBar(): ReactElement {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [markpromptExtras, setMarkpromptExtras] = useState<any>({});
const {siteConfig} = useDocusaurusContext();

useEffect(() => {
if (typeof window === 'undefined') {
return;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
setMarkpromptExtras((window as any).markpromptConfigExtras || {});
}, []);
const markpromptConfigProps = (siteConfig.themeConfig.markprompt || {}) as MarkpromptProps;
const markpromptProps = {
...markpromptConfigProps,
references: {
...(markpromptConfigProps || {}).references,
...(markpromptExtras || {}).references,
getHref: (reference) => {
return reference.file?.path;
},
getLabel: (reference) => {
return reference.meta?.leadHeading?.value || reference.file?.title;
},
},
search: {
...(markpromptConfigProps || {}).search,
...(markpromptExtras || {}).search,
getHref: (result) => result.url,
getHeading: (result) => result.hierarchy?.lvl0,
getTitle: (result) => result.hierarchy?.lvl1,
getSubtitle: (result) => result.hierarchy?.lvl2,
},
};
if (markpromptProps.trigger?.floating) {
return <Markprompt {...markpromptProps} />;
} else {
return (
<>
<div id="markprompt"/>
<div className="navbar__search" key="search-box">
<span
aria-label="expand searchbar"
role="button"
className="search-icon"
onClick={openMarkprompt}
tabIndex={0}
/>
<input
id="search_input_react"
type="search"
placeholder={
markpromptProps.trigger?.placeholder || 'Search or ask'
}
aria-label={markpromptProps.trigger?.label || 'Search or ask'}
className="navbar__search-input search-bar"
onClick={openMarkprompt}
/>
<Markprompt
{...markpromptProps}
trigger={{
...markpromptProps.trigger,
customElement: true,
}}
/>
</div>
</>
);
}
}

Finally, update the docusaurus.config.js file to use the Markprompt search bar.

const config: Config = = {
// ...
themeConfig: {
// ...
markprompt: {
projectKey: process.env.REACT_APP_MARKPROMPT_API_KEY,
trigger: {
floating: true,
},
},
} satisfies Preset.ThemeConfig,
};
note

Remember to set the REACT_APP_MARKPROMPT_API_KEY environment variable to your Markprompt API key on your CI/CD system.

Configure GitHub Actions workflow

Create a new JS file at scripts/upload-docs.js with the following contents:

const fs = require('fs');
const validExtensions = ['.md', '.mdx'];

function walk(dir) {
let results = [];
const list = fs.readdirSync(dir);
list.forEach(function (file) {
file = dir + '/' + file;
const stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
results = results.concat(walk(file));
} else {
results.push(file);
}
});
return results;
}

(async () => {
let json;
const files = [];

// Create an array of objects with the path and content of each file.
const docs = walk('docs');

// Filter out files that don't have a valid extension.
for (const doc of docs) {
if (!validExtensions.includes(doc.slice(doc.lastIndexOf('.')))) {
continue;
}
files.push({
path: doc,
content: (await fs.promises.readFile(doc)).toString(),
});
}

// Upload the files to Markprompt.
try {
const result = await fetch('https://api.markprompt.com/v1/train', {
method: 'POST',
body: JSON.stringify({
files,
}),
headers: {
'Authorization': `Bearer ${process.env.MARKPROMPT_API_KEY}`,
'Content-Type': 'application/json',
},
});
json = await result.json();
} catch (error) {
console.error(error);
throw error.message || error;
}

// Check for errors.
if (json && (json.success || json.status === 'ok')) {
console.log(json);
} else {
console.error(json);
throw (json || {}).message || 'Unknown error';
}
})();

Finally, create a new GitHub Actions workflow file at .github/workflows/train.yml with the following contents:

name: Train Markprompt Model
on:
push:
branches:
- master
permissions:
contents: write
jobs:
train:
name: Train Markprompt Model
runs-on: ubuntu-latest
steps:
# Checkout the repo
- uses: actions/checkout@v3
# Upload the zip file using the `scripts/upload-docs.js` script
- name: Upload docs
run: node scripts/upload-docs.js
env:
MARKPROMPT_API_KEY: ${{ secrets.MARKPROMPT_API_KEY }}

This workflow will run the upload-docs.js script when you push to the master branch. The script will upload the contents of the docs directory to Markprompt.

note

Remember to set the MARKPROMPT_API_KEY secret on your GitHub repository.

After you merge new changes to the main branch, you’ll see the documents on Markprompt.

You can also run the indexing locally:

node scripts/upload-docs.js

Then, go back to the Markprompt dashboard to confirm all the documents were uploaded successfully.

Done

That's it! You can now ask questions in natural language and get answers from your documentation.

References