Widget Embed Guide

RobertsAutomotiveAIChat — v1.0.0

Preview Widget

Simplest Install — 1 Line

Paste this before the closing </body> tag. Replace YOUR_SITE_KEY with the key from your ClientSite record. The widget auto-initializes — no additional JavaScript needed.

<!-- Paste before </body> — widget launches automatically -->
<script src="https://roberts.blokbyblok.org/api/apps/__APP_ID__/functions/widgetLoader?siteKey=YOUR_SITE_KEY" async></script>
For non-technical teams: Send this snippet to your webmaster. They paste it once, anywhere before </body>, and the chat widget appears immediately.

Manual Init — Separate Script + Call

Load the script in <head>, then call init() at the bottom of your page body.

<!-- Option B: separate script + init call (more control) -->
<script src="https://roberts.blokbyblok.org/api/apps/__APP_ID__/functions/widgetLoader" async></script>
<script>
  window.RobertsAutomotiveAIChat.init({
    siteKey: "YOUR_SITE_KEY",
    domain: window.location.hostname
  });
</script>

Inline / Embedded Mode

Mount the chat panel inside a specific container instead of a floating launcher. Size the container however you like — the widget fills it.

<!-- Inline / embedded mode — widget fills this container -->
<div id="dealer-ai-chat" style="width:420px; height:640px;"></div>

<script src="https://roberts.blokbyblok.org/api/apps/__APP_ID__/functions/widgetLoader?siteKey=YOUR_SITE_KEY" async></script>
<script>
  window.RobertsAutomotiveAIChat.init({
    siteKey: "YOUR_SITE_KEY",
    container: "#dealer-ai-chat"
  });
</script>

Full Configuration Options

window.RobertsAutomotiveAIChat.init({
  siteKey:       "YOUR_SITE_KEY",       // required
  domain:        window.location.hostname, // recommended
  position:      "bottom-right",        // "bottom-right" | "bottom-left"
  openOnLoad:    false,                 // open panel immediately
  initialPrompt: null,                  // auto-send this text on open
  container:     null,                  // CSS selector → inline mode
  debug:         false,                 // console logging

  theme: {
    primaryColor: "#0066B1",            // overrides brand color
  },

  pageContextOverrides: {},             // merge into auto-detected context
  userContext: null,                    // { name, email, phone }

  onError: function(err) {              // optional error callback
    console.error("Widget error:", err.code, err.message);
  },
});

Events & Callbacks

var chat = window.RobertsAutomotiveAIChat;

// Subscribe to events
chat.on("ready",    function() { console.log("Widget ready"); });
chat.on("opened",   function() { /* panel visible */ });
chat.on("closed",   function() { /* panel hidden */ });

chat.on("messageSent", function(data) {
  // data.text, data.sessionId
  myAnalytics.track("chat_message", { text: data.text });
});

chat.on("inventoryCardClicked", function(data) {
  // data.vehicle = { id, year, make, model, price, ... }
  window.dataLayer.push({ event: "vehicle_card_click", vehicle_id: data.vehicle.id });
});

chat.on("handoffRequested", function(data) {
  // AI wants a human — route to CRM
  fetch("/crm/escalation", { method: "POST", body: JSON.stringify(data) });
});

chat.on("error", function(err) {
  // err.code = MISSING_SITE_KEY | BLOCKED_DOMAIN | CONFIG_NOT_FOUND | BACKEND_UNAVAILABLE
  console.warn("Widget error:", err.code);
});
ready{}Widget iframe loaded and ready
opened{}Chat panel became visible
closed{}Chat panel was hidden
messageSent{ text, sessionId }User submitted a message
responseReceived{ text, intent, resultType, vehicleCount, sessionId }AI response returned
leadSubmitted{ name, email, phone, sessionId }User submitted contact info
handoffRequested{ reason, sessionId }AI recommended human handoff
inventoryCardClicked{ vehicle, sessionId }User clicked a vehicle card
error{ code, message }An error occurred (see error codes above)

API Methods

init(config)Mount the widget. Safe to call multiple times — subsequent calls updateConfig().
open()Show the chat panel.
close()Hide the chat panel.
destroy()Remove all DOM nodes and event listeners.
updateConfig(nextConfig)Merge new config into the running widget.
identifyUser({ name, email, phone })Pass visitor identity — stored in session.
setPageContext(data)Override auto-detected page context sent to the AI.
on(event, fn)Subscribe to an event. Returns unsubscribe function.
off(event, fn)Unsubscribe from an event.
isOpen()Boolean — is the panel visible?
isReady()Boolean — has the widget fully initialized?

SPA & Page Context

Page context (URL, page type, visible vehicles) is detected automatically via pushState interception. Override manually for precise control.

// SPA navigation is detected automatically via pushState/popstate/polling.
// Override page context manually when needed:

router.afterEach(function(to) {
  window.RobertsAutomotiveAIChat.setPageContext({
    pageType: to.meta.pageType || "other",
    currentBrand: to.query.brand || null,
  });
});

// On VDP pages — pass current vehicle so AI has full context:
window.RobertsAutomotiveAIChat.setPageContext({
  pageType: "vdp",
  currentVDPVehicle: {
    id: vehicle.id, make: vehicle.make,
    model: vehicle.model, year: vehicle.year,
    trim: vehicle.trim, price: vehicle.price,
    stock_number: vehicle.stock,
  }
});

Graceful Error Handling

Every error surfaces via on("error", fn) and as a DOM CustomEvent. The widget never throws or breaks the host page.

MISSING_SITE_KEYsiteKey not provided in init()Logs warning, emits error event, widget does not mount
BLOCKED_DOMAINRequest from unauthorized domainWidget iframe shows lock icon + friendly message
CONFIG_NOT_FOUNDsiteKey has no matching ClientSite recordWidget iframe shows configuration error
BACKEND_UNAVAILABLEBackend function returns non-200 or times outWidget shows "Try again" button, retries on click
ALREADY_INITIALIZEDinit() called twiceCalls updateConfig() instead, no duplicate mount

Live Test Harness

// Inject the widget above to start testing...