open-chat-studio-widget 0.7.0 → 0.9.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 -26
- package/dist/cjs/{index-CvB341El.js → index-DDod9Zyw.js} +3 -3
- package/dist/cjs/{index-CvB341El.js.map → index-DDod9Zyw.js.map} +1 -1
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/open-chat-studio-widget.cjs.entry.js +282 -49
- 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/ocs-chat.js +176 -29
- package/dist/collection/components/ocs-chat/ocs-chat.js.map +1 -1
- package/dist/collection/services/chat-session-service.js +112 -8
- package/dist/collection/services/chat-session-service.js.map +1 -1
- package/dist/collection/services/file-attachment-manager.js +6 -0
- package/dist/collection/services/file-attachment-manager.js.map +1 -1
- package/dist/components/open-chat-studio-widget.js +284 -49
- package/dist/components/open-chat-studio-widget.js.map +1 -1
- package/dist/esm/{index-C2QZK0Ui.js → index-iUBQH9om.js} +3 -3
- package/dist/esm/{index-C2QZK0Ui.js.map → index-iUBQH9om.js.map} +1 -1
- package/dist/esm/loader.js +3 -3
- package/dist/esm/open-chat-studio-widget.entry.js +282 -49
- 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-9c925476.entry.js +4 -0
- package/dist/open-chat-studio-widget/p-9c925476.entry.js.map +1 -0
- package/dist/open-chat-studio-widget/{p-C2QZK0Ui.js → p-iUBQH9om.js} +2 -2
- package/dist/open-chat-studio-widget/{p-C2QZK0Ui.js.map → p-iUBQH9om.js.map} +1 -1
- package/dist/types/components/ocs-chat/ocs-chat.d.ts +29 -1
- package/dist/types/components.d.ts +18 -2
- package/dist/types/services/chat-session-service.d.ts +27 -0
- package/dist/types/services/file-attachment-manager.d.ts +2 -0
- package/package.json +1 -1
- package/dist/open-chat-studio-widget/p-e87d4e31.entry.js +0 -4
- package/dist/open-chat-studio-widget/p-e87d4e31.entry.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { XMarkIcon, GripDotsVerticalIcon, PlusWithCircleIcon, ArrowsPointingOutI
|
|
|
4
4
|
import { renderMarkdownSync as renderMarkdownComplete } from "../../utils/markdown";
|
|
5
5
|
import { varToPixels } from "../../utils/utils";
|
|
6
6
|
import { TranslationManager, defaultTranslations } from "../../utils/translations";
|
|
7
|
-
import { ChatSessionService } from "../../services/chat-session-service";
|
|
7
|
+
import { ChatSessionService, SessionAccessError } from "../../services/chat-session-service";
|
|
8
8
|
import { FileAttachmentManager } from "../../services/file-attachment-manager";
|
|
9
9
|
export class OcsChat {
|
|
10
10
|
constructor() {
|
|
@@ -38,6 +38,7 @@ export class OcsChat {
|
|
|
38
38
|
this.position = 'right';
|
|
39
39
|
/**
|
|
40
40
|
* Whether to persist session data to local storage to allow resuming previous conversations after page reload.
|
|
41
|
+
* Ignored when `sessionId` is provided.
|
|
41
42
|
*/
|
|
42
43
|
this.persistentSession = true;
|
|
43
44
|
/**
|
|
@@ -233,12 +234,18 @@ export class OcsChat {
|
|
|
233
234
|
this.visible = true;
|
|
234
235
|
}
|
|
235
236
|
await this.initializeTranslations();
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
+
this.applySessionToken(this.sessionToken);
|
|
241
|
+
}
|
|
242
|
+
else if (this.persistentSession && this.isLocalStorageAvailable()) {
|
|
243
|
+
// Always try to load existing session if localStorage is available
|
|
244
|
+
const { sessionId, messages, sessionToken } = this.loadSessionFromStorage();
|
|
239
245
|
if (sessionId && messages) {
|
|
240
|
-
this.
|
|
246
|
+
this.activeSessionId = sessionId;
|
|
241
247
|
this.messages = messages;
|
|
248
|
+
this.applySessionToken(sessionToken);
|
|
242
249
|
}
|
|
243
250
|
}
|
|
244
251
|
this.parseWelcomeMessages();
|
|
@@ -270,8 +277,13 @@ export class OcsChat {
|
|
|
270
277
|
}
|
|
271
278
|
}
|
|
272
279
|
// Resume polling for existing session (don't auto-start new sessions)
|
|
273
|
-
if (this.visible && this.
|
|
274
|
-
this.
|
|
280
|
+
if (this.visible && this.activeSessionId) {
|
|
281
|
+
if (this.isSessionBound()) {
|
|
282
|
+
void this.loadBoundSessionHistory();
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
this.startMessagePolling();
|
|
286
|
+
}
|
|
275
287
|
}
|
|
276
288
|
}, 0);
|
|
277
289
|
window.addEventListener('resize', this.handleWindowResize);
|
|
@@ -282,6 +294,11 @@ export class OcsChat {
|
|
|
282
294
|
this.removeButtonEventListeners();
|
|
283
295
|
window.removeEventListener('resize', this.handleWindowResize);
|
|
284
296
|
}
|
|
297
|
+
applySessionToken(token) {
|
|
298
|
+
var _a;
|
|
299
|
+
this.currentSessionToken = token;
|
|
300
|
+
(_a = this.chatService) === null || _a === void 0 ? void 0 : _a.setSessionToken(token);
|
|
301
|
+
}
|
|
285
302
|
getChatService() {
|
|
286
303
|
if (!this.chatService) {
|
|
287
304
|
this.chatService = new ChatSessionService({
|
|
@@ -291,6 +308,7 @@ export class OcsChat {
|
|
|
291
308
|
taskPollingIntervalMs: OcsChat.TASK_POLLING_INTERVAL_MS,
|
|
292
309
|
taskPollingMaxAttempts: OcsChat.TASK_POLLING_MAX_ATTEMPTS,
|
|
293
310
|
messagePollingIntervalMs: OcsChat.MESSAGE_POLLING_INTERVAL_MS,
|
|
311
|
+
sessionToken: this.currentSessionToken,
|
|
294
312
|
});
|
|
295
313
|
}
|
|
296
314
|
return this.chatService;
|
|
@@ -306,6 +324,27 @@ export class OcsChat {
|
|
|
306
324
|
this.saveSessionToStorage();
|
|
307
325
|
this.scrollToBottom();
|
|
308
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Recover from a rejected session token (403). Unbound widgets discard the
|
|
329
|
+
* dead session/token, show a notice, and start fresh on the next send; bound
|
|
330
|
+
* widgets cannot restart a host-owned session, so they surface an error.
|
|
331
|
+
*/
|
|
332
|
+
handleSessionAccessError() {
|
|
333
|
+
this.cleanup();
|
|
334
|
+
this.isLoading = false;
|
|
335
|
+
this.isTyping = false;
|
|
336
|
+
this.isUploadingFiles = false;
|
|
337
|
+
this.typingProgressMessage = '';
|
|
338
|
+
if (this.isSessionBound()) {
|
|
339
|
+
this.addErrorMessage(this.translationManager.get('status.sessionError', 'This chat session is no longer available.'));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
this.sessionEpoch += 1;
|
|
343
|
+
this.activeSessionId = undefined;
|
|
344
|
+
this.applySessionToken(undefined);
|
|
345
|
+
this.clearSessionStorage();
|
|
346
|
+
this.addErrorMessage(this.translationManager.get('status.sessionExpired', 'Your chat session expired. Starting a new chat — please resend your message.'));
|
|
347
|
+
}
|
|
309
348
|
handleError(errorText) {
|
|
310
349
|
// show as system message
|
|
311
350
|
this.addErrorMessage(errorText);
|
|
@@ -378,12 +417,14 @@ export class OcsChat {
|
|
|
378
417
|
this.currentPollTaskId = '';
|
|
379
418
|
}
|
|
380
419
|
async startSession() {
|
|
420
|
+
var _a;
|
|
381
421
|
const epoch = this.sessionEpoch;
|
|
382
422
|
try {
|
|
383
423
|
this.isLoading = true;
|
|
384
424
|
const userId = this.getOrGenerateUserId();
|
|
385
425
|
const requestBody = {
|
|
386
426
|
chatbot_id: this.chatbotId,
|
|
427
|
+
use_session_token: true,
|
|
387
428
|
session_data: {
|
|
388
429
|
source: 'widget',
|
|
389
430
|
page_url: window.location.href,
|
|
@@ -399,7 +440,8 @@ export class OcsChat {
|
|
|
399
440
|
const data = await this.getChatService().startSession(requestBody);
|
|
400
441
|
if (epoch !== this.sessionEpoch)
|
|
401
442
|
return;
|
|
402
|
-
this.
|
|
443
|
+
this.activeSessionId = data.session_id;
|
|
444
|
+
this.applySessionToken((_a = data.session_token) !== null && _a !== void 0 ? _a : undefined);
|
|
403
445
|
this.saveSessionToStorage();
|
|
404
446
|
this.startMessagePolling();
|
|
405
447
|
}
|
|
@@ -412,19 +454,51 @@ export class OcsChat {
|
|
|
412
454
|
this.isLoading = false;
|
|
413
455
|
}
|
|
414
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Load the full message history for a session provided via the `session-id`
|
|
459
|
+
* prop, then begin regular polling.
|
|
460
|
+
*/
|
|
461
|
+
async loadBoundSessionHistory() {
|
|
462
|
+
const epoch = this.sessionEpoch;
|
|
463
|
+
try {
|
|
464
|
+
const history = await this.getChatService().fetchAllMessages(this.activeSessionId);
|
|
465
|
+
if (epoch !== this.sessionEpoch)
|
|
466
|
+
return;
|
|
467
|
+
// Keep messages added while the history was loading (e.g. an optimistic
|
|
468
|
+
// user message) by appending any that aren't part of the fetched history.
|
|
469
|
+
const known = new Set(history.map(m => `${m.created_at}|${m.role}|${m.content}`));
|
|
470
|
+
const pending = this.messages.filter(m => !known.has(`${m.created_at}|${m.role}|${m.content}`));
|
|
471
|
+
this.messages = [...history, ...pending];
|
|
472
|
+
this.scrollToBottom(true);
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
if (epoch !== this.sessionEpoch)
|
|
476
|
+
return;
|
|
477
|
+
if (error instanceof SessionAccessError) {
|
|
478
|
+
this.handleSessionAccessError();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
console.warn('Failed to load chat history:', error);
|
|
482
|
+
}
|
|
483
|
+
this.startMessagePolling();
|
|
484
|
+
}
|
|
415
485
|
async uploadFiles() {
|
|
416
|
-
if (this.selectedFiles.length === 0 || !this.
|
|
486
|
+
if (this.selectedFiles.length === 0 || !this.activeSessionId || !this.allowAttachments) {
|
|
417
487
|
return [];
|
|
418
488
|
}
|
|
419
489
|
this.isUploadingFiles = true;
|
|
420
490
|
try {
|
|
421
491
|
const uploadResult = await this.attachmentManager.uploadPendingFiles(this.selectedFiles, {
|
|
422
492
|
apiBaseUrl: this.apiBaseUrl || 'https://www.openchatstudio.com',
|
|
423
|
-
sessionId: this.
|
|
493
|
+
sessionId: this.activeSessionId,
|
|
424
494
|
participantId: this.getOrGenerateUserId(),
|
|
425
495
|
participantName: this.userName,
|
|
496
|
+
sessionToken: this.currentSessionToken,
|
|
426
497
|
});
|
|
427
498
|
this.selectedFiles = uploadResult.selectedFiles;
|
|
499
|
+
if (uploadResult.tokenRejected) {
|
|
500
|
+
throw new SessionAccessError(403, 'session_token_required', uploadResult.errorMessage || 'Session token rejected');
|
|
501
|
+
}
|
|
428
502
|
return uploadResult.uploadedIds;
|
|
429
503
|
}
|
|
430
504
|
finally {
|
|
@@ -436,14 +510,14 @@ export class OcsChat {
|
|
|
436
510
|
return;
|
|
437
511
|
const epoch = this.sessionEpoch;
|
|
438
512
|
// Start session if we don't have one yet
|
|
439
|
-
if (!this.
|
|
513
|
+
if (!this.activeSessionId) {
|
|
440
514
|
// Prevent concurrent session initialization
|
|
441
515
|
if (this.isLoading) {
|
|
442
516
|
return;
|
|
443
517
|
}
|
|
444
518
|
await this.startSession();
|
|
445
519
|
// Check if session started successfully
|
|
446
|
-
if (!this.
|
|
520
|
+
if (!this.activeSessionId) {
|
|
447
521
|
return; // startSession already handled the error
|
|
448
522
|
}
|
|
449
523
|
}
|
|
@@ -504,7 +578,7 @@ export class OcsChat {
|
|
|
504
578
|
if (this.versionNumber != null) {
|
|
505
579
|
requestBody.version_number = this.versionNumber;
|
|
506
580
|
}
|
|
507
|
-
const data = await this.getChatService().sendMessage(this.
|
|
581
|
+
const data = await this.getChatService().sendMessage(this.activeSessionId, requestBody);
|
|
508
582
|
if (epoch !== this.sessionEpoch)
|
|
509
583
|
return;
|
|
510
584
|
if (data.status === 'error') {
|
|
@@ -516,6 +590,10 @@ export class OcsChat {
|
|
|
516
590
|
catch (error) {
|
|
517
591
|
if (epoch !== this.sessionEpoch)
|
|
518
592
|
return;
|
|
593
|
+
if (error instanceof SessionAccessError) {
|
|
594
|
+
this.handleSessionAccessError();
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
519
597
|
const errorText = error instanceof Error ? error.message : 'Failed to send message';
|
|
520
598
|
this.handleError(errorText);
|
|
521
599
|
}
|
|
@@ -632,9 +710,15 @@ export class OcsChat {
|
|
|
632
710
|
this.initializePosition();
|
|
633
711
|
}
|
|
634
712
|
// Resume polling for existing session (don't auto-start new sessions)
|
|
635
|
-
if (this.
|
|
713
|
+
if (this.activeSessionId) {
|
|
636
714
|
this.scrollToBottom(true);
|
|
637
|
-
this.
|
|
715
|
+
if (this.isSessionBound() && this.messages.length === 0) {
|
|
716
|
+
// A bound widget that was hidden at load has not fetched its history yet.
|
|
717
|
+
void this.loadBoundSessionHistory();
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
this.startMessagePolling();
|
|
721
|
+
}
|
|
638
722
|
}
|
|
639
723
|
}
|
|
640
724
|
else {
|
|
@@ -642,7 +726,7 @@ export class OcsChat {
|
|
|
642
726
|
}
|
|
643
727
|
}
|
|
644
728
|
startTaskPolling(taskId) {
|
|
645
|
-
if (!this.
|
|
729
|
+
if (!this.activeSessionId)
|
|
646
730
|
return;
|
|
647
731
|
this.currentPollTaskId = taskId;
|
|
648
732
|
this.isTyping = true;
|
|
@@ -650,7 +734,7 @@ export class OcsChat {
|
|
|
650
734
|
if (this.taskPollingHandle) {
|
|
651
735
|
this.taskPollingHandle.cancel();
|
|
652
736
|
}
|
|
653
|
-
this.taskPollingHandle = this.getChatService().pollTask(this.
|
|
737
|
+
this.taskPollingHandle = this.getChatService().pollTask(this.activeSessionId, taskId, {
|
|
654
738
|
onMessage: message => {
|
|
655
739
|
this.messages = [...this.messages, message];
|
|
656
740
|
this.saveSessionToStorage();
|
|
@@ -684,20 +768,24 @@ export class OcsChat {
|
|
|
684
768
|
},
|
|
685
769
|
onError: error => {
|
|
686
770
|
this.typingProgressMessage = '';
|
|
687
|
-
this.handleError(error.message);
|
|
688
771
|
this.taskPollingHandle = undefined;
|
|
772
|
+
if (error instanceof SessionAccessError) {
|
|
773
|
+
this.handleSessionAccessError();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
this.handleError(error.message);
|
|
689
777
|
this.startMessagePolling();
|
|
690
778
|
},
|
|
691
779
|
});
|
|
692
780
|
}
|
|
693
781
|
startMessagePolling() {
|
|
694
|
-
if (!this.
|
|
782
|
+
if (!this.activeSessionId || this.currentPollTaskId || !this.visible) {
|
|
695
783
|
return;
|
|
696
784
|
}
|
|
697
785
|
if (this.messagePollingHandle) {
|
|
698
786
|
return;
|
|
699
787
|
}
|
|
700
|
-
this.messagePollingHandle = this.getChatService().startMessagePolling(this.
|
|
788
|
+
this.messagePollingHandle = this.getChatService().startMessagePolling(this.activeSessionId, {
|
|
701
789
|
getSince: () => { var _a; return (this.messages.length > 0 ? (_a = this.messages.at(-1)) === null || _a === void 0 ? void 0 : _a.created_at : undefined); },
|
|
702
790
|
onMessages: messages => {
|
|
703
791
|
if (messages.length === 0)
|
|
@@ -1047,17 +1135,24 @@ export class OcsChat {
|
|
|
1047
1135
|
messages: `ocs-chat-messages-${this.chatbotId}`,
|
|
1048
1136
|
lastActivity: `ocs-chat-activity-${this.chatbotId}`,
|
|
1049
1137
|
visible: `ocs-chat-visible-${this.chatbotId}`,
|
|
1138
|
+
sessionToken: `ocs-chat-token-${this.chatbotId}`,
|
|
1050
1139
|
};
|
|
1051
1140
|
}
|
|
1052
1141
|
saveSessionToStorage() {
|
|
1053
|
-
if (!this.persistentSession) {
|
|
1142
|
+
if (!this.persistentSession || this.isSessionBound()) {
|
|
1054
1143
|
return;
|
|
1055
1144
|
}
|
|
1056
1145
|
const keys = this.getStorageKeys();
|
|
1057
1146
|
try {
|
|
1058
|
-
if (this.
|
|
1059
|
-
localStorage.setItem(keys.sessionId, this.
|
|
1147
|
+
if (this.activeSessionId) {
|
|
1148
|
+
localStorage.setItem(keys.sessionId, this.activeSessionId);
|
|
1060
1149
|
localStorage.setItem(keys.lastActivity, new Date().toISOString());
|
|
1150
|
+
if (this.currentSessionToken) {
|
|
1151
|
+
localStorage.setItem(keys.sessionToken, this.currentSessionToken);
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
localStorage.removeItem(keys.sessionToken);
|
|
1155
|
+
}
|
|
1061
1156
|
}
|
|
1062
1157
|
localStorage.setItem(keys.messages, JSON.stringify(this.messages));
|
|
1063
1158
|
}
|
|
@@ -1066,6 +1161,7 @@ export class OcsChat {
|
|
|
1066
1161
|
}
|
|
1067
1162
|
}
|
|
1068
1163
|
loadSessionFromStorage() {
|
|
1164
|
+
var _a;
|
|
1069
1165
|
const keys = this.getStorageKeys();
|
|
1070
1166
|
try {
|
|
1071
1167
|
if (this.persistentSessionExpire > 0) {
|
|
@@ -1093,7 +1189,8 @@ export class OcsChat {
|
|
|
1093
1189
|
messages = [];
|
|
1094
1190
|
}
|
|
1095
1191
|
}
|
|
1096
|
-
|
|
1192
|
+
const sessionToken = (_a = localStorage.getItem(keys.sessionToken)) !== null && _a !== void 0 ? _a : undefined;
|
|
1193
|
+
return { sessionId, messages, sessionToken };
|
|
1097
1194
|
}
|
|
1098
1195
|
catch (error) {
|
|
1099
1196
|
// fall back to starting a new session
|
|
@@ -1165,6 +1262,7 @@ export class OcsChat {
|
|
|
1165
1262
|
localStorage.removeItem(keys.messages);
|
|
1166
1263
|
localStorage.removeItem(keys.lastActivity);
|
|
1167
1264
|
localStorage.removeItem(keys.visible);
|
|
1265
|
+
localStorage.removeItem(keys.sessionToken);
|
|
1168
1266
|
}
|
|
1169
1267
|
catch (error) {
|
|
1170
1268
|
console.warn('Failed to clear chat session from localStorage:', error);
|
|
@@ -1173,6 +1271,9 @@ export class OcsChat {
|
|
|
1173
1271
|
isKioskMode() {
|
|
1174
1272
|
return this.mode === 'kiosk';
|
|
1175
1273
|
}
|
|
1274
|
+
isSessionBound() {
|
|
1275
|
+
return !!this.sessionId;
|
|
1276
|
+
}
|
|
1176
1277
|
isLocalStorageAvailable() {
|
|
1177
1278
|
try {
|
|
1178
1279
|
localStorage.setItem(OcsChat.LOCALSTORAGE_TEST_KEY, 'test');
|
|
@@ -1200,7 +1301,10 @@ export class OcsChat {
|
|
|
1200
1301
|
async clearSession() {
|
|
1201
1302
|
this.sessionEpoch += 1;
|
|
1202
1303
|
this.clearSessionStorage();
|
|
1203
|
-
|
|
1304
|
+
// A session provided by the host page (session-id prop) cannot be cleared;
|
|
1305
|
+
// stay bound to it. Unbound widgets start a new session on the next message.
|
|
1306
|
+
this.activeSessionId = this.sessionId;
|
|
1307
|
+
this.applySessionToken(this.isSessionBound() ? this.sessionToken : undefined);
|
|
1204
1308
|
this.messages = [];
|
|
1205
1309
|
this.isTyping = false;
|
|
1206
1310
|
this.currentPollTaskId = '';
|
|
@@ -1208,6 +1312,11 @@ export class OcsChat {
|
|
|
1208
1312
|
this.selectedFiles = [];
|
|
1209
1313
|
}
|
|
1210
1314
|
this.cleanup();
|
|
1315
|
+
if (this.isSessionBound()) {
|
|
1316
|
+
// The host-owned session cannot be cleared: reload its history and
|
|
1317
|
+
// resume polling so the widget doesn't end up in a dead state.
|
|
1318
|
+
void this.loadBoundSessionHistory();
|
|
1319
|
+
}
|
|
1211
1320
|
}
|
|
1212
1321
|
toggleFullscreen() {
|
|
1213
1322
|
this.isFullscreen = !this.isFullscreen;
|
|
@@ -1216,10 +1325,10 @@ export class OcsChat {
|
|
|
1216
1325
|
}
|
|
1217
1326
|
render() {
|
|
1218
1327
|
// Only show error state for critical errors that prevent the widget from functioning
|
|
1219
|
-
if (this.error && !this.
|
|
1328
|
+
if (this.error && !this.activeSessionId) {
|
|
1220
1329
|
return (h(Host, null, h("p", { class: "error-message" }, this.error)));
|
|
1221
1330
|
}
|
|
1222
|
-
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 && (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.
|
|
1331
|
+
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 => {
|
|
1223
1332
|
// Unclear why but after removing all attachments this is being set to `null`.
|
|
1224
1333
|
if (el) {
|
|
1225
1334
|
this.fileInputRef = el;
|
|
@@ -1563,7 +1672,7 @@ export class OcsChat {
|
|
|
1563
1672
|
"optional": false,
|
|
1564
1673
|
"docs": {
|
|
1565
1674
|
"tags": [],
|
|
1566
|
-
"text": "Whether to persist session data to local storage to allow resuming previous conversations after page reload."
|
|
1675
|
+
"text": "Whether to persist session data to local storage to allow resuming previous conversations after page reload.\nIgnored when `sessionId` is provided."
|
|
1567
1676
|
},
|
|
1568
1677
|
"getter": false,
|
|
1569
1678
|
"setter": false,
|
|
@@ -1731,6 +1840,44 @@ export class OcsChat {
|
|
|
1731
1840
|
"getter": false,
|
|
1732
1841
|
"setter": false,
|
|
1733
1842
|
"reflect": false
|
|
1843
|
+
},
|
|
1844
|
+
"sessionId": {
|
|
1845
|
+
"type": "string",
|
|
1846
|
+
"attribute": "session-id",
|
|
1847
|
+
"mutable": false,
|
|
1848
|
+
"complexType": {
|
|
1849
|
+
"original": "string",
|
|
1850
|
+
"resolved": "string",
|
|
1851
|
+
"references": {}
|
|
1852
|
+
},
|
|
1853
|
+
"required": false,
|
|
1854
|
+
"optional": true,
|
|
1855
|
+
"docs": {
|
|
1856
|
+
"tags": [],
|
|
1857
|
+
"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)."
|
|
1858
|
+
},
|
|
1859
|
+
"getter": false,
|
|
1860
|
+
"setter": false,
|
|
1861
|
+
"reflect": false
|
|
1862
|
+
},
|
|
1863
|
+
"sessionToken": {
|
|
1864
|
+
"type": "string",
|
|
1865
|
+
"attribute": "session-token",
|
|
1866
|
+
"mutable": false,
|
|
1867
|
+
"complexType": {
|
|
1868
|
+
"original": "string",
|
|
1869
|
+
"resolved": "string",
|
|
1870
|
+
"references": {}
|
|
1871
|
+
},
|
|
1872
|
+
"required": false,
|
|
1873
|
+
"optional": true,
|
|
1874
|
+
"docs": {
|
|
1875
|
+
"tags": [],
|
|
1876
|
+
"text": "A session token proving access to the session named by `session-id`. Host\npages that create the session server-side pass a server-minted token here so\nthe widget can authenticate its requests. Only meaningful with `session-id`."
|
|
1877
|
+
},
|
|
1878
|
+
"getter": false,
|
|
1879
|
+
"setter": false,
|
|
1880
|
+
"reflect": false
|
|
1734
1881
|
}
|
|
1735
1882
|
};
|
|
1736
1883
|
}
|
|
@@ -1738,7 +1885,7 @@ export class OcsChat {
|
|
|
1738
1885
|
return {
|
|
1739
1886
|
"error": {},
|
|
1740
1887
|
"messages": {},
|
|
1741
|
-
"
|
|
1888
|
+
"activeSessionId": {},
|
|
1742
1889
|
"isLoading": {},
|
|
1743
1890
|
"isTyping": {},
|
|
1744
1891
|
"typingProgressMessage": {},
|