@cognitiondesk/widget 1.2.1 → 1.2.3
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 -268
- package/dist/react.es.js +230 -125
- package/dist/react.umd.cjs +4 -4
- package/dist/widget.es.js +144 -47
- package/dist/widget.umd.cjs +4 -4
- package/package.json +1 -1
- package/src/react.jsx +25 -2
- package/src/widget.js +204 -36
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();
|
|
@@ -345,46 +387,170 @@ export default class CognitionDeskWidget {
|
|
|
345
387
|
|
|
346
388
|
if (this._cfg.widgetId) {
|
|
347
389
|
return this._fetchWidgetConfig()
|
|
348
|
-
.then(()
|
|
349
|
-
.catch(() => _mount()); //
|
|
390
|
+
.then(active => { if (active !== false) _mount(); })
|
|
391
|
+
.catch(() => _mount()); // network error — mount with local config
|
|
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
|
+
const active = await this._fetchWidgetConfig();
|
|
423
|
+
if (active === false) {
|
|
424
|
+
this.unmount();
|
|
425
|
+
} else {
|
|
426
|
+
this._applyConfig();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ── Config internals ────────────────────────────────────────────────────────
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Returns true if `key` is explicitly controlled by `overrideSettings`.
|
|
434
|
+
* Those keys are immune to server config overwrites.
|
|
435
|
+
*/
|
|
436
|
+
_userSet(key) {
|
|
437
|
+
return this._overrides.has(key);
|
|
438
|
+
}
|
|
439
|
+
|
|
356
440
|
/**
|
|
357
441
|
* Fetch public widget config from the platform and merge into this._cfg.
|
|
358
|
-
*
|
|
442
|
+
* Server values apply to any key NOT present in `overrideSettings`.
|
|
443
|
+
* After merging, overrides are re-applied so they always win.
|
|
359
444
|
*/
|
|
360
445
|
async _fetchWidgetConfig() {
|
|
361
446
|
const url = `${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;
|
|
362
447
|
try {
|
|
363
448
|
const res = await fetch(url);
|
|
364
|
-
|
|
449
|
+
// Widget deleted or disabled — signal caller to unmount
|
|
450
|
+
if (res.status === 404 || res.status === 403) return false;
|
|
451
|
+
if (!res.ok) return true; // other HTTP error — keep existing config
|
|
365
452
|
const { config } = await res.json();
|
|
366
453
|
if (!config) return;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
454
|
+
|
|
455
|
+
// Apply each server field unless the developer has overridden it
|
|
456
|
+
const apply = (key, value) => {
|
|
457
|
+
if (value != null && !this._userSet(key)) this._cfg[key] = value;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
apply('botName', config.botName);
|
|
461
|
+
apply('welcomeMessage', config.welcomeMessage);
|
|
462
|
+
apply('primaryColor', config.primaryColor);
|
|
463
|
+
apply('secondaryColor', config.secondaryColor);
|
|
464
|
+
apply('placeholder', config.placeholder);
|
|
465
|
+
apply('theme', config.theme);
|
|
466
|
+
apply('position', config.position);
|
|
467
|
+
apply('style', config.style);
|
|
468
|
+
apply('size', config.size);
|
|
469
|
+
|
|
470
|
+
// assistantId: use server value only if not set inline
|
|
471
|
+
if (config.assistantId && !this._cfg.assistantId) {
|
|
472
|
+
this._cfg.assistantId = config.assistantId;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Avatar emoji
|
|
476
|
+
if (config.avatar?.value && !this._userSet('botEmoji')) {
|
|
477
|
+
this._cfg.botEmoji = config.avatar.value;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Rate limiting — server is always authoritative (security-sensitive)
|
|
375
481
|
if (config.rateLimiting) {
|
|
376
482
|
this._cfg.rateLimiting = { ...this._cfg.rateLimiting, ...config.rateLimiting };
|
|
377
483
|
}
|
|
484
|
+
|
|
485
|
+
// Re-apply overrides so they always take precedence over server values
|
|
486
|
+
if (this._cfg.overrideSettings) {
|
|
487
|
+
this._applyOverrides(this._cfg.overrideSettings);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return true; // config applied successfully
|
|
378
491
|
} catch {
|
|
379
|
-
//
|
|
492
|
+
return true; // network error — keep current config, stay mounted
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Write override values into this._cfg (flat fields only; rateLimiting is merged).
|
|
498
|
+
*/
|
|
499
|
+
_applyOverrides(overrides) {
|
|
500
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
501
|
+
if (key === 'rateLimiting' && typeof value === 'object') {
|
|
502
|
+
this._cfg.rateLimiting = { ...this._cfg.rateLimiting, ...value };
|
|
503
|
+
} else {
|
|
504
|
+
this._cfg[key] = value;
|
|
505
|
+
}
|
|
380
506
|
}
|
|
381
507
|
}
|
|
382
508
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
509
|
+
/**
|
|
510
|
+
* Update live DOM elements to reflect the current this._cfg.
|
|
511
|
+
* Safe to call before or after mounting.
|
|
512
|
+
*/
|
|
513
|
+
_applyConfig() {
|
|
514
|
+
// ── Theme & colors ──
|
|
515
|
+
const isDark = this._cfg.theme === 'dark'
|
|
516
|
+
|| (this._cfg.theme === 'auto' && window.matchMedia?.('(prefers-color-scheme: dark)').matches);
|
|
517
|
+
|
|
518
|
+
this._root?.classList.toggle('cd-dark', isDark);
|
|
519
|
+
this._root?.style.setProperty('--cd-primary', this._cfg.primaryColor);
|
|
520
|
+
this._panel?.style.setProperty('--cd-primary', this._cfg.primaryColor);
|
|
521
|
+
this._toggleBtn?.style.setProperty('--cd-primary', this._cfg.primaryColor);
|
|
522
|
+
|
|
523
|
+
// ── Header: avatar + bot name ──
|
|
524
|
+
const avatarEl = this._shadow?.querySelector('.cd-header-avatar');
|
|
525
|
+
if (avatarEl) avatarEl.textContent = this._cfg.botEmoji;
|
|
526
|
+
|
|
527
|
+
const nameEl = this._shadow?.querySelector('.cd-header-name');
|
|
528
|
+
if (nameEl) nameEl.textContent = this._cfg.botName;
|
|
529
|
+
|
|
530
|
+
// ── Input placeholder ──
|
|
531
|
+
if (this._textarea) this._textarea.placeholder = this._cfg.placeholder;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Silently refresh config from server in the background.
|
|
536
|
+
* Called each time the panel opens so dashboard changes
|
|
537
|
+
* are reflected without reloading the page.
|
|
538
|
+
* Uses a debounce flag to avoid concurrent fetches.
|
|
539
|
+
*/
|
|
540
|
+
_silentRefresh() {
|
|
541
|
+
if (!this._cfg.widgetId || this._refreshPending) return;
|
|
542
|
+
this._refreshPending = true;
|
|
543
|
+
this._fetchWidgetConfig()
|
|
544
|
+
.then(active => {
|
|
545
|
+
if (active === false) {
|
|
546
|
+
// Widget was deleted or disabled — remove it from the page silently
|
|
547
|
+
this.unmount();
|
|
548
|
+
} else {
|
|
549
|
+
this._applyConfig();
|
|
550
|
+
}
|
|
551
|
+
})
|
|
552
|
+
.catch(() => {})
|
|
553
|
+
.finally(() => { this._refreshPending = false; });
|
|
388
554
|
}
|
|
389
555
|
|
|
390
556
|
unmount() {
|
|
@@ -400,6 +566,8 @@ export default class CognitionDeskWidget {
|
|
|
400
566
|
this._syncViewportMetrics();
|
|
401
567
|
this._panel?.classList.add('open');
|
|
402
568
|
this._textarea?.focus();
|
|
569
|
+
// Background refresh so config changes from dashboard appear on next open
|
|
570
|
+
this._silentRefresh();
|
|
403
571
|
}
|
|
404
572
|
|
|
405
573
|
close() {
|