@cognitiondesk/widget 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -100
- package/dist/react.es.js +222 -124
- package/dist/react.umd.cjs +4 -4
- package/dist/widget.es.js +135 -45
- package/dist/widget.umd.cjs +4 -4
- package/package.json +1 -1
- package/src/react.jsx +25 -2
- package/src/widget.js +187 -34
package/src/widget.js
CHANGED
|
@@ -271,23 +271,57 @@ function generateSessionId() {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
export default class CognitionDeskWidget {
|
|
274
|
+
/**
|
|
275
|
+
* @param {Object} config
|
|
276
|
+
* @param {string} config.apiKey - Required. Your CognitionDesk API key.
|
|
277
|
+
* @param {string} [config.widgetId] - Widget ID from the dashboard. Enables remote config.
|
|
278
|
+
* @param {string} [config.assistantId] - Assistant ID (overrides server value if set).
|
|
279
|
+
* @param {string} [config.backendUrl] - Custom backend URL.
|
|
280
|
+
* @param {string} [config.theme] - 'light' | 'dark' | 'auto'
|
|
281
|
+
* @param {string} [config.primaryColor] - Hex color for buttons / header.
|
|
282
|
+
* @param {string} [config.botName] - Display name shown in the header.
|
|
283
|
+
* @param {string} [config.botEmoji] - Emoji shown as avatar.
|
|
284
|
+
* @param {string} [config.welcomeMessage] - First message shown to the user.
|
|
285
|
+
* @param {string} [config.placeholder] - Textarea placeholder text.
|
|
286
|
+
* @param {string} [config.position] - 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
|
|
287
|
+
* @param {boolean} [config.streaming] - Enable streaming responses (default: true).
|
|
288
|
+
* @param {Object} [config.overrideSettings]
|
|
289
|
+
* Explicit code-level overrides. Any key listed here takes precedence over
|
|
290
|
+
* the server-side dashboard config. Use this when you want to manage
|
|
291
|
+
* specific settings from code rather than the CognitionDesk dashboard.
|
|
292
|
+
*
|
|
293
|
+
* Example — let the dashboard control everything except color:
|
|
294
|
+
* overrideSettings: { primaryColor: '#e11d48' }
|
|
295
|
+
*
|
|
296
|
+
* Example — full code control (dashboard settings ignored):
|
|
297
|
+
* overrideSettings: {
|
|
298
|
+
* primaryColor: '#e11d48', botName: 'Aria', welcomeMessage: 'Hi!'
|
|
299
|
+
* }
|
|
300
|
+
*
|
|
301
|
+
* When omitted (default), ALL settings come from the dashboard.
|
|
302
|
+
*/
|
|
274
303
|
constructor(config = {}) {
|
|
275
304
|
if (!config.apiKey) throw new Error('[CognitionDesk] apiKey is required');
|
|
276
305
|
|
|
306
|
+
// Keys the developer explicitly wants to control from code.
|
|
307
|
+
// These override whatever the dashboard says.
|
|
308
|
+
this._overrides = new Set(
|
|
309
|
+
config.overrideSettings ? Object.keys(config.overrideSettings) : []
|
|
310
|
+
);
|
|
311
|
+
|
|
277
312
|
this._cfg = {
|
|
278
313
|
apiKey: config.apiKey,
|
|
279
314
|
widgetId: config.widgetId || null,
|
|
280
315
|
assistantId: config.assistantId || null,
|
|
281
316
|
backendUrl: config.backendUrl || DEFAULT_BACKEND,
|
|
282
317
|
primaryColor: config.primaryColor || '#2563eb',
|
|
283
|
-
theme: config.theme || 'light',
|
|
318
|
+
theme: config.theme || 'light',
|
|
284
319
|
botName: config.botName || 'AI Assistant',
|
|
285
320
|
botEmoji: config.botEmoji || '🤖',
|
|
286
321
|
welcomeMessage: config.welcomeMessage || 'Hello! How can I help you today?',
|
|
287
322
|
placeholder: config.placeholder || 'Type a message…',
|
|
288
323
|
position: config.position || 'bottom-right',
|
|
289
|
-
streaming: config.streaming !== false,
|
|
290
|
-
// Rate limiting — can be overridden by inline config or merged from server config
|
|
324
|
+
streaming: config.streaming !== false,
|
|
291
325
|
rateLimiting: {
|
|
292
326
|
enabled: config.rateLimiting?.enabled !== false,
|
|
293
327
|
maxMessagesPerSession: config.rateLimiting?.maxMessagesPerSession ?? 0,
|
|
@@ -295,30 +329,38 @@ export default class CognitionDeskWidget {
|
|
|
295
329
|
limitReachedMessage: config.rateLimiting?.limitReachedMessage || "You've reached the message limit for this session.",
|
|
296
330
|
rateLimitMessage: config.rateLimiting?.rateLimitMessage || "You're sending messages too quickly. Please wait a moment.",
|
|
297
331
|
},
|
|
332
|
+
// Store overrides for re-application after each config refresh
|
|
333
|
+
overrideSettings: config.overrideSettings || null,
|
|
298
334
|
};
|
|
299
335
|
|
|
336
|
+
// Apply overrides immediately on top of defaults
|
|
337
|
+
if (config.overrideSettings) {
|
|
338
|
+
this._applyOverrides(config.overrideSettings);
|
|
339
|
+
}
|
|
340
|
+
|
|
300
341
|
this._sessionId = generateSessionId();
|
|
301
|
-
this._messageCount = 0;
|
|
302
|
-
this._minuteCount = 0;
|
|
303
|
-
this._minuteStart = Date.now();
|
|
304
|
-
this._messages
|
|
305
|
-
this._open
|
|
306
|
-
this._loading
|
|
307
|
-
this._container
|
|
308
|
-
this._shadow
|
|
309
|
-
this._panel
|
|
310
|
-
this._messagesEl
|
|
311
|
-
this._textarea
|
|
312
|
-
this._sendBtn
|
|
342
|
+
this._messageCount = 0;
|
|
343
|
+
this._minuteCount = 0;
|
|
344
|
+
this._minuteStart = Date.now();
|
|
345
|
+
this._messages = [];
|
|
346
|
+
this._open = false;
|
|
347
|
+
this._loading = false;
|
|
348
|
+
this._container = null;
|
|
349
|
+
this._shadow = null;
|
|
350
|
+
this._panel = null;
|
|
351
|
+
this._messagesEl = null;
|
|
352
|
+
this._textarea = null;
|
|
353
|
+
this._sendBtn = null;
|
|
313
354
|
this._viewportHandler = null;
|
|
355
|
+
this._refreshPending = false;
|
|
314
356
|
}
|
|
315
357
|
|
|
316
358
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
317
359
|
|
|
318
360
|
/**
|
|
319
361
|
* Mount the widget.
|
|
320
|
-
* If `widgetId`
|
|
321
|
-
* Returns a Promise so callers can await
|
|
362
|
+
* If `widgetId` is set, fetches server config first (async).
|
|
363
|
+
* Returns a Promise so callers can `await widget.mount()`.
|
|
322
364
|
*/
|
|
323
365
|
mount(target) {
|
|
324
366
|
const _mount = () => {
|
|
@@ -333,7 +375,7 @@ export default class CognitionDeskWidget {
|
|
|
333
375
|
this._shadow.appendChild(style);
|
|
334
376
|
|
|
335
377
|
this._buildDOM();
|
|
336
|
-
this.
|
|
378
|
+
this._applyConfig();
|
|
337
379
|
this._syncViewportMetrics();
|
|
338
380
|
this._bindViewportMetrics();
|
|
339
381
|
this._bindEvents();
|
|
@@ -346,16 +388,55 @@ export default class CognitionDeskWidget {
|
|
|
346
388
|
if (this._cfg.widgetId) {
|
|
347
389
|
return this._fetchWidgetConfig()
|
|
348
390
|
.then(() => _mount())
|
|
349
|
-
.catch(() => _mount());
|
|
391
|
+
.catch(() => _mount());
|
|
350
392
|
}
|
|
351
393
|
|
|
352
394
|
_mount();
|
|
353
395
|
return Promise.resolve(this);
|
|
354
396
|
}
|
|
355
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Programmatically update settings at runtime.
|
|
400
|
+
* Merges `newSettings` into `overrideSettings` and re-applies to the live DOM.
|
|
401
|
+
* Use this to change colors, bot name, etc. without remounting.
|
|
402
|
+
*
|
|
403
|
+
* widget.updateSettings({ primaryColor: '#dc2626', botName: 'Support Bot' });
|
|
404
|
+
*/
|
|
405
|
+
updateSettings(newSettings) {
|
|
406
|
+
if (!newSettings || typeof newSettings !== 'object') return;
|
|
407
|
+
// Merge into overrides
|
|
408
|
+
const merged = { ...(this._cfg.overrideSettings || {}), ...newSettings };
|
|
409
|
+
this._cfg.overrideSettings = merged;
|
|
410
|
+
this._overrides = new Set(Object.keys(merged));
|
|
411
|
+
this._applyOverrides(merged);
|
|
412
|
+
this._applyConfig();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Fetch fresh config from the dashboard right now and apply it.
|
|
417
|
+
* Useful after the user changes settings in the CognitionDesk dashboard
|
|
418
|
+
* and wants them reflected immediately without reloading the page.
|
|
419
|
+
*/
|
|
420
|
+
async refreshConfig() {
|
|
421
|
+
if (!this._cfg.widgetId) return;
|
|
422
|
+
await this._fetchWidgetConfig();
|
|
423
|
+
this._applyConfig();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ── Config internals ────────────────────────────────────────────────────────
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Returns true if `key` is explicitly controlled by `overrideSettings`.
|
|
430
|
+
* Those keys are immune to server config overwrites.
|
|
431
|
+
*/
|
|
432
|
+
_userSet(key) {
|
|
433
|
+
return this._overrides.has(key);
|
|
434
|
+
}
|
|
435
|
+
|
|
356
436
|
/**
|
|
357
437
|
* Fetch public widget config from the platform and merge into this._cfg.
|
|
358
|
-
*
|
|
438
|
+
* Server values apply to any key NOT present in `overrideSettings`.
|
|
439
|
+
* After merging, overrides are re-applied so they always win.
|
|
359
440
|
*/
|
|
360
441
|
async _fetchWidgetConfig() {
|
|
361
442
|
const url = `${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;
|
|
@@ -364,27 +445,97 @@ export default class CognitionDeskWidget {
|
|
|
364
445
|
if (!res.ok) return;
|
|
365
446
|
const { config } = await res.json();
|
|
366
447
|
if (!config) return;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
448
|
+
|
|
449
|
+
// Apply each server field unless the developer has overridden it
|
|
450
|
+
const apply = (key, value) => {
|
|
451
|
+
if (value != null && !this._userSet(key)) this._cfg[key] = value;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
apply('botName', config.botName);
|
|
455
|
+
apply('welcomeMessage', config.welcomeMessage);
|
|
456
|
+
apply('primaryColor', config.primaryColor);
|
|
457
|
+
apply('secondaryColor', config.secondaryColor);
|
|
458
|
+
apply('placeholder', config.placeholder);
|
|
459
|
+
apply('theme', config.theme);
|
|
460
|
+
apply('position', config.position);
|
|
461
|
+
apply('style', config.style);
|
|
462
|
+
apply('size', config.size);
|
|
463
|
+
|
|
464
|
+
// assistantId: use server value only if not set inline
|
|
465
|
+
if (config.assistantId && !this._cfg.assistantId) {
|
|
466
|
+
this._cfg.assistantId = config.assistantId;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Avatar emoji
|
|
470
|
+
if (config.avatar?.value && !this._userSet('botEmoji')) {
|
|
471
|
+
this._cfg.botEmoji = config.avatar.value;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Rate limiting — server is always authoritative (security-sensitive)
|
|
375
475
|
if (config.rateLimiting) {
|
|
376
476
|
this._cfg.rateLimiting = { ...this._cfg.rateLimiting, ...config.rateLimiting };
|
|
377
477
|
}
|
|
478
|
+
|
|
479
|
+
// Re-apply overrides so they always take precedence over server values
|
|
480
|
+
if (this._cfg.overrideSettings) {
|
|
481
|
+
this._applyOverrides(this._cfg.overrideSettings);
|
|
482
|
+
}
|
|
378
483
|
} catch {
|
|
379
|
-
//
|
|
484
|
+
// Network error — keep current config
|
|
380
485
|
}
|
|
381
486
|
}
|
|
382
487
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
488
|
+
/**
|
|
489
|
+
* Write override values into this._cfg (flat fields only; rateLimiting is merged).
|
|
490
|
+
*/
|
|
491
|
+
_applyOverrides(overrides) {
|
|
492
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
493
|
+
if (key === 'rateLimiting' && typeof value === 'object') {
|
|
494
|
+
this._cfg.rateLimiting = { ...this._cfg.rateLimiting, ...value };
|
|
495
|
+
} else {
|
|
496
|
+
this._cfg[key] = value;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Update live DOM elements to reflect the current this._cfg.
|
|
503
|
+
* Safe to call before or after mounting.
|
|
504
|
+
*/
|
|
505
|
+
_applyConfig() {
|
|
506
|
+
// ── Theme & colors ──
|
|
507
|
+
const isDark = this._cfg.theme === 'dark'
|
|
508
|
+
|| (this._cfg.theme === 'auto' && window.matchMedia?.('(prefers-color-scheme: dark)').matches);
|
|
509
|
+
|
|
510
|
+
this._root?.classList.toggle('cd-dark', isDark);
|
|
511
|
+
this._root?.style.setProperty('--cd-primary', this._cfg.primaryColor);
|
|
512
|
+
this._panel?.style.setProperty('--cd-primary', this._cfg.primaryColor);
|
|
513
|
+
this._toggleBtn?.style.setProperty('--cd-primary', this._cfg.primaryColor);
|
|
514
|
+
|
|
515
|
+
// ── Header: avatar + bot name ──
|
|
516
|
+
const avatarEl = this._shadow?.querySelector('.cd-header-avatar');
|
|
517
|
+
if (avatarEl) avatarEl.textContent = this._cfg.botEmoji;
|
|
518
|
+
|
|
519
|
+
const nameEl = this._shadow?.querySelector('.cd-header-name');
|
|
520
|
+
if (nameEl) nameEl.textContent = this._cfg.botName;
|
|
521
|
+
|
|
522
|
+
// ── Input placeholder ──
|
|
523
|
+
if (this._textarea) this._textarea.placeholder = this._cfg.placeholder;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Silently refresh config from server in the background.
|
|
528
|
+
* Called each time the panel opens so dashboard changes
|
|
529
|
+
* are reflected without reloading the page.
|
|
530
|
+
* Uses a debounce flag to avoid concurrent fetches.
|
|
531
|
+
*/
|
|
532
|
+
_silentRefresh() {
|
|
533
|
+
if (!this._cfg.widgetId || this._refreshPending) return;
|
|
534
|
+
this._refreshPending = true;
|
|
535
|
+
this._fetchWidgetConfig()
|
|
536
|
+
.then(() => this._applyConfig())
|
|
537
|
+
.catch(() => {})
|
|
538
|
+
.finally(() => { this._refreshPending = false; });
|
|
388
539
|
}
|
|
389
540
|
|
|
390
541
|
unmount() {
|
|
@@ -400,6 +551,8 @@ export default class CognitionDeskWidget {
|
|
|
400
551
|
this._syncViewportMetrics();
|
|
401
552
|
this._panel?.classList.add('open');
|
|
402
553
|
this._textarea?.focus();
|
|
554
|
+
// Background refresh so config changes from dashboard appear on next open
|
|
555
|
+
this._silentRefresh();
|
|
403
556
|
}
|
|
404
557
|
|
|
405
558
|
close() {
|