Skip to main content

Search & AI Chatbot

The docs site has two help affordances:

  1. Local full-text search — a search bar in the navbar, indexes every page at build time. No external service.
  2. AI chatbot — a floating help button in the lower-right that streams answers from Claude, grounded in the entire docs corpus.

Powered by @easyops-cn/docusaurus-search-local. The plugin runs at build time, generates a Lucene-style index, and serves it from /search-index*.json. Zero runtime cost; everything is client-side.

Enable / config in docusaurus.config.ts:

themes: [
[
'@easyops-cn/docusaurus-search-local',
{
hashed: true, // hash the index filename for cache-busting
indexBlog: false, // we have no blog
docsRouteBasePath: '/', // because docs serve at site root
},
],
],

Rebuild after content changes (npm run build) — the index is regenerated each build.

AI Chatbot

A small Express server (server/index.ts) loads every docs/**/*.md file at startup, concatenates them into a system prompt, and streams Claude responses via Server-Sent Events. The frontend is a React widget in src/components/ChatWidget/ that injects via src/theme/Root.tsx so it appears on every page.

Architecture

┌──────────────────────────────┐ ┌────────────────────────────┐
│ Browser │ fetch │ Apache (port 443) │
│ ChatWidget streams via SSE │ ──────▶ │ ProxyPass /api/ → :3940 │
└──────────────────────────────┘ └─────────────┬──────────────┘


┌────────────────────────────┐
│ ygc-docs-chat (systemd) │
│ Express + tsx, port 3940 │
│ loads docs/ at startup │
└─────────────┬──────────────┘


┌────────────────────────────┐
│ api.anthropic.com │
│ claude-opus-4-7 │
│ + prompt caching │
└────────────────────────────┘

Model and caching strategy

SettingValueWhy
Modelclaude-opus-4-7Latest Opus — most accurate Q&A. Switch to claude-sonnet-4-6 if cost matters more than peak quality.
effortmediumDefault high is overkill for docs Q&A; medium halves token spend with imperceptible quality loss.
cache_control: ephemeral on the system prompt5-minute TTLThe full docs corpus (the system prompt) is the stable prefix shared across every request. Cache hits cost ~10% of the uncached price.
max_tokens4096Plenty for a docs answer; harder cap than the model would naturally hit.

The cache hit/miss stats are returned in the final SSE event (cache_read_input_tokens / cache_creation_input_tokens) — useful for verifying the cache actually warms up across requests.

Local development

In one terminal — start the docs site as usual:

cd /Users/jsalinga/odoo/odoo17/custom/ygc17/ygc17-docs
npm start
# http://localhost:3000

In another terminal — start the chat server:

cd /Users/jsalinga/odoo/odoo17/custom/ygc17/ygc17-docs/server
npm install # one-time
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env # one-time
npm run dev # tsx watch mode, restarts on file changes
# http://localhost:3940

The widget detects localhost and points to http://localhost:3940/api/chat directly. In production it uses the same-origin path /api/chat and Apache reverse-proxies it.

Deploying the chat server

One-time server setup:

# 1. Copy the server folder to the production host
rsync -avz --delete \
-e "ssh -p 1005" \
server/ \
root@ygc-docs.redtechitsolutions.com:/root/ygc-docs-chat/

# 2. SSH in
ssh -p 1005 root@ygc-docs.redtechitsolutions.com

# 3. Install Node 20+ and dependencies
cd /root/ygc-docs-chat
npm install --omit=dev
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
chmod 600 .env

# 4. Install the systemd unit
cp /path/to/scripts/ygc-docs-chat.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable ygc-docs-chat
systemctl start ygc-docs-chat

# 5. Verify
journalctl -u ygc-docs-chat -f
curl http://localhost:3940/api/health
# {"status":"ok","docsSize":...}

Apache reverse proxy

Enable required modules:

sudo a2enmod proxy proxy_http proxy_wstunnel headers
sudo systemctl reload apache2

Open /etc/apache2/sites-available/ygc-docs.redtechitsolutions.com.conf (or whatever the SSL vhost is named on this host) and paste the contents of scripts/ygc-docs.redtechitsolutions.com-chat.conf inside the <VirtualHost *:443> block. Then:

sudo apache2ctl configtest
sudo systemctl reload apache2

Verify end-to-end:

curl https://ygc-docs.redtechitsolutions.com/api/health
# Should return the same {"status":"ok",...} as the local check above.

Updating the docs corpus

The chat server loads docs/**/*.md at startup — content edits don't take effect until the service restarts. After deploying new docs:

ssh -p 1005 root@ygc-docs.redtechitsolutions.com 'systemctl restart ygc-docs-chat'

To automate, add this line to scripts/deploy.sh after the rsync step.

Cost guardrails

A typical Q&A turn (system prompt cached, ~1K user tokens, ~500 output tokens):

Token typeTokensCost (Opus 4.7)
Cache read (docs corpus)~30,000~$0.015
Input (user question)~1,000~$0.005
Output~500~$0.0125
Per turn~$0.03

First question of a 5-minute window pays the cache-write premium (~1.25× the read cost). Switch to claude-sonnet-4-6 to roughly halve all of these numbers if usage scales up.

Troubleshooting

SymptomLikely causeFix
Widget says "couldn't connect to the help server"systemd unit not runningsystemctl status ygc-docs-chat, check journalctl -u ygc-docs-chat -e
503 Service Unavailable from ApacheReverse proxy enabled but chat server not listening on 3940Confirm port with ss -tlnp | grep 3940
Chat says "API error 401"ANTHROPIC_API_KEY missing/invalid in .envRe-create .env, restart service
Tokens don't show cache hits after a few requestsSystem prompt changed between requests (e.g. a new doc was deployed but service wasn't restarted, leaving stale state)Restart the service to reload docs
Search bar missing from navbarPlugin not in themes arrayVerify docusaurus.config.ts, rebuild
Search returns no resultsIndex out of dateRebuild with npm run build and redeploy