メインコンテンツまでスキップ
This page is not available in 日本語 yet. You are reading the English version.

Attach Kaleidr Chat to a Leaflet map

At the end of this guide you have a page with a Leaflet map and a chat panel docked inside it. A visitor asks a question, the answer streams into the panel, and the places in it appear as pins on your Leaflet map while the view moves to show them. This works on every plan, including Free, with a publishable key. Questions spend your organization's AI credits.

Leaflet is a 2-D map. Everything the assistant does on it (pins, moving the view, fitting several places, a route line) works in two dimensions. See Limits for what that excludes.

Before you start​

  • A Kaleidr publishable key (kld_pk_live_…) from kaleidr.com/api-keys. New keys carry the ai scope that Chat needs. See Get an API key.
  • Your page's origin in the key's allowed origins, if the key lists any. For local work, add the origin you serve from, for example http://localhost:8080.
  • A tile layer for Leaflet. The examples use OpenStreetMap's public tiles, which are for light use; use your own tile provider in production.
  • Leaflet loaded with a script tag, so that window.L exists. If you import Leaflet from npm, read Making an npm-imported library a page global first.
  • A way to serve the page over HTTP, such as npx serve or python -m http.server 8080. A file opened from disk does not work.
  • If your site sends a Content Security Policy, the hosts on the Content Security Policy page allowed.

Step 1: Lay out the map and the chat layer​

Put the map and the chat mount target in one positioned wrapper. The chat layer sits on top of the map and lets pointer events through.

<div id="mapHost" style="position:relative; height:520px">
<div id="map" style="position:absolute; inset:0; z-index:0"></div>
<div id="chat" style="position:absolute; inset:0; z-index:1; pointer-events:none"></div>
</div>

On Leaflet, z-index:0 on the map element is required, not optional. Leaflet gives its panes and controls z-index values from 200 to 1000. Without a stacking context on the map element, those values compete directly with the chat layer's z-index:1, and the map's tiles are drawn over the panel.

You should see: nothing yet, a 520 px tall empty block.

Step 2: Load Leaflet and create the map​

Load the stylesheet in the page head and the script before your own code.

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const map = L.map('map').setView([51.5, -0.12], 11);

L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
</script>

Leaflet takes coordinates as latitude, then longitude. That is Leaflet's convention; the chat panel handles the conversion.

You should see: a map of central London that you can drag and zoom.

Step 3: Load kaleidr.js​

Add the loader after the Leaflet script and before the code that mounts the panel.

<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>

You should see: no change in the page. In the console, Kaleidr.version returns a version string.

Step 4: Mount the chat panel​

Pass your key and the Leaflet map object, the value L.map(…) returned.

const handle = Kaleidr.mount('#chat', {
product: 'chat',
publishableKey: 'kld_pk_live_…',
map,
contained: true,
});

Kaleidr.mount returns immediately; the panel appears once the chat bundle has loaded and your key has been exchanged for a session.

You should see: an input bar that reads Ask Kaleidr at the top-left corner of the map, above the tiles. Leaflet's zoom control is also at the top-left by default, underneath the panel; step 5 moves it.

Step 5: Move Leaflet's zoom control out of the way​

The panel docks at the top-left of its container, which is where Leaflet puts its zoom buttons. Replace the line from step 2 that creates the map: create it without the default control, and add a zoom control in another corner.

const map = L.map('map', { zoomControl: false }).setView([51.5, -0.12], 11);
L.control.zoom({ position: 'bottomright' }).addTo(map);

You should see: the zoom buttons at the bottom-right, clear of the panel.

The complete page​

Save this as index.html, replace the key, and serve the folder over HTTP.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kaleidr Chat on Leaflet</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
body { margin: 0; font-family: system-ui, sans-serif; }
#mapHost { position: relative; height: 100vh; }
#map { position: absolute; inset: 0; z-index: 0; }
#chat { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
</style>
</head>
<body>
<div id="mapHost">
<div id="map"></div>
<div id="chat"></div>
</div>

<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdn.kaleidr.com/embed/v1/kaleidr.js"></script>
<script>
const map = L.map('map', { zoomControl: false }).setView([51.5, -0.12], 11);
L.control.zoom({ position: 'bottomright' }).addTo(map);

L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);

const handle = Kaleidr.mount('#chat', {
product: 'chat',
publishableKey: 'kld_pk_live_…',
map,
contained: true,
});
</script>
</body>
</html>

How it behaves​

The visitorThe panelYour Leaflet map
Sends a questionExpands into a chat window; the answer streams in as textNothing yet
WaitsShows a result card for each place in the answerA pin appears for each place as it is located. One place: the view flies to it. Several: the view fits them with a 56 px margin.
Clicks a pin or a resultOpens a place card with an overview, photos and a directions toolClicking a result moves the view to that place
Requests directionsShows the route's distance and durationA route line is drawn and framed
Asks another questionAdds to the same conversationNew pins are added; earlier pins stay
Closes the chat windowReturns to the input bar; the conversation endsThe pins and the route are removed

