Implement a Live Chat Widget¶
Overview¶
The Primethink Live Chat Widget is a floating support chat interface that embeds Primethink public chat sessions into any website. It provides a seamless way to offer AI-powered support to your users without leaving your page.
What It Does¶
- Floating Button: A customizable circular button that floats in the corner of your page
- Chat Interface: A draggable, resizable chat window that opens when the button is clicked
- Session Management: Automatically creates and persists chat sessions across page refreshes
- iframe Integration: Loads Primethink chat sessions in a secure, sandboxed iframe
- Local Storage: Maintains session continuity using browser localStorage
Key Features¶
- ✅ Session Persistence - Chat sessions survive page refreshes and navigation
- ✅ Draggable Interface - Users can move the chat window anywhere on screen
- ✅ Minimize/Maximize - Collapse the chat to title bar only
- ✅ Mobile Responsive - Adapts to mobile screens automatically
- ✅ No Dependencies - Pure HTML, CSS, and JavaScript
- ✅ Easy Configuration - Just update the chat ID to use your own chat
How It Works¶
Flow Diagram¶
User Action Widget Action Backend Action
─────────── ───────────── ──────────────
1. Click Button → Check localStorage
├─ Has session? → Load saved URL
└─ No session? → Continue to step 2
2. Show Preview → GET /preview → Return agent name,
(optional, creates nothing) greeting, upload settings
3. Create Session → POST /public-session → Generate session ID
(on first message) → Return session ID
4. Receive Response ← Read session_id from the JSON object
Build URL: /live/{chat-id}/public?sid=abc-123
5. Load Chat → Set iframe.src = URL
Save URL to localStorage
6. Chat Ready → User interacts with chat
7. Page Refresh → Check localStorage
Load saved session URL
Restore chat window
Technical Flow¶
- Initialization: Widget loads and checks localStorage for existing session
- Button Click: User clicks the floating button
- Preview (optional): GET request to
https://app.primethink.ai/api/v1/public/chats/{chat-id}/previewto learn the agent's name, greeting and upload settings without creating anything - API Call: POST request to
https://app.primethink.ai/api/v1/public/chats/{chat-id}/public-session, best deferred until the visitor sends their first message - Session Creation: Backend returns a session ID (e.g.,
"00136af9-a102-42e1-9ca1-e24ba6c30b4a") - URL Construction: Widget builds URL:
https://app.primethink.ai/live/{chat-id}/public?sid={session-id} - Load Chat: iframe loads the chat with session ID
- Persist Session: URL saved to localStorage
- Restore on Refresh: Next page load/refresh restores the saved session
The simple widgets later on this page skip the preview and create the session when the button is clicked, which is fine for a button a visitor has deliberately pressed. Fetch the preview instead when the chat surface is visible without any deliberate action, or when you want to show the agent's name or greeting in your own UI.
Minimal Implementation¶
Step 1: Basic HTML Structure¶
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Support Chat Example</title>
<style>
/* Styles will go here */
</style>
</head>
<body>
<!-- Your page content -->
<h1>My Website</h1>
<!-- Floating Chat Button -->
<button id="chat-button">💬</button>
<!-- Chat Frame -->
<div id="chat-frame">
<div class="title-bar">
<h3>Support</h3>
<button id="close-btn">×</button>
</div>
<div class="chat-content">
<iframe id="support-iframe"></iframe>
</div>
</div>
<script>
/* JavaScript will go here */
</script>
</body>
</html>
Step 2: Essential Styles¶
/* Floating Button */
#chat-button {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
font-size: 24px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 1000;
}
#chat-button:hover {
transform: scale(1.1);
}
#chat-button.hidden {
display: none;
}
/* Chat Frame */
#chat-frame {
position: fixed;
bottom: 100px;
right: 30px;
width: 380px;
height: 600px;
background: white;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
display: none;
flex-direction: column;
z-index: 1001;
overflow: hidden;
}
#chat-frame.open {
display: flex;
}
/* Title Bar */
.title-bar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 16px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.title-bar button {
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 28px;
height: 28px;
border-radius: 50%;
cursor: pointer;
font-size: 20px;
}
/* Chat Content */
.chat-content {
flex: 1;
position: relative;
}
#support-iframe {
width: 100%;
height: 100%;
border: none;
}
Step 3: Core JavaScript¶
class SupportChat {
constructor() {
// DOM elements
this.chatButton = document.getElementById('chat-button');
this.chatFrame = document.getElementById('chat-frame');
this.closeBtn = document.getElementById('close-btn');
this.iframe = document.getElementById('support-iframe');
// Configuration - CHANGE THESE VALUES
this.chatId = 'f6a7b8c9-d0e1-4234-9123-456789012345';
this.baseUrl = `https://app.primethink.ai/live/${this.chatId}/public`;
this.apiUrl = `https://app.primethink.ai/api/v1/public/chats/${this.chatId}/public-session`;
// Storage keys
this.STORAGE_KEY = 'support_chat_session_url';
this.init();
}
init() {
// Event listeners
this.chatButton.addEventListener('click', () => this.openChat());
this.closeBtn.addEventListener('click', () => this.closeChat());
// Restore saved session
this.restoreSession();
}
async openChat() {
// Show chat
this.chatFrame.classList.add('open');
this.chatButton.classList.add('hidden');
// Check for saved session
const savedUrl = this.readSavedSession();
if (savedUrl) {
this.iframe.src = savedUrl;
return;
}
// Create new session
try {
const response = await fetch(this.apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new Error(`Too many requests. Try again in ${retryAfter ?? 'a few'} seconds.`);
}
if (!response.ok) {
throw new Error(`Session creation failed with status ${response.status}`);
}
// The endpoint returns { session_id, scoped_token } — not a bare string.
const { session_id: sessionId } = await response.json();
const sessionUrl = `${this.baseUrl}?sid=${sessionId}`;
this.iframe.src = sessionUrl;
localStorage.setItem(this.STORAGE_KEY, sessionUrl);
} catch (error) {
console.error('Error creating session:', error);
this.iframe.srcdoc = '<p role="alert">Chat is temporarily unavailable. Please wait and try again.</p>';
}
}
closeChat() {
this.chatFrame.classList.remove('open');
this.chatButton.classList.remove('hidden');
this.iframe.src = '';
localStorage.removeItem(this.STORAGE_KEY);
}
restoreSession() {
const savedUrl = this.readSavedSession();
if (savedUrl) {
this.chatFrame.classList.add('open');
this.chatButton.classList.add('hidden');
this.iframe.src = savedUrl;
}
}
// A visitor who used the chat before the URL changed still has the old
// address in localStorage. Keep their session id, but rebuild the URL from
// the current baseUrl so they are not sent to the deprecated path.
readSavedSession() {
const savedUrl = localStorage.getItem(this.STORAGE_KEY);
if (!savedUrl) return null;
let sid = null;
try {
sid = new URL(savedUrl, window.location.origin).searchParams.get('sid');
} catch {
// Not a URL we can read — drop it and start a fresh session.
localStorage.removeItem(this.STORAGE_KEY);
return null;
}
if (!sid) {
localStorage.removeItem(this.STORAGE_KEY);
return null;
}
const currentUrl = `${this.baseUrl}?sid=${sid}`;
if (currentUrl !== savedUrl) localStorage.setItem(this.STORAGE_KEY, currentUrl);
return currentUrl;
}
}
// Initialize
new SupportChat();
Configuration¶
Required Values¶
To use your own Primethink chat, you need:
- Chat ID: Your public chat UUID
- Base URL: The public chat page URL
- API URL: The endpoint to create sessions
// Example for chat ID: abc-123-def-456
this.chatId = 'abc-123-def-456';
this.baseUrl = `https://app.primethink.ai/live/${this.chatId}/public`;
this.apiUrl = `https://app.primethink.ai/api/v1/public/chats/${this.chatId}/public-session`;
Finding Your Chat ID¶
Turn on Make Public in the chat's Settings tab and the full public chat URL appears underneath the switch, with a button to copy it, a button to open it, and a QR code beside it for pointing a phone at. Copying it from there is the reliable way to get the address; the chat ID is the segment in the middle of it.
Your Primethink public chat URL looks like:
https://app.primethink.ai/live/f6a7b8c9-d0e1-4234-9123-456789012345/public
└─────────────────┬────────────────┘
This is your Chat ID
The older /public/chats/{chat-id} address still works
Public chats used to be served at https://app.primethink.ai/public/chats/{chat-id}. Links already shared with that address are forwarded to the new one, carrying their query string with them, so an existing session and an embedder's UI overrides both survive the hop. Use the /live/{chat-id}/public form in anything new — the older path is kept only for links that are already out in the world.
Note that the session endpoint is unchanged: it is still POST /api/v1/public/chats/{chat-id}/public-session.
Enabling File Uploads¶
By default, public chats do not allow file uploads. To enable file upload functionality, you need to:
- Enable Documents & Collections in the chat settings (required for any file upload to work)
- Add the following Extra setting to either the Task or the Chat (the values
true,"true", and"1"are all accepted):
Where to set this:
- On the Task: Go to the task settings and add this to the Extra field — chats created from the task inherit it
- On the Chat: Go to the chat settings and add this to the Extra field
When enabled, users in the public chat will see a file upload button and can attach files to their messages.
UI Customization Parameters¶
You can customize the appearance of an embedded public chat by appending query parameters to the chat URL. These parameters override the default UI for that session.
URL Format¶
Available Parameters¶
| Parameter | Description | Accepted Values | Example |
|---|---|---|---|
ui_chat_title | Overrides the chat name shown in the top bar | Any string | ui_chat_title=Sales+Assistant |
ui_chat_footer_msg | Displays a message below the chat input (e.g. a disclaimer) | Any string | ui_chat_footer_msg=AI+may+make+mistakes |
ui_theme | Forces light or dark mode for the chat UI | light, dark | ui_theme=dark |
ui_bg_color | Sets the background behind the messages | Hex code or color name | ui_bg_color=%23f5f5f5 |
ui_agent_msg_color | Sets the color accent for agent (bot) messages | Hex code or color name | ui_agent_msg_color=teal |
ui_guest_msg_color | Sets the color accent for guest (user) messages | Hex code or color name | ui_guest_msg_color=%23667eea |
All parameters are optional. When omitted, the chat falls back to its default appearance.
Color Values¶
Color parameters (ui_bg_color, ui_agent_msg_color, ui_guest_msg_color) accept:
- Hex codes:
#rgb,#rgba,#rrggbband#rrggbbaa, with or without the#prefix. The 4- and 8-digit forms carry an alpha channel and are accepted, but read it as CSS does — see the note below if you are moving a link over from the old chat. Five- and seven-digit values are not hex codes in CSS and are ignored. - Named colors: any CSS color keyword —
red,teal,indigo,rebeccapurple,transparentand so on - Functional notation:
rgb(...),rgba(...),hsl(...),hsla(...)
Note: When using hex codes in a URL, encode the
#character as%23. For example:ui_bg_color=%23f0f0f0
A value that is not recognised is ignored and that part of the UI keeps its default, so a typo cannot break the page.
Changed: the public chat is now served by the app runner rather than by the main app, and colors are resolved as CSS rather than as Material colors. Two things follow:
- Material-only names —
amber,deeppurple,deeporange,bluegrey/bluegray— are no longer recognised. Use a hex code, or the nearest CSS keyword.- The 4- and 8-digit hex forms still work, but they no longer mean the same thing: CSS puts the alpha channel last (
#rgba,#rrggbbaa) where Material put it first (#argb,#aarrggbb). A value written for the old chat is therefore read differently —#8000was semi-transparent black and is now fully transparent red. Rewrite those with the alpha last, or use the 3- or 6-digit form, which means the same in both.Names shared by both —
red,teal,indigoand the rest — keep working, though the exact shade shifts a little: CSSredis#ff0000where Materialredwas#f44336.
Example: Fully Customized Embed¶
const chatId = 'abc-123-def-456';
const sessionId = 'session-id-here';
const params = new URLSearchParams({
sid: sessionId,
ui_chat_title: 'Acme Support',
ui_chat_footer_msg: 'AI-generated responses may not always be accurate.',
ui_theme: 'light',
ui_bg_color: '#f8f9fa',
ui_agent_msg_color: 'indigo',
ui_guest_msg_color: '#28a745',
});
const chatUrl = `https://app.primethink.ai/live/${chatId}/public?${params}`;
iframe.src = chatUrl;
Behavior Notes¶
- Parameters are preserved across redirects (e.g. when a session is created and the URL changes).
ui_chat_titleoverrides the chat name everywhere it appears (top bar and browser tab title).ui_chat_footer_msgis the only source of the footer line: the public chat shows nothing there unless the link asks for it.ui_themeoverrides the user's system theme preference for the chat window only.
API Details¶
Preview Endpoint¶
URL: GET https://app.primethink.ai/api/v1/public/chats/{chat-id}/preview
Read-only. It describes what a visitor is about to walk into — the agent's name, whether uploads are available, and the greeting the conversation will open with — without creating anything. Call it as often as you like; no session, no anonymous visitor, and no conversation is created by it.
No authentication is required, and the same public-chat rate limits apply as to every other endpoint on this page.
Response:
{
"assistant_name": "Support",
"documents_and_collections_enabled": true,
"extra": {
"PUBLIC_CHAT_UPLOAD_FILES": true
},
"greeting": "Hello! How can I help you today?",
"starts_immediately": false
}
| Field | Meaning |
|---|---|
assistant_name | The name of the agent answering this chat, or null if none is set. Useful for branding the widget header before the visitor says anything. |
documents_and_collections_enabled | Whether documents and collections are turned on for the chat. Uploads require this to be true. |
extra | The chat's extra settings, passed through as configured. This is where PUBLIC_CHAT_UPLOAD_FILES appears when uploads have been enabled. |
greeting | The opening message the conversation will start with, taken from the task behind the chat, or null when there is no task or the task has no initial prompt. A task that has been deleted counts as no task. |
starts_immediately | true when the task is set to speak first. Open the session right away rather than waiting for the visitor to type. |
Example with cURL:
Preview on load, create the session on the first message
Creating a session on page load leaves behind an empty conversation and an anonymous visitor for everybody who merely opens the page. Show the preview when the widget opens, and only call the session endpoint once the visitor actually sends something — unless starts_immediately is true, in which case the agent speaks first and the session is needed straight away.
Create Session Endpoint¶
URL: POST https://app.primethink.ai/api/v1/public/chats/{chat-id}/public-session
Headers:
Request Body:
Response: JSON object carrying the session ID
Read session_id from it. scoped_token is the chat page's own Socket.IO credential — the embedded page obtains its own, so a widget does not need it.
Usage: Append to URL as ?sid= parameter
When a link cannot be used: both the preview and the session endpoint refuse a link that is not open to visitors, and they refuse it the same way. The session endpoint checks before it creates anything, so a refused request leaves no visitor and no conversation behind.
| Status | Meaning |
|---|---|
404 | No chat matches that ID. |
400 | The chat exists but has not been made public. Every chat has a shareable ID from the moment it is created, so an ID resolving is not the same as the chat being published. |
403 | The chat has been archived by its owner, so it is no longer taking visitors. |
Example with cURL¶
curl -X POST "https://app.primethink.ai/api/v1/public/chats/<chat_share_id>/public-session" \
-H "Content-Type: application/json" \
-H "Accept: */*" \
-d '{}'
Response:
Public Chat Rate Limits¶
Every public-chat API request consumes two independent request budgets:
- A per-visitor budget for each public chat, identified by the share ID and client IP. The default is 20 requests per 60 seconds.
- A per-chat budget shared by all visitors to that public chat. The default is 200 requests per 60 seconds.
These defaults can be changed by the PrimeThink deployment. The limits apply to public session creation, chat details, messages, message editing, socket-token creation, and streaming requests.
When either budget is exhausted, the API returns HTTP 429 Too Many Requests:
Use the response headers rather than parsing the message text:
Retry-After— seconds until the fixed window resets.X-RateLimit-Limit— the ceiling for the budget that was exhausted.X-RateLimit-Remaining—0for the rejected request.
Wait for the indicated Retry-After interval before allowing another attempt; do not immediately retry or hard-code the default 60-second window.
LocalStorage Structure¶
Keys Used¶
The key names are up to your implementation — what matters is persisting the session URL (with its sid parameter) so the session survives page reloads. The two examples in this guide use:
// Minimal single-chat widget
'support_chat_session_url' = 'https://app.primethink.ai/live/abc-123/public?sid=def-456'
// Multi-chat widget
'chat_session_url' = 'https://app.primethink.ai/live/abc-123/public?sid=def-456'
'chat_selected_city' = 'london'
'chat_is_open' = 'true' | 'false'
Session Lifecycle¶
-
Create: When user opens chat for first time
-
Restore: On page load/refresh
-
Clear: When user closes chat
What the Chat Page Does With sid¶
The embedded chat page reads the session from the sid query parameter and from nowhere else. It does not store the session itself, deliberately: a shared or kiosk browser must not hand the next visitor the previous one's conversation. Two consequences for an embedder:
- A link without
sidalways starts a new conversation. Keeping the session across reloads is your job, which is what the storage above is for. - A
sidthe server will not accept is replaced, not reported. If the conversation behind it was deleted, or the public chat has since been unpublished or archived, the page drops the parameter and offers a fresh conversation instead of showing the visitor an error they cannot get past. A session URL you stored can therefore stop resuming without anything appearing to fail.
Loading the page does not create anything either. It fetches the preview, shows the greeting, and waits — the conversation is created when the visitor sends their first message, or immediately if the task is set to speak first. So an embedded chat that is visible but never used costs nothing.
Advanced Features¶
1. Loading Indicator¶
Add a loading spinner while the chat loads:
<div class="chat-content">
<div class="loading-spinner" id="spinner">Loading...</div>
<iframe id="support-iframe"></iframe>
</div>
iframe.addEventListener('load', () => {
document.getElementById('spinner').style.display = 'none';
});
2. Minimize Feature¶
Allow users to minimize the chat:
3. Draggable Window¶
Make the chat draggable:
dragStart(e) {
this.isDragging = true;
const rect = this.chatFrame.getBoundingClientRect();
this.offsetX = e.clientX - rect.left;
this.offsetY = e.clientY - rect.top;
}
drag(e) {
if (!this.isDragging) return;
this.chatFrame.style.left = (e.clientX - this.offsetX) + 'px';
this.chatFrame.style.top = (e.clientY - this.offsetY) + 'px';
}
4. Mobile Responsive¶
Adjust for mobile devices:
@media (max-width: 768px) {
#chat-frame {
width: calc(100vw - 40px);
height: calc(100vh - 120px);
right: 20px;
bottom: 90px;
}
}
Multiple Chats Example¶
Use Case: City-Specific Support¶
If you want to offer different chat sessions based on user selection (e.g., London vs Sydney support):
HTML Structure¶
<!-- Floating Button -->
<button id="chat-button">💬</button>
<!-- Chat Frame -->
<div id="chat-frame">
<div class="title-bar">
<h3>Support</h3>
<button id="close-btn">×</button>
</div>
<!-- Welcome Screen - Choose Location -->
<div id="welcome-screen" class="chat-content">
<h2>Select Your Location</h2>
<button class="city-btn" data-city="london">🇬🇧 London</button>
<button class="city-btn" data-city="sydney">🇦🇺 Sydney</button>
</div>
<!-- Chat Screen - After Selection -->
<div id="chat-screen" class="chat-content" style="display: none;">
<div class="chat-header">
<span id="city-badge"></span>
<button id="back-btn">← Back</button>
</div>
<iframe id="support-iframe"></iframe>
</div>
</div>
JavaScript Implementation¶
class MultiChatSupport {
constructor() {
this.chatButton = document.getElementById('chat-button');
this.chatFrame = document.getElementById('chat-frame');
this.closeBtn = document.getElementById('close-btn');
this.welcomeScreen = document.getElementById('welcome-screen');
this.chatScreen = document.getElementById('chat-screen');
this.iframe = document.getElementById('support-iframe');
this.cityBadge = document.getElementById('city-badge');
this.backBtn = document.getElementById('back-btn');
// Configuration - Multiple Chats
this.chats = {
london: {
chatId: 'f6a7b8c9-d0e1-4234-9123-456789012345',
name: 'London Support'
},
sydney: {
chatId: 'a7b8c9d0-e1f2-4345-8234-567890123456',
name: 'Sydney Support'
}
};
this.STORAGE_KEYS = {
SESSION_URL: 'chat_session_url',
SELECTED_CITY: 'chat_selected_city',
IS_OPEN: 'chat_is_open'
};
this.init();
}
init() {
// Button event listeners
this.chatButton.addEventListener('click', () => this.openChat());
this.closeBtn.addEventListener('click', () => this.closeChat());
this.backBtn.addEventListener('click', () => this.showWelcome());
// City selection buttons
document.querySelectorAll('.city-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const city = e.target.dataset.city;
this.selectCity(city);
});
});
// Restore session
this.restoreSession();
}
openChat() {
this.chatFrame.classList.add('open');
this.chatButton.classList.add('hidden');
localStorage.setItem(this.STORAGE_KEYS.IS_OPEN, 'true');
}
closeChat() {
this.chatFrame.classList.remove('open');
this.chatButton.classList.remove('hidden');
this.iframe.src = '';
// Clear session
localStorage.removeItem(this.STORAGE_KEYS.SESSION_URL);
localStorage.removeItem(this.STORAGE_KEYS.SELECTED_CITY);
localStorage.setItem(this.STORAGE_KEYS.IS_OPEN, 'false');
// Show welcome screen
this.showWelcome();
}
showWelcome() {
this.welcomeScreen.style.display = 'flex';
this.chatScreen.style.display = 'none';
}
async selectCity(city) {
const chatConfig = this.chats[city];
if (!chatConfig) return;
// Update UI
this.cityBadge.textContent = chatConfig.name;
this.welcomeScreen.style.display = 'none';
this.chatScreen.style.display = 'flex';
// Create session
try {
const apiUrl = `https://app.primethink.ai/api/v1/public/chats/${chatConfig.chatId}/public-session`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new Error(`Too many requests. Try again in ${retryAfter ?? 'a few'} seconds.`);
}
if (!response.ok) {
throw new Error(`Session creation failed with status ${response.status}`);
}
// The endpoint returns { session_id, scoped_token } — not a bare string.
const { session_id: sessionId } = await response.json();
const sessionUrl = `https://app.primethink.ai/live/${chatConfig.chatId}/public?sid=${sessionId}`;
// Load chat
this.iframe.src = sessionUrl;
// Save session
localStorage.setItem(this.STORAGE_KEYS.SESSION_URL, sessionUrl);
localStorage.setItem(this.STORAGE_KEYS.SELECTED_CITY, city);
} catch (error) {
console.error('Error creating session:', error);
this.iframe.srcdoc = '<p role="alert">Chat is temporarily unavailable. Please wait and try again.</p>';
}
}
restoreSession() {
const savedUrl = localStorage.getItem(this.STORAGE_KEYS.SESSION_URL);
const selectedCity = localStorage.getItem(this.STORAGE_KEYS.SELECTED_CITY);
const wasOpen = localStorage.getItem(this.STORAGE_KEYS.IS_OPEN) === 'true';
if (savedUrl && selectedCity && wasOpen) {
const chatConfig = this.chats[selectedCity];
// A session stored before the chat URL changed still points at the
// deprecated path. Keep the session id, rebuild the rest.
let sid = null;
try {
sid = new URL(savedUrl, window.location.origin).searchParams.get('sid');
} catch { /* unreadable — fall through and start fresh */ }
if (!sid) {
localStorage.removeItem(this.STORAGE_KEYS.SESSION_URL);
return;
}
const currentUrl =
`https://app.primethink.ai/live/${chatConfig.chatId}/public?sid=${sid}`;
if (currentUrl !== savedUrl) {
localStorage.setItem(this.STORAGE_KEYS.SESSION_URL, currentUrl);
}
// Restore chat state
this.chatFrame.classList.add('open');
this.chatButton.classList.add('hidden');
this.welcomeScreen.style.display = 'none';
this.chatScreen.style.display = 'flex';
// Restore content
this.cityBadge.textContent = chatConfig.name;
this.iframe.src = currentUrl;
}
}
}
// Initialize
new MultiChatSupport();
CSS for Multiple Chats¶
/* Welcome Screen */
#welcome-screen {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
}
#welcome-screen h2 {
font-size: 18px;
margin-bottom: 30px;
text-align: center;
color: #333;
}
.city-btn {
width: 100%;
max-width: 250px;
padding: 16px 24px;
margin: 8px 0;
border: 2px solid #667eea;
background: white;
color: #667eea;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.city-btn:hover {
background: #667eea;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
/* Chat Screen */
#chat-screen {
flex-direction: column;
}
.chat-header {
padding: 16px 20px;
background: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
#city-badge {
background: #667eea;
color: white;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
#back-btn {
padding: 6px 16px;
background: white;
border: 1px solid #ddd;
border-radius: 6px;
color: #666;
cursor: pointer;
font-size: 13px;
}
#back-btn:hover {
border-color: #667eea;
color: #667eea;
}
#chat-screen iframe {
flex: 1;
width: 100%;
border: none;
}
Multiple Chats Flow¶
1. User clicks button → Welcome screen shows
2. User selects "London" →
- POST /chats/london-id/public-session
- Get session ID
- Load London chat with ?sid=session-id
- Save "london" + session URL to localStorage
3. User refreshes page →
- Check localStorage
- Find "london" + saved URL
- Restore London chat automatically
4. User clicks "Back" →
- Return to welcome screen
- Keep session in localStorage (optional)
5. User closes chat →
- Clear all localStorage
- Reset to initial state
Best Practices¶
1. Error Handling¶
Always provide fallbacks:
try {
const response = await fetch(apiUrl, requestOptions);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
showTemporaryError(`Too many requests. Try again in ${retryAfter ?? 'a few'} seconds.`);
return; // Wait for user action; do not retry immediately.
}
if (!response.ok) {
throw new Error(`Session creation failed with status ${response.status}`);
}
// The endpoint returns { session_id, scoped_token } — not a bare string.
const { session_id: sessionId } = await response.json();
iframe.src = `${baseUrl}?sid=${sessionId}`;
} catch (error) {
console.error('Session creation failed:', error);
showTemporaryError('Chat is temporarily unavailable. Please try again later.');
}
2. CORS Considerations¶
Ensure your domain is allowed to make requests to Primethink API. If you encounter CORS errors, the Primethink backend needs to allow your domain.
3. Security¶
- Use the
sandboxattribute on the iframe for security - Don't store sensitive data in localStorage
- Clear session on logout if needed
Grant the narrowest set of sandbox permissions the widget actually needs. Note that combining allow-same-origin with allow-scripts lets sandboxed content reach back into its own origin and can effectively remove the sandbox's protection — only include allow-same-origin if the embedded page genuinely requires same-origin access, and prefer serving the widget from a separate origin so you can drop it:
<!-- Minimal: scripts + forms, cross-origin (no allow-same-origin) -->
<iframe sandbox="allow-scripts allow-forms allow-popups"></iframe>
4. Performance¶
- Load chat only when user clicks button (lazy loading)
- Clear iframe when chat closes to free memory
- Limit localStorage size by removing old sessions
5. Accessibility¶
- Add ARIA labels for screen readers
- Ensure keyboard navigation works
- Provide clear visual indicators
Troubleshooting¶
Chat doesn't load¶
Problem: iframe remains blank after clicking button
Solutions: - Check browser console for errors - Verify chat ID is correct - Ensure API endpoint is accessible - Check CORS configuration - Verify network connectivity
Session not persisting¶
Problem: Chat resets after page refresh
Solutions: - Check localStorage is enabled in browser - Verify storage key is correct - Ensure session URL is being saved - Check browser privacy settings
API returns 404, 400 or 403¶
Problem: The preview or session request is refused
Solutions: - 404 — verify the chat ID is correct and check the API URL format; test with cURL first - 400 — the chat has not been made public. Publish it in PrimeThink; having a shareable ID is not the same as being published - 403 — the chat has been archived by its owner and no longer takes visitors; unarchive it
API returns 429¶
Problem: A public-chat request fails with 429 Too Many Requests
Solutions: - Read the Retry-After response header and wait that many seconds before another attempt - Do not automatically retry in a tight loop - Reuse stored public sessions instead of creating a new session for every interaction or page load - If the problem affects many visitors, the public chat may have reached its shared per-chat budget
Multiple instances conflict¶
Problem: Multiple chat widgets on same page interfere
Solutions: - Use unique storage keys per widget - Namespace your class/variables - Consider using data attributes for configuration
Examples¶
The two complete implementations in this guide cover both patterns: - The minimal widget — single chat implementation - The multi-chat widget — multiple chats with city selection
Both examples include: - Full HTML, CSS, and JavaScript - Session persistence - Drag and drop - Minimize/maximize - Mobile responsive - Loading indicators
Support¶
For issues or questions: 1. Check browser console for errors 2. Verify API responses with cURL 3. Review localStorage in DevTools 4. Test with minimal example first
License¶
This implementation guide is provided for developers integrating Primethink chat into their applications.