Docusaurus search with ChatGPT
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
- Existing or new Docusaurus v3 site
- Existing or new GitHub repository
- Markprompt account
- Node.js v18 or higher
Setup
Overview of the steps:
- Create a new Docusaurus site
- Install Markprompt
- 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
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,
};
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.
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.