open-chat-studio-widget 0.3.0 → 0.4.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 +14 -7
- package/dist/cjs/{index-d39b7c53.js → index-b700441a.js} +83 -4
- package/dist/cjs/index-b700441a.js.map +1 -0
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/open-chat-studio-widget.cjs.entry.js +4846 -50
- 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/collection/components/ocs-chat/heroicons.js +6 -12
- package/dist/collection/components/ocs-chat/heroicons.js.map +1 -1
- package/dist/collection/components/ocs-chat/ocs-chat.css +1096 -73
- package/dist/collection/components/ocs-chat/ocs-chat.js +752 -40
- package/dist/collection/components/ocs-chat/ocs-chat.js.map +1 -1
- package/dist/collection/utils/markdown.js +64 -0
- package/dist/collection/utils/markdown.js.map +1 -0
- package/dist/components/open-chat-studio-widget.js +4869 -52
- package/dist/components/open-chat-studio-widget.js.map +1 -1
- package/dist/esm/{index-b73ebc69.js → index-b188b488.js} +83 -4
- package/dist/esm/index-b188b488.js.map +1 -0
- package/dist/esm/loader.js +3 -3
- package/dist/esm/open-chat-studio-widget.entry.js +4846 -50
- 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.esm.js +1 -1
- package/dist/open-chat-studio-widget/open-chat-studio-widget.esm.js.map +1 -1
- package/dist/open-chat-studio-widget/p-a0fbe1b4.js +3 -0
- package/dist/open-chat-studio-widget/p-a0fbe1b4.js.map +1 -0
- package/dist/open-chat-studio-widget/p-d2d76b54.entry.js +3 -0
- package/dist/open-chat-studio-widget/p-d2d76b54.entry.js.map +1 -0
- package/dist/types/components/ocs-chat/heroicons.d.ts +2 -4
- package/dist/types/components/ocs-chat/ocs-chat.d.ts +129 -4
- package/dist/types/components.d.ts +61 -5
- package/dist/types/utils/markdown.d.ts +6 -0
- package/package.json +4 -3
- package/dist/cjs/index-d39b7c53.js.map +0 -1
- package/dist/esm/index-b73ebc69.js.map +0 -1
- package/dist/open-chat-studio-widget/p-4cdc34c1.js +0 -3
- package/dist/open-chat-studio-widget/p-4cdc34c1.js.map +0 -1
- package/dist/open-chat-studio-widget/p-acba9216.entry.js +0 -2
- package/dist/open-chat-studio-widget/p-acba9216.entry.js.map +0 -1
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { Host, h
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { Host, h } from "@stencil/core";
|
|
2
|
+
import { XMarkIcon, ChevronDownIcon, ChevronUpIcon, GripDotsVerticalIcon, PencilSquare, } from "./heroicons";
|
|
3
|
+
import { renderMarkdownSync as renderMarkdownComplete } from "../../utils/markdown";
|
|
4
4
|
export class OcsChat {
|
|
5
5
|
constructor() {
|
|
6
6
|
/**
|
|
7
|
-
* The
|
|
7
|
+
* The base URL for the API (defaults to current origin).
|
|
8
8
|
*/
|
|
9
|
-
this.
|
|
9
|
+
this.apiBaseUrl = "https://chatbots.dimagi.com";
|
|
10
|
+
/**
|
|
11
|
+
* The shape of the chat button. 'round' makes it circular, 'square' keeps it rectangular.
|
|
12
|
+
*/
|
|
13
|
+
this.buttonShape = 'square';
|
|
10
14
|
/**
|
|
11
15
|
* Whether the chat widget is visible on load.
|
|
12
16
|
*/
|
|
@@ -19,26 +23,375 @@ export class OcsChat {
|
|
|
19
23
|
* Whether the chat widget is initially expanded.
|
|
20
24
|
*/
|
|
21
25
|
this.expanded = false;
|
|
26
|
+
/**
|
|
27
|
+
* Whether to persist session data to local storage to allow resuming previous conversations after page reload.
|
|
28
|
+
*/
|
|
29
|
+
this.persistentSession = true;
|
|
30
|
+
/**
|
|
31
|
+
* Minutes since the most recent message after which the session data in local storage will expire. Set this to
|
|
32
|
+
* `0` to never expire.
|
|
33
|
+
*/
|
|
34
|
+
this.persistentSessionExpire = 60 * 24;
|
|
22
35
|
this.loaded = false;
|
|
23
36
|
this.error = "";
|
|
37
|
+
this.messages = [];
|
|
38
|
+
this.isLoading = false;
|
|
39
|
+
this.isTyping = false;
|
|
40
|
+
this.messageInput = "";
|
|
41
|
+
this.isTaskPolling = false;
|
|
42
|
+
this.isDragging = false;
|
|
43
|
+
this.dragOffset = { x: 0, y: 0 };
|
|
44
|
+
this.windowPosition = { x: 0, y: 0 };
|
|
45
|
+
this.showStarterQuestions = true;
|
|
46
|
+
this.parsedWelcomeMessages = [];
|
|
47
|
+
this.parsedStarterQuestions = [];
|
|
48
|
+
this.handleMouseDown = (event) => {
|
|
49
|
+
if (window.innerWidth < OcsChat.MOBILE_BREAKPOINT)
|
|
50
|
+
return;
|
|
51
|
+
if (event.target.closest('button'))
|
|
52
|
+
return;
|
|
53
|
+
const pointer = this.getPointerCoordinates(event);
|
|
54
|
+
if (!pointer)
|
|
55
|
+
return;
|
|
56
|
+
this.startDrag(pointer);
|
|
57
|
+
this.addEventListeners();
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
};
|
|
60
|
+
this.handleMouseMove = (event) => {
|
|
61
|
+
const pointer = this.getPointerCoordinates(event);
|
|
62
|
+
if (!pointer)
|
|
63
|
+
return;
|
|
64
|
+
this.updateDragPosition(pointer);
|
|
65
|
+
};
|
|
66
|
+
this.handleMouseUp = () => {
|
|
67
|
+
this.endDrag();
|
|
68
|
+
};
|
|
69
|
+
this.handleTouchStart = (event) => {
|
|
70
|
+
if (event.target.closest('button'))
|
|
71
|
+
return;
|
|
72
|
+
if (!this.chatWindowRef)
|
|
73
|
+
return;
|
|
74
|
+
const pointer = this.getPointerCoordinates(event);
|
|
75
|
+
if (!pointer)
|
|
76
|
+
return;
|
|
77
|
+
this.startDrag(pointer);
|
|
78
|
+
this.addEventListeners();
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
};
|
|
81
|
+
this.handleTouchMove = (event) => {
|
|
82
|
+
const pointer = this.getPointerCoordinates(event);
|
|
83
|
+
if (!pointer)
|
|
84
|
+
return;
|
|
85
|
+
this.updateDragPosition(pointer);
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
};
|
|
88
|
+
this.handleTouchEnd = () => {
|
|
89
|
+
this.endDrag();
|
|
90
|
+
};
|
|
91
|
+
this.handleWindowResize = () => {
|
|
92
|
+
this.initializePosition();
|
|
93
|
+
};
|
|
24
94
|
}
|
|
25
95
|
componentWillLoad() {
|
|
26
96
|
this.loaded = this.visible;
|
|
27
|
-
if (!
|
|
97
|
+
if (!this.chatbotId) {
|
|
98
|
+
this.error = 'Chatbot ID is required';
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Always try to load existing session if localStorage is available
|
|
102
|
+
if (this.persistentSession && this.isLocalStorageAvailable()) {
|
|
103
|
+
const { sessionId, messages } = this.loadSessionFromStorage();
|
|
104
|
+
if (sessionId && messages) {
|
|
105
|
+
this.sessionId = sessionId;
|
|
106
|
+
this.messages = messages;
|
|
107
|
+
this.showStarterQuestions = messages.length === 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.parseWelcomeMessages();
|
|
111
|
+
this.parseStarterQuestions();
|
|
112
|
+
}
|
|
113
|
+
componentDidLoad() {
|
|
114
|
+
// Only auto-start session if we don't have an existing one
|
|
115
|
+
if (this.visible && !this.sessionId) {
|
|
116
|
+
this.startSession();
|
|
117
|
+
}
|
|
118
|
+
else if (this.visible && this.sessionId) {
|
|
119
|
+
// Resume polling for existing session
|
|
120
|
+
this.startPolling();
|
|
121
|
+
}
|
|
122
|
+
this.initializePosition();
|
|
123
|
+
window.addEventListener('resize', this.handleWindowResize);
|
|
124
|
+
}
|
|
125
|
+
disconnectedCallback() {
|
|
126
|
+
this.cleanup();
|
|
127
|
+
this.removeEventListeners();
|
|
128
|
+
window.removeEventListener('resize', this.handleWindowResize);
|
|
129
|
+
}
|
|
130
|
+
parseJSONProp(propValue, propName) {
|
|
131
|
+
try {
|
|
132
|
+
if (propValue) {
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(propValue);
|
|
135
|
+
}
|
|
136
|
+
catch (_a) {
|
|
137
|
+
const fixedValue = propValue.replace(/'/g, '"');
|
|
138
|
+
return JSON.parse(fixedValue);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.warn(`Failed to parse ${propName}:`, error);
|
|
144
|
+
}
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
parseWelcomeMessages() {
|
|
148
|
+
this.parsedWelcomeMessages = this.parseJSONProp(this.welcomeMessages, 'welcome messages');
|
|
149
|
+
}
|
|
150
|
+
parseStarterQuestions() {
|
|
151
|
+
this.parsedStarterQuestions = this.parseJSONProp(this.starterQuestions, 'starter questions');
|
|
152
|
+
}
|
|
153
|
+
cleanup() {
|
|
154
|
+
if (this.pollingInterval) {
|
|
155
|
+
clearInterval(this.pollingInterval);
|
|
156
|
+
this.pollingInterval = undefined;
|
|
157
|
+
}
|
|
158
|
+
this.isTaskPolling = false;
|
|
159
|
+
}
|
|
160
|
+
getApiBaseUrl() {
|
|
161
|
+
return this.apiBaseUrl || window.location.origin;
|
|
162
|
+
}
|
|
163
|
+
async startSession() {
|
|
164
|
+
try {
|
|
165
|
+
this.isLoading = true;
|
|
166
|
+
this.error = '';
|
|
167
|
+
const response = await fetch(`${this.getApiBaseUrl()}/api/chat/start/`, {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: {
|
|
170
|
+
'Content-Type': 'application/json',
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
chatbot_id: this.chatbotId,
|
|
174
|
+
session_data: {
|
|
175
|
+
source: 'widget',
|
|
176
|
+
page_url: window.location.href
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
});
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
throw new Error(`Failed to start session: ${response.statusText}`);
|
|
182
|
+
}
|
|
183
|
+
const data = await response.json();
|
|
184
|
+
this.sessionId = data.session_id;
|
|
185
|
+
this.saveSessionToStorage();
|
|
186
|
+
// Handle seed message if present
|
|
187
|
+
if (data.seed_message_task_id) {
|
|
188
|
+
this.isTyping = true; // Show typing indicator for seed message
|
|
189
|
+
await this.pollTaskResponse(data.seed_message_task_id);
|
|
190
|
+
}
|
|
191
|
+
// Start polling for messages
|
|
192
|
+
this.startPolling();
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
this.error = error instanceof Error ? error.message : 'Failed to start chat session';
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
this.isLoading = false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async sendMessage(message) {
|
|
202
|
+
if (!this.sessionId || !message.trim())
|
|
203
|
+
return;
|
|
204
|
+
// Hide starter questions on any user interaction
|
|
205
|
+
this.showStarterQuestions = false;
|
|
206
|
+
try {
|
|
207
|
+
// If this is the first user message and there are welcome messages,
|
|
208
|
+
// add them to chat history as assistant messages
|
|
209
|
+
if (this.messages.length === 0 && this.parsedWelcomeMessages.length > 0) {
|
|
210
|
+
const now = new Date();
|
|
211
|
+
const welcomeMessages = this.parsedWelcomeMessages.map((welcomeMsg, index) => ({
|
|
212
|
+
created_at: new Date(now.getTime() - (this.parsedWelcomeMessages.length - index) * 1000).toISOString(),
|
|
213
|
+
role: 'assistant',
|
|
214
|
+
content: welcomeMsg,
|
|
215
|
+
attachments: []
|
|
216
|
+
}));
|
|
217
|
+
this.messages = [...this.messages, ...welcomeMessages];
|
|
218
|
+
}
|
|
219
|
+
// Add user message immediately
|
|
220
|
+
const userMessage = {
|
|
221
|
+
created_at: new Date().toISOString(),
|
|
222
|
+
role: 'user',
|
|
223
|
+
content: message.trim(),
|
|
224
|
+
attachments: []
|
|
225
|
+
};
|
|
226
|
+
this.messages = [...this.messages, userMessage];
|
|
227
|
+
this.saveSessionToStorage();
|
|
228
|
+
this.messageInput = '';
|
|
229
|
+
this.scrollToBottom();
|
|
230
|
+
// Start typing indicator - it will stay on during task polling
|
|
231
|
+
this.isTyping = true;
|
|
232
|
+
const response = await fetch(`${this.getApiBaseUrl()}/api/chat/${this.sessionId}/message/`, {
|
|
233
|
+
method: 'POST',
|
|
234
|
+
headers: {
|
|
235
|
+
'Content-Type': 'application/json',
|
|
236
|
+
},
|
|
237
|
+
body: JSON.stringify({ message: message.trim() })
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
throw new Error(`Failed to send message: ${response.statusText}`);
|
|
241
|
+
}
|
|
242
|
+
const data = await response.json();
|
|
243
|
+
if (data.status === 'error') {
|
|
244
|
+
throw new Error(data.error || 'Failed to send message');
|
|
245
|
+
}
|
|
246
|
+
// Poll for the response - typing indicator will be managed in pollTaskResponse
|
|
247
|
+
await this.pollTaskResponse(data.task_id);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
this.error = error instanceof Error ? error.message : 'Failed to send message';
|
|
251
|
+
// Clear typing indicator on error
|
|
252
|
+
this.isTyping = false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
handleStarterQuestionClick(question) {
|
|
256
|
+
this.sendMessage(question);
|
|
257
|
+
}
|
|
258
|
+
async pollTaskResponse(taskId) {
|
|
259
|
+
if (!this.sessionId)
|
|
260
|
+
return;
|
|
261
|
+
// Stop message polling while task polling is active
|
|
262
|
+
this.isTaskPolling = true;
|
|
263
|
+
this.pauseMessagePolling();
|
|
264
|
+
let attempts = 0;
|
|
265
|
+
const poll = async () => {
|
|
28
266
|
try {
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
31
|
-
|
|
267
|
+
const response = await fetch(`${this.getApiBaseUrl()}/api/chat/${this.sessionId}/${taskId}/poll/`);
|
|
268
|
+
if (!response.ok) {
|
|
269
|
+
throw new Error(`Failed to poll task: ${response.statusText}`);
|
|
270
|
+
}
|
|
271
|
+
const data = await response.json();
|
|
272
|
+
if (data.error) {
|
|
273
|
+
throw new Error(data.error);
|
|
32
274
|
}
|
|
275
|
+
if (data.status === 'complete' && data.message) {
|
|
276
|
+
this.messages = [...this.messages, data.message];
|
|
277
|
+
this.saveSessionToStorage();
|
|
278
|
+
this.scrollToBottom();
|
|
279
|
+
// Task polling complete, clear typing indicator and resume message polling
|
|
280
|
+
this.isTyping = false;
|
|
281
|
+
this.isTaskPolling = false;
|
|
282
|
+
this.resumeMessagePolling();
|
|
283
|
+
this.focusInput();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (data.status === 'processing' && attempts < OcsChat.TASK_POLLING_MAX_ATTEMPTS) {
|
|
287
|
+
attempts++;
|
|
288
|
+
setTimeout(poll, OcsChat.TASK_POLLING_INTERVAL_MS);
|
|
289
|
+
}
|
|
290
|
+
else if (attempts >= OcsChat.TASK_POLLING_MAX_ATTEMPTS) {
|
|
291
|
+
// Task polling timed out, clear typing indicator and resume message polling
|
|
292
|
+
this.isTyping = false;
|
|
293
|
+
this.isTaskPolling = false;
|
|
294
|
+
this.resumeMessagePolling();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
this.error = error instanceof Error ? error.message : 'Failed to get response';
|
|
299
|
+
// Error in task polling, clear typing indicator and resume message polling
|
|
300
|
+
this.isTyping = false;
|
|
301
|
+
this.isTaskPolling = false;
|
|
302
|
+
this.resumeMessagePolling();
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
await poll();
|
|
306
|
+
}
|
|
307
|
+
startPolling() {
|
|
308
|
+
if (this.pollingInterval)
|
|
309
|
+
return;
|
|
310
|
+
this.pollingInterval = setInterval(async () => {
|
|
311
|
+
// Only poll for messages if not currently polling for a task
|
|
312
|
+
if (!this.isTaskPolling) {
|
|
313
|
+
await this.pollForMessages();
|
|
314
|
+
}
|
|
315
|
+
}, OcsChat.MESSAGE_POLLING_INTERVAL_MS);
|
|
316
|
+
}
|
|
317
|
+
pauseMessagePolling() {
|
|
318
|
+
if (this.pollingInterval) {
|
|
319
|
+
clearInterval(this.pollingInterval);
|
|
320
|
+
this.pollingInterval = undefined;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
resumeMessagePolling() {
|
|
324
|
+
// Resume message polling after task polling is complete
|
|
325
|
+
this.startPolling();
|
|
326
|
+
}
|
|
327
|
+
async pollForMessages() {
|
|
328
|
+
if (!this.sessionId)
|
|
329
|
+
return;
|
|
330
|
+
try {
|
|
331
|
+
const url = new URL(`${this.getApiBaseUrl()}/api/chat/${this.sessionId}/poll/`);
|
|
332
|
+
if (this.messages && this.messages.length > 0) {
|
|
333
|
+
url.searchParams.set('since', this.messages.at(-1).created_at);
|
|
33
334
|
}
|
|
34
|
-
|
|
35
|
-
|
|
335
|
+
const response = await fetch(url.toString());
|
|
336
|
+
if (!response.ok)
|
|
337
|
+
return; // Silently fail for polling
|
|
338
|
+
const data = await response.json();
|
|
339
|
+
if (data.messages.length > 0) {
|
|
340
|
+
this.messages = [...this.messages, ...data.messages];
|
|
341
|
+
this.saveSessionToStorage();
|
|
342
|
+
this.scrollToBottom();
|
|
343
|
+
this.focusInput();
|
|
36
344
|
}
|
|
345
|
+
this.lastPollTime = new Date();
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
// Silently fail for polling
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
clearError() {
|
|
352
|
+
this.error = '';
|
|
353
|
+
}
|
|
354
|
+
scrollToBottom() {
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
if (this.messageListRef) {
|
|
357
|
+
this.messageListRef.scrollTop = this.messageListRef.scrollHeight;
|
|
358
|
+
}
|
|
359
|
+
}, OcsChat.SCROLL_DELAY_MS);
|
|
360
|
+
}
|
|
361
|
+
focusInput() {
|
|
362
|
+
setTimeout(() => {
|
|
363
|
+
if (this.textareaRef && !this.isTyping) {
|
|
364
|
+
this.textareaRef.focus();
|
|
365
|
+
}
|
|
366
|
+
}, OcsChat.FOCUS_DELAY_MS);
|
|
367
|
+
}
|
|
368
|
+
handleKeyPress(event) {
|
|
369
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
370
|
+
event.preventDefault();
|
|
371
|
+
this.sendMessage(this.messageInput);
|
|
37
372
|
}
|
|
38
373
|
}
|
|
39
|
-
|
|
374
|
+
handleInputChange(event) {
|
|
375
|
+
this.messageInput = event.target.value;
|
|
376
|
+
// Hide starter questions when user starts typing
|
|
377
|
+
if (this.messageInput.trim().length > 0) {
|
|
378
|
+
this.showStarterQuestions = false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
formatTime(dateString) {
|
|
382
|
+
const date = new Date(dateString);
|
|
383
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
384
|
+
}
|
|
385
|
+
async load() {
|
|
40
386
|
this.visible = !this.visible;
|
|
41
387
|
this.loaded = true;
|
|
388
|
+
if (this.visible && !this.sessionId) {
|
|
389
|
+
this.clearError();
|
|
390
|
+
await this.startSession();
|
|
391
|
+
}
|
|
392
|
+
else if (!this.visible) {
|
|
393
|
+
// Don't reset session when closing, allow resume
|
|
394
|
+
}
|
|
42
395
|
}
|
|
43
396
|
setPosition(position) {
|
|
44
397
|
if (position === this.position)
|
|
@@ -49,31 +402,229 @@ export class OcsChat {
|
|
|
49
402
|
this.expanded = !this.expanded;
|
|
50
403
|
}
|
|
51
404
|
getPositionClasses() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
405
|
+
return `fixed w-full sm:w-[450px] ${this.expanded ? 'h-5/6' : 'h-3/5'} bg-white border border-gray-200 ${this.isDragging ? 'shadow-2xl cursor-grabbing' : 'shadow-lg transition-shadow duration-200'} rounded-lg overflow-hidden flex flex-col`;
|
|
406
|
+
}
|
|
407
|
+
getPositionStyles() {
|
|
408
|
+
return {
|
|
409
|
+
left: `${this.windowPosition.x}px`,
|
|
410
|
+
top: `${this.windowPosition.y}px`,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
initializePosition() {
|
|
414
|
+
const windowWidth = window.innerWidth;
|
|
415
|
+
const windowHeight = window.innerHeight;
|
|
416
|
+
const chatWidth = windowWidth < OcsChat.MOBILE_BREAKPOINT ? windowWidth : OcsChat.CHAT_WIDTH_DESKTOP;
|
|
417
|
+
const chatHeight = this.expanded
|
|
418
|
+
? (windowHeight * OcsChat.CHAT_HEIGHT_EXPANDED_RATIO)
|
|
419
|
+
: (windowHeight * OcsChat.CHAT_HEIGHT_COLLAPSED_RATIO);
|
|
420
|
+
const isMobile = windowWidth < OcsChat.MOBILE_BREAKPOINT;
|
|
421
|
+
if (isMobile) {
|
|
422
|
+
this.windowPosition = { x: 0, y: 0 };
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
switch (this.position) {
|
|
426
|
+
case 'left':
|
|
427
|
+
this.windowPosition = {
|
|
428
|
+
x: OcsChat.WINDOW_MARGIN,
|
|
429
|
+
y: windowHeight - chatHeight - OcsChat.WINDOW_MARGIN
|
|
430
|
+
};
|
|
431
|
+
break;
|
|
432
|
+
case 'right':
|
|
433
|
+
this.windowPosition = {
|
|
434
|
+
x: windowWidth - chatWidth - OcsChat.WINDOW_MARGIN,
|
|
435
|
+
y: windowHeight - chatHeight - OcsChat.WINDOW_MARGIN
|
|
436
|
+
};
|
|
437
|
+
break;
|
|
438
|
+
case 'center':
|
|
439
|
+
this.windowPosition = {
|
|
440
|
+
x: (windowWidth - chatWidth) / 2,
|
|
441
|
+
y: (windowHeight - chatHeight) / 2
|
|
442
|
+
};
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
getPointerCoordinates(event) {
|
|
447
|
+
if (event instanceof MouseEvent) {
|
|
448
|
+
return { clientX: event.clientX, clientY: event.clientY };
|
|
449
|
+
}
|
|
450
|
+
else if (event instanceof TouchEvent && event.touches.length === 1) {
|
|
451
|
+
const touch = event.touches[0];
|
|
452
|
+
return { clientX: touch.clientX, clientY: touch.clientY };
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
startDrag(pointer) {
|
|
457
|
+
if (!this.chatWindowRef)
|
|
458
|
+
return;
|
|
459
|
+
this.isDragging = true;
|
|
460
|
+
const rect = this.chatWindowRef.getBoundingClientRect();
|
|
461
|
+
this.dragOffset = {
|
|
462
|
+
x: pointer.clientX - rect.left,
|
|
463
|
+
y: pointer.clientY - rect.top
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
updateDragPosition(pointer) {
|
|
467
|
+
if (!this.isDragging)
|
|
468
|
+
return;
|
|
469
|
+
const newX = pointer.clientX - this.dragOffset.x;
|
|
470
|
+
const newY = pointer.clientY - this.dragOffset.y;
|
|
471
|
+
// Constrain chatbox to window
|
|
472
|
+
const windowWidth = window.innerWidth;
|
|
473
|
+
const windowHeight = window.innerHeight;
|
|
474
|
+
const chatWidth = windowWidth < OcsChat.MOBILE_BREAKPOINT ? windowWidth : OcsChat.CHAT_WIDTH_DESKTOP;
|
|
475
|
+
const chatHeight = this.expanded
|
|
476
|
+
? (windowHeight * OcsChat.CHAT_HEIGHT_EXPANDED_RATIO)
|
|
477
|
+
: (windowHeight * OcsChat.CHAT_HEIGHT_COLLAPSED_RATIO);
|
|
478
|
+
this.windowPosition = {
|
|
479
|
+
x: Math.max(0, Math.min(newX, windowWidth - chatWidth)),
|
|
480
|
+
y: Math.max(0, Math.min(newY, windowHeight - chatHeight))
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
endDrag() {
|
|
484
|
+
this.isDragging = false;
|
|
485
|
+
this.removeEventListeners();
|
|
486
|
+
}
|
|
487
|
+
addEventListeners() {
|
|
488
|
+
document.addEventListener('mousemove', this.handleMouseMove);
|
|
489
|
+
document.addEventListener('mouseup', this.handleMouseUp);
|
|
490
|
+
document.addEventListener('touchmove', this.handleTouchMove, { passive: false });
|
|
491
|
+
document.addEventListener('touchend', this.handleTouchEnd);
|
|
492
|
+
}
|
|
493
|
+
removeEventListeners() {
|
|
494
|
+
document.removeEventListener('mousemove', this.handleMouseMove);
|
|
495
|
+
document.removeEventListener('mouseup', this.handleMouseUp);
|
|
496
|
+
document.removeEventListener('touchmove', this.handleTouchMove);
|
|
497
|
+
document.removeEventListener('touchend', this.handleTouchEnd);
|
|
498
|
+
}
|
|
499
|
+
getDefaultIconUrl() {
|
|
500
|
+
return `${this.getApiBaseUrl()}/static/images/favicons/favicon.svg`;
|
|
501
|
+
}
|
|
502
|
+
getButtonClasses() {
|
|
503
|
+
const hasText = this.buttonText && this.buttonText.trim();
|
|
504
|
+
const baseClass = hasText ? 'chat-btn-text' : 'chat-btn-icon';
|
|
505
|
+
const shapeClass = this.buttonShape === 'round' ? 'round' : '';
|
|
506
|
+
return `${baseClass} ${shapeClass}`.trim();
|
|
507
|
+
}
|
|
508
|
+
renderButton() {
|
|
509
|
+
const hasText = this.buttonText && this.buttonText.trim();
|
|
510
|
+
const hasCustomIcon = this.iconUrl && this.iconUrl.trim();
|
|
511
|
+
const iconSrc = hasCustomIcon ? this.iconUrl : this.getDefaultIconUrl();
|
|
512
|
+
const buttonClasses = this.getButtonClasses();
|
|
513
|
+
if (hasText) {
|
|
514
|
+
return (h("button", { class: buttonClasses, onClick: () => this.load(), "aria-label": `Open chat - ${this.buttonText}`, title: this.buttonText }, h("img", { src: iconSrc, alt: "" }), h("span", null, this.buttonText)));
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
return (h("button", { class: buttonClasses, onClick: () => this.load(), "aria-label": "Open chat", title: "Open chat" }, h("img", { src: iconSrc, alt: "Chat" })));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
getStorageKeys() {
|
|
521
|
+
return {
|
|
522
|
+
sessionId: `ocs-chat-session-${this.chatbotId}`,
|
|
523
|
+
messages: `ocs-chat-messages-${this.chatbotId}`,
|
|
524
|
+
lastActivity: `ocs-chat-activity-${this.chatbotId}`
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
saveSessionToStorage() {
|
|
528
|
+
if (!this.persistentSession) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const keys = this.getStorageKeys();
|
|
532
|
+
try {
|
|
533
|
+
if (this.sessionId) {
|
|
534
|
+
localStorage.setItem(keys.sessionId, this.sessionId);
|
|
535
|
+
localStorage.setItem(keys.lastActivity, new Date().toISOString());
|
|
536
|
+
}
|
|
537
|
+
localStorage.setItem(keys.messages, JSON.stringify(this.messages));
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
console.warn('Failed to save chat session to localStorage:', error);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
loadSessionFromStorage() {
|
|
544
|
+
const keys = this.getStorageKeys();
|
|
545
|
+
try {
|
|
546
|
+
if (this.persistentSessionExpire > 0) {
|
|
547
|
+
const lastActivity = localStorage.getItem(keys.lastActivity);
|
|
548
|
+
if (lastActivity) {
|
|
549
|
+
const lastActivityDate = new Date(lastActivity);
|
|
550
|
+
const minutesSinceActivity = (Date.now() - lastActivityDate.getTime()) / (1000 * 60);
|
|
551
|
+
if (minutesSinceActivity > this.persistentSessionExpire) {
|
|
552
|
+
this.clearSessionStorage();
|
|
553
|
+
return { messages: [] };
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const storedSessionId = localStorage.getItem(keys.sessionId);
|
|
558
|
+
const sessionId = storedSessionId ? storedSessionId : undefined;
|
|
559
|
+
const messagesJson = localStorage.getItem(keys.messages);
|
|
560
|
+
let messages = [];
|
|
561
|
+
if (messagesJson) {
|
|
562
|
+
try {
|
|
563
|
+
const parsedMessages = JSON.parse(messagesJson);
|
|
564
|
+
messages = Array.isArray(parsedMessages) ? parsedMessages : [];
|
|
565
|
+
}
|
|
566
|
+
catch (parseError) {
|
|
567
|
+
console.warn('Failed to parse messages from localStorage:', parseError);
|
|
568
|
+
messages = [];
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return { sessionId, messages };
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
// fall back to starting a new session
|
|
575
|
+
console.warn('Failed to load chat session from localStorage, starting new session:', error);
|
|
576
|
+
return { messages: [] };
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
clearSessionStorage() {
|
|
580
|
+
const keys = this.getStorageKeys();
|
|
581
|
+
try {
|
|
582
|
+
localStorage.removeItem(keys.sessionId);
|
|
583
|
+
localStorage.removeItem(keys.messages);
|
|
584
|
+
localStorage.removeItem(keys.lastActivity);
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
console.warn('Failed to clear chat session from localStorage:', error);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
isLocalStorageAvailable() {
|
|
591
|
+
try {
|
|
592
|
+
localStorage.setItem(OcsChat.LOCALSTORAGE_TEST_KEY, 'test');
|
|
593
|
+
localStorage.removeItem(OcsChat.LOCALSTORAGE_TEST_KEY);
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
catch (_a) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async startNewChat() {
|
|
601
|
+
this.clearSessionStorage();
|
|
602
|
+
this.sessionId = undefined;
|
|
603
|
+
this.messages = [];
|
|
604
|
+
this.showStarterQuestions = true;
|
|
605
|
+
this.isTyping = false;
|
|
606
|
+
this.error = '';
|
|
607
|
+
this.cleanup();
|
|
608
|
+
await this.startSession();
|
|
59
609
|
}
|
|
60
610
|
render() {
|
|
61
611
|
if (this.error) {
|
|
62
|
-
return (h(Host, null, h("p",
|
|
63
|
-
}
|
|
64
|
-
return (h(Host, null, h("button", { class: "
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
}
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
'text-gray-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
|
|
612
|
+
return (h(Host, null, h("p", { class: "text-red-500 p-2" }, this.error)));
|
|
613
|
+
}
|
|
614
|
+
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() }, h("div", { class: `flex justify-between items-center px-2 py-2 border-b border-gray-100 sm:${this.isDragging ? 'cursor-grabbing' : 'cursor-grab'} active:bg-gray-50 sm:hover:bg-gray-25 transition-colors duration-150`, onMouseDown: this.handleMouseDown, onTouchStart: this.handleTouchStart }, h("div", { class: "hidden sm:flex gap-1" }, h("div", { class: "flex gap-0.5 ml-2 pointer-events-none" }, h(GripDotsVerticalIcon, null))), h("div", null), h("div", { class: "flex gap-1 items-center" }, this.sessionId && this.messages.length > 0 && (h("button", { class: "p-1.5 rounded-md transition-colors duration-200 hover:bg-gray-100 text-gray-500", onClick: () => this.startNewChat(), title: "Start new chat", "aria-label": "Start new chat" }, h(PencilSquare, null))), h("button", { class: "p-1.5 rounded-md transition-colors duration-200 hover:bg-gray-100 text-gray-500", onClick: () => this.toggleSize(), "aria-label": this.expanded ? "Collapse" : "Expand", title: this.expanded ? "Collapse" : "Expand" }, this.expanded ? h(ChevronDownIcon, null) : h(ChevronUpIcon, null)), h("button", { class: "p-1.5 hover:bg-gray-100 rounded-md transition-colors duration-200 text-gray-500", onClick: () => this.visible = false, "aria-label": "Close" }, h(XMarkIcon, null)))), h("div", { class: "flex flex-col flex-grow overflow-hidden" }, this.isLoading && !this.sessionId && (h("div", { class: "flex items-center justify-center flex-grow" }, h("div", { class: "loading-spinner" }), h("span", { class: "ml-2 text-gray-500" }, "Starting chat..."))), this.sessionId && (h("div", { ref: (el) => this.messageListRef = el, class: "flex-grow overflow-y-auto p-4 space-y-4" }, this.messages.length === 0 && !this.isTyping && this.parsedWelcomeMessages.length > 0 && (h("div", { class: "space-y-4" }, this.parsedWelcomeMessages.map((message, index) => (h("div", { key: `welcome-${index}`, class: "flex justify-start" }, h("div", { class: "bg-gray-200 text-gray-800 max-w-xs lg:max-w-md px-4 py-2 rounded-lg" }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownComplete(message) }))))))), this.messages.map((message, index) => (h("div", { key: index, class: {
|
|
615
|
+
'flex': true,
|
|
616
|
+
'justify-end': message.role === 'user',
|
|
617
|
+
'justify-start': message.role !== 'user'
|
|
618
|
+
} }, h("div", { class: {
|
|
619
|
+
'max-w-xs lg:max-w-md px-4 py-2 rounded-lg': true,
|
|
620
|
+
'bg-blue-500 text-white': message.role === 'user',
|
|
621
|
+
'bg-gray-200 text-gray-800': message.role === 'assistant',
|
|
622
|
+
'bg-gray-100 text-gray-600 text-sm': message.role === 'system'
|
|
623
|
+
} }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownComplete(message.content) }), message.attachments && message.attachments.length > 0 && (h("div", { class: "mt-2 space-y-1" }, message.attachments.map((attachment, attachmentIndex) => (h("a", { key: attachmentIndex, href: attachment.content_url, target: "_blank", rel: "noopener noreferrer", class: "block text-sm underline hover:no-underline" }, "\uD83D\uDCCE ", attachment.name))))), h("div", { class: "text-xs opacity-70 mt-1" }, this.formatTime(message.created_at)))))), this.isTyping && (h("div", { class: "flex justify-start" }, h("div", { class: "bg-gray-200 text-gray-800 max-w-xs lg:max-w-md px-2 py-2 rounded-lg" }, h("div", { class: "flex items-center gap-0.5" }, h("span", { class: "inline-block w-2 h-2 rounded-full bg-gray-400 animate-bounce" }), h("span", { class: "inline-block w-2 h-2 rounded-full bg-gray-400 animate-bounce", style: { animationDelay: '0.1s' } }), h("span", { class: "inline-block w-2 h-2 rounded-full bg-gray-400 animate-bounce", style: { animationDelay: '0.2s' } }))))))), this.sessionId && this.showStarterQuestions && this.messages.length === 0 && !this.isTyping && (h("div", { class: "p-4 space-y-2" }, this.parsedStarterQuestions.map((question, index) => (h("div", { key: `starter-${index}`, class: "flex justify-end" }, h("button", { class: "starter-question", onClick: () => this.handleStarterQuestionClick(question) }, question)))))), this.sessionId && (h("div", { class: "border-t border-gray-200 p-4" }, h("div", { class: "flex gap-2" }, h("textarea", { ref: (el) => this.textareaRef = el, class: "flex-grow px-3 py-2 border border-gray-300 rounded-md resize-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent", rows: 1, placeholder: "Type your message...", value: this.messageInput, onInput: (e) => this.handleInputChange(e), onKeyPress: (e) => this.handleKeyPress(e), disabled: this.isTyping }), h("button", { class: {
|
|
624
|
+
'px-4 py-2 rounded-md font-medium transition-colors duration-200': true,
|
|
625
|
+
'bg-blue-500 hover:bg-blue-600 text-white': !this.isTyping && !!this.messageInput.trim(),
|
|
626
|
+
'bg-gray-300 text-gray-500 cursor-not-allowed': this.isTyping || !this.messageInput.trim()
|
|
627
|
+
}, onClick: () => this.sendMessage(this.messageInput), disabled: this.isTyping || !this.messageInput.trim() }, "Send")))))))));
|
|
77
628
|
}
|
|
78
629
|
static get is() { return "open-chat-studio-widget"; }
|
|
79
630
|
static get encapsulation() { return "shadow"; }
|
|
@@ -89,7 +640,7 @@ export class OcsChat {
|
|
|
89
640
|
}
|
|
90
641
|
static get properties() {
|
|
91
642
|
return {
|
|
92
|
-
"
|
|
643
|
+
"chatbotId": {
|
|
93
644
|
"type": "string",
|
|
94
645
|
"mutable": false,
|
|
95
646
|
"complexType": {
|
|
@@ -101,13 +652,33 @@ export class OcsChat {
|
|
|
101
652
|
"optional": false,
|
|
102
653
|
"docs": {
|
|
103
654
|
"tags": [],
|
|
104
|
-
"text": "The
|
|
655
|
+
"text": "The ID of the chatbot to connect to."
|
|
105
656
|
},
|
|
106
657
|
"getter": false,
|
|
107
658
|
"setter": false,
|
|
108
|
-
"attribute": "
|
|
659
|
+
"attribute": "chatbot-id",
|
|
109
660
|
"reflect": false
|
|
110
661
|
},
|
|
662
|
+
"apiBaseUrl": {
|
|
663
|
+
"type": "string",
|
|
664
|
+
"mutable": false,
|
|
665
|
+
"complexType": {
|
|
666
|
+
"original": "string",
|
|
667
|
+
"resolved": "string",
|
|
668
|
+
"references": {}
|
|
669
|
+
},
|
|
670
|
+
"required": false,
|
|
671
|
+
"optional": true,
|
|
672
|
+
"docs": {
|
|
673
|
+
"tags": [],
|
|
674
|
+
"text": "The base URL for the API (defaults to current origin)."
|
|
675
|
+
},
|
|
676
|
+
"getter": false,
|
|
677
|
+
"setter": false,
|
|
678
|
+
"attribute": "api-base-url",
|
|
679
|
+
"reflect": false,
|
|
680
|
+
"defaultValue": "\"https://chatbots.dimagi.com\""
|
|
681
|
+
},
|
|
111
682
|
"buttonText": {
|
|
112
683
|
"type": "string",
|
|
113
684
|
"mutable": false,
|
|
@@ -117,7 +688,7 @@ export class OcsChat {
|
|
|
117
688
|
"references": {}
|
|
118
689
|
},
|
|
119
690
|
"required": false,
|
|
120
|
-
"optional":
|
|
691
|
+
"optional": true,
|
|
121
692
|
"docs": {
|
|
122
693
|
"tags": [],
|
|
123
694
|
"text": "The text to display on the button."
|
|
@@ -125,8 +696,46 @@ export class OcsChat {
|
|
|
125
696
|
"getter": false,
|
|
126
697
|
"setter": false,
|
|
127
698
|
"attribute": "button-text",
|
|
699
|
+
"reflect": false
|
|
700
|
+
},
|
|
701
|
+
"iconUrl": {
|
|
702
|
+
"type": "string",
|
|
703
|
+
"mutable": false,
|
|
704
|
+
"complexType": {
|
|
705
|
+
"original": "string",
|
|
706
|
+
"resolved": "string",
|
|
707
|
+
"references": {}
|
|
708
|
+
},
|
|
709
|
+
"required": false,
|
|
710
|
+
"optional": true,
|
|
711
|
+
"docs": {
|
|
712
|
+
"tags": [],
|
|
713
|
+
"text": "URL of the icon to display on the button. If not provided, uses the default OCS logo."
|
|
714
|
+
},
|
|
715
|
+
"getter": false,
|
|
716
|
+
"setter": false,
|
|
717
|
+
"attribute": "icon-url",
|
|
718
|
+
"reflect": false
|
|
719
|
+
},
|
|
720
|
+
"buttonShape": {
|
|
721
|
+
"type": "string",
|
|
722
|
+
"mutable": false,
|
|
723
|
+
"complexType": {
|
|
724
|
+
"original": "'round' | 'square'",
|
|
725
|
+
"resolved": "\"round\" | \"square\"",
|
|
726
|
+
"references": {}
|
|
727
|
+
},
|
|
728
|
+
"required": false,
|
|
729
|
+
"optional": false,
|
|
730
|
+
"docs": {
|
|
731
|
+
"tags": [],
|
|
732
|
+
"text": "The shape of the chat button. 'round' makes it circular, 'square' keeps it rectangular."
|
|
733
|
+
},
|
|
734
|
+
"getter": false,
|
|
735
|
+
"setter": false,
|
|
736
|
+
"attribute": "button-shape",
|
|
128
737
|
"reflect": false,
|
|
129
|
-
"defaultValue": "
|
|
738
|
+
"defaultValue": "'square'"
|
|
130
739
|
},
|
|
131
740
|
"visible": {
|
|
132
741
|
"type": "boolean",
|
|
@@ -187,14 +796,117 @@ export class OcsChat {
|
|
|
187
796
|
"attribute": "expanded",
|
|
188
797
|
"reflect": false,
|
|
189
798
|
"defaultValue": "false"
|
|
799
|
+
},
|
|
800
|
+
"welcomeMessages": {
|
|
801
|
+
"type": "string",
|
|
802
|
+
"mutable": false,
|
|
803
|
+
"complexType": {
|
|
804
|
+
"original": "string",
|
|
805
|
+
"resolved": "string",
|
|
806
|
+
"references": {}
|
|
807
|
+
},
|
|
808
|
+
"required": false,
|
|
809
|
+
"optional": true,
|
|
810
|
+
"docs": {
|
|
811
|
+
"tags": [],
|
|
812
|
+
"text": "Welcome messages to display above starter questions (JSON array of strings)"
|
|
813
|
+
},
|
|
814
|
+
"getter": false,
|
|
815
|
+
"setter": false,
|
|
816
|
+
"attribute": "welcome-messages",
|
|
817
|
+
"reflect": false
|
|
818
|
+
},
|
|
819
|
+
"starterQuestions": {
|
|
820
|
+
"type": "string",
|
|
821
|
+
"mutable": false,
|
|
822
|
+
"complexType": {
|
|
823
|
+
"original": "string",
|
|
824
|
+
"resolved": "string",
|
|
825
|
+
"references": {}
|
|
826
|
+
},
|
|
827
|
+
"required": false,
|
|
828
|
+
"optional": true,
|
|
829
|
+
"docs": {
|
|
830
|
+
"tags": [],
|
|
831
|
+
"text": "Array of starter questions that users can click to send (JSON array of strings)"
|
|
832
|
+
},
|
|
833
|
+
"getter": false,
|
|
834
|
+
"setter": false,
|
|
835
|
+
"attribute": "starter-questions",
|
|
836
|
+
"reflect": false
|
|
837
|
+
},
|
|
838
|
+
"persistentSession": {
|
|
839
|
+
"type": "boolean",
|
|
840
|
+
"mutable": false,
|
|
841
|
+
"complexType": {
|
|
842
|
+
"original": "boolean",
|
|
843
|
+
"resolved": "boolean",
|
|
844
|
+
"references": {}
|
|
845
|
+
},
|
|
846
|
+
"required": false,
|
|
847
|
+
"optional": false,
|
|
848
|
+
"docs": {
|
|
849
|
+
"tags": [],
|
|
850
|
+
"text": "Whether to persist session data to local storage to allow resuming previous conversations after page reload."
|
|
851
|
+
},
|
|
852
|
+
"getter": false,
|
|
853
|
+
"setter": false,
|
|
854
|
+
"attribute": "persistent-session",
|
|
855
|
+
"reflect": false,
|
|
856
|
+
"defaultValue": "true"
|
|
857
|
+
},
|
|
858
|
+
"persistentSessionExpire": {
|
|
859
|
+
"type": "number",
|
|
860
|
+
"mutable": false,
|
|
861
|
+
"complexType": {
|
|
862
|
+
"original": "number",
|
|
863
|
+
"resolved": "number",
|
|
864
|
+
"references": {}
|
|
865
|
+
},
|
|
866
|
+
"required": false,
|
|
867
|
+
"optional": false,
|
|
868
|
+
"docs": {
|
|
869
|
+
"tags": [],
|
|
870
|
+
"text": "Minutes since the most recent message after which the session data in local storage will expire. Set this to\n`0` to never expire."
|
|
871
|
+
},
|
|
872
|
+
"getter": false,
|
|
873
|
+
"setter": false,
|
|
874
|
+
"attribute": "persistent-session-expire",
|
|
875
|
+
"reflect": false,
|
|
876
|
+
"defaultValue": "60 * 24"
|
|
190
877
|
}
|
|
191
878
|
};
|
|
192
879
|
}
|
|
193
880
|
static get states() {
|
|
194
881
|
return {
|
|
195
882
|
"loaded": {},
|
|
196
|
-
"error": {}
|
|
883
|
+
"error": {},
|
|
884
|
+
"messages": {},
|
|
885
|
+
"sessionId": {},
|
|
886
|
+
"isLoading": {},
|
|
887
|
+
"isTyping": {},
|
|
888
|
+
"messageInput": {},
|
|
889
|
+
"pollingInterval": {},
|
|
890
|
+
"lastPollTime": {},
|
|
891
|
+
"isTaskPolling": {},
|
|
892
|
+
"isDragging": {},
|
|
893
|
+
"dragOffset": {},
|
|
894
|
+
"windowPosition": {},
|
|
895
|
+
"showStarterQuestions": {},
|
|
896
|
+
"parsedWelcomeMessages": {},
|
|
897
|
+
"parsedStarterQuestions": {}
|
|
197
898
|
};
|
|
198
899
|
}
|
|
199
900
|
}
|
|
901
|
+
OcsChat.TASK_POLLING_MAX_ATTEMPTS = 30;
|
|
902
|
+
OcsChat.TASK_POLLING_INTERVAL_MS = 1000;
|
|
903
|
+
OcsChat.MESSAGE_POLLING_INTERVAL_MS = 30000;
|
|
904
|
+
OcsChat.SCROLL_DELAY_MS = 100;
|
|
905
|
+
OcsChat.FOCUS_DELAY_MS = 100;
|
|
906
|
+
OcsChat.CHAT_WIDTH_DESKTOP = 450;
|
|
907
|
+
OcsChat.CHAT_HEIGHT_EXPANDED_RATIO = 0.83; // 83% of window height
|
|
908
|
+
OcsChat.CHAT_HEIGHT_COLLAPSED_RATIO = 0.6; // 60% of window height
|
|
909
|
+
OcsChat.MOBILE_BREAKPOINT = 640;
|
|
910
|
+
OcsChat.WINDOW_MARGIN = 20;
|
|
911
|
+
OcsChat.LOCALSTORAGE_TEST_KEY = '__ocs_test__';
|
|
200
912
|
//# sourceMappingURL=ocs-chat.js.map
|