Search & AI Chatbot
The docs site has two help affordances:
- Local full-text search — a search bar in the navbar, indexes every page at build time. No external service.
- AI chatbot — a floating help button in the lower-right that streams answers from Claude, grounded in the entire docs corpus.
Search
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
| Setting | Value | Why |
|---|---|---|
| Model | claude-opus-4-7 | Latest Opus — most accurate Q&A. Switch to claude-sonnet-4-6 if cost matters more than peak quality. |
effort | medium | Default high is overkill for docs Q&A; medium halves token spend with imperceptible quality loss. |
cache_control: ephemeral on the system prompt | 5-minute TTL | The full docs corpus (the system prompt) is the stable prefix shared across every request. Cache hits cost ~10% of the uncached price. |
max_tokens | 4096 | Plenty 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 type | Tokens | Cost (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
| Symptom | Likely cause | Fix |
|---|---|---|
| Widget says "couldn't connect to the help server" | systemd unit not running | systemctl status ygc-docs-chat, check journalctl -u ygc-docs-chat -e |
503 Service Unavailable from Apache | Reverse proxy enabled but chat server not listening on 3940 | Confirm port with ss -tlnp | grep 3940 |
| Chat says "API error 401" | ANTHROPIC_API_KEY missing/invalid in .env | Re-create .env, restart service |
| Tokens don't show cache hits after a few requests | System 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 navbar | Plugin not in themes array | Verify docusaurus.config.ts, rebuild |
| Search returns no results | Index out of date | Rebuild with npm run build and redeploy |