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 │ nginx (port 443) │
│ ChatWidget streams via SSE │ ──────▶ │ proxy_pass /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 nginx reverse-proxies it.

Deploying the chat server

End-to-end host setup is in Deploy the Docs Site. The short version, once the host has Node 20, the /opt/ygc-docs/{server,docs} layout, and /etc/ygc-docs-chat.env:

# From your laptop
rsync -az --exclude=node_modules --exclude=.env -e "ssh -p 827" \
server/ ubuntu@docs.yatcogroup.com:/opt/ygc-docs/server/

ssh -p 827 ubuntu@docs.yatcogroup.com 'cd /opt/ygc-docs/server && npm install'

# Install + start the systemd unit
ssh -p 827 ubuntu@docs.yatcogroup.com '
sudo cp /opt/ygc-docs/server/../*.service /etc/systemd/system/ 2>/dev/null || true
sudo systemctl daemon-reload
sudo systemctl enable --now ygc-docs-chat
'

# Verify
ssh -p 827 ubuntu@docs.yatcogroup.com 'curl -s http://localhost:3940/api/health'
# {"status":"ok","docsSize":...}

tsx is in devDependencies on purpose (it's the runtime for the TypeScript entrypoint), so use a plain npm install rather than --omit=dev.

nginx reverse proxy

The vhost at /etc/nginx/sites-available/docs.yatcogroup.com already proxies /api/ to 127.0.0.1:3940 with proxy_buffering off (required for SSE streaming) and a 600 s read timeout. The reference config lives in the repo at scripts/docs.yatcogroup.com-nginx.conf.

Verify end-to-end:

curl https://docs.yatcogroup.com/api/health
# {"status":"ok","docsSize":...}

Updating the docs corpus

The chat server loads docs/**/*.md at startup — content edits don't take effect until the service restarts. scripts/deploy.sh already does this:

ssh -p 827 ubuntu@docs.yatcogroup.com 'sudo systemctl restart ygc-docs-chat'

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 runningsudo systemctl status ygc-docs-chat, check sudo journalctl -u ygc-docs-chat -e
502 Bad Gateway from nginxReverse proxy enabled but chat server not listening on 3940Confirm port with sudo ss -tlnp | grep 3940
Chat says "API error 401"ANTHROPIC_API_KEY missing/invalidUpdate /etc/ygc-docs-chat.env (root, mode 600), sudo systemctl restart ygc-docs-chat
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