mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
docs: website widget spec + implementation plan
- Widget design spec (embeddable AI chat + booking + lead capture) - Implementation plan (6 tasks) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
337
docs/superpowers/specs/2026-04-05-website-widget-design.md
Normal file
337
docs/superpowers/specs/2026-04-05-website-widget-design.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# Website Widget — Embeddable AI Chat + Appointment Booking
|
||||
|
||||
**Date**: 2026-04-05
|
||||
**Status**: Draft
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
A single JavaScript file that hospitals embed on their website via a `<script>` tag. Renders a floating chat bubble that opens to an AI chatbot (hospital knowledge base), appointment booking flow, and lead capture form. Themed to match the hospital's branding. All write endpoints are captcha-gated.
|
||||
|
||||
---
|
||||
|
||||
## Embed Code
|
||||
|
||||
```html
|
||||
<script src="https://engage-api.srv1477139.hstgr.cloud/widget.js"
|
||||
data-key="a8f3e2b1.7c4d9e6f2a1b8c3d5e0f4a2b6c8d1e3f7a9b0c2d4e6f8a1b3c5d7e9f0a2b"></script>
|
||||
```
|
||||
|
||||
The `data-key` is an HMAC-signed token: `{siteId}.{hmacSignature}`. Cannot be guessed or forged without the server-side secret.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Hospital Website (any tech stack)
|
||||
└─ <script data-key="xxx"> loads widget.js from sidecar
|
||||
└─ Widget initializes:
|
||||
1. GET /api/widget/init?key=xxx → validates key, returns theme + config
|
||||
2. Renders shadow DOM (CSS-isolated from host page)
|
||||
3. All interactions go to /api/widget/* endpoints
|
||||
|
||||
Sidecar (helix-engage-server):
|
||||
└─ src/widget/
|
||||
├── widget.controller.ts — REST endpoints for the widget
|
||||
├── widget.service.ts — lead creation, appointment booking, key validation
|
||||
├── widget.guard.ts — HMAC key validation + origin check
|
||||
├── captcha.guard.ts — reCAPTCHA/Turnstile verification
|
||||
└── widget-keys.service.ts — generate/validate site keys
|
||||
|
||||
Widget Bundle:
|
||||
└─ packages/helix-engage-widget/
|
||||
├── src/
|
||||
│ ├── main.ts — entry point, reads data-key, initializes
|
||||
│ ├── widget.ts — shadow DOM mount, theming, tab routing
|
||||
│ ├── chat.ts — AI chatbot (streaming)
|
||||
│ ├── booking.ts — appointment booking flow
|
||||
│ ├── contact.ts — lead capture form
|
||||
│ ├── captcha.ts — captcha integration
|
||||
│ ├── api.ts — HTTP client for widget endpoints
|
||||
│ └── styles.ts — CSS-in-JS (injected into shadow DOM)
|
||||
├── vite.config.ts — library mode, single IIFE bundle
|
||||
└── package.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sidecar Endpoints
|
||||
|
||||
All prefixed with `/api/widget/`. Public endpoints validate the site key. Write endpoints require captcha.
|
||||
|
||||
| Method | Path | Auth | Captcha | Description |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/init` | Key | No | Returns theme, config, captcha site key |
|
||||
| POST | `/chat` | Key | Yes (first message only) | AI chat stream (same knowledge base as agent AI) |
|
||||
| GET | `/doctors` | Key | No | Department + doctor list with visiting hours |
|
||||
| GET | `/slots` | Key | No | Available time slots for a doctor + date |
|
||||
| POST | `/book` | Key | Yes | Create appointment + lead + patient |
|
||||
| POST | `/lead` | Key | Yes | Create lead (contact form submission) |
|
||||
| POST | `/keys/generate` | Admin JWT | No | Generate a new site key for a hospital |
|
||||
| GET | `/keys` | Admin JWT | No | List all site keys |
|
||||
| DELETE | `/keys/:siteId` | Admin JWT | No | Revoke a site key |
|
||||
|
||||
---
|
||||
|
||||
## Site Key System
|
||||
|
||||
### Generation
|
||||
|
||||
```
|
||||
siteId = uuid v4 (random)
|
||||
payload = siteId
|
||||
signature = HMAC-SHA256(payload, SERVER_SECRET)
|
||||
key = `${siteId}.${signature}`
|
||||
```
|
||||
|
||||
The `SERVER_SECRET` is an environment variable on the sidecar. Never leaves the server.
|
||||
|
||||
### Validation
|
||||
|
||||
```
|
||||
input = "a8f3e2b1.7c4d9e6f2a1b8c3d5e0f4a2b6c8d1e3f7a9b0c2d4e6f8a1b3c5d7e9f0a2b"
|
||||
[siteId, signature] = input.split('.')
|
||||
expectedSignature = HMAC-SHA256(siteId, SERVER_SECRET)
|
||||
valid = timingSafeEqual(signature, expectedSignature)
|
||||
```
|
||||
|
||||
### Storage
|
||||
|
||||
Site keys are stored in Redis (already running in the stack):
|
||||
|
||||
```
|
||||
Key: widget:keys:{siteId}
|
||||
Value: JSON { hospitalName, allowedOrigins, active, createdAt }
|
||||
TTL: none (persistent until revoked)
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
widget:keys:a8f3e2b1 → {
|
||||
"hospitalName": "Global Hospital",
|
||||
"allowedOrigins": ["https://globalhospital.com", "https://www.globalhospital.com"],
|
||||
"createdAt": "2026-04-05T10:00:00Z",
|
||||
"active": true
|
||||
}
|
||||
```
|
||||
|
||||
CRUD via `SessionService` (getCache/setCache/deleteCache/scanKeys) — same pattern as caller cache and agent names.
|
||||
|
||||
### Origin Validation
|
||||
|
||||
On every widget request, the sidecar checks:
|
||||
1. Key signature is valid (HMAC)
|
||||
2. `siteId` exists and is active
|
||||
3. `Referer` or `Origin` header matches `allowedOrigins` for this site key
|
||||
4. If origin doesn't match → 403
|
||||
|
||||
---
|
||||
|
||||
## Widget UI
|
||||
|
||||
### Collapsed State (Floating Bubble)
|
||||
|
||||
- Position: fixed bottom-right, 20px margin
|
||||
- Size: 56px circle
|
||||
- Shows hospital logo (from theme)
|
||||
- Pulse animation on first load
|
||||
- Click → expands panel
|
||||
- Z-index: 999999 (above host page content)
|
||||
|
||||
### Expanded State (Panel)
|
||||
|
||||
- Size: 380px wide × 520px tall
|
||||
- Anchored bottom-right
|
||||
- Shadow DOM container (CSS isolation from host page)
|
||||
- Header: hospital logo + name + close button
|
||||
- Three tabs: Chat (default) | Book | Contact
|
||||
- All styled with brand colors from theme
|
||||
|
||||
### Chat Tab (Default)
|
||||
|
||||
- AI chatbot interface
|
||||
- Streaming responses (same endpoint as agent AI, but with widget system prompt)
|
||||
- Quick action chips: "Doctor availability", "Clinic timings", "Book appointment", "Treatment packages"
|
||||
- If AI detects it can't help → shows: "An agent will call you shortly" + lead capture fields (name, phone)
|
||||
- First message triggers captcha verification (invisible reCAPTCHA v3)
|
||||
|
||||
### Book Tab
|
||||
|
||||
Step-by-step appointment booking:
|
||||
|
||||
1. **Department** — dropdown populated from `/api/widget/doctors`
|
||||
2. **Doctor** — dropdown filtered by department, shows visiting hours
|
||||
3. **Date** — date picker (min: today, max: 30 days)
|
||||
4. **Time Slot** — grid of available slots from `/api/widget/slots`
|
||||
5. **Patient Details** — name, phone, age, gender, chief complaint
|
||||
6. **Captcha** — invisible reCAPTCHA v3 on submit
|
||||
7. **Confirmation** — "Appointment booked! Reference: ABC123. We'll send a confirmation SMS."
|
||||
|
||||
On successful booking:
|
||||
- Creates patient (if new phone number)
|
||||
- Creates lead with `source: 'WEBSITE'`
|
||||
- Creates appointment linked to patient + doctor
|
||||
- Rules engine scores the lead
|
||||
- Pushes to agent worklist
|
||||
- Real-time notification to agents
|
||||
|
||||
### Contact Tab
|
||||
|
||||
Simple lead capture form:
|
||||
- Name (required)
|
||||
- Phone (required)
|
||||
- Interest / Department (dropdown, optional)
|
||||
- Message (textarea, optional)
|
||||
- Captcha on submit
|
||||
- Success: "Thank you! An agent will call you shortly."
|
||||
|
||||
On submit:
|
||||
- Creates lead with `source: 'WEBSITE'`, `interestedService: interest`
|
||||
- Rules engine scores it
|
||||
- Pushes to agent worklist + notification
|
||||
|
||||
---
|
||||
|
||||
## Theming
|
||||
|
||||
Widget fetches theme from `/api/widget/init`:
|
||||
|
||||
```json
|
||||
{
|
||||
"brand": { "name": "Global Hospital", "logo": "https://..." },
|
||||
"colors": {
|
||||
"primary": "rgb(29 78 216)",
|
||||
"primaryLight": "rgb(219 234 254)",
|
||||
"text": "rgb(15 23 42)",
|
||||
"textLight": "rgb(100 116 139)"
|
||||
},
|
||||
"captchaSiteKey": "6Lc..."
|
||||
}
|
||||
```
|
||||
|
||||
Colors are injected as CSS variables inside the shadow DOM:
|
||||
```css
|
||||
:host {
|
||||
--widget-primary: rgb(29 78 216);
|
||||
--widget-primary-light: rgb(219 234 254);
|
||||
--widget-text: rgb(15 23 42);
|
||||
--widget-text-light: rgb(100 116 139);
|
||||
}
|
||||
```
|
||||
|
||||
All widget elements reference these variables. Changing the theme API → widget auto-updates on next load.
|
||||
|
||||
---
|
||||
|
||||
## Widget System Prompt (AI Chat)
|
||||
|
||||
Different from the agent AI prompt — tailored for website visitors:
|
||||
|
||||
```
|
||||
You are a virtual assistant for {hospitalName}.
|
||||
You help website visitors with:
|
||||
- Doctor availability and visiting hours
|
||||
- Clinic locations and timings
|
||||
- Health packages and pricing
|
||||
- Booking appointments
|
||||
- General hospital information
|
||||
|
||||
RULES:
|
||||
1. Be friendly and welcoming — this is the hospital's first impression
|
||||
2. If someone wants to book an appointment, guide them to the Book tab
|
||||
3. If you can't answer a question, say "I'd be happy to have our team call you" and ask for their name and phone number
|
||||
4. Never give medical advice
|
||||
5. Keep responses under 80 words — visitors are scanning, not reading
|
||||
6. Always mention the hospital name naturally in first response
|
||||
|
||||
KNOWLEDGE BASE:
|
||||
{same KB as agent AI — clinics, doctors, packages, insurance}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Captcha
|
||||
|
||||
- **Provider**: Google reCAPTCHA v3 (invisible) or Cloudflare Turnstile
|
||||
- **When**: On first chat message, appointment booking submit, lead form submit
|
||||
- **How**: Widget loads captcha script, gets token, sends with request. Sidecar validates via provider API before processing.
|
||||
- **Fallback**: If captcha fails to load (ad blocker), show a simple challenge or allow with rate limiting
|
||||
|
||||
---
|
||||
|
||||
## Widget Bundle
|
||||
|
||||
### Tech Stack
|
||||
|
||||
- **Preact** — 3KB, React-compatible API, sufficient for the widget UI
|
||||
- **Vite** — library mode build, outputs single IIFE bundle
|
||||
- **CSS-in-JS** — styles injected into shadow DOM (no external CSS files)
|
||||
- **Target**: ~60KB gzipped (Preact + UI + styles)
|
||||
|
||||
### Build Output
|
||||
|
||||
```
|
||||
dist/
|
||||
└── widget.js — single IIFE bundle, self-contained
|
||||
```
|
||||
|
||||
### Serving
|
||||
|
||||
Sidecar serves `widget.js` as a static file:
|
||||
```
|
||||
GET /widget.js → serves dist/widget.js with Cache-Control: public, max-age=3600
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lead Flow (all channels)
|
||||
|
||||
```
|
||||
Widget submit (chat/book/contact)
|
||||
→ POST /api/widget/lead or /api/widget/book
|
||||
→ captcha validation
|
||||
→ key + origin validation
|
||||
→ create patient (if new phone)
|
||||
→ create lead (source: WEBSITE, channel metadata)
|
||||
→ rules engine scores lead (source weight, campaign weight)
|
||||
→ push to agent worklist
|
||||
→ WebSocket notification to agents (bell + toast)
|
||||
→ response to widget: success + reference number
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
| Endpoint | Limit |
|
||||
|---|---|
|
||||
| `/init` | 60/min per IP |
|
||||
| `/chat` | 10/min per IP |
|
||||
| `/doctors`, `/slots` | 30/min per IP |
|
||||
| `/book` | 5/min per IP |
|
||||
| `/lead` | 5/min per IP |
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
**In scope:**
|
||||
- Widget JS bundle (Preact + shadow DOM + theming)
|
||||
- Sidecar widget endpoints (init, chat, doctors, slots, book, lead)
|
||||
- Site key generation + validation (HMAC)
|
||||
- Captcha integration (reCAPTCHA v3)
|
||||
- Lead creation with worklist integration
|
||||
- Appointment booking end-to-end
|
||||
- Origin validation
|
||||
- Rate limiting
|
||||
- Widget served from sidecar
|
||||
|
||||
**Out of scope:**
|
||||
- Live agent chat in widget (shows "agent will call you" instead)
|
||||
- Widget analytics/tracking dashboard
|
||||
- A/B testing widget variations
|
||||
- Multi-language widget UI
|
||||
- File upload in widget
|
||||
- Payment integration in widget
|
||||
Reference in New Issue
Block a user