Appearance
Embed Architecture ​
The Gnosari widget embedding system uses a three-layer architecture to provide complete isolation, security, and automatic updates for embedded chat interfaces.
Architecture Overview ​
Layer 1: Widget Script ​
The widget loader script handles integration with external websites.
File Location ​
/public/gnosari-chat-widget-advanced.jsResponsibilities ​
iframe Creation
- Dynamically creates iframe elements
- Constructs embed URLs with query parameters
- Injects iframe into page DOM
UI Management
- Renders floating button (bubble mode)
- Handles positioning and sizing
- Manages open/close animations
Event System
- Listens for postMessage from iframe
- Triggers custom DOM events
- Provides callback functions
Public API
- Exposes global
GnosariChatAdvancedobject - Methods:
open(),close(),toggle(),sendMessage(),destroy() - State management
- Exposes global
Initialization Flow ​
javascript
// 1. Script loads
window.gnosariConfig = { agentId: 'bot', apiUrl: 'https://...' };
// 2. Widget initializes
// - Reads config from window.gnosariConfig
// - Creates iframe element
// - Sets up event listeners
// - Renders UI (button or container)
// 3. Widget ready
window.dispatchEvent(new CustomEvent('gnosari-chat-ready'));
// 4. User opens chat
// - iframe becomes visible
// - Embed page loads inside iframeCross-Origin Communication ​
Widget script communicates with the iframe via postMessage:
Parent → Iframe:
javascript
iframe.contentWindow.postMessage({
type: 'send-message',
message: 'Hello!'
}, '*');Iframe → Parent:
javascript
window.parent.postMessage({
type: 'message-received',
data: { ... },
source: 'gnosari-chat-embed'
}, '*');Layer 2: Embed Pages ​
Specialized Nuxt pages designed to be loaded in iframes.
Available Embed Routes ​
| Route | Purpose | Example URL |
|---|---|---|
/embed/agent/[identifier] | Agent chat | /embed/agent/support-bot |
/embed/team/[id] | Team chat | /embed/team/123 |
/embed/session/[id] | Resume session | /embed/session/sess_abc |
/embed/l/[slug] | Link chat/conversation | /embed/l/vip-dinner |
Agent Embed ​
File: /app/pages/embed/agent/[identifier].vue
Features:
- Pre-fetches agent data for theme/config
- Resolves agent
chat_uisettings with theme defaults - Handles password-protected agents
- Supports query param configuration overrides
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
password | string | Skip password prompt for protected agents |
session_id | string | Resume existing chat session |
theme | string | Override theme: light, dark, auto |
disableTheme | boolean | Ignore agent's chat theme |
autoMessage | string | Send message automatically on connect |
showCloseButton | boolean | Show close button in chat |
channel | string | Origin channel override for the created session. Defaults to WIDGET when absent or invalid |
Example URL:
/embed/agent/support-bot?theme=dark&autoMessage=Hi%20there!Link Embed ​
File: /app/pages/embed/l/[slug].vue
Features:
- Resolves the Link's slug server-side via
GET /resolve/l/{slug}(the solevisit_countincrement site) - Presentation-aware:
conversationrendersConversationSurfacedirectly from the resolve payload (unchanged).chatperforms a SECOND, isolated, non-fatal SSR fetch (GET /api/resolve?uri=) and appliesapplyAgentConfig()to pull the agent's fullchat_ui(empty-state copy, suggested prompts, avatar, selector visibility) with the link's 3-tier effective theme -- parity with/embed/agent/[uri]. A failed agent fetch degrades to the minimal chat render, never the link error screen - Self-sufficient 410 (expired) / 404 (not found) / generic-error states, with retry
- Supports host-blend query params for merging the canvas into the surrounding page
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
theme | string | Force color mode: light or dark. Unset follows the visitor's OS preference |
transparent | boolean (1) | See-through canvas -- merges into the host page's background. Requires an explicit theme matching the host's own color scheme, or Chromium opacifies the canvas anyway |
fade | boolean (1) | Soft gradient behind the composer band instead of a hard edge |
bg | string | Exact canvas fill color as a hex value, no # (e.g. bg=0a0a0a) -- for hosts where transparent can't apply |
Precedence when combined: transparent > bg > the derived theme surface.
Example URL:
/embed/l/vip-dinner?transparent=1&theme=dark&fade=1Team Embed ​
File: /app/pages/embed/team/[id].vue
Similar to agent embed, but connects to a team instead.
Example URL:
/embed/team/123?password=secretSession Embed ​
File: /app/pages/embed/session/[id].vue
Resume a specific chat session by ID.
Example URL:
/embed/session/sess_abc123Embed Layout ​
File: /app/layouts/embed.vue
Minimal layout with no header, sidebar, or footer. Provides clean iframe experience.
Features:
- No navigation chrome
- Full-height layout (100dvh — dynamic viewport height so the input stays visible above mobile browser chrome)
- Transparent background
- No scroll (chat handles scrolling internally)
Configuration Resolution ​
Embed pages resolve configuration in this priority order:
- Agent
chat_uisettings (highest priority) - Chat theme defaults (if theme exists and not disabled)
- Query parameter overrides
- Component defaults (fallback)
Example:
javascript
// Agent has chat_ui.primaryColor = '#10b981'
// Theme has primaryColor = '#6366f1'
// URL has ?primaryColor=#8b5cf6
// Result: Agent chat_ui wins (#10b981)
// Unless disableTheme=true, then query param wins (#8b5cf6)Layer 3: GnosariChat Component ​
The core chat component that provides full chat functionality.
File: /app/components/chat/GnosariChat.vue
Features ​
- Authentication: Handles JWT tokens, password prompts
- Message Streaming: Real-time message streaming via WebSocket
- Session Management: Create, resume, and manage chat sessions
- Team/Agent Selection: Switch between team members
- File Upload: Image and document upload support
- Message History: Scroll to load older messages
- Typing Indicators: Shows when agent is typing
- Error Handling: Graceful degradation on errors
Props Passed from Embed Page ​
| Prop | Type | Description |
|---|---|---|
agent-id | string | Agent identifier |
agent | object | Pre-fetched agent data |
session-id | string | Session to resume |
password | string | Password for auth |
skip-auth | boolean | Skip JWT auth (embeds use this) |
show-close-button | boolean | Show close button |
height | string | Component height (defaults to 100dvh) |
features | object | Feature configuration |
...resolvedProps | various | Theme/config overrides |
Events Emitted ​
| Event | Data | Description |
|---|---|---|
ready | { agentIdentifier } | Chat initialized |
connected | { agentId, agentName } | Connected to agent |
session-created | { sessionId } | New session created |
session-loaded | { sessionId } | Existing session loaded |
message-sent | { message } | User sent message |
message-received | { messageId, role, content } | Agent responded |
error | { error } | Error occurred |
close | - | User clicked close button |
Cross-Origin Security ​
Content Security Policy ​
Embed pages set strict CSP headers:
Content-Security-Policy:
frame-ancestors 'self' https://*.yourdomain.com;
script-src 'self' 'unsafe-inline';
connect-src 'self' wss://*.yourdomain.com;CORS Configuration ​
API endpoints allow cross-origin requests from configured domains:
javascript
// Backend CORS config
{
allowedOrigins: ['*'], // or specific domains
allowCredentials: true,
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}postMessage Validation ​
Widget script validates all incoming postMessage events:
javascript
window.addEventListener('message', (event) => {
// Validate source
if (event.data.source !== 'gnosari-chat-embed') return;
// Validate structure
if (!event.data.type) return;
// Process trusted message
handleMessage(event.data);
});Performance Considerations ​
Lazy Loading ​
The iframe is not created until needed:
javascript
// Bubble mode: iframe created on first button click
// Sidebar mode: iframe created when sidebar opens
// autoOpen: true: iframe created immediatelyResource Optimization ​
- Code Splitting: Embed pages use separate Nuxt chunks
- Caching: Widget script cached with long TTL
- Compression: All assets served with gzip/brotli
Bundle Sizes ​
| Asset | Size (Compressed) |
|---|---|
| Widget script | ~50KB |
| Embed page (initial) | ~200KB |
| Chat component | ~150KB |
| Total | ~400KB |
Load Performance ​
Typical load times:
- Widget script load: 100-200ms
- Iframe creation: 50-100ms
- Embed page load: 200-500ms
- Chat ready: 100-200ms
- Total to interactive: 500-1000ms
Debugging ​
Enable Debug Mode ​
javascript
window.gnosariConfig = {
agentId: 'support-bot',
debug: true // Enables console logging
};Debug Output ​
With debug enabled, you'll see:
🎨 Embed: Theme disabled, applying agent chat_ui settings only
📡 Embed: Chat ready
📡 Embed: Connected to agent: support-bot
📡 Embed: Session created: sess_abc123
📡 Embed: Message sent
📡 Embed: Message receivedInspecting postMessage Events ​
javascript
// Monitor all postMessage traffic
window.addEventListener('message', (event) => {
console.log('postMessage received:', event.data);
});Browser DevTools ​
To inspect the iframe:
- Open browser DevTools (F12)
- Go to Elements/Inspector tab
- Find the
<iframe>element - Right-click → Inspect
- DevTools context switches to iframe
- Now you can debug the embed page directly
Common Patterns ​
Dynamic Agent Switching ​
javascript
// Change agent dynamically
function switchAgent(agentId) {
window.GnosariChatAdvanced.destroy();
window.gnosariConfig.agentId = agentId;
// Re-initialize with new config
// (Implementation depends on widget script version)
}Session Persistence ​
javascript
// Save session ID for later
window.addEventListener('gnosari-chat-session-created', (e) => {
localStorage.setItem('chatSessionId', e.detail.sessionId);
});
// Resume session on next page load
window.gnosariConfig = {
agentId: 'support-bot',
sessionId: localStorage.getItem('chatSessionId')
};Custom Event Tracking ​
javascript
window.addEventListener('gnosari-chat-open', () => {
// Track in Google Analytics
gtag('event', 'chat_opened', { agent: 'support-bot' });
});
window.addEventListener('gnosari-chat-new-message', () => {
// Track message received
gtag('event', 'chat_message_received');
});Conditional Widget Loading ​
javascript
// Only load widget for specific pages or conditions
if (window.location.pathname.includes('/pricing')) {
window.gnosariConfig = {
agentId: 'sales-bot',
autoOpen: true
};
const script = document.createElement('script');
script.src = 'https://your-domain.com/gnosari-chat-widget-advanced.js';
document.body.appendChild(script);
}Migration from Basic to Advanced ​
If you're currently using the basic widget (gnosari-chat-widget.js):
Basic Widget:
html
<script src=".../gnosari-chat-widget.js"></script>- Opens chat in new window/popup
- Minimal customization
- < 10KB bundle size
Advanced Widget:
html
<script src=".../gnosari-chat-widget-advanced.js"></script>- Embedded iframe chat
- Full customization
- ~50KB bundle size
- Better UX on mobile
Migration steps:
- Change script URL from
gnosari-chat-widget.jstognosari-chat-widget-advanced.js - Update
window.gnosariConfigwith new options (see Configuration Options) - Test on your site
- Update any event listeners (event names may differ)