Skip to content

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 Gateway

Component responsibilities

ComponentPackage / LocationRole
ChatProvider@teei/chat-widgetContext provider — holds apiUrl, platform, userRole, language
ChatWidget@teei/chat-widgetFloating button + chat panel, rendered as a React island
RAG Workerknowledge-api.theeducationalequalityinstitute.orgREST API: /api/chat, /api/ingest, /api/feedback, /health
Vectorize indexCloudflare Vectorize (teei-knowledge-index)1024-dim cosine similarity search via bge-m3
D1 databaseCloudflare D1 (teei-knowledge-db)Full-text chunk store, ingestion log, feedback, sessions
AI GatewayCloudflare 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 validation

For 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:

RoleSees
publicPublic content only
volunteerPublic + volunteer-only content
employeePublic + volunteer + employee content
staffAll of the above + staff-only content
managerAll of the above + manager content
adminEverything

This is enforced in the RAG Worker via Vectorize metadata filtering — not in the widget itself.


Installation

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:

Terminal window
cd teei-knowledge/chat-widget
pnpm install
pnpm build

Option 2 — npm package (when published)

Terminal window
npm install @teei/chat-widget
# or
pnpm add @teei/chat-widget

The 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 file
import { ChatProvider, ChatWidget } from '@teei/chat-widget';
import '@teei/chat-widget/styles';
// Your existing auth/session logic
const 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

PlatformGuideplatform valueuserRole values
CSR Cockpit (manager)csr-cockpitcsr-cockpitviewer, csr_manager, platform_admin → mapped to manager/admin
CSR Cockpit (employee portal)csr-cockpitcsr-portalemployee
WBP Portalwbp-portalwbpvolunteer
Admin Dashboardadmin-dashboardadminstaff, admin
Main Websitemain-websitewebsitepublic
Skills Academyskills-academyskills-academyvolunteer
Standalone / Docsstandalonedocspublic

Configuration reference

ChatProvider props

PropTypeRequiredDefaultDescription
apiUrlstringYesBase URL of the RAG Worker. No trailing slash.
platformstringYesPlatform identifier. Filters vector search results.
userRolestringYesResolved role for the current session. Controls content access.
languagestringNo'en'ISO 639-1 language code. Affects answer language.
position'bottom-right' | 'bottom-left'No'bottom-right'FAB position.
childrenReactNodeYesMust contain <ChatWidget />.

ChatWidget props

PropTypeRequiredDefaultDescription
defaultOpenbooleanNofalseOpen the panel on mount.
greetingstringNoBuilt-in greetingOverride 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 project
const 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