Things specific to Leaflet:

  • Pins are Leaflet markers with an HTML icon, added to the marker pane next to any markers of your own. The panel never touches your markers.
  • Moving to one place uses Leaflet's animated flyTo.
  • A fit uses one margin for both sides of each axis. It does not account for the chat panel, so a result can land underneath it. The visitor can pan, or minimize the panel.
  • The route is two polylines in the overlay pane: a white casing under a colored line.
  • The session is renewed in the background for as long as the panel is mounted. You do not handle tokens.

Check that it worked​

In the page

  • The Ask Kaleidr input bar is over the top-left of the map.
  • Type Show me three museums in London and press Enter. Text streams in, three pins appear, and the map fits them.

In the console

  • No error or warning that contains [kaleidr.
  • typeof window.L is 'object'.

In the Network tab

RequestExpect
GET https://cdn.kaleidr.com/embed/v1/kaleidr.js200
GET https://cdn.kaleidr.com/embed/v1/chat.js200
POST https://api.kaleidr.com/inference-api/b2b/v1/sdk/sessions200, with a JSON body like the one below
POST https://api.kaleidr.com/inference-api/b2b/v1/chat/control/stream (after you ask)200, content-type: text/event-stream
{ "session_token": "kld_sess_live_…", "expires_in": 900, "product": "chat" }

If it does not work​

The SDK logs a short message and does not repeat the server's reason. When a request fails, open it in the Network tab and read its JSON response. A 403 or 404 carries its code in the detail field. A 429 carries its code in the error field, with a sentence in detail.

SymptomCauseFix
The panel is missing, but the console is clean and the session request returned 200The panel is behind the map. Leaflet's panes have a higher z-index than the chat layer.Add z-index:0 to the map element, as in step 1.
No panel. Console: a CORS error for …/sdk/sessions, then an error containing [kaleidr:chat]The key is unknown or revoked, or the page's origin is not in the key's allowed origins. The API only sends CORS headers to an admitted origin, so the browser hides the response.Check the key. Add the exact origin, including scheme and port, at kaleidr.com/api-keys.
No panel. Console: [kaleidr:chat] kaleidr session exchange failed (403)The key is valid here but may not open Chat. The response detail is insufficient_scope (no ai scope), product_not_allowed (the key is restricted to other products) or tier_capability_not_allowed.Use a key that carries ai and allows the chat product.
No panel. Console: [kaleidr:chat] Kaleidr: a server key (kld_sk_…) must never be used in the browser …You passed a server key. No request was made.Use a publishable key (kld_pk_live_…). If the server key was in a deployed page, revoke it.
No panel. Console: [kaleidr:chat] mount target not found: #chatThe selector matched nothing when mount ran.Run mount after the element exists, or pass the element itself.
No panel. Console: [kaleidr:chat] [kaleidr] failed to load https://cdn.kaleidr.com/embed/v1/chat.jsThe bundle was blocked, usually by a Content Security Policy or an ad blocker.Allow https://cdn.kaleidr.com in script-src.
Kaleidr is not definedYour code ran before kaleidr.js loaded, for example because the loader tag has async or defer.Load kaleidr.js with a plain script tag before your code, or run your code from the tag's load event.
The panel is at the top-left of the browser window, not over the mapcontained is missing, or #chat is not positioned.Add contained: true and the styles from step 1.
The panel answers and the map moves to a single place, but no pins or route lines appear and several places are not framedwindow.L does not exist, usually because Leaflet was imported from npm. The chat bundle needs it to create markers, route lines and bounds.Set window.L = L before mounting. See Supported maps.
The panel answers, but the map never changesThe object passed as map is not the Leaflet map, for example a container element or a React ref.Pass the value L.map(…) returned.
Leaflet's zoom buttons are covered by the panelBoth use the top-left corner.Move the zoom control, as in step 5.
The panel shows "Sorry — that didn't go through. Please try again." on every questionThe session is no longer valid: the key was revoked, or its allowed origins changed.Check the key, then reload the page.
The panel shows "You've reached your usage limit for now."A 429: your organization's AI credits are spent, or the key's rate limit was hit. All visitors of a page share one key's rate limit.See Quota and rate limits.
The map cannot be draggedThe chat layer is covering it.Add pointer-events:none to #chat.

More symptoms are on the Troubleshooting page.

Limits​

  • 2-D only. Leaflet has no bearing or pitch. The map stays flat and north-up, and there is no 3-D content.
  • Fits do not avoid the chat panel. On Leaflet the panel fits results with the same margin on both sides of the map, so the panel's side is not given extra room.
  • Your page gets no callback or event from the panel. You cannot react to a question, an answer or a pin click in code.
  • The assistant does not see your layers, your markers or the area the map is showing. Only the zoom level is sent with a question.
  • You cannot give the assistant instructions or your own data through the panel.
  • The language of the answers, the panel's title and its input placeholder are mount options (lang, title, placeholder): see the Chat reference. The panel's other labels are in English.
  • handle.setCamera() does nothing on a chat mount and logs a warning. Move the view with Leaflet's own API.
  • In a single-page app, call handle.destroy() when the map's view is removed. Mount again when it returns.

The full list is on Capabilities and limits.

Next steps​