open-chat-studio-widget 0.6.0 → 0.8.0
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/README.md +28 -25
- package/dist/cjs/{index-CcvroTR_.js → index-Cf6K60f1.js} +3 -3
- package/dist/cjs/{index-CcvroTR_.js.map → index-Cf6K60f1.js.map} +1 -1
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/open-chat-studio-widget.cjs.entry.js +480 -178
- package/dist/cjs/open-chat-studio-widget.cjs.entry.js.map +1 -1
- package/dist/cjs/open-chat-studio-widget.cjs.js +2 -2
- package/dist/cjs/open-chat-studio-widget.entry.cjs.js.map +1 -1
- package/dist/collection/components/ocs-chat/icons.js +2 -2
- package/dist/collection/components/ocs-chat/icons.js.map +1 -1
- package/dist/collection/components/ocs-chat/ocs-chat.css +29 -34
- package/dist/collection/components/ocs-chat/ocs-chat.js +374 -102
- package/dist/collection/components/ocs-chat/ocs-chat.js.map +1 -1
- package/dist/collection/services/chat-session-service.js +39 -1
- package/dist/collection/services/chat-session-service.js.map +1 -1
- package/dist/collection/services/file-attachment-manager.js +4 -7
- package/dist/collection/services/file-attachment-manager.js.map +1 -1
- package/dist/collection/utils/cookies.js.map +1 -1
- package/dist/collection/utils/markdown.js +43 -17
- package/dist/collection/utils/markdown.js.map +1 -1
- package/dist/collection/utils/translations.js +1 -3
- package/dist/collection/utils/translations.js.map +1 -1
- package/dist/collection/utils/utils.js +2 -2
- package/dist/collection/utils/utils.js.map +1 -1
- package/dist/components/open-chat-studio-widget.js +487 -178
- package/dist/components/open-chat-studio-widget.js.map +1 -1
- package/dist/esm/{index-BKVXO_5E.js → index-DXf2dIht.js} +3 -3
- package/dist/esm/{index-BKVXO_5E.js.map → index-DXf2dIht.js.map} +1 -1
- package/dist/esm/loader.js +3 -3
- package/dist/esm/open-chat-studio-widget.entry.js +480 -178
- package/dist/esm/open-chat-studio-widget.entry.js.map +1 -1
- package/dist/esm/open-chat-studio-widget.js +3 -3
- package/dist/open-chat-studio-widget/open-chat-studio-widget.entry.esm.js.map +1 -1
- package/dist/open-chat-studio-widget/open-chat-studio-widget.esm.js +1 -1
- package/dist/open-chat-studio-widget/{p-BKVXO_5E.js → p-DXf2dIht.js} +2 -2
- package/dist/open-chat-studio-widget/{p-BKVXO_5E.js.map → p-DXf2dIht.js.map} +1 -1
- package/dist/open-chat-studio-widget/p-ff47dabf.entry.js +4 -0
- package/dist/open-chat-studio-widget/p-ff47dabf.entry.js.map +1 -0
- package/dist/types/components/ocs-chat/ocs-chat.d.ts +44 -2
- package/dist/types/components.d.ts +33 -4
- package/dist/types/services/chat-session-service.d.ts +7 -0
- package/dist/types/utils/markdown.d.ts +8 -0
- package/package.json +7 -2
- package/dist/open-chat-studio-widget/p-a0d04423.entry.js +0 -4
- package/dist/open-chat-studio-widget/p-a0d04423.entry.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2
2
|
import { Host, h, Env } from "@stencil/core";
|
|
3
|
-
import { XMarkIcon, GripDotsVerticalIcon, PlusWithCircleIcon, ArrowsPointingOutIcon, ArrowsPointingInIcon, PaperClipIcon, CheckDocumentIcon, XIcon, OcsWidgetAvatar } from "./icons";
|
|
3
|
+
import { XMarkIcon, GripDotsVerticalIcon, PlusWithCircleIcon, ArrowsPointingOutIcon, ArrowsPointingInIcon, PaperClipIcon, CheckDocumentIcon, XIcon, OcsWidgetAvatar, } from "./icons";
|
|
4
4
|
import { renderMarkdownSync as renderMarkdownComplete } from "../../utils/markdown";
|
|
5
5
|
import { varToPixels } from "../../utils/utils";
|
|
6
6
|
import { TranslationManager, defaultTranslations } from "../../utils/translations";
|
|
@@ -11,11 +11,23 @@ export class OcsChat {
|
|
|
11
11
|
/**
|
|
12
12
|
* The base URL for the API.
|
|
13
13
|
*/
|
|
14
|
-
this.apiBaseUrl =
|
|
14
|
+
this.apiBaseUrl = 'https://www.openchatstudio.com';
|
|
15
15
|
/**
|
|
16
16
|
* The shape of the chat button. 'round' makes it circular, 'square' keeps it rectangular.
|
|
17
17
|
*/
|
|
18
18
|
this.buttonShape = 'square';
|
|
19
|
+
/**
|
|
20
|
+
* Whether to show the launcher button. Set to false to hide the button
|
|
21
|
+
* and open the chat window programmatically via the `visible` property.
|
|
22
|
+
*/
|
|
23
|
+
this.showButton = true;
|
|
24
|
+
/**
|
|
25
|
+
* The operating mode of the widget.
|
|
26
|
+
* - 'standard': Default floating window with launcher button.
|
|
27
|
+
* - 'kiosk': Fills parent container, always visible, no header or launcher button.
|
|
28
|
+
* The parent element must establish a containing block (e.g. `position: relative`).
|
|
29
|
+
*/
|
|
30
|
+
this.mode = 'standard';
|
|
19
31
|
/**
|
|
20
32
|
* Whether the chat widget is visible on load.
|
|
21
33
|
*/
|
|
@@ -26,6 +38,7 @@ export class OcsChat {
|
|
|
26
38
|
this.position = 'right';
|
|
27
39
|
/**
|
|
28
40
|
* Whether to persist session data to local storage to allow resuming previous conversations after page reload.
|
|
41
|
+
* Ignored when `sessionId` is provided.
|
|
29
42
|
*/
|
|
30
43
|
this.persistentSession = true;
|
|
31
44
|
/**
|
|
@@ -41,12 +54,13 @@ export class OcsChat {
|
|
|
41
54
|
* Allow the user to attach files to their messages.
|
|
42
55
|
*/
|
|
43
56
|
this.allowAttachments = false;
|
|
44
|
-
this.error =
|
|
57
|
+
this.error = '';
|
|
45
58
|
this.messages = [];
|
|
46
59
|
this.isLoading = false;
|
|
47
60
|
this.isTyping = false;
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
61
|
+
this.typingProgressMessage = '';
|
|
62
|
+
this.messageInput = '';
|
|
63
|
+
this.currentPollTaskId = '';
|
|
50
64
|
this.isDragging = false;
|
|
51
65
|
this.dragOffset = { x: 0, y: 0 };
|
|
52
66
|
this.windowPosition = { x: 0, y: 0 };
|
|
@@ -75,6 +89,7 @@ export class OcsChat {
|
|
|
75
89
|
this.chatWindowWidth = 450;
|
|
76
90
|
this.chatWindowFullscreenWidth = 1024;
|
|
77
91
|
this.positionInitialized = false;
|
|
92
|
+
this.sessionEpoch = 0;
|
|
78
93
|
this.handleMouseDown = (event) => {
|
|
79
94
|
if (!this.isFullscreen && window.innerWidth < OcsChat.MOBILE_BREAKPOINT)
|
|
80
95
|
return;
|
|
@@ -120,6 +135,8 @@ export class OcsChat {
|
|
|
120
135
|
};
|
|
121
136
|
this.handleWindowResize = () => {
|
|
122
137
|
var _a, _b;
|
|
138
|
+
if (this.isKioskMode())
|
|
139
|
+
return;
|
|
123
140
|
this.positionInitialized = false;
|
|
124
141
|
this.initializePosition();
|
|
125
142
|
// Revalidate button position after resize to keep it within viewport bounds
|
|
@@ -131,7 +148,7 @@ export class OcsChat {
|
|
|
131
148
|
const minPadding = 10;
|
|
132
149
|
this.buttonPosition = {
|
|
133
150
|
x: Math.max(minPadding, Math.min(this.buttonPosition.x, windowWidth - buttonWidth - minPadding)),
|
|
134
|
-
y: Math.max(minPadding, Math.min(this.buttonPosition.y, windowHeight - buttonHeight - minPadding))
|
|
151
|
+
y: Math.max(minPadding, Math.min(this.buttonPosition.y, windowHeight - buttonHeight - minPadding)),
|
|
135
152
|
};
|
|
136
153
|
this.updateHostPosition();
|
|
137
154
|
}
|
|
@@ -149,7 +166,7 @@ export class OcsChat {
|
|
|
149
166
|
const rect = this.host.getBoundingClientRect();
|
|
150
167
|
this.buttonDragOffset = {
|
|
151
168
|
x: pointer.clientX - rect.left,
|
|
152
|
-
y: pointer.clientY - rect.top
|
|
169
|
+
y: pointer.clientY - rect.top,
|
|
153
170
|
};
|
|
154
171
|
this.addButtonEventListeners();
|
|
155
172
|
};
|
|
@@ -166,7 +183,7 @@ export class OcsChat {
|
|
|
166
183
|
const rect = this.host.getBoundingClientRect();
|
|
167
184
|
this.buttonDragOffset = {
|
|
168
185
|
x: pointer.clientX - rect.left,
|
|
169
|
-
y: pointer.clientY - rect.top
|
|
186
|
+
y: pointer.clientY - rect.top,
|
|
170
187
|
};
|
|
171
188
|
this.addButtonEventListeners();
|
|
172
189
|
};
|
|
@@ -213,12 +230,19 @@ export class OcsChat {
|
|
|
213
230
|
this.error = 'Chatbot ID is required';
|
|
214
231
|
return;
|
|
215
232
|
}
|
|
233
|
+
if (this.isKioskMode()) {
|
|
234
|
+
this.visible = true;
|
|
235
|
+
}
|
|
216
236
|
await this.initializeTranslations();
|
|
217
|
-
|
|
218
|
-
|
|
237
|
+
if (this.isSessionBound()) {
|
|
238
|
+
// Bound to an externally-managed session: the host page is the source of truth.
|
|
239
|
+
this.activeSessionId = this.sessionId;
|
|
240
|
+
}
|
|
241
|
+
else if (this.persistentSession && this.isLocalStorageAvailable()) {
|
|
242
|
+
// Always try to load existing session if localStorage is available
|
|
219
243
|
const { sessionId, messages } = this.loadSessionFromStorage();
|
|
220
244
|
if (sessionId && messages) {
|
|
221
|
-
this.
|
|
245
|
+
this.activeSessionId = sessionId;
|
|
222
246
|
this.messages = messages;
|
|
223
247
|
}
|
|
224
248
|
}
|
|
@@ -235,15 +259,29 @@ export class OcsChat {
|
|
|
235
259
|
this.chatWindowWidth = varToPixels(windowWidthVar, window.innerWidth, this.chatWindowWidth);
|
|
236
260
|
this.chatWindowFullscreenWidth = varToPixels(fullscreenWidthVar, window.innerWidth, this.chatWindowFullscreenWidth);
|
|
237
261
|
// Initialize button position from computed styles
|
|
238
|
-
this.
|
|
239
|
-
|
|
262
|
+
if (this.showButton && !this.isKioskMode()) {
|
|
263
|
+
this.initializeButtonPosition();
|
|
264
|
+
}
|
|
265
|
+
// Defer state changes to avoid triggering them during componentDidLoad
|
|
240
266
|
setTimeout(() => {
|
|
267
|
+
// Restore visible state after dimensions are read so initializePosition
|
|
268
|
+
// uses the correct CSS-derived chatWindowWidth/chatWindowHeight.
|
|
269
|
+
if (!this.isKioskMode() && this.showButton && this.persistentSession && this.isLocalStorageAvailable()) {
|
|
270
|
+
this.restoreVisibleState();
|
|
271
|
+
}
|
|
241
272
|
if (this.visible) {
|
|
242
|
-
this.
|
|
273
|
+
if (!this.isKioskMode()) {
|
|
274
|
+
this.initializePosition();
|
|
275
|
+
}
|
|
243
276
|
}
|
|
244
277
|
// Resume polling for existing session (don't auto-start new sessions)
|
|
245
|
-
if (this.visible && this.
|
|
246
|
-
this.
|
|
278
|
+
if (this.visible && this.activeSessionId) {
|
|
279
|
+
if (this.isSessionBound()) {
|
|
280
|
+
void this.loadBoundSessionHistory();
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
this.startMessagePolling();
|
|
284
|
+
}
|
|
247
285
|
}
|
|
248
286
|
}, 0);
|
|
249
287
|
window.addEventListener('resize', this.handleWindowResize);
|
|
@@ -272,7 +310,7 @@ export class OcsChat {
|
|
|
272
310
|
created_at: new Date().toISOString(),
|
|
273
311
|
role: 'system',
|
|
274
312
|
content: `**Error:** ${errorText}\nPlease try again.`,
|
|
275
|
-
attachments: []
|
|
313
|
+
attachments: [],
|
|
276
314
|
};
|
|
277
315
|
this.messages = [...this.messages, errorMessage];
|
|
278
316
|
this.saveSessionToStorage();
|
|
@@ -322,7 +360,7 @@ export class OcsChat {
|
|
|
322
360
|
return;
|
|
323
361
|
}
|
|
324
362
|
if (typeof this.pageContext !== 'object' || Array.isArray(this.pageContext)) {
|
|
325
|
-
console.error(
|
|
363
|
+
console.error('pageContext is expected to be a plain JavaScript object.');
|
|
326
364
|
return;
|
|
327
365
|
}
|
|
328
366
|
this.internalPageContext = this.pageContext;
|
|
@@ -350,6 +388,7 @@ export class OcsChat {
|
|
|
350
388
|
this.currentPollTaskId = '';
|
|
351
389
|
}
|
|
352
390
|
async startSession() {
|
|
391
|
+
const epoch = this.sessionEpoch;
|
|
353
392
|
try {
|
|
354
393
|
this.isLoading = true;
|
|
355
394
|
const userId = this.getOrGenerateUserId();
|
|
@@ -357,34 +396,65 @@ export class OcsChat {
|
|
|
357
396
|
chatbot_id: this.chatbotId,
|
|
358
397
|
session_data: {
|
|
359
398
|
source: 'widget',
|
|
360
|
-
page_url: window.location.href
|
|
399
|
+
page_url: window.location.href,
|
|
361
400
|
},
|
|
362
|
-
participant_remote_id: userId
|
|
401
|
+
participant_remote_id: userId,
|
|
363
402
|
};
|
|
364
403
|
if (this.userName) {
|
|
365
404
|
requestBody.participant_name = this.userName;
|
|
366
405
|
}
|
|
406
|
+
if (this.versionNumber != null) {
|
|
407
|
+
requestBody.version_number = this.versionNumber;
|
|
408
|
+
}
|
|
367
409
|
const data = await this.getChatService().startSession(requestBody);
|
|
368
|
-
|
|
410
|
+
if (epoch !== this.sessionEpoch)
|
|
411
|
+
return;
|
|
412
|
+
this.activeSessionId = data.session_id;
|
|
369
413
|
this.saveSessionToStorage();
|
|
370
414
|
this.startMessagePolling();
|
|
371
415
|
}
|
|
372
416
|
catch (_error) {
|
|
417
|
+
if (epoch !== this.sessionEpoch)
|
|
418
|
+
return;
|
|
373
419
|
this.handleError('Failed to start chat session');
|
|
374
420
|
}
|
|
375
421
|
finally {
|
|
376
422
|
this.isLoading = false;
|
|
377
423
|
}
|
|
378
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Load the full message history for a session provided via the `session-id`
|
|
427
|
+
* prop, then begin regular polling.
|
|
428
|
+
*/
|
|
429
|
+
async loadBoundSessionHistory() {
|
|
430
|
+
const epoch = this.sessionEpoch;
|
|
431
|
+
try {
|
|
432
|
+
const history = await this.getChatService().fetchAllMessages(this.activeSessionId);
|
|
433
|
+
if (epoch !== this.sessionEpoch)
|
|
434
|
+
return;
|
|
435
|
+
// Keep messages added while the history was loading (e.g. an optimistic
|
|
436
|
+
// user message) by appending any that aren't part of the fetched history.
|
|
437
|
+
const known = new Set(history.map(m => `${m.created_at}|${m.role}|${m.content}`));
|
|
438
|
+
const pending = this.messages.filter(m => !known.has(`${m.created_at}|${m.role}|${m.content}`));
|
|
439
|
+
this.messages = [...history, ...pending];
|
|
440
|
+
this.scrollToBottom(true);
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
if (epoch !== this.sessionEpoch)
|
|
444
|
+
return;
|
|
445
|
+
console.warn('Failed to load chat history:', error);
|
|
446
|
+
}
|
|
447
|
+
this.startMessagePolling();
|
|
448
|
+
}
|
|
379
449
|
async uploadFiles() {
|
|
380
|
-
if (this.selectedFiles.length === 0 || !this.
|
|
450
|
+
if (this.selectedFiles.length === 0 || !this.activeSessionId || !this.allowAttachments) {
|
|
381
451
|
return [];
|
|
382
452
|
}
|
|
383
453
|
this.isUploadingFiles = true;
|
|
384
454
|
try {
|
|
385
455
|
const uploadResult = await this.attachmentManager.uploadPendingFiles(this.selectedFiles, {
|
|
386
456
|
apiBaseUrl: this.apiBaseUrl || 'https://www.openchatstudio.com',
|
|
387
|
-
sessionId: this.
|
|
457
|
+
sessionId: this.activeSessionId,
|
|
388
458
|
participantId: this.getOrGenerateUserId(),
|
|
389
459
|
participantName: this.userName,
|
|
390
460
|
});
|
|
@@ -398,15 +468,16 @@ export class OcsChat {
|
|
|
398
468
|
async sendMessage(message) {
|
|
399
469
|
if (!message.trim())
|
|
400
470
|
return;
|
|
471
|
+
const epoch = this.sessionEpoch;
|
|
401
472
|
// Start session if we don't have one yet
|
|
402
|
-
if (!this.
|
|
473
|
+
if (!this.activeSessionId) {
|
|
403
474
|
// Prevent concurrent session initialization
|
|
404
475
|
if (this.isLoading) {
|
|
405
476
|
return;
|
|
406
477
|
}
|
|
407
478
|
await this.startSession();
|
|
408
479
|
// Check if session started successfully
|
|
409
|
-
if (!this.
|
|
480
|
+
if (!this.activeSessionId) {
|
|
410
481
|
return; // startSession already handled the error
|
|
411
482
|
}
|
|
412
483
|
}
|
|
@@ -431,7 +502,7 @@ export class OcsChat {
|
|
|
431
502
|
created_at: new Date(now.getTime() - (welcomeMessagesToAdd.length - index) * 1000).toISOString(),
|
|
432
503
|
role: 'assistant',
|
|
433
504
|
content: welcomeMsg,
|
|
434
|
-
attachments: []
|
|
505
|
+
attachments: [],
|
|
435
506
|
}));
|
|
436
507
|
this.messages = [...this.messages, ...welcomeMessages];
|
|
437
508
|
}
|
|
@@ -440,13 +511,15 @@ export class OcsChat {
|
|
|
440
511
|
created_at: new Date().toISOString(),
|
|
441
512
|
role: 'user',
|
|
442
513
|
content: message.trim(),
|
|
443
|
-
attachments: this.allowAttachments
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
514
|
+
attachments: this.allowAttachments
|
|
515
|
+
? this.selectedFiles
|
|
516
|
+
.filter(sf => !sf.error && sf.uploaded)
|
|
517
|
+
.map(sf => ({
|
|
518
|
+
name: sf.file.name,
|
|
519
|
+
content_type: sf.file.type,
|
|
520
|
+
size: sf.file.size,
|
|
521
|
+
}))
|
|
522
|
+
: [],
|
|
450
523
|
};
|
|
451
524
|
this.messages = [...this.messages, userMessage];
|
|
452
525
|
this.saveSessionToStorage();
|
|
@@ -462,7 +535,12 @@ export class OcsChat {
|
|
|
462
535
|
if (this.internalPageContext) {
|
|
463
536
|
requestBody.context = this.internalPageContext;
|
|
464
537
|
}
|
|
465
|
-
|
|
538
|
+
if (this.versionNumber != null) {
|
|
539
|
+
requestBody.version_number = this.versionNumber;
|
|
540
|
+
}
|
|
541
|
+
const data = await this.getChatService().sendMessage(this.activeSessionId, requestBody);
|
|
542
|
+
if (epoch !== this.sessionEpoch)
|
|
543
|
+
return;
|
|
466
544
|
if (data.status === 'error') {
|
|
467
545
|
throw new Error(data.error || 'Failed to send message');
|
|
468
546
|
}
|
|
@@ -470,6 +548,8 @@ export class OcsChat {
|
|
|
470
548
|
this.startTaskPolling(data.task_id);
|
|
471
549
|
}
|
|
472
550
|
catch (error) {
|
|
551
|
+
if (epoch !== this.sessionEpoch)
|
|
552
|
+
return;
|
|
473
553
|
const errorText = error instanceof Error ? error.message : 'Failed to send message';
|
|
474
554
|
this.handleError(errorText);
|
|
475
555
|
}
|
|
@@ -492,10 +572,10 @@ export class OcsChat {
|
|
|
492
572
|
const childRect = lastChild.getBoundingClientRect();
|
|
493
573
|
const currentScrollTop = this.messageListRef.scrollTop;
|
|
494
574
|
const childTopRelativeToParent = childRect.top - parentRect.top;
|
|
495
|
-
const targetScroll = currentScrollTop + childTopRelativeToParent -
|
|
575
|
+
const targetScroll = currentScrollTop + childTopRelativeToParent - parentRect.height / 2;
|
|
496
576
|
this.messageListRef.scrollTo({
|
|
497
577
|
top: targetScroll,
|
|
498
|
-
behavior: 'smooth'
|
|
578
|
+
behavior: 'smooth',
|
|
499
579
|
});
|
|
500
580
|
}
|
|
501
581
|
else {
|
|
@@ -540,10 +620,10 @@ export class OcsChat {
|
|
|
540
620
|
const k = 1024;
|
|
541
621
|
if (bytes < k * k) {
|
|
542
622
|
// Less than 1MB, show in KB
|
|
543
|
-
return Math.round(bytes / k * 100) / 100 + ' KB';
|
|
623
|
+
return Math.round((bytes / k) * 100) / 100 + ' KB';
|
|
544
624
|
}
|
|
545
625
|
else {
|
|
546
|
-
return Math.round(bytes / (k * k) * 100) / 100 + ' MB';
|
|
626
|
+
return Math.round((bytes / (k * k)) * 100) / 100 + ' MB';
|
|
547
627
|
}
|
|
548
628
|
}
|
|
549
629
|
formatTime(dateString) {
|
|
@@ -561,23 +641,40 @@ export class OcsChat {
|
|
|
561
641
|
pageContextHandler() {
|
|
562
642
|
this.loadInternalPageContext();
|
|
563
643
|
}
|
|
644
|
+
async chatbotConfigHandler() {
|
|
645
|
+
await this.clearSession();
|
|
646
|
+
}
|
|
564
647
|
/**
|
|
565
648
|
* Watch for changes to the `visible` attribute and update accordingly.
|
|
566
649
|
*
|
|
567
650
|
* @param visible - The new value for the field.
|
|
568
651
|
*/
|
|
569
652
|
async visibilityHandler(visible) {
|
|
653
|
+
// Kiosk mode is always visible
|
|
654
|
+
if (this.isKioskMode() && !visible) {
|
|
655
|
+
this.visible = true;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
this.saveVisibleState(visible);
|
|
570
659
|
if (this.isButtonDragging) {
|
|
571
660
|
this.isButtonDragging = false;
|
|
572
661
|
this.buttonWasDragged = false;
|
|
573
662
|
this.removeButtonEventListeners();
|
|
574
663
|
}
|
|
575
664
|
if (visible) {
|
|
576
|
-
this.
|
|
665
|
+
if (!this.isKioskMode()) {
|
|
666
|
+
this.initializePosition();
|
|
667
|
+
}
|
|
577
668
|
// Resume polling for existing session (don't auto-start new sessions)
|
|
578
|
-
if (this.
|
|
669
|
+
if (this.activeSessionId) {
|
|
579
670
|
this.scrollToBottom(true);
|
|
580
|
-
this.
|
|
671
|
+
if (this.isSessionBound() && this.messages.length === 0) {
|
|
672
|
+
// A bound widget that was hidden at load has not fetched its history yet.
|
|
673
|
+
void this.loadBoundSessionHistory();
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
this.startMessagePolling();
|
|
677
|
+
}
|
|
581
678
|
}
|
|
582
679
|
}
|
|
583
680
|
else {
|
|
@@ -585,7 +682,7 @@ export class OcsChat {
|
|
|
585
682
|
}
|
|
586
683
|
}
|
|
587
684
|
startTaskPolling(taskId) {
|
|
588
|
-
if (!this.
|
|
685
|
+
if (!this.activeSessionId)
|
|
589
686
|
return;
|
|
590
687
|
this.currentPollTaskId = taskId;
|
|
591
688
|
this.isTyping = true;
|
|
@@ -593,50 +690,56 @@ export class OcsChat {
|
|
|
593
690
|
if (this.taskPollingHandle) {
|
|
594
691
|
this.taskPollingHandle.cancel();
|
|
595
692
|
}
|
|
596
|
-
this.taskPollingHandle = this.getChatService().pollTask(this.
|
|
597
|
-
onMessage:
|
|
693
|
+
this.taskPollingHandle = this.getChatService().pollTask(this.activeSessionId, taskId, {
|
|
694
|
+
onMessage: message => {
|
|
598
695
|
this.messages = [...this.messages, message];
|
|
599
696
|
this.saveSessionToStorage();
|
|
600
697
|
this.scrollToBottom();
|
|
601
698
|
this.isTyping = false;
|
|
699
|
+
this.typingProgressMessage = '';
|
|
602
700
|
this.currentPollTaskId = '';
|
|
603
701
|
this.taskPollingHandle = undefined;
|
|
604
702
|
this.startMessagePolling();
|
|
605
703
|
this.focusInput();
|
|
606
704
|
},
|
|
705
|
+
onProgress: message => {
|
|
706
|
+
this.typingProgressMessage = message;
|
|
707
|
+
},
|
|
607
708
|
onTimeout: () => {
|
|
608
709
|
const timeoutMessage = {
|
|
609
710
|
created_at: new Date().toISOString(),
|
|
610
711
|
role: 'system',
|
|
611
712
|
content: 'The response is taking longer than expected. The system may be experiencing delays. Please try sending your message again.',
|
|
612
|
-
attachments: []
|
|
713
|
+
attachments: [],
|
|
613
714
|
};
|
|
614
715
|
this.messages = [...this.messages, timeoutMessage];
|
|
615
716
|
this.saveSessionToStorage();
|
|
616
717
|
this.scrollToBottom();
|
|
617
718
|
this.isTyping = false;
|
|
719
|
+
this.typingProgressMessage = '';
|
|
618
720
|
this.currentPollTaskId = '';
|
|
619
721
|
this.taskPollingHandle = undefined;
|
|
620
722
|
this.startMessagePolling();
|
|
621
723
|
this.focusInput();
|
|
622
724
|
},
|
|
623
|
-
onError:
|
|
725
|
+
onError: error => {
|
|
726
|
+
this.typingProgressMessage = '';
|
|
624
727
|
this.handleError(error.message);
|
|
625
728
|
this.taskPollingHandle = undefined;
|
|
626
729
|
this.startMessagePolling();
|
|
627
|
-
}
|
|
730
|
+
},
|
|
628
731
|
});
|
|
629
732
|
}
|
|
630
733
|
startMessagePolling() {
|
|
631
|
-
if (!this.
|
|
734
|
+
if (!this.activeSessionId || this.currentPollTaskId || !this.visible) {
|
|
632
735
|
return;
|
|
633
736
|
}
|
|
634
737
|
if (this.messagePollingHandle) {
|
|
635
738
|
return;
|
|
636
739
|
}
|
|
637
|
-
this.messagePollingHandle = this.getChatService().startMessagePolling(this.
|
|
638
|
-
getSince: () => { var _a; return this.messages.length > 0 ? (_a = this.messages.at(-1)) === null || _a === void 0 ? void 0 : _a.created_at : undefined; },
|
|
639
|
-
onMessages:
|
|
740
|
+
this.messagePollingHandle = this.getChatService().startMessagePolling(this.activeSessionId, {
|
|
741
|
+
getSince: () => { var _a; return (this.messages.length > 0 ? (_a = this.messages.at(-1)) === null || _a === void 0 ? void 0 : _a.created_at : undefined); },
|
|
742
|
+
onMessages: messages => {
|
|
640
743
|
if (messages.length === 0)
|
|
641
744
|
return;
|
|
642
745
|
this.messages = [...this.messages, ...messages];
|
|
@@ -646,7 +749,7 @@ export class OcsChat {
|
|
|
646
749
|
},
|
|
647
750
|
onError: () => {
|
|
648
751
|
// Silently ignore polling errors to match previous behaviour
|
|
649
|
-
}
|
|
752
|
+
},
|
|
650
753
|
});
|
|
651
754
|
}
|
|
652
755
|
stopMessagePolling() {
|
|
@@ -665,6 +768,9 @@ export class OcsChat {
|
|
|
665
768
|
this.position = position;
|
|
666
769
|
}
|
|
667
770
|
getPositionClasses() {
|
|
771
|
+
if (this.isKioskMode()) {
|
|
772
|
+
return 'chat-window-kiosk';
|
|
773
|
+
}
|
|
668
774
|
if (this.isFullscreen) {
|
|
669
775
|
return 'chat-window-fullscreen';
|
|
670
776
|
}
|
|
@@ -680,6 +786,9 @@ export class OcsChat {
|
|
|
680
786
|
return { windowWidth, actualChatWidth, centeredX, maxOffset };
|
|
681
787
|
}
|
|
682
788
|
getPositionStyles() {
|
|
789
|
+
if (this.isKioskMode()) {
|
|
790
|
+
return {};
|
|
791
|
+
}
|
|
683
792
|
if (this.isFullscreen) {
|
|
684
793
|
const { centeredX } = this.getFullscreenBounds();
|
|
685
794
|
const finalX = centeredX + this.fullscreenPosition.x;
|
|
@@ -711,19 +820,19 @@ export class OcsChat {
|
|
|
711
820
|
case 'left':
|
|
712
821
|
this.windowPosition = {
|
|
713
822
|
x: OcsChat.WINDOW_MARGIN,
|
|
714
|
-
y: windowHeight - this.chatWindowHeight - OcsChat.WINDOW_MARGIN
|
|
823
|
+
y: windowHeight - this.chatWindowHeight - OcsChat.WINDOW_MARGIN,
|
|
715
824
|
};
|
|
716
825
|
break;
|
|
717
826
|
case 'right':
|
|
718
827
|
this.windowPosition = {
|
|
719
828
|
x: windowWidth - chatWidth - OcsChat.WINDOW_MARGIN,
|
|
720
|
-
y: windowHeight - this.chatWindowHeight - OcsChat.WINDOW_MARGIN
|
|
829
|
+
y: windowHeight - this.chatWindowHeight - OcsChat.WINDOW_MARGIN,
|
|
721
830
|
};
|
|
722
831
|
break;
|
|
723
832
|
case 'center':
|
|
724
833
|
this.windowPosition = {
|
|
725
834
|
x: (windowWidth - chatWidth) / 2,
|
|
726
|
-
y: (windowHeight - this.chatWindowHeight) / 2
|
|
835
|
+
y: (windowHeight - this.chatWindowHeight) / 2,
|
|
727
836
|
};
|
|
728
837
|
break;
|
|
729
838
|
}
|
|
@@ -746,14 +855,14 @@ export class OcsChat {
|
|
|
746
855
|
// For fullscreen, track relative to current position
|
|
747
856
|
this.dragOffset = {
|
|
748
857
|
x: pointer.clientX,
|
|
749
|
-
y: pointer.clientY
|
|
858
|
+
y: pointer.clientY,
|
|
750
859
|
};
|
|
751
860
|
}
|
|
752
861
|
else {
|
|
753
862
|
const rect = this.chatWindowRef.getBoundingClientRect();
|
|
754
863
|
this.dragOffset = {
|
|
755
864
|
x: pointer.clientX - rect.left,
|
|
756
|
-
y: pointer.clientY - rect.top
|
|
865
|
+
y: pointer.clientY - rect.top,
|
|
757
866
|
};
|
|
758
867
|
}
|
|
759
868
|
}
|
|
@@ -765,7 +874,7 @@ export class OcsChat {
|
|
|
765
874
|
const { maxOffset } = this.getFullscreenBounds();
|
|
766
875
|
const deltaX = pointer.clientX - this.dragOffset.x;
|
|
767
876
|
this.fullscreenPosition = {
|
|
768
|
-
x: Math.max(-maxOffset, Math.min(maxOffset, deltaX))
|
|
877
|
+
x: Math.max(-maxOffset, Math.min(maxOffset, deltaX)),
|
|
769
878
|
};
|
|
770
879
|
}
|
|
771
880
|
else {
|
|
@@ -778,7 +887,7 @@ export class OcsChat {
|
|
|
778
887
|
const chatHeight = this.chatWindowRef.offsetHeight;
|
|
779
888
|
this.windowPosition = {
|
|
780
889
|
x: Math.max(0, Math.min(newX, windowWidth - chatWidth)),
|
|
781
|
-
y: Math.max(0, Math.min(newY, windowHeight - chatHeight))
|
|
890
|
+
y: Math.max(0, Math.min(newY, windowHeight - chatHeight)),
|
|
782
891
|
};
|
|
783
892
|
}
|
|
784
893
|
}
|
|
@@ -825,7 +934,7 @@ export class OcsChat {
|
|
|
825
934
|
const verticalValue = this.buttonVerticalSide === 'top' ? resolvedTop : resolvedBottom;
|
|
826
935
|
this.buttonPosition = {
|
|
827
936
|
x: horizontalValue,
|
|
828
|
-
y: verticalValue
|
|
937
|
+
y: verticalValue,
|
|
829
938
|
};
|
|
830
939
|
// Apply the position to the host
|
|
831
940
|
this.updateHostPosition();
|
|
@@ -868,12 +977,8 @@ export class OcsChat {
|
|
|
868
977
|
const maxTop = windowHeight - buttonHeight - minPadding;
|
|
869
978
|
const constrainedLeft = Math.max(minLeft, Math.min(candidateLeft, maxLeft));
|
|
870
979
|
const constrainedTop = Math.max(minTop, Math.min(candidateTop, maxTop));
|
|
871
|
-
const newHorizontalValue = this.buttonHorizontalSide === 'left'
|
|
872
|
-
|
|
873
|
-
: Math.max(minPadding, windowWidth - (constrainedLeft + buttonWidth));
|
|
874
|
-
const newVerticalValue = this.buttonVerticalSide === 'top'
|
|
875
|
-
? constrainedTop
|
|
876
|
-
: Math.max(minPadding, windowHeight - (constrainedTop + buttonHeight));
|
|
980
|
+
const newHorizontalValue = this.buttonHorizontalSide === 'left' ? constrainedLeft : Math.max(minPadding, windowWidth - (constrainedLeft + buttonWidth));
|
|
981
|
+
const newVerticalValue = this.buttonVerticalSide === 'top' ? constrainedTop : Math.max(minPadding, windowHeight - (constrainedTop + buttonHeight));
|
|
877
982
|
if (newHorizontalValue !== this.buttonPosition.x || newVerticalValue !== this.buttonPosition.y) {
|
|
878
983
|
this.buttonWasDragged = true;
|
|
879
984
|
this.buttonPosition = { x: newHorizontalValue, y: newVerticalValue };
|
|
@@ -936,16 +1041,12 @@ export class OcsChat {
|
|
|
936
1041
|
return fallback;
|
|
937
1042
|
}
|
|
938
1043
|
getWelcomeMessages() {
|
|
939
|
-
const translated = this.translationManager.getArray(
|
|
940
|
-
return translated && translated.length > 0
|
|
941
|
-
? translated
|
|
942
|
-
: this.parsedWelcomeMessages;
|
|
1044
|
+
const translated = this.translationManager.getArray('content.welcomeMessages');
|
|
1045
|
+
return translated && translated.length > 0 ? translated : this.parsedWelcomeMessages;
|
|
943
1046
|
}
|
|
944
1047
|
getStarterQuestions() {
|
|
945
|
-
const translated = this.translationManager.getArray(
|
|
946
|
-
return translated && translated.length > 0
|
|
947
|
-
? translated
|
|
948
|
-
: this.parsedStarterQuestions;
|
|
1048
|
+
const translated = this.translationManager.getArray('content.starterQuestions');
|
|
1049
|
+
return translated && translated.length > 0 ? translated : this.parsedStarterQuestions;
|
|
949
1050
|
}
|
|
950
1051
|
getButtonClasses() {
|
|
951
1052
|
const buttonText = this.translationManager.get('branding.buttonText', this.buttonText);
|
|
@@ -956,6 +1057,9 @@ export class OcsChat {
|
|
|
956
1057
|
}
|
|
957
1058
|
renderButton() {
|
|
958
1059
|
var _a;
|
|
1060
|
+
if (!this.showButton || this.isKioskMode()) {
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
959
1063
|
const buttonText = this.translationManager.get('branding.buttonText', this.buttonText);
|
|
960
1064
|
const hasText = !!(buttonText && buttonText.trim());
|
|
961
1065
|
const hasCustomIcon = this.iconUrl && this.iconUrl.trim();
|
|
@@ -965,31 +1069,34 @@ export class OcsChat {
|
|
|
965
1069
|
const buttonAriaLabel = finalButtonText ? `${openLabel} - ${finalButtonText}` : openLabel;
|
|
966
1070
|
// Only show drag cursor if button is draggable
|
|
967
1071
|
const isDraggable = this.isButtonDraggable();
|
|
968
|
-
const buttonStyle = isDraggable
|
|
969
|
-
|
|
970
|
-
|
|
1072
|
+
const buttonStyle = isDraggable
|
|
1073
|
+
? {
|
|
1074
|
+
cursor: this.isButtonDragging ? 'grabbing' : 'grab',
|
|
1075
|
+
}
|
|
1076
|
+
: {};
|
|
971
1077
|
if (hasText) {
|
|
972
|
-
return (h("button", { ref:
|
|
1078
|
+
return (h("button", { ref: el => (this.buttonRef = el), class: buttonClasses, "aria-label": buttonAriaLabel, title: finalButtonText || openLabel, style: buttonStyle, onClick: () => this.handleButtonClick(), onMouseDown: e => this.handleButtonMouseDown(e), onTouchStart: e => this.handleButtonTouchStart(e), "aria-grabbed": this.isButtonDragging, "aria-describedby": isDraggable ? 'chat-button-drag-hint' : undefined }, hasCustomIcon ? h("img", { src: this.iconUrl, alt: "" }) : h(OcsWidgetAvatar, null), h("span", null, finalButtonText), isDraggable && (h("span", { id: "chat-button-drag-hint", style: { display: 'none' } }, "Draggable. Use mouse or touch to reposition."))));
|
|
973
1079
|
}
|
|
974
1080
|
else {
|
|
975
|
-
return (h("button", { ref:
|
|
1081
|
+
return (h("button", { ref: el => (this.buttonRef = el), class: buttonClasses, "aria-label": openLabel, title: openLabel, style: buttonStyle, onClick: () => this.handleButtonClick(), onMouseDown: e => this.handleButtonMouseDown(e), onTouchStart: e => this.handleButtonTouchStart(e), "aria-grabbed": this.isButtonDragging, "aria-describedby": isDraggable ? 'chat-button-drag-hint' : undefined }, hasCustomIcon ? h("img", { src: this.iconUrl, alt: "" }) : h(OcsWidgetAvatar, null), isDraggable && (h("span", { id: "chat-button-drag-hint", style: { display: 'none' } }, "Draggable. Use mouse or touch to reposition."))));
|
|
976
1082
|
}
|
|
977
1083
|
}
|
|
978
1084
|
getStorageKeys() {
|
|
979
1085
|
return {
|
|
980
1086
|
sessionId: `ocs-chat-session-${this.chatbotId}`,
|
|
981
1087
|
messages: `ocs-chat-messages-${this.chatbotId}`,
|
|
982
|
-
lastActivity: `ocs-chat-activity-${this.chatbotId}
|
|
1088
|
+
lastActivity: `ocs-chat-activity-${this.chatbotId}`,
|
|
1089
|
+
visible: `ocs-chat-visible-${this.chatbotId}`,
|
|
983
1090
|
};
|
|
984
1091
|
}
|
|
985
1092
|
saveSessionToStorage() {
|
|
986
|
-
if (!this.persistentSession) {
|
|
1093
|
+
if (!this.persistentSession || this.isSessionBound()) {
|
|
987
1094
|
return;
|
|
988
1095
|
}
|
|
989
1096
|
const keys = this.getStorageKeys();
|
|
990
1097
|
try {
|
|
991
|
-
if (this.
|
|
992
|
-
localStorage.setItem(keys.sessionId, this.
|
|
1098
|
+
if (this.activeSessionId) {
|
|
1099
|
+
localStorage.setItem(keys.sessionId, this.activeSessionId);
|
|
993
1100
|
localStorage.setItem(keys.lastActivity, new Date().toISOString());
|
|
994
1101
|
}
|
|
995
1102
|
localStorage.setItem(keys.messages, JSON.stringify(this.messages));
|
|
@@ -1042,30 +1149,73 @@ export class OcsChat {
|
|
|
1042
1149
|
return this.generatedUserId;
|
|
1043
1150
|
}
|
|
1044
1151
|
const storageKey = `ocs-user-id`;
|
|
1045
|
-
|
|
1152
|
+
let stored = null;
|
|
1153
|
+
try {
|
|
1154
|
+
stored = localStorage.getItem(storageKey);
|
|
1155
|
+
}
|
|
1156
|
+
catch (_a) {
|
|
1157
|
+
// localStorage blocked; fall through to in-memory id generation
|
|
1158
|
+
}
|
|
1046
1159
|
if (stored) {
|
|
1047
1160
|
this.generatedUserId = stored;
|
|
1048
1161
|
return stored;
|
|
1049
1162
|
}
|
|
1050
1163
|
const array = new Uint8Array(9);
|
|
1051
1164
|
window.crypto.getRandomValues(array);
|
|
1052
|
-
const randomString = Array.from(array, byte => byte.toString(36))
|
|
1165
|
+
const randomString = Array.from(array, byte => byte.toString(36))
|
|
1166
|
+
.join('')
|
|
1167
|
+
.substr(0, 9);
|
|
1053
1168
|
const newUserId = `ocs:${Date.now()}_${randomString}`;
|
|
1054
1169
|
this.generatedUserId = newUserId;
|
|
1055
|
-
|
|
1170
|
+
try {
|
|
1171
|
+
localStorage.setItem(storageKey, newUserId);
|
|
1172
|
+
}
|
|
1173
|
+
catch (_b) {
|
|
1174
|
+
// localStorage blocked; the generated id lives in component state for this page
|
|
1175
|
+
}
|
|
1056
1176
|
return newUserId;
|
|
1057
1177
|
}
|
|
1178
|
+
saveVisibleState(visible) {
|
|
1179
|
+
if (!this.persistentSession)
|
|
1180
|
+
return;
|
|
1181
|
+
try {
|
|
1182
|
+
const keys = this.getStorageKeys();
|
|
1183
|
+
localStorage.setItem(keys.visible, visible ? '1' : '0');
|
|
1184
|
+
}
|
|
1185
|
+
catch (_a) {
|
|
1186
|
+
// ignore
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
restoreVisibleState() {
|
|
1190
|
+
try {
|
|
1191
|
+
const keys = this.getStorageKeys();
|
|
1192
|
+
const stored = localStorage.getItem(keys.visible);
|
|
1193
|
+
if (stored === '1') {
|
|
1194
|
+
this.visible = true;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
catch (_a) {
|
|
1198
|
+
// ignore
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1058
1201
|
clearSessionStorage() {
|
|
1059
1202
|
const keys = this.getStorageKeys();
|
|
1060
1203
|
try {
|
|
1061
1204
|
localStorage.removeItem(keys.sessionId);
|
|
1062
1205
|
localStorage.removeItem(keys.messages);
|
|
1063
1206
|
localStorage.removeItem(keys.lastActivity);
|
|
1207
|
+
localStorage.removeItem(keys.visible);
|
|
1064
1208
|
}
|
|
1065
1209
|
catch (error) {
|
|
1066
1210
|
console.warn('Failed to clear chat session from localStorage:', error);
|
|
1067
1211
|
}
|
|
1068
1212
|
}
|
|
1213
|
+
isKioskMode() {
|
|
1214
|
+
return this.mode === 'kiosk';
|
|
1215
|
+
}
|
|
1216
|
+
isSessionBound() {
|
|
1217
|
+
return !!this.sessionId;
|
|
1218
|
+
}
|
|
1069
1219
|
isLocalStorageAvailable() {
|
|
1070
1220
|
try {
|
|
1071
1221
|
localStorage.setItem(OcsChat.LOCALSTORAGE_TEST_KEY, 'test');
|
|
@@ -1091,8 +1241,11 @@ export class OcsChat {
|
|
|
1091
1241
|
* will start when the user sends a message.
|
|
1092
1242
|
*/
|
|
1093
1243
|
async clearSession() {
|
|
1244
|
+
this.sessionEpoch += 1;
|
|
1094
1245
|
this.clearSessionStorage();
|
|
1095
|
-
|
|
1246
|
+
// A session provided by the host page (session-id prop) cannot be cleared;
|
|
1247
|
+
// stay bound to it. Unbound widgets start a new session on the next message.
|
|
1248
|
+
this.activeSessionId = this.sessionId;
|
|
1096
1249
|
this.messages = [];
|
|
1097
1250
|
this.isTyping = false;
|
|
1098
1251
|
this.currentPollTaskId = '';
|
|
@@ -1100,6 +1253,11 @@ export class OcsChat {
|
|
|
1100
1253
|
this.selectedFiles = [];
|
|
1101
1254
|
}
|
|
1102
1255
|
this.cleanup();
|
|
1256
|
+
if (this.isSessionBound()) {
|
|
1257
|
+
// The host-owned session cannot be cleared: reload its history and
|
|
1258
|
+
// resume polling so the widget doesn't end up in a dead state.
|
|
1259
|
+
void this.loadBoundSessionHistory();
|
|
1260
|
+
}
|
|
1103
1261
|
}
|
|
1104
1262
|
toggleFullscreen() {
|
|
1105
1263
|
this.isFullscreen = !this.isFullscreen;
|
|
@@ -1108,21 +1266,15 @@ export class OcsChat {
|
|
|
1108
1266
|
}
|
|
1109
1267
|
render() {
|
|
1110
1268
|
// Only show error state for critical errors that prevent the widget from functioning
|
|
1111
|
-
if (this.error && !this.
|
|
1269
|
+
if (this.error && !this.activeSessionId) {
|
|
1112
1270
|
return (h(Host, null, h("p", { class: "error-message" }, this.error)));
|
|
1113
1271
|
}
|
|
1114
|
-
return (h(Host, null, this.renderButton(), this.visible && (h("div", { ref:
|
|
1115
|
-
? 'message-bubble-user'
|
|
1116
|
-
: message.role === 'assistant'
|
|
1117
|
-
? 'message-bubble-assistant'
|
|
1118
|
-
: 'message-bubble-system'}` }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownComplete(message.content) }), message.attachments && message.attachments.length > 0 && (h("div", { class: "message-attachments" }, message.attachments.map((attachment, attachmentIndex) => (h("div", { key: attachmentIndex, class: "flex items-center gap-[0.5em]" }, h("span", { class: "message-attachment-icon" }, h(PaperClipIcon, null)), h("span", { class: "message-attachment-name" }, attachment.name)))))), h("div", { class: "message-timestamp" }, this.formatTime(message.created_at)))))), this.isTyping && (h("div", null, h("div", { class: "typing-indicator" }, h("div", { class: "typing-progress" })), h("div", { class: "typing-text" }, h("span", null, this.translationManager.get('status.typing', this.typingIndicatorText)), h("span", { class: "typing-dots loading" })))))), this.messages.length === 0 && this.getStarterQuestions().length > 0 && (h("div", { class: "starter-questions" }, this.getStarterQuestions().map((question, index) => (h("div", { key: `starter-${index}`, class: "starter-question-row" }, h("button", { class: "starter-question", onClick: () => this.handleStarterQuestionClick(question) }, question)))))), this.allowAttachments && this.selectedFiles.length > 0 && (h("div", { class: "selected-files-container" }, h("div", { class: "space-y-[0.25em]" }, this.selectedFiles.map((selectedFile, index) => (h("div", { key: index, class: "selected-file-item" }, h("div", { class: "flex items-center gap-[0.5em]" }, h("span", { class: "selected-file-icon" }, h(PaperClipIcon, null)), h("span", null, selectedFile.file.name), h("span", { class: "selected-file-size" }, "(", this.formatFileSize(selectedFile.file.size), ")"), selectedFile.error && (h("span", { class: "selected-file-error" }, selectedFile.error)), selectedFile.uploaded && (h("span", { class: "selected-file-success-icon" }, h(CheckDocumentIcon, null)))), h("button", { onClick: () => this.removeSelectedFile(index), class: "selected-file-remove-button", "aria-label": this.translationManager.get('attach.remove') }, h(XIcon, null)))))))), h("div", { class: "input-area" }, h("div", { class: "input-container" }, h("textarea", { ref: (el) => this.textareaRef = el, class: "message-textarea", rows: 1, placeholder: this.translationManager.get('composer.placeholder'), value: this.messageInput, onInput: (e) => this.handleInputChange(e), onKeyPress: (e) => this.handleKeyPress(e), disabled: this.isTyping || this.isUploadingFiles || this.isLoading }), this.allowAttachments && (h("input", { ref: (el) => {
|
|
1272
|
+
return (h(Host, null, this.renderButton(), this.visible && (h("div", { ref: el => (this.chatWindowRef = el), id: "ocs-chat-window", class: this.getPositionClasses(), style: this.getPositionStyles() }, !this.isKioskMode() && (h("div", { class: `chat-header ${this.isDragging ? 'chat-header-dragging' : 'chat-header-draggable'}`, onMouseDown: this.handleMouseDown, onTouchStart: this.handleTouchStart }, h("div", { class: "drag-indicator" }, h("div", { class: "drag-dots header-button" }, h(GripDotsVerticalIcon, null))), h("div", { class: "header-text" }, this.translationManager.get('branding.headerText', this.headerText)), h("div", { class: "header-buttons" }, this.messages.length > 0 && !this.isSessionBound() && (h("button", { class: "header-button", onClick: () => this.showConfirmationDialog(), title: this.translationManager.get('window.newChat'), "aria-label": this.translationManager.get('window.newChat') }, h(PlusWithCircleIcon, null))), this.allowFullScreen && (h("button", { class: "header-button fullscreen-button", onClick: () => this.toggleFullscreen(), title: this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen'), "aria-label": this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen') }, this.isFullscreen ? h(ArrowsPointingInIcon, null) : h(ArrowsPointingOutIcon, null))), h("button", { class: "header-button", onClick: () => (this.visible = false), "aria-label": this.translationManager.get('window.close') }, h(XMarkIcon, null))))), !this.isKioskMode() && this.showNewChatConfirmation && (h("div", { class: "confirmation-overlay" }, h("div", { class: "confirmation-dialog" }, h("div", { class: "confirmation-content" }, h("h3", { class: "confirmation-title" }, this.translationManager.get('modal.newChatTitle')), h("p", { class: "confirmation-message" }, this.translationManager.get('modal.newChatBody', this.newChatConfirmationMessage)), h("div", { class: "confirmation-buttons" }, h("button", { class: "confirmation-button confirmation-button-cancel", onClick: () => this.hideConfirmationDialog() }, this.translationManager.get('modal.cancel')), h("button", { class: "confirmation-button confirmation-button-confirm", onClick: () => this.confirmNewChat() }, this.translationManager.get('modal.confirm'))))))), h("div", { class: "chat-content" }, this.isLoading && !this.activeSessionId && (h("div", { class: "loading-container" }, h("div", { class: "loading-spinner" }), h("span", { class: "loading-text" }, this.translationManager.get('status.starting')))), h("div", { ref: el => (this.messageListRef = el), class: "messages-container" }, this.messages.length === 0 && this.getWelcomeMessages().length > 0 && (h("div", { class: "welcome-messages" }, this.getWelcomeMessages().map((message, index) => (h("div", { key: `welcome-${index}`, class: "message-row message-row-assistant" }, h("div", { class: "message-bubble message-bubble-assistant" }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownComplete(message) }))))))), this.messages.map((message, index) => (h("div", { key: index, class: `message-row ${message.role === 'user' ? 'message-row-user' : 'message-row-assistant'}` }, h("div", { class: `message-bubble ${message.role === 'user' ? 'message-bubble-user' : message.role === 'assistant' ? 'message-bubble-assistant' : 'message-bubble-system'}` }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownComplete(message.content) }), message.attachments && message.attachments.length > 0 && (h("div", { class: "message-attachments" }, message.attachments.map((attachment, attachmentIndex) => (h("div", { key: attachmentIndex, class: "flex items-center gap-[0.5em]" }, h("span", { class: "message-attachment-icon" }, h(PaperClipIcon, null)), h("span", { class: "message-attachment-name" }, attachment.name)))))), h("div", { class: "message-timestamp" }, this.formatTime(message.created_at)))))), this.isTyping && (h("div", null, h("div", { class: "typing-indicator" }, h("div", { class: "typing-progress" })), h("div", { class: "typing-text" }, h("span", null, this.typingProgressMessage || this.translationManager.get('status.typing', this.typingIndicatorText)), h("span", { class: "typing-dots loading" }))))), this.messages.length === 0 && this.getStarterQuestions().length > 0 && (h("div", { class: "starter-questions" }, this.getStarterQuestions().map((question, index) => (h("div", { key: `starter-${index}`, class: "starter-question-row" }, h("button", { class: "starter-question", onClick: () => this.handleStarterQuestionClick(question) }, question)))))), this.allowAttachments && this.selectedFiles.length > 0 && (h("div", { class: "selected-files-container" }, h("div", { class: "space-y-[0.25em]" }, this.selectedFiles.map((selectedFile, index) => (h("div", { key: index, class: "selected-file-item" }, h("div", { class: "flex items-center gap-[0.5em]" }, h("span", { class: "selected-file-icon" }, h(PaperClipIcon, null)), h("span", null, selectedFile.file.name), h("span", { class: "selected-file-size" }, "(", this.formatFileSize(selectedFile.file.size), ")"), selectedFile.error && h("span", { class: "selected-file-error" }, selectedFile.error), selectedFile.uploaded && (h("span", { class: "selected-file-success-icon" }, h(CheckDocumentIcon, null)))), h("button", { onClick: () => this.removeSelectedFile(index), class: "selected-file-remove-button", "aria-label": this.translationManager.get('attach.remove') }, h(XIcon, null)))))))), h("div", { class: "input-area" }, h("div", { class: "input-container" }, h("textarea", { ref: el => (this.textareaRef = el), class: "message-textarea", rows: 1, placeholder: this.translationManager.get('composer.placeholder'), value: this.messageInput, onInput: e => this.handleInputChange(e), onKeyPress: e => this.handleKeyPress(e), disabled: this.isTyping || this.isUploadingFiles || this.isLoading }), this.allowAttachments && (h("input", { ref: el => {
|
|
1119
1273
|
// Unclear why but after removing all attachments this is being set to `null`.
|
|
1120
1274
|
if (el) {
|
|
1121
1275
|
this.fileInputRef = el;
|
|
1122
1276
|
}
|
|
1123
|
-
}, id: "ocs-file-input", type: "file", multiple: true, accept: OcsChat.SUPPORTED_FILE_EXTENSIONS.join(',') + ',text/*', onChange:
|
|
1124
|
-
? 'send-button-enabled'
|
|
1125
|
-
: 'send-button-disabled'}`, onClick: () => this.sendMessage(this.messageInput), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || !this.messageInput.trim() }, this.isUploadingFiles ? `${this.translationManager.get('status.uploading')}...` : this.translationManager.get('composer.send')))), h("div", { class: "flex items-center justify-center text-[0.8em] font-light w-full text-slate-500 py-[2px]" }, h("p", null, this.translationManager.get('branding.poweredBy'), ' ', " ", h("a", { class: "underline", href: "https://www.dimagi.com", target: "_blank" }, "Dimagi"))))))));
|
|
1277
|
+
}, id: "ocs-file-input", type: "file", multiple: true, accept: OcsChat.SUPPORTED_FILE_EXTENSIONS.join(',') + ',text/*', onChange: e => this.handleFileSelect(e), class: "hidden" })), this.allowAttachments && (h("button", { class: "file-attachment-button", onClick: () => { var _a; return (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click(); }, disabled: this.isTyping || this.isUploadingFiles || this.isLoading, title: this.translationManager.get('attach.add'), "aria-label": this.translationManager.get('attach.add') }, h(PaperClipIcon, null))), h("button", { class: `send-button ${!this.isTyping && !this.isLoading && !!this.messageInput.trim() ? 'send-button-enabled' : 'send-button-disabled'}`, onClick: () => this.sendMessage(this.messageInput), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || !this.messageInput.trim() }, this.isUploadingFiles ? `${this.translationManager.get('status.uploading')}...` : this.translationManager.get('composer.send')))), h("div", { class: "flex items-center justify-center text-[0.8em] font-light w-full text-slate-500 py-[2px]" }, h("p", null, this.translationManager.get('branding.poweredBy'), ' ', h("a", { class: "underline", href: "https://www.dimagi.com", target: "_blank", rel: "noopener noreferrer" }, "Dimagi"))))))));
|
|
1126
1278
|
}
|
|
1127
1279
|
static get is() { return "open-chat-studio-widget"; }
|
|
1128
1280
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1175,7 +1327,7 @@ export class OcsChat {
|
|
|
1175
1327
|
"getter": false,
|
|
1176
1328
|
"setter": false,
|
|
1177
1329
|
"reflect": false,
|
|
1178
|
-
"defaultValue": "
|
|
1330
|
+
"defaultValue": "'https://www.openchatstudio.com'"
|
|
1179
1331
|
},
|
|
1180
1332
|
"buttonText": {
|
|
1181
1333
|
"type": "string",
|
|
@@ -1254,6 +1406,46 @@ export class OcsChat {
|
|
|
1254
1406
|
"reflect": false,
|
|
1255
1407
|
"defaultValue": "'square'"
|
|
1256
1408
|
},
|
|
1409
|
+
"showButton": {
|
|
1410
|
+
"type": "boolean",
|
|
1411
|
+
"attribute": "show-button",
|
|
1412
|
+
"mutable": false,
|
|
1413
|
+
"complexType": {
|
|
1414
|
+
"original": "boolean",
|
|
1415
|
+
"resolved": "boolean",
|
|
1416
|
+
"references": {}
|
|
1417
|
+
},
|
|
1418
|
+
"required": false,
|
|
1419
|
+
"optional": false,
|
|
1420
|
+
"docs": {
|
|
1421
|
+
"tags": [],
|
|
1422
|
+
"text": "Whether to show the launcher button. Set to false to hide the button\nand open the chat window programmatically via the `visible` property."
|
|
1423
|
+
},
|
|
1424
|
+
"getter": false,
|
|
1425
|
+
"setter": false,
|
|
1426
|
+
"reflect": false,
|
|
1427
|
+
"defaultValue": "true"
|
|
1428
|
+
},
|
|
1429
|
+
"mode": {
|
|
1430
|
+
"type": "string",
|
|
1431
|
+
"attribute": "mode",
|
|
1432
|
+
"mutable": false,
|
|
1433
|
+
"complexType": {
|
|
1434
|
+
"original": "'standard' | 'kiosk'",
|
|
1435
|
+
"resolved": "\"kiosk\" | \"standard\"",
|
|
1436
|
+
"references": {}
|
|
1437
|
+
},
|
|
1438
|
+
"required": false,
|
|
1439
|
+
"optional": false,
|
|
1440
|
+
"docs": {
|
|
1441
|
+
"tags": [],
|
|
1442
|
+
"text": "The operating mode of the widget.\n- 'standard': Default floating window with launcher button.\n- 'kiosk': Fills parent container, always visible, no header or launcher button.\n The parent element must establish a containing block (e.g. `position: relative`)."
|
|
1443
|
+
},
|
|
1444
|
+
"getter": false,
|
|
1445
|
+
"setter": false,
|
|
1446
|
+
"reflect": false,
|
|
1447
|
+
"defaultValue": "'standard'"
|
|
1448
|
+
},
|
|
1257
1449
|
"headerText": {
|
|
1258
1450
|
"type": "string",
|
|
1259
1451
|
"attribute": "header-text",
|
|
@@ -1421,7 +1613,7 @@ export class OcsChat {
|
|
|
1421
1613
|
"optional": false,
|
|
1422
1614
|
"docs": {
|
|
1423
1615
|
"tags": [],
|
|
1424
|
-
"text": "Whether to persist session data to local storage to allow resuming previous conversations after page reload."
|
|
1616
|
+
"text": "Whether to persist session data to local storage to allow resuming previous conversations after page reload.\nIgnored when `sessionId` is provided."
|
|
1425
1617
|
},
|
|
1426
1618
|
"getter": false,
|
|
1427
1619
|
"setter": false,
|
|
@@ -1567,6 +1759,47 @@ export class OcsChat {
|
|
|
1567
1759
|
},
|
|
1568
1760
|
"getter": false,
|
|
1569
1761
|
"setter": false
|
|
1762
|
+
},
|
|
1763
|
+
"versionNumber": {
|
|
1764
|
+
"type": "number",
|
|
1765
|
+
"attribute": "version-number",
|
|
1766
|
+
"mutable": false,
|
|
1767
|
+
"complexType": {
|
|
1768
|
+
"original": "number",
|
|
1769
|
+
"resolved": "number",
|
|
1770
|
+
"references": {}
|
|
1771
|
+
},
|
|
1772
|
+
"required": false,
|
|
1773
|
+
"optional": true,
|
|
1774
|
+
"docs": {
|
|
1775
|
+
"tags": [{
|
|
1776
|
+
"name": "internal",
|
|
1777
|
+
"text": "Optional version number of the chatbot to use. Requires authentication.\nThis is for internal use only and is not intended for public-facing widgets."
|
|
1778
|
+
}],
|
|
1779
|
+
"text": ""
|
|
1780
|
+
},
|
|
1781
|
+
"getter": false,
|
|
1782
|
+
"setter": false,
|
|
1783
|
+
"reflect": false
|
|
1784
|
+
},
|
|
1785
|
+
"sessionId": {
|
|
1786
|
+
"type": "string",
|
|
1787
|
+
"attribute": "session-id",
|
|
1788
|
+
"mutable": false,
|
|
1789
|
+
"complexType": {
|
|
1790
|
+
"original": "string",
|
|
1791
|
+
"resolved": "string",
|
|
1792
|
+
"references": {}
|
|
1793
|
+
},
|
|
1794
|
+
"required": false,
|
|
1795
|
+
"optional": true,
|
|
1796
|
+
"docs": {
|
|
1797
|
+
"tags": [],
|
|
1798
|
+
"text": "The ID of an existing chat session to connect to. When provided, the widget\nis bound to that session: local session persistence is disabled and the\nmessage history is loaded from the server. Intended for host pages that\ncreate the session server-side (e.g. the OCS web chat page)."
|
|
1799
|
+
},
|
|
1800
|
+
"getter": false,
|
|
1801
|
+
"setter": false,
|
|
1802
|
+
"reflect": false
|
|
1570
1803
|
}
|
|
1571
1804
|
};
|
|
1572
1805
|
}
|
|
@@ -1574,9 +1807,10 @@ export class OcsChat {
|
|
|
1574
1807
|
return {
|
|
1575
1808
|
"error": {},
|
|
1576
1809
|
"messages": {},
|
|
1577
|
-
"
|
|
1810
|
+
"activeSessionId": {},
|
|
1578
1811
|
"isLoading": {},
|
|
1579
1812
|
"isTyping": {},
|
|
1813
|
+
"typingProgressMessage": {},
|
|
1580
1814
|
"messageInput": {},
|
|
1581
1815
|
"currentPollTaskId": {},
|
|
1582
1816
|
"isDragging": {},
|
|
@@ -1599,6 +1833,12 @@ export class OcsChat {
|
|
|
1599
1833
|
return [{
|
|
1600
1834
|
"propName": "pageContext",
|
|
1601
1835
|
"methodName": "pageContextHandler"
|
|
1836
|
+
}, {
|
|
1837
|
+
"propName": "chatbotId",
|
|
1838
|
+
"methodName": "chatbotConfigHandler"
|
|
1839
|
+
}, {
|
|
1840
|
+
"propName": "versionNumber",
|
|
1841
|
+
"methodName": "chatbotConfigHandler"
|
|
1602
1842
|
}, {
|
|
1603
1843
|
"propName": "visible",
|
|
1604
1844
|
"methodName": "visibilityHandler"
|
|
@@ -1615,7 +1855,39 @@ OcsChat.WINDOW_MARGIN = 20;
|
|
|
1615
1855
|
OcsChat.LOCALSTORAGE_TEST_KEY = '__ocs_test__';
|
|
1616
1856
|
OcsChat.MAX_FILE_SIZE_MB = 50;
|
|
1617
1857
|
OcsChat.MAX_TOTAL_SIZE_MB = 50;
|
|
1618
|
-
OcsChat.SUPPORTED_FILE_EXTENSIONS = [
|
|
1619
|
-
'.
|
|
1620
|
-
'.
|
|
1858
|
+
OcsChat.SUPPORTED_FILE_EXTENSIONS = [
|
|
1859
|
+
'.txt',
|
|
1860
|
+
'.pdf',
|
|
1861
|
+
'.doc',
|
|
1862
|
+
'.docx',
|
|
1863
|
+
'.xls',
|
|
1864
|
+
'.xlsx',
|
|
1865
|
+
'.csv',
|
|
1866
|
+
'.jpg',
|
|
1867
|
+
'.jpeg',
|
|
1868
|
+
'.png',
|
|
1869
|
+
'.gif',
|
|
1870
|
+
'.bmp',
|
|
1871
|
+
'.webp',
|
|
1872
|
+
'.svg',
|
|
1873
|
+
'.mp4',
|
|
1874
|
+
'.mov',
|
|
1875
|
+
'.avi',
|
|
1876
|
+
'.mp3',
|
|
1877
|
+
'.wav',
|
|
1878
|
+
'.html',
|
|
1879
|
+
'.htm',
|
|
1880
|
+
'.css',
|
|
1881
|
+
'.js',
|
|
1882
|
+
'.xml',
|
|
1883
|
+
'.md',
|
|
1884
|
+
'.ics',
|
|
1885
|
+
'.vcf',
|
|
1886
|
+
'.rtf',
|
|
1887
|
+
'.tsv',
|
|
1888
|
+
'.yaml',
|
|
1889
|
+
'.yml',
|
|
1890
|
+
'.py',
|
|
1891
|
+
'.c',
|
|
1892
|
+
];
|
|
1621
1893
|
//# sourceMappingURL=ocs-chat.js.map
|