Evolve Chat Widget
Embeddable chat widget for the Evolve e-commerce platform. Allows customers to interact with an AI shopping assistant directly on your website.
Features
- Floating button or inline embed mode
- Product cards with images, prices, and quantities
- Shopping cart and wishlist integration
- Configurable branding and labels
- Automatic user name extraction from JWT
- Cross-origin support with shared cookies
Installation
Script Tag (easiest)
Add the widget script to your HTML:
<script src="https://chat.evolve-platform.com/widget.js"></script>
<script>
EvolveChat.init({
tenantId: 'evolve',
mode: 'floating',
position: { horizontal: 'right', vertical: 'bottom' },
});
</script>
pnpm Package
pnpm install @evolve-packages/chat-widget
import { EvolveChatWidget } from '@evolve-platform/chat-widget';
function App() {
return (
<EvolveChatWidget
tenantId="evolve"
mode="floating"
branding={{ primaryColor: '#f97316' }}
/>
);
}
Configuration
Required Options
| Option | Type | Description |
|---|---|---|
tenantId | string | Tenant identifier for multi-tenant support |
Display Options
| Option | Type | Default | Description |
|---|---|---|---|
mode | 'floating' | 'inline' | 'floating' | Display mode |
container | string | - | CSS selector for inline mode container |
position | WidgetPosition | See below | Position for floating mode |
zIndex | number | 9999 | z-index for floating widget |
Position Configuration
interface WidgetPosition {
horizontal: 'left' | 'right'; // default: 'right'
vertical: 'top' | 'bottom'; // default: 'bottom'
offsetX?: number; // default: 20
offsetY?: number; // default: 20
}
Branding Options
interface WidgetBranding {
primaryColor?: string; // Primary color (default: '#f97316')
primaryTextColor?: string; // Text on primary (default: '#ffffff')
accentColor?: string; // Call-to-action colour (default: primaryColor)
accentTextColor?: string; // Text on accent (default: primaryTextColor)
headerTitle?: string; // Widget header title
logoUrl?: string; // Custom logo URL
avatarUrl?: string; // Avatar for assistant messages
showHeader?: boolean; // Inline only: header bar with logo and title (default: true)
}
Label Options
interface WidgetLabels {
placeholder?: string; // Input placeholder text
greeting?: string; // Greeting message (use {name} for user name)
thinking?: string; // "Thinking" indicator text
close?: string; // Close button label
sendLabel?: string; // Accessible name of the send button (default: 'Send message')
}
Context Options
| Option | Type | Description |
|---|---|---|
apiBaseUrl | string | API base URL (default: current origin) |
cartId | string | Shopping cart ID for cart operations |
firstName | string | User's first name for personalized greeting |
modelId | string | AI model ID to use |
Callbacks
interface WidgetCallbacks {
onOpen?: () => void; // Called when widget opens
onClose?: () => void; // Called when widget closes
onMessage?: (message: string) => void; // Called when user sends message
onError?: (error: Error) => void; // Called on error
}
Project assistant
The project assistant is an opt-in feature that turns the backend's project-assistant tools (ask_user, select_product, present_kit, offer_help, …) into interactive cards: question tiles, a product lane, a materials list with cart/list actions, manual links with a contact form, a quick-start screen and a progress bar.
<EvolveChatWidget
tenantId="evolve"
features={["projectAssistant"]}
isAuthenticated={isLoggedIn}
labels={{
projectAssistant: {
quickStartJobs: [
{ id: "roof", emoji: "🏠", title: "Solar panels on a roof", prompt: "I want to install solar panels on my roof" },
],
questSteps: ["Roof", "Panels", "Inverter", "Materials list"],
},
}}
/>
features={["projectAssistant"]}turns the feature on; without it the widget renders these tools as before.WidgetFeatureIdis exported for typing the list.isAuthenticateddecides which materials-list actions are offered: signed-in shoppers get "Add to cart" and "Save as list", anonymous shoppers only "Share list".loadShoppingLists(optional,() => Promise<WidgetShoppingList[]>, each{ id, version, name }) turns a signed-in shopper's "Save as list" into a favourites menu. The widget calls it every time the menu opens and never fetches lists itself, so the host loads them with its own session. Picking a list sendssave_listwith that list'slistIdandversion; "New shareable list" sends it without alistId. Without the callback, "Save as list" sendssave_liststraight away as before.createShoppingList(optional,(name: string) => Promise<WidgetShoppingList>) puts a "New list" in that menu instead of "New shareable list": the shopper enters a name, the host creates the list in the customer's account with its own session and returns{ id, version, name }, and the widget then sendssave_listwith thatlistIdandversion. A rejected promise keeps the menu open withreceiptNewListError. Without it the menu keeps the anonymous "New shareable list".labels.projectAssistant(ProjectAssistantLabels) overrides any copy. The package ships generic defaults and no catalogue content, so the host suppliesquickStartJobs(tiles on the empty state;promptis sent as the first message) andquestSteps(the progress bar; its names must match thequestStepvalues the backend tools emit). Leaving either empty hides that element.- When
quickStartJobsis non-empty, the quick-start screen replaces the hostgreetingon an empty chat. It showsquickStartTitle,quickStartSubtitle,quickStartQuestion, the job tiles andquickStartHint; an empty string hides any of the texts. - While the feature is active the composer is a large rounded field with a round send button.
branding.primaryColordrives the stepper, links, tile borders, card header bars and the bubble tints;branding.accentColordrives the calls to action: the send button, the receipt's first action and the help offer's contact button, with secondary receipt actions outlined in the primary colour. Without an accent both fall back to the primary colour. Either may be a CSS variable such asvar(--brand-main). - A page that already titles the chat can pass
branding={{ showHeader: false }}in inline mode; the widget then also drops its card shadow and radius so the host container frames it.
Backend contract
Every answer a project-assistant card sends is a regular user message whose text is a JSON object with a pa_action field. The backend system prompt must understand these six actions:
pa_action | Fields |
|---|---|
answer | question, values: string[] |
product_selected | sku, name |
product_entered | text |
add_to_cart | items: { sku, quantity }[] |
save_list | items: { sku, quantity }[], version, optional listId |
contact | firstName, lastName, email, companyName, phone, question |
The widget shows these messages as short badges instead of raw JSON.
AI SDK version
The widget runs on AI SDK 7. Chat-backend releases built on AI SDK 7 add toolMetadata to MCP tool chunks, which older widgets reject, so upgrade this package together with @evolve-framework/chat-backend (its ai-7 releases). The widget still parses streams from an AI SDK 6 backend.
Examples
Floating Widget (bottom-right)
EvolveChat.init({
tenantId: 'evolve',
mode: 'floating',
position: {
horizontal: 'right',
vertical: 'bottom',
offsetX: 20,
offsetY: 20,
},
branding: {
primaryColor: '#f97316',
},
});
Floating Widget (bottom-left)
EvolveChat.init({
tenantId: 'evolve',
mode: 'floating',
position: {
horizontal: 'left',
vertical: 'bottom',
},
});
Inline Embed
<div id="chat-container" style="width: 400px; height: 600px;"></div>
<script>
EvolveChat.init({
tenantId: 'evolve',
mode: 'inline',
container: '#chat-container',
});
</script>
With Cart Integration
EvolveChat.init({
tenantId: 'evolve',
mode: 'floating',
cartId: 'abc123-cart-id',
firstName: 'Jan',
});
Custom Branding
EvolveChat.init({
tenantId: 'evolve',
mode: 'floating',
branding: {
primaryColor: '#1e40af',
primaryTextColor: '#ffffff',
headerTitle: 'Shop Assistant',
},
labels: {
placeholder: 'Stel je vraag...',
greeting: 'Hoi {name}! Hoe kan ik je helpen?',
thinking: 'Even denken...',
},
});
Development
Build
pnpm run build
Outputs:
dist/widget.js- UMD bundle (for script tag, includes React)dist/widget.css- Extracted stylesdist/index.esm.js- ESM bundle (for pnpm, React as peer dep)dist/index.js- CJS bundle (for pnpm, React as peer dep)
Development Mode
pnpm run dev # Watch mode with auto-rebuild
pnpm run serve # Serve dist folder for testing
Type Check
pnpm run typecheck
Architecture
widget/
├── src/
│ ├── index.ts # pnpm package entry
│ ├── init.ts # Script tag entry (EvolveChat.init)
│ ├── components/
│ │ ├── ChatWidget.tsx # Main wrapper
│ │ ├── FloatingButton.tsx
│ │ ├── ChatPanel.tsx
│ │ ├── WidgetChat.tsx # Chat UI (no Next.js deps)
│ │ ├── WidgetProductCard.tsx
│ │ └── WidgetToolQuery.tsx
│ ├── context/
│ │ └── WidgetProvider.tsx
│ ├── styles/
│ │ └── widget.css
│ ├── types/
│ │ └── config.ts
│ └── utils/
│ ├── api.ts
│ └── graphql-product-mapper.ts
├── dist/ # Build output
├── package.json
├── rollup.config.js
└── tsconfig.json
Cookie Requirements
For authentication to work across domains, cookies must be configured:
- Cookie domain:
.evolve-platform.com(with leading dot for subdomains) - SameSite:
LaxorNone(withSecureflag) - CORS: Server must send
Access-Control-Allow-Credentials: true
Browser Support
- Chrome 80+
- Firefox 75+
- Safari 13+
- Edge 80+
License
MIT