Web Widget Architecture
The rdv.ai-widget repository contains the embeddable text-based chat interface. It is designed to be injected into any client’s existing website, allowing their visitors to interact with the same AI intelligence that powers their telephony systems.
Because it needs to be embedded on third-party websites without conflicting with existing code, the widget is built with a focus on isolation, speed, and bundle size.
Tech Stack & Tooling
- Framework: React + Vite
- Styling: Tailwind CSS (scoped)
- State Management: Zustand (for lightweight, unopinionated state)
- Network: Standard
fetchAPI utilizing Streams for real-time text generation (Server-Sent Events).
Embedding Strategy (Shadow DOM)
One of the biggest challenges with embeddable widgets is CSS bleeding. We don’t want the client website’s CSS rules to accidentally break the widget’s layout, and conversely, we don’t want the widget’s Tailwind classes to affect the host website.
To solve this, the widget wraps itself in a Shadow DOM.
When a client embeds the widget, they place a single <script> tag in their HTML:
<script
src="[https://cdn.rendez-vous.ai/widget.js](https://cdn.rendez-vous.ai/widget.js)"
data-client-id="clk123abc456def"
defer>
</script>Upon execution, the script:
- Creates a new HTML element (e.g.,
<rdv-widget-container>). - Attaches a
shadowRootto it. - Injects the compiled React application and the scoped Tailwind CSS stylesheet inside the Shadow DOM, ensuring complete isolation.
The Data Flow
The widget is completely stateless across page reloads by default, though it can optionally persist conversation IDs in sessionStorage to maintain chat context as the user navigates between different pages on the client’s website.
1. Initialization
When the widget mounts, it reads the data-client-id from its script tag and makes a GET request to the Core API’s /api/widget/config endpoint.
This returns the branding configuration fetched from Payload CMS:
primaryColor&secondaryColorbotName(e.g., “Support Agent”)greetingMessageavatarUrl
The widget dynamically applies these colors to its internal CSS variables to match the client’s brand.
2. Message Streaming
To provide a fast, conversational experience, the widget does not wait for the LLM to generate the entire response before displaying it.
When the user sends a message:
- The widget appends the user’s message to the local Zustand state and displays it immediately.
- It sends a
POSTrequest to the Core API’s/api/widget/chatendpoint. - The API responds with a
text/event-stream(Server-Sent Events). - The widget listens to the stream, appending chunks to a “typing” message block in real-time.
3. Tool Execution UI
If the AI decides to use a tool (e.g., searching for a calendar slot), the Core API sends specific event chunks indicating tool usage. The widget intercepts these and can display contextual UI, such as a “Loading calendar…” spinner or a custom React component for selecting a specific date.