@ibti-tech/chatbot 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,1101 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/contexts/Chatbot/useChatbot.ts
5
+ import { useContext } from "react";
6
+
7
+ // src/contexts/Chatbot/context.ts
8
+ import * as React from "react";
9
+ var ChatbotContext = React.createContext({});
10
+
11
+ // src/contexts/Chatbot/useChatbot.ts
12
+ var useChatbot = /* @__PURE__ */ __name(() => {
13
+ const context = useContext(ChatbotContext);
14
+ if (!context) throw new Error("ChatbotContext was not found to be consumed.");
15
+ return context;
16
+ }, "useChatbot");
17
+ var useChatbot_default = useChatbot;
18
+
19
+ // src/contexts/Chatbot/provider.tsx
20
+ import React3, { useMemo, useRef, useState as useState3 } from "react";
21
+
22
+ // src/contexts/Chatbot/useChatbotMessages.ts
23
+ import { useEffect, useState as useState2 } from "react";
24
+ import { v4 as uuidv4 } from "uuid";
25
+
26
+ // src/contexts/Chatbot/useChatbotSuggestedQuestions.ts
27
+ import { useState } from "react";
28
+
29
+ // src/services/chatbot-api.ts
30
+ var sendChatContext = /* @__PURE__ */ __name(async ({
31
+ chatContext,
32
+ locale = "en",
33
+ onReceiving,
34
+ onDone,
35
+ apiURL,
36
+ onError
37
+ }) => fetch(`${apiURL}/chat/completion?locale=${locale}`, {
38
+ method: "POST",
39
+ headers: {
40
+ "Content-type": "application/json"
41
+ },
42
+ body: JSON.stringify({
43
+ context: chatContext
44
+ })
45
+ }).then((response) => {
46
+ if (!response.body) return;
47
+ const reader = response.body.getReader();
48
+ const decoder = new TextDecoder();
49
+ let message = "";
50
+ function read() {
51
+ reader.read().then(({ done, value }) => {
52
+ if (done) {
53
+ onDone && onDone();
54
+ return;
55
+ }
56
+ const decodifiedChunk = decoder.decode(value, { stream: true });
57
+ message += decodifiedChunk;
58
+ onReceiving(message, decodifiedChunk);
59
+ read();
60
+ });
61
+ }
62
+ __name(read, "read");
63
+ read();
64
+ }).catch((err) => {
65
+ onError(err);
66
+ }), "sendChatContext");
67
+ var getSuggestedQuestions = /* @__PURE__ */ __name(async ({
68
+ chatContext,
69
+ apiURL,
70
+ locale = "en"
71
+ }) => {
72
+ const res = await fetch(
73
+ `${apiURL}/chat/suggested_questions?locale=${locale}`,
74
+ {
75
+ method: "POST",
76
+ headers: {
77
+ "Content-type": "application/json"
78
+ },
79
+ body: JSON.stringify({
80
+ context: chatContext
81
+ })
82
+ }
83
+ );
84
+ const data = await res.json();
85
+ return data.suggestedQuestions;
86
+ }, "getSuggestedQuestions");
87
+ var sendUserChatFeedback = /* @__PURE__ */ __name(async ({
88
+ apiURL,
89
+ chatContext,
90
+ locale = "en",
91
+ ratingScore,
92
+ description
93
+ }) => fetch(`${apiURL}/chat/feedback`, {
94
+ method: "POST",
95
+ headers: {
96
+ "Content-type": "application/json"
97
+ },
98
+ body: JSON.stringify({
99
+ locale,
100
+ rating_score: ratingScore,
101
+ chat_context: chatContext,
102
+ message: description
103
+ })
104
+ }), "sendUserChatFeedback");
105
+
106
+ // src/contexts/Chatbot/useChatbotSuggestedQuestions.ts
107
+ var useChatbotSuggestedQuestions = /* @__PURE__ */ __name(({
108
+ chatContext,
109
+ locale = "en",
110
+ apiURL
111
+ }) => {
112
+ const [loading, setLoading] = useState(false);
113
+ const [data, setData] = useState([]);
114
+ const fetchData = /* @__PURE__ */ __name(async () => {
115
+ if (chatContext.length === 0) return;
116
+ setLoading(true);
117
+ const suggestedData = await getSuggestedQuestions({
118
+ chatContext,
119
+ locale,
120
+ apiURL
121
+ });
122
+ setData(suggestedData);
123
+ setLoading(false);
124
+ }, "fetchData");
125
+ const resetData = /* @__PURE__ */ __name(() => {
126
+ setData([]);
127
+ }, "resetData");
128
+ return {
129
+ loading,
130
+ data,
131
+ fetchData,
132
+ resetData
133
+ };
134
+ }, "useChatbotSuggestedQuestions");
135
+
136
+ // src/events/eventNames.ts
137
+ var CUSTOM_EVENT_NAMES = {
138
+ ASSISTANT_ANSWER_FINISHED: "ASSISTANT_ANSWER_FINISHED"
139
+ };
140
+
141
+ // src/events/assistant-answer.ts
142
+ var dispatchAssitantAnswer = /* @__PURE__ */ __name(() => {
143
+ const evt = new CustomEvent(CUSTOM_EVENT_NAMES.ASSISTANT_ANSWER_FINISHED);
144
+ document.dispatchEvent(evt);
145
+ }, "dispatchAssitantAnswer");
146
+
147
+ // src/contexts/Chatbot/useChatbotMessages.ts
148
+ var useChatbotMessages = /* @__PURE__ */ __name(({
149
+ apiURL,
150
+ locale
151
+ }) => {
152
+ const [chatMessages, setChatMessages] = useState2(
153
+ []
154
+ );
155
+ const [assistantIntroduced, setAssistantIntroduced] = useState2(false);
156
+ const [loading, setLoading] = useState2(false);
157
+ const [writing, setWriting] = useState2(false);
158
+ const [error, setError] = useState2();
159
+ const suggestedQuestions = useChatbotSuggestedQuestions({
160
+ chatContext: chatMessages.map((item) => ({
161
+ role: item.role,
162
+ content: item.content
163
+ })),
164
+ apiURL,
165
+ locale
166
+ });
167
+ const createQuestionMessageIntoHistory = /* @__PURE__ */ __name((question, id) => {
168
+ const timestamp = (/* @__PURE__ */ new Date()).toUTCString();
169
+ const messageData = {
170
+ id,
171
+ timestamp,
172
+ role: "user",
173
+ content: question
174
+ };
175
+ const newHistory = [
176
+ ...chatMessages,
177
+ messageData
178
+ ];
179
+ setChatMessages(newHistory);
180
+ return messageData;
181
+ }, "createQuestionMessageIntoHistory");
182
+ const createOrUpdateAnswerMessageIntoHistory = /* @__PURE__ */ __name((answer, id) => {
183
+ const timestamp = (/* @__PURE__ */ new Date()).toUTCString();
184
+ const messageData = {
185
+ id,
186
+ timestamp,
187
+ role: "assistant",
188
+ content: answer
189
+ };
190
+ setChatMessages((prev) => {
191
+ const itemAlreadyExists = prev.some((item) => item.id == id);
192
+ if (itemAlreadyExists) {
193
+ return prev.map((item) => {
194
+ if (item.id == id)
195
+ return {
196
+ ...item,
197
+ content: messageData.content
198
+ };
199
+ return item;
200
+ });
201
+ }
202
+ return [...prev, messageData];
203
+ });
204
+ return messageData;
205
+ }, "createOrUpdateAnswerMessageIntoHistory");
206
+ const makeQuestion = /* @__PURE__ */ __name(async (userQuestion) => {
207
+ setLoading(true);
208
+ suggestedQuestions.resetData();
209
+ const answerId = uuidv4();
210
+ const questionId = uuidv4();
211
+ const chatContext = createQuestionMessageIntoHistory(
212
+ userQuestion,
213
+ questionId
214
+ );
215
+ await sendChatContext({
216
+ apiURL,
217
+ locale,
218
+ chatContext: [
219
+ ...chatMessages.map((item) => ({
220
+ role: item.role,
221
+ content: item.content
222
+ })),
223
+ { role: chatContext.role, content: chatContext.content }
224
+ ],
225
+ onReceiving(receivedMessage) {
226
+ setLoading(false);
227
+ setWriting(true);
228
+ createOrUpdateAnswerMessageIntoHistory(receivedMessage, answerId);
229
+ },
230
+ onDone: /* @__PURE__ */ __name(() => {
231
+ setWriting(false);
232
+ suggestedQuestions.fetchData();
233
+ }, "onDone"),
234
+ onError: setError
235
+ });
236
+ dispatchAssitantAnswer();
237
+ }, "makeQuestion");
238
+ const introduceAssistant = /* @__PURE__ */ __name(() => {
239
+ const firstMessage = {
240
+ "pt-BR": "Ol\xE1!",
241
+ en: "Hello!"
242
+ }[locale];
243
+ makeQuestion(firstMessage);
244
+ setAssistantIntroduced(true);
245
+ setChatMessages((prev) => prev.slice(1));
246
+ }, "introduceAssistant");
247
+ useEffect(() => {
248
+ if (assistantIntroduced) return;
249
+ introduceAssistant();
250
+ }, [assistantIntroduced]);
251
+ return {
252
+ chatMessages,
253
+ makeQuestion,
254
+ loading,
255
+ writing,
256
+ error,
257
+ suggestedQuestions
258
+ };
259
+ }, "useChatbotMessages");
260
+ var useChatbotMessages_default = useChatbotMessages;
261
+
262
+ // src/themes/index.ts
263
+ import { defaultDarkTheme, defaultLightTheme } from "@ibti-tech/ui";
264
+ var themes = {
265
+ light: defaultLightTheme,
266
+ dark: defaultDarkTheme
267
+ };
268
+
269
+ // src/contexts/Chatbot/provider.tsx
270
+ import { ThemeProvider } from "styled-components";
271
+ var ChatbotProvider = /* @__PURE__ */ __name(({
272
+ locale,
273
+ children,
274
+ apiURL,
275
+ theme
276
+ }) => {
277
+ const {
278
+ makeQuestion,
279
+ chatMessages,
280
+ loading,
281
+ writing,
282
+ error,
283
+ suggestedQuestions
284
+ } = useChatbotMessages_default({
285
+ apiURL,
286
+ locale
287
+ });
288
+ const [opened, setOpened] = useState3(false);
289
+ const scrollRef = useRef(null);
290
+ const openedToggle = /* @__PURE__ */ __name(() => {
291
+ setOpened((state) => !state);
292
+ }, "openedToggle");
293
+ const status = useMemo(() => {
294
+ if (writing) return "writing";
295
+ if (error) return "unavailable";
296
+ if (loading) return "loading";
297
+ return "online";
298
+ }, [writing, loading, error]);
299
+ return /* @__PURE__ */ React3.createElement(
300
+ ChatbotContext.Provider,
301
+ {
302
+ value: {
303
+ chatMessages,
304
+ makeQuestion,
305
+ loading,
306
+ writing,
307
+ locale,
308
+ scrollRef,
309
+ opened,
310
+ openedToggle,
311
+ status,
312
+ suggestedQuestions,
313
+ apiURL
314
+ }
315
+ },
316
+ /* @__PURE__ */ React3.createElement(ThemeProvider, { theme: themes[theme] }, children)
317
+ );
318
+ }, "ChatbotProvider");
319
+
320
+ // src/components/ChatbotDevice/index.tsx
321
+ import React13 from "react";
322
+
323
+ // src/components/ChatbotDevice/styles.ts
324
+ import { breakpoints, screens } from "@ibti-tech/ui";
325
+ import { mix } from "polished";
326
+ import { css, styled } from "styled-components";
327
+ var Wrapper = styled.div`
328
+ width: 100%;
329
+ /* background-color: ${(props) => mix(
330
+ 0.05,
331
+ props.theme.colors.palette.primary.normal,
332
+ props.theme.colors.layers[0].background
333
+ )}; */
334
+ background-color: ${(props) => props.theme.colors.layers[1].background};
335
+ border: 1px solid ${(props) => props.theme.colors.layers[1].border};
336
+ display: flex;
337
+ flex-direction: column;
338
+ transition: ${(props) => props.theme.transitionDurations.fast};
339
+ border-end-end-radius: ${(props) => props.theme.borderRadius.medium};
340
+ border-end-start-radius: ${(props) => props.theme.borderRadius.medium};
341
+ overflow: hidden;
342
+ pointer-events: all;
343
+ height: 100vh;
344
+ position: relative;
345
+
346
+ ${screens.tablet} {
347
+ height: 500px;
348
+ max-width: ${breakpoints.mobileL}px;
349
+ }
350
+
351
+ ${(props) => !props.$opened && css`
352
+ transform: translateY(calc(-100% + 32px));
353
+ `}
354
+ `;
355
+
356
+ // src/components/ChatbotHeader/index.tsx
357
+ import React6 from "react";
358
+
359
+ // src/components/ChatbotHeader/styles.ts
360
+ import { styled as styled2 } from "styled-components";
361
+ var Name = styled2.span`
362
+ color: ${(props) => props.theme.colors.content.title};
363
+ font-weight: ${(props) => props.theme.fontFamily.montserrat.weights.bold};
364
+ `;
365
+ var ProfileInfo = styled2.div`
366
+ display: flex;
367
+ flex-direction: column;
368
+ `;
369
+ var ProfileImage = styled2.span`
370
+ height: 2.5rem;
371
+ width: 2.5rem;
372
+ display: flex;
373
+ align-items: center;
374
+ justify-content: center;
375
+ background-color: ${(props) => props.theme.colors.palette.primary.normal};
376
+ font-size: 1.5rem;
377
+ border-radius: ${(props) => props.theme.borderRadius.circle};
378
+
379
+ svg {
380
+ color: white;
381
+ }
382
+ `;
383
+ var Wrapper2 = styled2.div`
384
+ background: ${(props) => props.theme.colors.layers[2].background};
385
+ border: 1px solid ${(props) => props.theme.colors.layers[2].border};
386
+ display: flex;
387
+ padding: ${(props) => props.theme.spacing.components.small};
388
+ gap: ${(props) => props.theme.spacing.components.small};
389
+ `;
390
+
391
+ // src/components/BotIcon/index.tsx
392
+ import React4 from "react";
393
+ var BotIcon = /* @__PURE__ */ __name(() => {
394
+ return /* @__PURE__ */ React4.createElement("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em" }, /* @__PURE__ */ React4.createElement(
395
+ "path",
396
+ {
397
+ d: "M17.7530511,13.999921 C18.9956918,13.999921 20.0030511,15.0072804 20.0030511,16.249921 L20.0030511,17.1550008 C20.0030511,18.2486786 19.5255957,19.2878579 18.6957793,20.0002733 C17.1303315,21.344244 14.8899962,22.0010712 12,22.0010712 C9.11050247,22.0010712 6.87168436,21.3444691 5.30881727,20.0007885 C4.48019625,19.2883988 4.00354153,18.2500002 4.00354153,17.1572408 L4.00354153,16.249921 C4.00354153,15.0072804 5.01090084,13.999921 6.25354153,13.999921 L17.7530511,13.999921 Z M11.8985607,2.00734093 L12.0003312,2.00049432 C12.380027,2.00049432 12.6938222,2.2826482 12.7434846,2.64872376 L12.7503312,2.75049432 L12.7495415,3.49949432 L16.25,3.5 C17.4926407,3.5 18.5,4.50735931 18.5,5.75 L18.5,10.254591 C18.5,11.4972317 17.4926407,12.504591 16.25,12.504591 L7.75,12.504591 C6.50735931,12.504591 5.5,11.4972317 5.5,10.254591 L5.5,5.75 C5.5,4.50735931 6.50735931,3.5 7.75,3.5 L11.2495415,3.49949432 L11.2503312,2.75049432 C11.2503312,2.37079855 11.5324851,2.05700336 11.8985607,2.00734093 L12.0003312,2.00049432 L11.8985607,2.00734093 Z M9.74928905,6.5 C9.05932576,6.5 8.5,7.05932576 8.5,7.74928905 C8.5,8.43925235 9.05932576,8.99857811 9.74928905,8.99857811 C10.4392523,8.99857811 10.9985781,8.43925235 10.9985781,7.74928905 C10.9985781,7.05932576 10.4392523,6.5 9.74928905,6.5 Z M14.2420255,6.5 C13.5520622,6.5 12.9927364,7.05932576 12.9927364,7.74928905 C12.9927364,8.43925235 13.5520622,8.99857811 14.2420255,8.99857811 C14.9319888,8.99857811 15.4913145,8.43925235 15.4913145,7.74928905 C15.4913145,7.05932576 14.9319888,6.5 14.2420255,6.5 Z",
398
+ fill: "currentColor"
399
+ }
400
+ ));
401
+ }, "BotIcon");
402
+
403
+ // src/i18n/en.json
404
+ var en_default = {
405
+ CHATBOT_NAME: "IBTI Chatbot (Beta)",
406
+ ORGANIZATION_NAME: "IBTI IT Solutions",
407
+ STATUS: {
408
+ ONLINE: "Now online",
409
+ LOADING: "Loading...",
410
+ WRITING: "Writing answer...",
411
+ UNAVAILABLE: "Unavailable"
412
+ },
413
+ ERRORS: {
414
+ UNKNOWN: "Sorry. We couldn't provide a response."
415
+ },
416
+ INPUT_PLACEHOLDER: "Ask something",
417
+ CHATBOT_BAR: {
418
+ OPEN: "Open {{CHATBOT_NAME}}",
419
+ CLOSE: "Close {{CHATBOT_NAME}}"
420
+ },
421
+ SUGGESTED_QUESTIONS: {
422
+ TITLE: "You may like to ask:",
423
+ LOADING: "Loading suggested questions..."
424
+ },
425
+ CHAT_FEEDBACK: {
426
+ TITLE: "Rate the Chatbot",
427
+ DESCRIPTION: "Give us a feedback about our bot assistant.",
428
+ MESSAGE_FIELD: "Description",
429
+ SUBMIT_BUTTON: "Send",
430
+ CLOSE_BUTTON: "Close"
431
+ }
432
+ };
433
+
434
+ // src/i18n/pt_BR.json
435
+ var pt_BR_default = {
436
+ CHATBOT_NAME: "Assistente do IBTI (Beta)",
437
+ ORGANIZATION_NAME: "IBTI Solu\xE7\xF5es em TI",
438
+ STATUS: {
439
+ ONLINE: "Ativo agora",
440
+ LOADING: "Carregando...",
441
+ WRITING: "Escrevendo resposta...",
442
+ UNAVAILABLE: "Indispon\xEDvel"
443
+ },
444
+ ERRORS: {
445
+ UNKNOWN: "Desculpe. N\xE3o conseguimos fornecer uma resposta."
446
+ },
447
+ INPUT_PLACEHOLDER: "Pergunte alguma coisa",
448
+ CHATBOT_BAR: {
449
+ OPEN: "Abrir IBTI ChatBot",
450
+ CLOSE: "Fechar IBTI ChatBot"
451
+ },
452
+ SUGGESTED_QUESTIONS: {
453
+ TITLE: "Voc\xEA pode querer perguntar:",
454
+ LOADING: "Carregando perguntas sugeridas..."
455
+ },
456
+ CHAT_FEEDBACK: {
457
+ TITLE: "Avalie o Chatbot",
458
+ DESCRIPTION: "D\xEA-nos um feedback sobre o nosso assistente virtual.",
459
+ MESSAGE_FIELD: "Descri\xE7\xE3o",
460
+ SUBMIT_BUTTON: "Enviar",
461
+ CLOSE_BUTTON: "Fechar"
462
+ }
463
+ };
464
+
465
+ // src/i18n/index.ts
466
+ var i18n = {
467
+ en: en_default,
468
+ "pt-BR": pt_BR_default
469
+ };
470
+
471
+ // src/hooks/useI18n.ts
472
+ var useI18n = /* @__PURE__ */ __name(() => {
473
+ const { locale } = useChatbot();
474
+ return i18n[locale];
475
+ }, "useI18n");
476
+
477
+ // src/components/ChatbotStatusLabel/index.tsx
478
+ import React5 from "react";
479
+
480
+ // src/components/ChatbotStatusLabel/styles.ts
481
+ import { styled as styled3 } from "styled-components";
482
+ var Wrapper3 = styled3.span`
483
+ color: ${(props) => props.theme.colors.content.detail};
484
+ font-size: ${(props) => props.theme.fontSizes.small};
485
+
486
+ &::before {
487
+ content: '';
488
+ display: inline-block;
489
+ height: 8px;
490
+ width: 8px;
491
+ border-radius: 4px;
492
+ background: ${(props) => props.$signalColor};
493
+ margin-right: 4px;
494
+ }
495
+ `;
496
+
497
+ // src/components/ChatbotStatusLabel/index.tsx
498
+ import { useTheme } from "styled-components";
499
+ var ChatbotStatusLabel = /* @__PURE__ */ __name(({}) => {
500
+ const { status } = useChatbot_default();
501
+ const i18nTerms = useI18n();
502
+ const theme = useTheme();
503
+ const statusLabel = {
504
+ online: i18nTerms.STATUS.ONLINE,
505
+ loading: i18nTerms.STATUS.LOADING,
506
+ writing: i18nTerms.STATUS.WRITING,
507
+ unavailable: i18nTerms.STATUS.UNAVAILABLE
508
+ };
509
+ const statusColor = {
510
+ online: theme.colors.palette.success.normal,
511
+ loading: theme.colors.palette.warning.normal,
512
+ writing: theme.colors.palette.warning.normal,
513
+ unavailable: theme.colors.palette.error.normal
514
+ };
515
+ return /* @__PURE__ */ React5.createElement(Wrapper3, { $signalColor: statusColor[status] }, statusLabel[status]);
516
+ }, "ChatbotStatusLabel");
517
+ var ChatbotStatusLabel_default = ChatbotStatusLabel;
518
+
519
+ // src/components/ChatbotHeader/index.tsx
520
+ var ChatbotHeader = /* @__PURE__ */ __name(({}) => {
521
+ const i18n2 = useI18n();
522
+ return /* @__PURE__ */ React6.createElement(Wrapper2, null, /* @__PURE__ */ React6.createElement(ProfileImage, null, /* @__PURE__ */ React6.createElement(BotIcon, null)), /* @__PURE__ */ React6.createElement(ProfileInfo, null, /* @__PURE__ */ React6.createElement(Name, null, i18n2.ORGANIZATION_NAME), /* @__PURE__ */ React6.createElement(ChatbotStatusLabel_default, null)));
523
+ }, "ChatbotHeader");
524
+ var ChatbotHeader_default = ChatbotHeader;
525
+
526
+ // src/components/ChatbotBody/index.tsx
527
+ import React9, { useEffect as useEffect5, useRef as useRef3 } from "react";
528
+
529
+ // src/components/ChatbotBody/styles.ts
530
+ import { styled as styled4 } from "styled-components";
531
+ var ChatbotInput = styled4.div``;
532
+ var MessagesList = styled4.ul`
533
+ padding: ${(props) => props.theme.spacing.components.medium};
534
+ display: flex;
535
+ flex-direction: column;
536
+ gap: ${(props) => props.theme.spacing.components.small};
537
+ `;
538
+ var Wrapper4 = styled4.div`
539
+ overflow: hidden;
540
+ overflow-y: auto;
541
+ flex: 1;
542
+ height: 100%;
543
+ `;
544
+
545
+ // src/components/MessageBalloon/index.tsx
546
+ import React7 from "react";
547
+
548
+ // src/components/MessageBalloon/styles.ts
549
+ import { css as css2, styled as styled5 } from "styled-components";
550
+ var Time = styled5.span`
551
+ font-size: ${(props) => props.theme.fontSizes.small};
552
+ `;
553
+ var Balloon = styled5.div`
554
+ padding: ${(props) => props.theme.spacing.components.small};
555
+ width: max-content;
556
+ max-width: 100%;
557
+ border-radius: ${(props) => props.theme.borderRadius.medium};
558
+ position: relative;
559
+ line-height: 1.5em;
560
+
561
+ &::after {
562
+ position: absolute;
563
+ content: '';
564
+ width: 0;
565
+ height: 0;
566
+ border-style: solid;
567
+ }
568
+
569
+ ${({ $type }) => {
570
+ switch ($type) {
571
+ case "received":
572
+ return css2`
573
+ background: ${(props) => props.theme.colors.layers[2].background};
574
+ border-top-left-radius: 0;
575
+
576
+ &::after {
577
+ border-width: 0px 10px 10px 0;
578
+ border-color: transparent
579
+ ${(props) => props.theme.colors.layers[2].background} transparent
580
+ transparent;
581
+ top: 0;
582
+ left: -10px;
583
+ }
584
+ `;
585
+ case "sent":
586
+ return css2`
587
+ background: ${(props) => props.theme.colors.palette.primary.normal};
588
+ border-top-right-radius: 0;
589
+ margin-left: auto;
590
+
591
+ &::after {
592
+ border-width: 0px 0px 10px 10px;
593
+ border-color: transparent transparent transparent
594
+ ${(props) => props.theme.colors.palette.primary.normal};
595
+ top: 0;
596
+ right: -10px;
597
+ }
598
+ `;
599
+ }
600
+ }}
601
+
602
+ // Markdown styles
603
+
604
+ display: flex;
605
+ flex-direction: column;
606
+ gap: 1.3em;
607
+
608
+ a {
609
+ color: ${(props) => props.theme.colors.palette.primary.normal};
610
+ font-weight: semibold;
611
+ }
612
+
613
+ ol,
614
+ ul {
615
+ display: flex;
616
+ flex-direction: column;
617
+ gap: 1.3em;
618
+ }
619
+ `;
620
+ var Label = styled5.span`
621
+ font-size: ${(props) => props.theme.fontSizes.small};
622
+ `;
623
+ var Wrapper5 = styled5.div`
624
+ font-size: ${(props) => props.theme.fontSizes.small};
625
+ display: flex;
626
+ flex-direction: column;
627
+ gap: ${(props) => props.theme.spacing.components.xsmall};
628
+
629
+ ${(props) => {
630
+ switch (props.$type) {
631
+ case "received":
632
+ return css2``;
633
+ default:
634
+ case "sent":
635
+ return css2`
636
+ text-align: right;
637
+ `;
638
+ }
639
+ }};
640
+ `;
641
+
642
+ // src/utils/formatMessageTime.ts
643
+ import { intlFormatDistance } from "date-fns";
644
+ var formatMessageTime = /* @__PURE__ */ __name((dateUTC, currentDateUTC, locale) => {
645
+ return intlFormatDistance(dateUTC, currentDateUTC, { locale });
646
+ }, "formatMessageTime");
647
+
648
+ // src/components/MessageBalloon/index.tsx
649
+ import Markdown from "react-markdown";
650
+ var messageTypes = {
651
+ assistant: "received",
652
+ user: "sent"
653
+ };
654
+ var MessageBalloon = /* @__PURE__ */ __name(({
655
+ data,
656
+ currentDateUTC
657
+ }) => {
658
+ const messageType = messageTypes[data.role];
659
+ const i18n2 = useI18n();
660
+ const { locale } = useChatbot_default();
661
+ return /* @__PURE__ */ React7.createElement(Wrapper5, { $type: messageType }, data.role == "assistant" && /* @__PURE__ */ React7.createElement(Label, null, i18n2.CHATBOT_NAME), /* @__PURE__ */ React7.createElement(Balloon, { $type: messageType }, /* @__PURE__ */ React7.createElement(Markdown, null, data.content)), /* @__PURE__ */ React7.createElement(Time, null, formatMessageTime(data.timestamp, currentDateUTC, locale)));
662
+ }, "MessageBalloon");
663
+ var MessageBalloon_default = MessageBalloon;
664
+
665
+ // src/hooks/useElementScroll.ts
666
+ import { useEffect as useEffect3, useState as useState4 } from "react";
667
+ var useElementScroll = /* @__PURE__ */ __name((elementRef) => {
668
+ const [scrollProgress, setScrollProgress] = useState4(0);
669
+ const calculateScrollPercentage = /* @__PURE__ */ __name(() => {
670
+ if (!elementRef.current) return;
671
+ const element = elementRef.current;
672
+ const elementHeight = element.clientHeight;
673
+ const elementScrollHeight = element.scrollHeight;
674
+ const scrollTop = element.scrollTop;
675
+ const scrollableHeight = elementScrollHeight - elementHeight;
676
+ const scrolled = scrollTop / scrollableHeight * 100;
677
+ const progressValue = Math.min(100, Math.max(0, scrolled));
678
+ setScrollProgress(isNaN(progressValue) ? 100 : progressValue);
679
+ }, "calculateScrollPercentage");
680
+ useEffect3(() => {
681
+ if (!elementRef.current) return;
682
+ const handleIntersection = /* @__PURE__ */ __name(() => {
683
+ calculateScrollPercentage();
684
+ }, "handleIntersection");
685
+ const handleScroll = /* @__PURE__ */ __name(() => {
686
+ calculateScrollPercentage();
687
+ }, "handleScroll");
688
+ const element = elementRef.current;
689
+ element.addEventListener("scroll", handleScroll);
690
+ const intersectionObserver = new IntersectionObserver(() => {
691
+ handleIntersection();
692
+ });
693
+ intersectionObserver.observe(element);
694
+ calculateScrollPercentage();
695
+ return () => {
696
+ if (!elementRef.current) return;
697
+ elementRef.current.removeEventListener("scroll", handleScroll);
698
+ intersectionObserver.disconnect();
699
+ };
700
+ }, [elementRef]);
701
+ const scrollToTop = /* @__PURE__ */ __name(() => {
702
+ elementRef.current?.scrollTo({ top: elementRef.current.scrollHeight });
703
+ }, "scrollToTop");
704
+ const scrollToEnd = /* @__PURE__ */ __name(() => {
705
+ elementRef.current?.scrollTo(0, elementRef.current.scrollHeight);
706
+ }, "scrollToEnd");
707
+ return {
708
+ scrollProgress,
709
+ scrollToTop,
710
+ scrollToEnd
711
+ };
712
+ }, "useElementScroll");
713
+
714
+ // src/hooks/useUpdatedTime.ts
715
+ import { useEffect as useEffect4, useState as useState5 } from "react";
716
+ var useUpdatedTime = /* @__PURE__ */ __name(({
717
+ intervalInSeconds
718
+ }) => {
719
+ const [date, setDate] = useState5(/* @__PURE__ */ new Date());
720
+ useEffect4(() => {
721
+ const time = 1e3 * intervalInSeconds;
722
+ const interval = setInterval(() => {
723
+ const now = /* @__PURE__ */ new Date();
724
+ setDate(now);
725
+ }, time);
726
+ return () => clearInterval(interval);
727
+ }, []);
728
+ return {
729
+ dateUTC: date.toUTCString()
730
+ };
731
+ }, "useUpdatedTime");
732
+
733
+ // src/components/SuggestedQuestions/index.tsx
734
+ import React8 from "react";
735
+
736
+ // src/components/SuggestedQuestions/styles.ts
737
+ import { Button } from "@ibti-tech/ui";
738
+ import { styled as styled6 } from "styled-components";
739
+ var QuestionButton = styled6(Button)`
740
+ text-align: left;
741
+ justify-content: flex-start;
742
+ height: auto;
743
+ padding: ${(props) => props.theme.spacing.components.small};
744
+ `;
745
+ var QuestionListItem = styled6.li``;
746
+ var QuestionList = styled6.ul`
747
+ display: flex;
748
+ flex-direction: column;
749
+ gap: ${(props) => props.theme.spacing.components.small};
750
+ `;
751
+ var Title = styled6.span`
752
+ display: block;
753
+ font-size: ${(props) => props.theme.fontSizes.small};
754
+ font-weight: ${(props) => props.theme.fontFamily.montserrat.weights.bold};
755
+ margin-bottom: ${(props) => props.theme.spacing.components.medium};
756
+ /* text-align: center; */
757
+ `;
758
+ var Wrapper6 = styled6.div`
759
+ padding: ${(props) => props.theme.spacing.components.medium};
760
+ `;
761
+
762
+ // src/components/SuggestedQuestions/index.tsx
763
+ var SuggestedQuestions = /* @__PURE__ */ __name(({}) => {
764
+ const i18n2 = useI18n();
765
+ const { suggestedQuestions, makeQuestion } = useChatbot_default();
766
+ if (suggestedQuestions.data.length === 0) return;
767
+ return /* @__PURE__ */ React8.createElement(Wrapper6, null, /* @__PURE__ */ React8.createElement(Title, null, i18n2.SUGGESTED_QUESTIONS.TITLE), /* @__PURE__ */ React8.createElement(QuestionList, null, suggestedQuestions.data.map((question) => /* @__PURE__ */ React8.createElement(QuestionListItem, { key: question }, /* @__PURE__ */ React8.createElement(
768
+ QuestionButton,
769
+ {
770
+ label: question,
771
+ size: "small",
772
+ variant: "layerBased",
773
+ layer: 2,
774
+ onClick: () => {
775
+ makeQuestion(question);
776
+ },
777
+ fillWidth: true,
778
+ shape: "rounded"
779
+ }
780
+ )))));
781
+ }, "SuggestedQuestions");
782
+ var SuggestedQuestions_default = SuggestedQuestions;
783
+
784
+ // src/components/ChatbotBody/index.tsx
785
+ var ChatbotBody = /* @__PURE__ */ __name(({}) => {
786
+ const { chatMessages, suggestedQuestions } = useChatbot();
787
+ const scrollableElementRef = useRef3(null);
788
+ const { scrollProgress, scrollToEnd } = useElementScroll(scrollableElementRef);
789
+ const { dateUTC } = useUpdatedTime({ intervalInSeconds: 15 });
790
+ useEffect5(() => {
791
+ if (scrollProgress > 85) scrollToEnd();
792
+ document.addEventListener(
793
+ CUSTOM_EVENT_NAMES.ASSISTANT_ANSWER_FINISHED,
794
+ scrollToEnd
795
+ );
796
+ return () => {
797
+ document.removeEventListener(
798
+ CUSTOM_EVENT_NAMES.ASSISTANT_ANSWER_FINISHED,
799
+ scrollToEnd
800
+ );
801
+ };
802
+ }, [chatMessages, suggestedQuestions]);
803
+ return /* @__PURE__ */ React9.createElement(Wrapper4, { ref: scrollableElementRef }, /* @__PURE__ */ React9.createElement(MessagesList, null, chatMessages.map((msgData) => /* @__PURE__ */ React9.createElement(
804
+ MessageBalloon_default,
805
+ {
806
+ key: msgData.id,
807
+ data: msgData,
808
+ currentDateUTC: dateUTC
809
+ }
810
+ ))), /* @__PURE__ */ React9.createElement(SuggestedQuestions_default, null));
811
+ }, "ChatbotBody");
812
+ var ChatbotBody_default = ChatbotBody;
813
+
814
+ // src/components/ChatbotFooter/index.tsx
815
+ import React10, { useState as useState6 } from "react";
816
+
817
+ // src/components/ChatbotFooter/styles.ts
818
+ import { styled as styled7 } from "styled-components";
819
+ var LoadingSuggestedQuestions = styled7.span`
820
+ display: flex;
821
+ align-items: center;
822
+ gap: ${(props) => props.theme.spacing.components.small};
823
+ font-size: ${(props) => props.theme.fontSizes.small};
824
+ padding: ${(props) => props.theme.spacing.components.small}
825
+ ${(props) => props.theme.spacing.components.medium};
826
+ `;
827
+ var Form = styled7.form`
828
+ display: flex;
829
+ gap: ${(props) => props.theme.spacing.components.small};
830
+ `;
831
+ var Wrapper7 = styled7.div`
832
+ padding: ${(props) => props.theme.spacing.components.small};
833
+ `;
834
+
835
+ // src/components/ChatbotFooter/index.tsx
836
+ import { ArrowRightIcon, Spinner, TextInput } from "@ibti-tech/ui";
837
+ var ChatbotFooter = /* @__PURE__ */ __name(({}) => {
838
+ const { makeQuestion, loading, writing, suggestedQuestions } = useChatbot_default();
839
+ const [questionInput, setQuestionInput] = useState6("");
840
+ const i18nTerms = useI18n();
841
+ const handleSubmit = /* @__PURE__ */ __name(async (e) => {
842
+ e.preventDefault();
843
+ setQuestionInput("");
844
+ await makeQuestion(questionInput);
845
+ }, "handleSubmit");
846
+ return /* @__PURE__ */ React10.createElement(Wrapper7, { "data-testid": "chatbot-footer" }, suggestedQuestions.loading && /* @__PURE__ */ React10.createElement(LoadingSuggestedQuestions, null, /* @__PURE__ */ React10.createElement(Spinner, { size: "extra-small" }), i18nTerms.SUGGESTED_QUESTIONS.LOADING), /* @__PURE__ */ React10.createElement(Form, { onSubmit: handleSubmit }, /* @__PURE__ */ React10.createElement(
847
+ TextInput,
848
+ {
849
+ fillWidth: true,
850
+ shape: "pill",
851
+ disabled: loading || writing,
852
+ value: questionInput,
853
+ onChange: (e) => setQuestionInput(e.target.value),
854
+ placeholder: i18nTerms.INPUT_PLACEHOLDER,
855
+ button: {
856
+ icon: loading || writing ? void 0 : ArrowRightIcon,
857
+ loading,
858
+ disabled: loading || writing || !questionInput,
859
+ type: "submit"
860
+ }
861
+ }
862
+ )));
863
+ }, "ChatbotFooter");
864
+ var ChatbotFooter_default = ChatbotFooter;
865
+
866
+ // src/components/ChatbotToggle/styles.ts
867
+ import { screens as screens2 } from "@ibti-tech/ui";
868
+ import { styled as styled8 } from "styled-components";
869
+ var ButtonWrapper = styled8.button`
870
+ height: 32px;
871
+ width: 100%;
872
+ background: transparent;
873
+ /* background-color: ${(props) => props.theme.colors.layers[1].background};
874
+ border: 1px solid ${(props) => props.theme.colors.layers[1].border}; */
875
+ color: ${(props) => props.theme.colors.palette.primary.normal};
876
+ padding: ${(props) => props.theme.spacing.components.small};
877
+ font-size: ${(props) => props.theme.fontSizes.small};
878
+ font-weight: ${(props) => props.theme.fontFamily.montserrat.weights.semibold};
879
+ display: flex;
880
+ align-items: center;
881
+ justify-content: center;
882
+ gap: ${(props) => props.theme.spacing.components.small};
883
+ border-radius: ${(props) => props.theme.borderRadius.medium};
884
+
885
+ ${screens2.tablet} {
886
+ transition: ${(props) => props.theme.transitionDurations.default};
887
+
888
+ &:hover {
889
+ background-color: ${(props) => props.theme.colors.layers[1].hoveredBackground};
890
+ cursor: pointer;
891
+ }
892
+ }
893
+ `;
894
+
895
+ // src/components/ChatbotToggle/index.tsx
896
+ import React11 from "react";
897
+ import { ChevronDownIcon, ChevronUpIcon } from "@ibti-tech/ui/dist/icons";
898
+ var ChatbotToggle = /* @__PURE__ */ __name(() => {
899
+ const { opened, openedToggle } = useChatbot_default();
900
+ const i18nTerms = useI18n();
901
+ const toggleText = (opened ? i18nTerms.CHATBOT_BAR.CLOSE : i18nTerms.CHATBOT_BAR.OPEN).replace("{{CHATBOT_NAME}}", i18nTerms.CHATBOT_NAME);
902
+ const Chevron = opened ? ChevronUpIcon : ChevronDownIcon;
903
+ return /* @__PURE__ */ React11.createElement(ButtonWrapper, { onClick: openedToggle }, /* @__PURE__ */ React11.createElement(BotIcon, null), /* @__PURE__ */ React11.createElement("span", null, toggleText), /* @__PURE__ */ React11.createElement(Chevron, null));
904
+ }, "ChatbotToggle");
905
+ var ChatbotToggle_default = ChatbotToggle;
906
+
907
+ // src/components/ChatUserFeedbackRating/index.tsx
908
+ import React12, { useState as useState8 } from "react";
909
+ import { Button as Button2, RatingStars, TextArea } from "@ibti-tech/ui";
910
+
911
+ // src/components/ChatUserFeedbackRating/styles.ts
912
+ import { CardBase } from "@ibti-tech/ui";
913
+ import { css as css3, styled as styled9 } from "styled-components";
914
+ var ActionButtons = styled9.div`
915
+ display: flex;
916
+ justify-content: flex-end;
917
+ gap: ${(props) => props.theme.spacing.components.small};
918
+ `;
919
+ var Form2 = styled9.form`
920
+ display: flex;
921
+ flex-direction: column;
922
+ gap: ${(props) => props.theme.spacing.components.medium};
923
+ `;
924
+ var Description = styled9.div`
925
+ font-size: ${(props) => props.theme.fontSizes.small};
926
+ margin-bottom: ${(props) => props.theme.spacing.components.medium};
927
+ `;
928
+ var Title2 = styled9.span`
929
+ display: block;
930
+ font-weight: ${(props) => props.theme.fontFamily.montserrat.weights.bold};
931
+ margin-bottom: ${(props) => props.theme.spacing.components.small};
932
+ `;
933
+ var Box = styled9(CardBase)`
934
+ width: 100%;
935
+ height: max-content;
936
+ padding: ${(props) => props.theme.spacing.components.medium};
937
+ transition: ${(props) => props.theme.transitionDurations.default};
938
+ `;
939
+ var Wrapper8 = styled9.div`
940
+ position: absolute;
941
+ z-index: 1;
942
+ left: 0;
943
+ top: 0;
944
+ height: 100%;
945
+ width: 100%;
946
+ display: flex;
947
+ align-items: center;
948
+ justify-content: center;
949
+ padding: ${(props) => props.theme.spacing.components.medium};
950
+ transition: ${(props) => props.theme.transitionDurations.default};
951
+
952
+ ${(props) => props.$opened ? css3`
953
+ background-color: rgba(0, 0, 0, 0.15);
954
+ backdrop-filter: blur(3px);
955
+ opacity: 1;
956
+ ` : css3`
957
+ pointer-events: none;
958
+ opacity: 0;
959
+
960
+ ${Box} {
961
+ transform: scale(0.9);
962
+ }
963
+ `}
964
+ `;
965
+
966
+ // src/contexts/Chatbot/useChatFeedbackBox.ts
967
+ import { useEffect as useEffect6, useState as useState7 } from "react";
968
+ var useChatFeedbackBox = /* @__PURE__ */ __name(() => {
969
+ const { chatMessages, apiURL, locale } = useChatbot_default();
970
+ const [rated, setRated] = useState7(false);
971
+ const [opened, setOpened] = useState7(false);
972
+ const [loading, setLoading] = useState7(false);
973
+ useEffect6(() => {
974
+ if (chatMessages.length > 6 && !rated) setOpened(true);
975
+ }, [chatMessages]);
976
+ const close = /* @__PURE__ */ __name(() => {
977
+ setOpened(false);
978
+ setRated(true);
979
+ }, "close");
980
+ const sendUserRating = /* @__PURE__ */ __name(async ({
981
+ ratingScore,
982
+ description
983
+ }) => {
984
+ try {
985
+ setLoading(true);
986
+ await sendUserChatFeedback({
987
+ apiURL,
988
+ chatContext: chatMessages.map((item) => ({
989
+ role: item.role,
990
+ content: item.content
991
+ })),
992
+ locale,
993
+ ratingScore,
994
+ description
995
+ });
996
+ } finally {
997
+ setLoading(false);
998
+ setRated(true);
999
+ setOpened(false);
1000
+ }
1001
+ }, "sendUserRating");
1002
+ return {
1003
+ opened,
1004
+ close,
1005
+ sendUserRating,
1006
+ loading
1007
+ };
1008
+ }, "useChatFeedbackBox");
1009
+
1010
+ // src/components/ChatUserFeedbackRating/index.tsx
1011
+ var ChatUserFeedbackRating = /* @__PURE__ */ __name(({}) => {
1012
+ const chatFeedbackBox = useChatFeedbackBox();
1013
+ const [ratingValue, setRatingValue] = useState8(5);
1014
+ const [descriptionValue, setDescriptionValue] = useState8("");
1015
+ const i18nTerms = useI18n();
1016
+ const handleSubmit = /* @__PURE__ */ __name((e) => {
1017
+ e.preventDefault();
1018
+ chatFeedbackBox.sendUserRating({
1019
+ description: descriptionValue,
1020
+ ratingScore: ratingValue
1021
+ });
1022
+ }, "handleSubmit");
1023
+ return /* @__PURE__ */ React12.createElement(Wrapper8, { $opened: chatFeedbackBox.opened }, /* @__PURE__ */ React12.createElement(Box, null, /* @__PURE__ */ React12.createElement(Title2, null, i18nTerms.CHAT_FEEDBACK.TITLE), /* @__PURE__ */ React12.createElement(Description, null, i18nTerms.CHAT_FEEDBACK.DESCRIPTION), /* @__PURE__ */ React12.createElement(Form2, { onSubmit: handleSubmit }, /* @__PURE__ */ React12.createElement(
1024
+ RatingStars,
1025
+ {
1026
+ value: ratingValue,
1027
+ setValue: setRatingValue,
1028
+ layer: 2
1029
+ }
1030
+ ), /* @__PURE__ */ React12.createElement(
1031
+ TextArea,
1032
+ {
1033
+ layer: 2,
1034
+ placeholder: i18nTerms.CHAT_FEEDBACK.MESSAGE_FIELD,
1035
+ fillWidth: true,
1036
+ value: descriptionValue,
1037
+ onChange: (e) => setDescriptionValue(e.target.value)
1038
+ }
1039
+ ), /* @__PURE__ */ React12.createElement(ActionButtons, null, /* @__PURE__ */ React12.createElement(
1040
+ Button2,
1041
+ {
1042
+ onClick: chatFeedbackBox.close,
1043
+ label: i18nTerms.CHAT_FEEDBACK.CLOSE_BUTTON,
1044
+ size: "small",
1045
+ variant: "layerBased"
1046
+ }
1047
+ ), /* @__PURE__ */ React12.createElement(
1048
+ Button2,
1049
+ {
1050
+ type: "submit",
1051
+ label: i18nTerms.CHAT_FEEDBACK.SUBMIT_BUTTON,
1052
+ size: "small",
1053
+ loading: chatFeedbackBox.loading
1054
+ }
1055
+ )))));
1056
+ }, "ChatUserFeedbackRating");
1057
+
1058
+ // src/components/ChatbotDevice/index.tsx
1059
+ var ChatbotDevice = /* @__PURE__ */ __name(({}) => {
1060
+ const { opened } = useChatbot_default();
1061
+ return /* @__PURE__ */ React13.createElement(Wrapper, { $opened: opened }, /* @__PURE__ */ React13.createElement(ChatUserFeedbackRating, null), /* @__PURE__ */ React13.createElement(ChatbotHeader_default, null), /* @__PURE__ */ React13.createElement(ChatbotBody_default, null), /* @__PURE__ */ React13.createElement(ChatbotFooter_default, null), /* @__PURE__ */ React13.createElement(ChatbotToggle_default, null));
1062
+ }, "ChatbotDevice");
1063
+ var ChatbotDevice_default = ChatbotDevice;
1064
+
1065
+ // src/components/ChatbotBar/styles.ts
1066
+ import { breakpoints as breakpoints3, screens as screens3 } from "@ibti-tech/ui";
1067
+ import { Container } from "@ibti-tech/ui/dist/components/Container";
1068
+ import { styled as styled10 } from "styled-components";
1069
+ var BarContainer = styled10(Container)`
1070
+ @media screen and (max-width: ${breakpoints3.tablet}px) {
1071
+ padding: 0;
1072
+ }
1073
+
1074
+ ${screens3.tablet} {
1075
+ display: flex;
1076
+ justify-content: flex-end;
1077
+ }
1078
+ `;
1079
+ var Wrapper9 = styled10.div`
1080
+ width: 100%;
1081
+ padding-bottom: ${(props) => props.theme.spacing.components.xsmall};
1082
+ background-color: ${(props) => props.theme.colors.layers[0].background};
1083
+ border-bottom: 1px solid ${(props) => props.theme.colors.layers[0].border};
1084
+ height: 40px;
1085
+ z-index: 2;
1086
+ pointer-events: none;
1087
+ z-index: ${(props) => props.theme.zIndex.modals};
1088
+ `;
1089
+
1090
+ // src/components/ChatbotBar/index.tsx
1091
+ import React14 from "react";
1092
+ var ChatbotBar = /* @__PURE__ */ __name(() => {
1093
+ return /* @__PURE__ */ React14.createElement(Wrapper9, null, /* @__PURE__ */ React14.createElement(BarContainer, null, /* @__PURE__ */ React14.createElement(ChatbotDevice_default, null)));
1094
+ }, "ChatbotBar");
1095
+ export {
1096
+ ChatbotBar,
1097
+ ChatbotContext,
1098
+ ChatbotDevice,
1099
+ ChatbotProvider,
1100
+ useChatbot
1101
+ };