Chat Widget Integration Guide
Chat Widget Integration Guide
The TEEI Knowledge Assistant chat widget provides instant, role-aware answers to questions about TEEI’s platforms, programmes, and operations. This guide explains the architecture, installation options, and integration patterns across all six TEEI platforms.
Architecture Overview
The widget is a three-layer system:
Browser (chat widget) ↕ HTTP + credentials: 'include'RAG Worker (Cloudflare Worker) ↕ Workers AI (bge-m3 embeddings) + Vectorize (semantic search) + D1 (chunk store)Claude via AI GatewayComponent responsibilities
| Component | Package / Location | Role |
|---|---|---|
ChatProvider | @teei/chat-widget | Context provider — holds apiUrl, platform, userRole, language |
ChatWidget | @teei/chat-widget | Floating button + chat panel, rendered as a React island |
| RAG Worker | knowledge-api.theeducationalequalityinstitute.org | REST API: /api/chat, /api/ingest, /api/feedback, /health |
| Vectorize index | Cloudflare Vectorize (teei-knowledge-index) | 1024-dim cosine similarity search via bge-m3 |
| D1 database | Cloudflare D1 (teei-knowledge-db) | Full-text chunk store, ingestion log, feedback, sessions |
| AI Gateway | Cloudflare AI Gateway (teei-knowledge) | Claude routing, caching, cost tracking |
How the widget communicates with the API
The widget uses the Vercel AI SDK (@ai-sdk/react) to stream chat responses. Every request includes three metadata fields:
// Sent on every message{ query: string; // User's question platform: string; // e.g. 'csr-cockpit', 'wbp', 'admin', 'website' user_role: string; // e.g. 'manager', 'employee', 'volunteer', 'staff', 'admin', 'public' language: string; // e.g. 'en', 'uk', 'no'}The API uses platform and user_role to filter the Vectorize results — employees only receive content tagged for their role level and below. See Role Access Hierarchy below.
Auth flow
The widget uses browser session cookies — it does not manage auth itself.
1. User logs into the platform (CSR Cockpit / WBP Portal / Admin) → Session cookie set (csr_session, volunteer_session, admin_session, etc.)
2. Widget sends POST to https://knowledge-api.theeducationalequalityinstitute.org/api/chat → fetch({ credentials: 'include' }) — browser sends session cookies automatically
3. RAG Worker reads the cookie, validates session (if configured), resolves user_role → Current behaviour: role is passed as a prop by the host platform (server-rendered) → Future: Worker can verify the session directly for zero-trust role validationFor public pages (main website, docs), no cookie is required. The host platform passes userRole="public" and the widget sends unauthenticated requests.
Role access hierarchy
Roles are cumulative — higher roles see everything lower roles see:
| Role | Sees |
|---|---|
public | Public content only |
volunteer | Public + volunteer-only content |
employee | Public + volunteer + employee content |
staff | All of the above + staff-only content |
manager | All of the above + manager content |
admin | Everything |
This is enforced in the RAG Worker via Vectorize metadata filtering — not in the widget itself.
Installation
Option 1 — Local package link (monorepo / development)
The widget package lives at teei-knowledge/chat-widget/. Install it as a local dependency in any Astro project:
// In the consuming project's package.json:{ "dependencies": { "@teei/chat-widget": "file:../../teei-knowledge/chat-widget" }}Then build the package first:
cd teei-knowledge/chat-widgetpnpm installpnpm buildOption 2 — npm package (when published)
npm install @teei/chat-widget# orpnpm add @teei/chat-widgetThe package name is @teei/chat-widget. It is not yet published to npm — use the local link until publication.
Stylesheet
The widget ships a self-contained CSS file. Import it once, globally:
---// In your layout file (CockpitLayout.astro, AdminLayout.astro, etc.)import '@teei/chat-widget/styles';---Or in a global CSS entry point:
@import '@teei/chat-widget/styles';The stylesheet is scoped to .teei-chat-widget — it does not pollute global styles. Dark mode is handled automatically via [data-theme="dark"] — the widget reads the data-theme attribute set by the host platform.
Minimal integration example
---// Any Astro layout fileimport { ChatProvider, ChatWidget } from '@teei/chat-widget';import '@teei/chat-widget/styles';
// Your existing auth/session logicconst userRole = 'manager'; // resolved from session---
<html> <body> <slot />
<ChatProvider client:idle apiUrl="https://knowledge-api.theeducationalequalityinstitute.org" platform="csr-cockpit" userRole={userRole} language="en" > <ChatWidget /> </ChatProvider> </body></html>client:idle is the correct hydration strategy for all platforms. It defers React hydration until the browser is idle — the widget does not block page paint, Time to Interactive, or Largest Contentful Paint.
Never use client:visible — CSR Cockpit’s CLAUDE.md prohibits it globally.
Platform-specific guides
| Platform | Guide | platform value | userRole values |
|---|---|---|---|
| CSR Cockpit (manager) | csr-cockpit | csr-cockpit | viewer, csr_manager, platform_admin → mapped to manager/admin |
| CSR Cockpit (employee portal) | csr-cockpit | csr-portal | employee |
| WBP Portal | wbp-portal | wbp | volunteer |
| Admin Dashboard | admin-dashboard | admin | staff, admin |
| Main Website | main-website | website | public |
| Skills Academy | skills-academy | skills-academy | volunteer |
| Standalone / Docs | standalone | docs | public |
Configuration reference
ChatProvider props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
apiUrl | string | Yes | — | Base URL of the RAG Worker. No trailing slash. |
platform | string | Yes | — | Platform identifier. Filters vector search results. |
userRole | string | Yes | — | Resolved role for the current session. Controls content access. |
language | string | No | 'en' | ISO 639-1 language code. Affects answer language. |
position | 'bottom-right' | 'bottom-left' | No | 'bottom-right' | FAB position. |
children | ReactNode | Yes | — | Must contain <ChatWidget />. |
ChatWidget props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
defaultOpen | boolean | No | false | Open the panel on mount. |
greeting | string | No | Built-in greeting | Override the initial greeting message. |
Environment configuration
The RAG Worker reads ALLOWED_ORIGINS from wrangler.toml (or Cloudflare dashboard secrets). When adding a new platform origin, add it to the comma-separated list:
ALLOWED_ORIGINS = "https://theeducationalequalityinstitute.org,https://cockpit.theeducationalequalityinstitute.org,https://wbp.theeducationalequalityinstitute.org,https://admin.theeducationalequalityinstitute.org,https://docs.theeducationalequalityinstitute.org"For local development, set ALLOWED_ORIGINS = "http://localhost:*" in the [env.dev] section of wrangler.toml.
Peer dependencies
The widget requires React 18 or 19 and react-dom. All TEEI Astro platforms already include React via @astrojs/react. No additional installation needed.
{ "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }}Common patterns
Conditional rendering (hide on specific routes)
---const path = Astro.url.pathname;const EXCLUDE_PATHS = ['/login', '/portal/login', '/onboarding'];const showWidget = !EXCLUDE_PATHS.some(p => path.startsWith(p));---
{showWidget && ( <ChatProvider client:idle apiUrl={...} platform={...} userRole={...}> <ChatWidget /> </ChatProvider>)}Dynamic language from Astro locale
---// Starlight / i18n Astro projectconst lang = Astro.currentLocale ?? 'en'; // 'en' | 'uk' | 'no'---
<ChatProvider client:idle apiUrl={...} platform="website" userRole="public" language={lang}> <ChatWidget /></ChatProvider>Left-positioned widget (avoid sidebar conflicts)
<ChatProvider client:idle apiUrl={...} platform={...} userRole={...} position="bottom-left"> <ChatWidget /></ChatProvider>Further reading
- Testing the Integration
- Troubleshooting
- RAG Worker API reference (coming soon)