The ShopperGPT widget for Carrefour Traiteur. Builds to a single dist/agent.js file injected via ScriptTag on the OpenMage site.
Repository: github.com/selim-redpill/shoppergpt-carrefour-scriptag
After npm run build, commit and push dist/agent.js to main, then load the widget from any page:
<div id="shoppergpt-chat"></div>
<!-- Full-page chat + product panel -->
<script
src="https://cdn.jsdelivr.net/gh/selim-redpill/shoppergpt-carrefour-scriptag@main/dist/agent.js"
defer
></script>
<!-- Optional: override API URL / client id before the script loads -->
<script>
window.SHOPPERGPT_CONFIG = {
apiUrl: "http://127.0.0.1:8000",
clientId: "carrefour_traiteur",
};
</script>Floating FAB mode (no #shoppergpt-chat mount): omit the div; the script injects #shoppergpt-root automatically.
Pin a release tag instead of @main for production, e.g. @v1.0.0.
| Tool | Purpose |
|---|---|
| Preact + preact/compat | React-compatible UI, ~3kb |
| TypeScript | Type safety |
| Webpack 5 (UMD) | Single-file bundle → agent.js |
| Tailwind CSS | Utility styles inside Shadow DOM |
| Zustand | Global state (session, cart, messages) |
| Tanstack Query | Data fetching (ready for real API) |
| Framer Motion | Panel/message animations |
| Zod | API payload validation |
| React Markdown | Render LLM markdown responses |
cd shopper-gpt-carrefour-scriptag
npm installnpm run dev # Watch mode with source maps → dist/agent.js
npm run build # Production minified bundlesrc/
├── index.tsx # Entry point — mounts widget into Shadow DOM
├── types.ts # Shared TypeScript interfaces
├── store.ts # Zustand global state
├── events.ts # DOM event listeners & dispatchers
├── mockData.ts # Mock products, messages, store (used until backend is ready)
├── styles/
│ └── tailwind.css # Tailwind entry (imported by Webpack)
└── components/
├── Widget.tsx # Root component — FAB + animated panel container
├── ChatPanel.tsx # Left panel: messages, input, quick replies
├── ProductPanel.tsx # Right panel: product grid
├── ProductCard.tsx # Individual product tile with add-to-cart
└── ProductDetail.tsx # Full product detail view
The widget communicates with the host OpenMage page via CustomEvent on window.
| Event | Payload | When |
|---|---|---|
shoppergpt:session |
{ session_id: string } |
Page load |
shoppergpt:page_context |
{ store_id: string, store_name: string } |
Store selection |
| Event | Payload | When |
|---|---|---|
shoppergpt:cart_updated |
{ success: bool, product_id: string, action: 'add' | 'remove' } |
Cart action |
shoppergpt:change_shop |
{ store_id: string } |
User changes store in widget |
On first open, the widget sets:
shoppergpt_interacted=true; path=/; max-age=31536000
Carrefour reads this via GTM → GA4 custom dimension on purchase events.
- LLM responses — in
ChatPanel.tsx, replace thesetTimeoutmock block with afetchto your FastAPI endpoint. - Cart — in
ProductCard.tsxandProductDetail.tsx, replace the directaddToCart()call withPOST https://shoppergpt.fr/api/cart/add. - Products — in
ProductPanel.tsx, use Tanstack Query'suseQueryto fetch from your recommendations endpoint instead of falling back tomockProducts.