@ibti-tech/chatbot 0.2.0 → 0.5.7

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 CHANGED
@@ -17,10 +17,10 @@ var useChatbot = /* @__PURE__ */ __name(() => {
17
17
  var useChatbot_default = useChatbot;
18
18
 
19
19
  // src/contexts/Chatbot/provider.tsx
20
- import React3, { useMemo, useRef, useState as useState3 } from "react";
20
+ import React3, { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState3 } from "react";
21
21
 
22
22
  // src/contexts/Chatbot/useChatbotMessages.ts
23
- import { useEffect, useState as useState2 } from "react";
23
+ import { useEffect, useState as useState2, useRef } from "react";
24
24
  import { v4 as uuidv4 } from "uuid";
25
25
 
26
26
  // src/contexts/Chatbot/useChatbotSuggestedQuestions.ts
@@ -66,8 +66,10 @@ var en_default = {
66
66
  ONLINE: "Now online",
67
67
  LOADING: "Loading...",
68
68
  WRITING: "Writing answer...",
69
- UNAVAILABLE: "Unavailable"
69
+ UNAVAILABLE: "Unavailable",
70
+ INACTIVE: "Inactive"
70
71
  },
72
+ WRITING_MESSAGE: "Writing",
71
73
  ERRORS: {
72
74
  UNKNOWN: "Sorry. We couldn't provide a response."
73
75
  },
@@ -87,7 +89,13 @@ var en_default = {
87
89
  SUBMIT_BUTTON: "Send",
88
90
  CLOSE_BUTTON: "Close",
89
91
  RATE: "Rate assistant"
90
- }
92
+ },
93
+ MESSAGE_ACTIONS: {
94
+ COPY: "Copy message",
95
+ SHARE: "Share message",
96
+ RESEND: "Resend message"
97
+ },
98
+ USER_LABEL: "Me"
91
99
  };
92
100
 
93
101
  // src/i18n/pt_BR.json
@@ -97,8 +105,10 @@ var pt_BR_default = {
97
105
  ONLINE: "Ativo agora",
98
106
  LOADING: "Carregando...",
99
107
  WRITING: "Escrevendo resposta...",
100
- UNAVAILABLE: "Indispon\xEDvel"
108
+ UNAVAILABLE: "Indispon\xEDvel",
109
+ INACTIVE: "Inativo"
101
110
  },
111
+ WRITING_MESSAGE: "Escrevendo",
102
112
  ERRORS: {
103
113
  UNKNOWN: "Desculpe. N\xE3o conseguimos fornecer uma resposta."
104
114
  },
@@ -118,7 +128,13 @@ var pt_BR_default = {
118
128
  SUBMIT_BUTTON: "Enviar",
119
129
  CLOSE_BUTTON: "Fechar",
120
130
  RATE: "Avaliar assistente"
121
- }
131
+ },
132
+ MESSAGE_ACTIONS: {
133
+ COPY: "Copiar mensagem",
134
+ SHARE: "Compartilhar mensagem",
135
+ RESEND: "Reenviar mensagem"
136
+ },
137
+ USER_LABEL: "Eu"
122
138
  };
123
139
 
124
140
  // src/i18n/index.ts
@@ -184,10 +200,46 @@ var sendUserChatFeedback = /* @__PURE__ */ __name(async ({
184
200
  message: description
185
201
  })
186
202
  }), "sendUserChatFeedback");
203
+ var checkApiHealth = /* @__PURE__ */ __name(async (apiUrl, locale = "en") => {
204
+ const controller = new AbortController();
205
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
206
+ try {
207
+ const req = await fetch(`${apiUrl}/chat/welcome?locale=${locale}`, {
208
+ method: "HEAD",
209
+ // Use HEAD to minimize data transfer
210
+ signal: controller.signal
211
+ });
212
+ clearTimeout(timeoutId);
213
+ return req.ok;
214
+ } catch (err) {
215
+ clearTimeout(timeoutId);
216
+ const fallbackController = new AbortController();
217
+ const fallbackTimeoutId = setTimeout(
218
+ () => fallbackController.abort(),
219
+ 5e3
220
+ );
221
+ try {
222
+ const req = await fetch(`${apiUrl}/chat/welcome?locale=${locale}`, {
223
+ method: "GET",
224
+ signal: fallbackController.signal
225
+ });
226
+ clearTimeout(fallbackTimeoutId);
227
+ return req.ok;
228
+ } catch {
229
+ clearTimeout(fallbackTimeoutId);
230
+ return false;
231
+ }
232
+ }
233
+ }, "checkApiHealth");
187
234
  var getWelcomeMessage = /* @__PURE__ */ __name(async (apiUrl, locale = "en") => {
188
235
  const req = await fetch(`${apiUrl}/chat/welcome?locale=${locale}`, {
189
236
  method: "GET"
190
237
  });
238
+ if (!req.ok) {
239
+ throw new Error(
240
+ i18n[locale].ERRORS.UNKNOWN + ` (status: ${req.status})`
241
+ );
242
+ }
191
243
  const data = await req.json();
192
244
  return data.welcomeMessage;
193
245
  }, "getWelcomeMessage");
@@ -196,11 +248,19 @@ var getWelcomeMessage = /* @__PURE__ */ __name(async (apiUrl, locale = "en") =>
196
248
  import { parseCookies, setCookie } from "nookies";
197
249
  var ChatCookieManager = class {
198
250
  constructor() {
251
+ this.storageKey = "ibtiChatbot@chat";
199
252
  this.cookieKey = "ibtiChatbot@chat";
253
+ // Keep for migration purposes
254
+ this.maxAgeInHours = 24;
200
255
  }
201
256
  static {
202
257
  __name(this, "ChatCookieManager");
203
258
  }
259
+ /**
260
+ * Validates that the data can be safely serialized to JSON
261
+ * @param data - Data to validate
262
+ * @throws Error if data cannot be serialized
263
+ */
204
264
  checkValidJSONData(data) {
205
265
  try {
206
266
  JSON.parse(JSON.stringify(data));
@@ -208,27 +268,144 @@ var ChatCookieManager = class {
208
268
  throw new Error("Invalid chat context data.");
209
269
  }
210
270
  }
211
- getData() {
212
- const cookies = parseCookies();
213
- if (!cookies[this.cookieKey]) {
214
- this.saveData([]);
215
- return [];
271
+ /**
272
+ * Checks if the stored data has expired based on creation timestamp
273
+ * @param createdAt - ISO timestamp string when data was created
274
+ * @returns true if data is older than maxAgeInHours
275
+ */
276
+ isDataExpired(createdAt) {
277
+ const now = (/* @__PURE__ */ new Date()).getTime();
278
+ const created = new Date(createdAt).getTime();
279
+ const maxAgeInMs = this.maxAgeInHours * 60 * 60 * 1e3;
280
+ return now - created > maxAgeInMs;
281
+ }
282
+ /**
283
+ * Migrates data from cookies to localStorage if cookies exist and localStorage doesn't
284
+ * @returns true if migration was performed
285
+ */
286
+ migrateFromCookies() {
287
+ if (typeof window === "undefined") return false;
288
+ if (localStorage.getItem(this.storageKey)) {
289
+ return false;
216
290
  }
217
291
  try {
218
- const data = JSON.parse(cookies[this.cookieKey]);
219
- return data;
292
+ const cookies = document.cookie.split(";");
293
+ const cookie = cookies.find((c) => {
294
+ const trimmed = c.trim();
295
+ return trimmed.startsWith(`${this.cookieKey}=`);
296
+ });
297
+ if (cookie) {
298
+ const equalIndex = cookie.indexOf("=");
299
+ if (equalIndex !== -1) {
300
+ const cookieValue = decodeURIComponent(cookie.substring(equalIndex + 1).trim());
301
+ const data = JSON.parse(cookieValue);
302
+ if (Array.isArray(data)) {
303
+ const storageData = {
304
+ messages: data,
305
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
306
+ };
307
+ localStorage.setItem(this.storageKey, JSON.stringify(storageData));
308
+ } else if (data.messages) {
309
+ localStorage.setItem(this.storageKey, JSON.stringify(data));
310
+ }
311
+ document.cookie = `${this.cookieKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
312
+ return true;
313
+ }
314
+ }
220
315
  } catch (err) {
221
- this.saveData([]);
222
- return [];
316
+ console.warn("Failed to migrate from cookies to localStorage:", err);
223
317
  }
318
+ return false;
224
319
  }
320
+ /**
321
+ * Retrieves chat messages from localStorage (client-side) or cookies (server-side).
322
+ * Automatically migrates from cookies to localStorage and cleans expired data.
323
+ * @returns Array of chat messages, empty array if none found or expired
324
+ */
325
+ getData() {
326
+ if (typeof window !== "undefined") {
327
+ this.migrateFromCookies();
328
+ const storedValue = localStorage.getItem(this.storageKey);
329
+ if (!storedValue) {
330
+ return [];
331
+ }
332
+ try {
333
+ const data = JSON.parse(storedValue);
334
+ if (Array.isArray(data)) {
335
+ const storageData = {
336
+ messages: data,
337
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
338
+ };
339
+ localStorage.setItem(this.storageKey, JSON.stringify(storageData));
340
+ return data;
341
+ }
342
+ if (data.createdAt && this.isDataExpired(data.createdAt)) {
343
+ this.saveData([]);
344
+ return [];
345
+ }
346
+ return data.messages || [];
347
+ } catch (err) {
348
+ console.warn("Failed to parse chat storage data:", err);
349
+ this.saveData([]);
350
+ return [];
351
+ }
352
+ } else {
353
+ const cookies = parseCookies();
354
+ const cookieValue = cookies[this.cookieKey];
355
+ if (!cookieValue) {
356
+ return [];
357
+ }
358
+ try {
359
+ const data = JSON.parse(cookieValue);
360
+ if (Array.isArray(data)) {
361
+ return data;
362
+ }
363
+ if (data.createdAt && this.isDataExpired(data.createdAt)) {
364
+ return [];
365
+ }
366
+ return data.messages || [];
367
+ } catch (err) {
368
+ console.warn("Failed to parse chat cookie:", err);
369
+ return [];
370
+ }
371
+ }
372
+ }
373
+ /**
374
+ * Saves chat messages to localStorage (client-side) or cookies (server-side).
375
+ * Data expires after 24 hours (checked on read, not enforced by storage).
376
+ * @param data - Array of chat messages to persist
377
+ */
225
378
  saveData(data) {
226
379
  try {
227
380
  this.checkValidJSONData(data);
228
- setCookie(null, this.cookieKey, JSON.stringify(data), {
229
- expiresIn: 60 * 60 * 24
230
- });
381
+ const storageData = {
382
+ messages: data,
383
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
384
+ };
385
+ const storageValue = JSON.stringify(storageData);
386
+ if (typeof window !== "undefined") {
387
+ try {
388
+ localStorage.setItem(this.storageKey, storageValue);
389
+ const verifyData = localStorage.getItem(this.storageKey);
390
+ if (!verifyData) {
391
+ throw new Error("Failed to verify localStorage save");
392
+ }
393
+ document.cookie = `${this.cookieKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
394
+ } catch (err) {
395
+ console.error("Failed to save to localStorage:", err);
396
+ if (err instanceof DOMException && err.code === 22) {
397
+ console.warn("localStorage quota exceeded. Consider clearing old data.");
398
+ }
399
+ throw new Error("Failed to save chat context data to localStorage");
400
+ }
401
+ } else {
402
+ setCookie(null, this.cookieKey, storageValue, {
403
+ expiresIn: 60 * 60 * 24
404
+ // 24 hours in seconds
405
+ });
406
+ }
231
407
  } catch (err) {
408
+ console.error("Failed to save chat data:", err);
232
409
  throw new Error("Fail to save chat context data");
233
410
  }
234
411
  }
@@ -254,6 +431,10 @@ var useChatbotMessages = /* @__PURE__ */ __name(({
254
431
  const [loading, setLoading] = useState2(false);
255
432
  const [writing, setWriting] = useState2(false);
256
433
  const [error, setError] = useState2();
434
+ const [apiConnectionError, setApiConnectionError] = useState2(false);
435
+ const prevWritingRef = useRef(false);
436
+ const chatMessagesRef = useRef([]);
437
+ const saveTimeoutRef = useRef(null);
257
438
  const suggestedQuestions = useChatbotSuggestedQuestions({
258
439
  chatContext: chatMessages.map((item) => ({
259
440
  role: item.role,
@@ -262,6 +443,30 @@ var useChatbotMessages = /* @__PURE__ */ __name(({
262
443
  apiURL,
263
444
  locale
264
445
  });
446
+ const persistMessages = /* @__PURE__ */ __name((messages, immediate = false) => {
447
+ if (!initialized) return;
448
+ chatMessagesRef.current = messages;
449
+ if (saveTimeoutRef.current) {
450
+ clearTimeout(saveTimeoutRef.current);
451
+ saveTimeoutRef.current = null;
452
+ }
453
+ if (immediate) {
454
+ try {
455
+ chatCookie.saveData(messages);
456
+ } catch (err) {
457
+ console.error("Failed to persist messages:", err);
458
+ }
459
+ } else {
460
+ saveTimeoutRef.current = setTimeout(() => {
461
+ try {
462
+ chatCookie.saveData(chatMessagesRef.current);
463
+ } catch (err) {
464
+ console.error("Failed to persist messages (debounced):", err);
465
+ }
466
+ saveTimeoutRef.current = null;
467
+ }, 500);
468
+ }
469
+ }, "persistMessages");
265
470
  const createQuestionMessageIntoHistory = /* @__PURE__ */ __name((question, id) => {
266
471
  const timestamp = (/* @__PURE__ */ new Date()).toUTCString();
267
472
  const messageData = {
@@ -275,9 +480,10 @@ var useChatbotMessages = /* @__PURE__ */ __name(({
275
480
  messageData
276
481
  ];
277
482
  setChatMessages(newHistory);
483
+ persistMessages(newHistory, true);
278
484
  return messageData;
279
485
  }, "createQuestionMessageIntoHistory");
280
- const createOrUpdateAnswerMessageIntoHistory = /* @__PURE__ */ __name((answer, id, error2 = false) => {
486
+ const createOrUpdateAnswerMessageIntoHistory = /* @__PURE__ */ __name((answer, id, error2 = false, shouldPersist = false) => {
281
487
  const timestamp = (/* @__PURE__ */ new Date()).toUTCString();
282
488
  const messageData = {
283
489
  id,
@@ -289,7 +495,7 @@ var useChatbotMessages = /* @__PURE__ */ __name(({
289
495
  setChatMessages((prev) => {
290
496
  const itemAlreadyExists = prev.some((item) => item.id == id);
291
497
  if (itemAlreadyExists) {
292
- return prev.map((item) => {
498
+ const updated = prev.map((item) => {
293
499
  if (item.id == id)
294
500
  return {
295
501
  ...item,
@@ -297,8 +503,12 @@ var useChatbotMessages = /* @__PURE__ */ __name(({
297
503
  };
298
504
  return item;
299
505
  });
506
+ persistMessages(updated, shouldPersist);
507
+ return updated;
300
508
  }
301
- return [...prev, messageData];
509
+ const newHistory = [...prev, messageData];
510
+ persistMessages(newHistory, shouldPersist);
511
+ return newHistory;
302
512
  });
303
513
  return messageData;
304
514
  }, "createOrUpdateAnswerMessageIntoHistory");
@@ -325,45 +535,107 @@ var useChatbotMessages = /* @__PURE__ */ __name(({
325
535
  onReceiving(receivedMessage) {
326
536
  setLoading(false);
327
537
  setWriting(true);
328
- createOrUpdateAnswerMessageIntoHistory(receivedMessage, answerId);
538
+ createOrUpdateAnswerMessageIntoHistory(receivedMessage, answerId, false, false);
329
539
  },
330
540
  onDone: /* @__PURE__ */ __name(() => {
331
541
  setWriting(false);
542
+ setTimeout(() => {
543
+ setChatMessages((currentMessages) => {
544
+ if (initialized && currentMessages.length > 0) {
545
+ if (saveTimeoutRef.current) {
546
+ clearTimeout(saveTimeoutRef.current);
547
+ saveTimeoutRef.current = null;
548
+ }
549
+ chatCookie.saveData(currentMessages);
550
+ chatMessagesRef.current = currentMessages;
551
+ }
552
+ return currentMessages;
553
+ });
554
+ }, 100);
332
555
  suggestedQuestions.fetchData();
333
556
  }, "onDone")
334
557
  });
335
558
  } catch (err) {
336
559
  if (err instanceof Error) {
337
- createOrUpdateAnswerMessageIntoHistory(err.message, answerId, true);
560
+ createOrUpdateAnswerMessageIntoHistory(err.message, answerId, true, true);
338
561
  }
339
- }
340
- chatCookie.saveData([
341
- ...chatMessages,
342
- {
343
- id: uuidv4(),
344
- timestamp: (/* @__PURE__ */ new Date()).toUTCString(),
345
- role: chatContext.role,
346
- content: chatContext.content
562
+ const isConnectionError = err instanceof TypeError || err instanceof Error && (err.message.toLowerCase().includes("fetch") || err.message.toLowerCase().includes("network") || err.message.toLowerCase().includes("failed to fetch"));
563
+ if (isConnectionError) {
564
+ setApiConnectionError(true);
347
565
  }
348
- ]);
566
+ setError(err);
567
+ setLoading(false);
568
+ setWriting(false);
569
+ }
349
570
  dispatchAssitantAnswer();
350
571
  }, "makeQuestion");
572
+ useEffect(() => {
573
+ if (!initialized) return;
574
+ if (prevWritingRef.current && !writing && !loading && chatMessages.length > 0) {
575
+ if (saveTimeoutRef.current) {
576
+ clearTimeout(saveTimeoutRef.current);
577
+ saveTimeoutRef.current = null;
578
+ }
579
+ try {
580
+ chatCookie.saveData(chatMessages);
581
+ chatMessagesRef.current = chatMessages;
582
+ } catch (err) {
583
+ console.error("Failed to persist messages on writing complete:", err);
584
+ }
585
+ }
586
+ prevWritingRef.current = writing;
587
+ }, [writing, loading, chatMessages, initialized]);
588
+ useEffect(() => {
589
+ chatMessagesRef.current = chatMessages;
590
+ }, [chatMessages]);
591
+ useEffect(() => {
592
+ return () => {
593
+ if (saveTimeoutRef.current) {
594
+ clearTimeout(saveTimeoutRef.current);
595
+ }
596
+ };
597
+ }, []);
351
598
  const init = /* @__PURE__ */ __name(async () => {
599
+ try {
600
+ const isApiAvailable = await checkApiHealth(apiURL, locale);
601
+ if (!isApiAvailable) {
602
+ setApiConnectionError(true);
603
+ setInitialized(true);
604
+ return;
605
+ }
606
+ setApiConnectionError(false);
607
+ } catch (err) {
608
+ setApiConnectionError(true);
609
+ setError(err);
610
+ setInitialized(true);
611
+ return;
612
+ }
352
613
  const savedData = chatCookie.getData();
353
614
  if (savedData && savedData.length > 0) {
354
615
  setChatMessages(savedData);
616
+ setInitialized(true);
355
617
  } else {
356
- const firstMessage = await getWelcomeMessage(apiURL, locale);
357
- setChatMessages([
358
- {
359
- id: uuidv4(),
360
- timestamp: (/* @__PURE__ */ new Date()).toUTCString(),
361
- role: "assistant",
362
- content: firstMessage
618
+ try {
619
+ const firstMessage = await getWelcomeMessage(apiURL, locale);
620
+ setChatMessages([
621
+ {
622
+ id: uuidv4(),
623
+ timestamp: (/* @__PURE__ */ new Date()).toUTCString(),
624
+ role: "assistant",
625
+ content: firstMessage
626
+ }
627
+ ]);
628
+ setApiConnectionError(false);
629
+ } catch (err) {
630
+ const isConnectionError = err instanceof TypeError || err instanceof Error && (err.message.toLowerCase().includes("fetch") || err.message.toLowerCase().includes("network") || err.message.toLowerCase().includes("failed to fetch"));
631
+ if (isConnectionError) {
632
+ setApiConnectionError(true);
363
633
  }
364
- ]);
634
+ setError(err);
635
+ } finally {
636
+ setInitialized(true);
637
+ }
365
638
  }
366
- setInitialized(true);
367
639
  }, "init");
368
640
  useEffect(() => {
369
641
  if (initialized) return;
@@ -375,6 +647,7 @@ var useChatbotMessages = /* @__PURE__ */ __name(({
375
647
  loading,
376
648
  writing,
377
649
  error,
650
+ apiConnectionError,
378
651
  suggestedQuestions
379
652
  };
380
653
  }, "useChatbotMessages");
@@ -393,7 +666,9 @@ var ChatbotProvider = /* @__PURE__ */ __name(({
393
666
  locale,
394
667
  children,
395
668
  apiURL,
396
- theme
669
+ theme,
670
+ isOpen = false,
671
+ texts
397
672
  }) => {
398
673
  const {
399
674
  makeQuestion,
@@ -401,22 +676,26 @@ var ChatbotProvider = /* @__PURE__ */ __name(({
401
676
  loading,
402
677
  writing,
403
678
  error,
679
+ apiConnectionError,
404
680
  suggestedQuestions
405
681
  } = useChatbotMessages_default({
406
682
  apiURL,
407
683
  locale
408
684
  });
409
- const [opened, setOpened] = useState3(false);
410
- const scrollRef = useRef(null);
685
+ const [opened, setOpened] = useState3(isOpen);
686
+ const scrollRef = useRef2(null);
687
+ useEffect2(() => {
688
+ setOpened(isOpen);
689
+ }, [isOpen]);
411
690
  const openedToggle = /* @__PURE__ */ __name(() => {
412
691
  setOpened((state) => !state);
413
692
  }, "openedToggle");
414
693
  const status = useMemo(() => {
415
694
  if (writing) return "writing";
416
- if (error) return "unavailable";
695
+ if (apiConnectionError || error) return "unavailable";
417
696
  if (loading) return "loading";
418
697
  return "online";
419
- }, [writing, loading, error]);
698
+ }, [writing, loading, error, apiConnectionError]);
420
699
  return /* @__PURE__ */ React3.createElement(
421
700
  ChatbotContext.Provider,
422
701
  {
@@ -430,8 +709,10 @@ var ChatbotProvider = /* @__PURE__ */ __name(({
430
709
  opened,
431
710
  openedToggle,
432
711
  status,
712
+ apiConnectionError,
433
713
  suggestedQuestions,
434
- apiURL
714
+ apiURL,
715
+ texts
435
716
  }
436
717
  },
437
718
  /* @__PURE__ */ React3.createElement(ThemeProvider, { theme: themes[theme] }, children)
@@ -439,7 +720,7 @@ var ChatbotProvider = /* @__PURE__ */ __name(({
439
720
  }, "ChatbotProvider");
440
721
 
441
722
  // src/components/ChatbotDevice/index.tsx
442
- import React13 from "react";
723
+ import React14 from "react";
443
724
 
444
725
  // src/components/ChatbotDevice/styles.ts
445
726
  import { breakpoints, screens } from "@ibti-tech/ui";
@@ -451,8 +732,6 @@ var Wrapper = styled.div`
451
732
  display: flex;
452
733
  flex-direction: column;
453
734
  transition: ${(props) => props.theme.transitionDurations.fast};
454
- border-end-end-radius: ${(props) => props.theme.borderRadius.medium};
455
- border-end-start-radius: ${(props) => props.theme.borderRadius.medium};
456
735
  overflow: hidden;
457
736
  pointer-events: all;
458
737
  height: 100vh;
@@ -460,14 +739,45 @@ var Wrapper = styled.div`
460
739
  box-sizing: border-box;
461
740
  position: relative;
462
741
 
742
+ @media screen and (max-width: ${breakpoints.tablet}px) {
743
+ ${(props) => props.$opened && props.$verticalPosition === "bottom" ? css`
744
+ max-height: 100vh;
745
+ ` : ""}
746
+ }
747
+
748
+ ${(props) => props.$verticalPosition === "top" ? css`
749
+ border-end-end-radius: ${(props2) => props2.theme.borderRadius.medium};
750
+ border-end-start-radius: ${(props2) => props2.theme.borderRadius.medium};
751
+ ` : css`
752
+ border-start-end-radius: ${(props2) => props2.theme.borderRadius.medium};
753
+ border-start-start-radius: ${(props2) => props2.theme.borderRadius.medium};
754
+ `}
755
+
463
756
  ${screens.tablet} {
464
757
  height: 500px;
465
758
  max-width: ${breakpoints.mobileL}px;
466
759
  }
467
760
 
468
- ${(props) => !props.$opened && css`
469
- transform: translateY(calc(-100% + 32px));
470
- `}
761
+ ${(props) => !props.$opened && (props.$verticalPosition === "top" ? css`
762
+ transform: translateY(calc(-100% + 32px));
763
+ ` : css`
764
+ transform: translateY(calc(100% - 32px));
765
+ width: 100%;
766
+ ${screens.tablet} {
767
+ width: ${breakpoints.mobileL}px;
768
+ max-width: ${breakpoints.mobileL}px;
769
+ min-width: ${breakpoints.mobileL}px;
770
+ }
771
+ > *:not(:last-child) {
772
+ display: none;
773
+ }
774
+ > *:last-child {
775
+ flex-shrink: 0;
776
+ min-height: 32px;
777
+ height: 32px;
778
+ width: 100%;
779
+ }
780
+ `)}
471
781
  `;
472
782
 
473
783
  // src/components/ChatbotHeader/index.tsx
@@ -518,6 +828,120 @@ var BotIcon = /* @__PURE__ */ __name(() => {
518
828
  ));
519
829
  }, "BotIcon");
520
830
 
831
+ // src/hooks/useChatbotTexts.ts
832
+ var useChatbotTexts = /* @__PURE__ */ __name(() => {
833
+ const { locale, texts } = useChatbot();
834
+ const defaultTexts = i18n[locale];
835
+ if (!texts || !texts[locale]) {
836
+ return defaultTexts;
837
+ }
838
+ const customTexts = texts[locale];
839
+ const getText = /* @__PURE__ */ __name((custom, defaultValue = "") => custom && custom.trim() !== "" ? custom : defaultValue, "getText");
840
+ return {
841
+ CHATBOT_NAME: getText(
842
+ customTexts.CHATBOT_NAME,
843
+ defaultTexts.CHATBOT_NAME
844
+ ),
845
+ INPUT_PLACEHOLDER: getText(
846
+ customTexts.INPUT_PLACEHOLDER,
847
+ defaultTexts.INPUT_PLACEHOLDER
848
+ ),
849
+ CHATBOT_BAR: {
850
+ OPEN: getText(
851
+ customTexts.CHATBOT_BAR?.OPEN,
852
+ defaultTexts.CHATBOT_BAR.OPEN
853
+ ),
854
+ CLOSE: getText(
855
+ customTexts.CHATBOT_BAR?.CLOSE,
856
+ defaultTexts.CHATBOT_BAR.CLOSE
857
+ )
858
+ },
859
+ CHAT_FEEDBACK: {
860
+ TITLE: getText(
861
+ customTexts.CHAT_FEEDBACK?.TITLE,
862
+ defaultTexts.CHAT_FEEDBACK.TITLE
863
+ ),
864
+ DESCRIPTION: getText(
865
+ customTexts.CHAT_FEEDBACK?.DESCRIPTION,
866
+ defaultTexts.CHAT_FEEDBACK.DESCRIPTION
867
+ ),
868
+ MESSAGE_FIELD: getText(
869
+ customTexts.CHAT_FEEDBACK?.MESSAGE_FIELD,
870
+ defaultTexts.CHAT_FEEDBACK.MESSAGE_FIELD
871
+ ),
872
+ SUBMIT_BUTTON: getText(
873
+ customTexts.CHAT_FEEDBACK?.SUBMIT_BUTTON,
874
+ defaultTexts.CHAT_FEEDBACK.SUBMIT_BUTTON
875
+ ),
876
+ CLOSE_BUTTON: getText(
877
+ customTexts.CHAT_FEEDBACK?.CLOSE_BUTTON,
878
+ defaultTexts.CHAT_FEEDBACK.CLOSE_BUTTON
879
+ ),
880
+ RATE: getText(
881
+ customTexts.CHAT_FEEDBACK?.RATE,
882
+ defaultTexts.CHAT_FEEDBACK.RATE
883
+ )
884
+ },
885
+ SUGGESTED_QUESTIONS: {
886
+ TITLE: getText(
887
+ customTexts.SUGGESTED_QUESTIONS?.TITLE,
888
+ defaultTexts.SUGGESTED_QUESTIONS.TITLE
889
+ ),
890
+ LOADING: getText(
891
+ customTexts.SUGGESTED_QUESTIONS?.LOADING,
892
+ defaultTexts.SUGGESTED_QUESTIONS.LOADING
893
+ )
894
+ },
895
+ STATUS: {
896
+ ONLINE: getText(customTexts.STATUS?.ONLINE, defaultTexts.STATUS.ONLINE),
897
+ LOADING: getText(
898
+ customTexts.STATUS?.LOADING,
899
+ defaultTexts.STATUS.LOADING
900
+ ),
901
+ WRITING: getText(
902
+ customTexts.STATUS?.WRITING,
903
+ defaultTexts.STATUS.WRITING
904
+ ),
905
+ UNAVAILABLE: getText(
906
+ customTexts.STATUS?.UNAVAILABLE,
907
+ defaultTexts.STATUS.UNAVAILABLE
908
+ ),
909
+ INACTIVE: getText(
910
+ customTexts.STATUS?.INACTIVE,
911
+ defaultTexts.STATUS.INACTIVE
912
+ )
913
+ },
914
+ ERRORS: {
915
+ UNKNOWN: getText(
916
+ customTexts.ERRORS?.UNKNOWN,
917
+ defaultTexts.ERRORS.UNKNOWN
918
+ )
919
+ },
920
+ MESSAGE_ACTIONS: {
921
+ COPY: getText(
922
+ customTexts.MESSAGE_ACTIONS?.COPY,
923
+ defaultTexts.MESSAGE_ACTIONS.COPY
924
+ ),
925
+ SHARE: getText(
926
+ customTexts.MESSAGE_ACTIONS?.SHARE,
927
+ defaultTexts.MESSAGE_ACTIONS.SHARE
928
+ ),
929
+ RESEND: getText(
930
+ customTexts.MESSAGE_ACTIONS?.RESEND,
931
+ defaultTexts.MESSAGE_ACTIONS.RESEND
932
+ )
933
+ },
934
+ USER_LABEL: getText(
935
+ customTexts.USER_LABEL,
936
+ defaultTexts.USER_LABEL
937
+ ),
938
+ WRITING_MESSAGE: getText(
939
+ customTexts.WRITING_MESSAGE,
940
+ defaultTexts.WRITING_MESSAGE
941
+ )
942
+ };
943
+ }, "useChatbotTexts");
944
+
521
945
  // src/components/ChatbotStatusLabel/index.tsx
522
946
  import React5 from "react";
523
947
 
@@ -541,14 +965,14 @@ var Wrapper3 = styled3.span`
541
965
  // src/components/ChatbotStatusLabel/index.tsx
542
966
  import { useTheme } from "styled-components";
543
967
  var ChatbotStatusLabel = /* @__PURE__ */ __name(({}) => {
544
- const { status } = useChatbot_default();
545
- const i18nTerms = useI18n();
968
+ const { status, apiConnectionError } = useChatbot_default();
969
+ const texts = useChatbotTexts();
546
970
  const theme = useTheme();
547
971
  const statusLabel = {
548
- online: i18nTerms.STATUS.ONLINE,
549
- loading: i18nTerms.STATUS.LOADING,
550
- writing: i18nTerms.STATUS.WRITING,
551
- unavailable: i18nTerms.STATUS.UNAVAILABLE
972
+ online: texts.STATUS.ONLINE,
973
+ loading: texts.STATUS.LOADING,
974
+ writing: texts.STATUS.WRITING,
975
+ unavailable: apiConnectionError ? texts.STATUS.INACTIVE : texts.STATUS.UNAVAILABLE
552
976
  };
553
977
  const statusColor = {
554
978
  online: theme.colors.palette.success.normal,
@@ -563,11 +987,11 @@ var ChatbotStatusLabel_default = ChatbotStatusLabel;
563
987
  // src/components/ChatbotHeader/index.tsx
564
988
  import { Button } from "@ibti-tech/ui";
565
989
  var ChatbotHeader = /* @__PURE__ */ __name(({ chatFeedbackBox }) => {
566
- const i18n2 = useI18n();
567
- 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.CHATBOT_NAME), /* @__PURE__ */ React6.createElement(ChatbotStatusLabel_default, null)), /* @__PURE__ */ React6.createElement(
990
+ const texts = useChatbotTexts();
991
+ 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, texts.CHATBOT_NAME), /* @__PURE__ */ React6.createElement(ChatbotStatusLabel_default, null)), /* @__PURE__ */ React6.createElement(
568
992
  Button,
569
993
  {
570
- label: i18n2.CHAT_FEEDBACK.RATE,
994
+ label: texts.CHAT_FEEDBACK.RATE,
571
995
  onClick: chatFeedbackBox.open,
572
996
  size: "small",
573
997
  layer: 2,
@@ -578,31 +1002,74 @@ var ChatbotHeader = /* @__PURE__ */ __name(({ chatFeedbackBox }) => {
578
1002
  var ChatbotHeader_default = ChatbotHeader;
579
1003
 
580
1004
  // src/components/ChatbotBody/index.tsx
581
- import React9, { useEffect as useEffect5, useRef as useRef3 } from "react";
1005
+ import React10, { useEffect as useEffect6, useRef as useRef5 } from "react";
582
1006
 
583
1007
  // src/components/ChatbotBody/styles.ts
584
1008
  import { styled as styled4 } from "styled-components";
585
1009
  var ChatbotInput = styled4.div``;
586
1010
  var MessagesList = styled4.ul`
587
- padding: ${(props) => props.theme.spacing.components.medium};
1011
+ padding: ${(props) => props.theme.spacing.components.medium}
1012
+ ${(props) => props.theme.spacing.components.large}
1013
+ ${(props) => props.theme.spacing.components.large}
1014
+ ${(props) => props.theme.spacing.components.medium};
588
1015
  display: flex;
589
1016
  flex-direction: column;
590
- gap: ${(props) => props.theme.spacing.components.small};
1017
+ gap: 0;
591
1018
  `;
592
1019
  var Wrapper4 = styled4.div`
593
1020
  overflow: hidden;
594
1021
  overflow-y: auto;
595
1022
  flex: 1;
596
1023
  height: 100%;
1024
+ padding-right: ${(props) => props.theme.spacing.components.small};
1025
+
1026
+ /* Custom scrollbar styles */
1027
+ &::-webkit-scrollbar {
1028
+ width: 8px;
1029
+ }
1030
+
1031
+ &::-webkit-scrollbar-track {
1032
+ background: transparent;
1033
+ }
1034
+
1035
+ &::-webkit-scrollbar-thumb {
1036
+ background: ${(props) => props.theme.colors.palette.primary.normal};
1037
+ border-radius: 4px;
1038
+ }
1039
+
1040
+ &::-webkit-scrollbar-thumb:hover {
1041
+ opacity: 0.8;
1042
+ }
1043
+
1044
+ /* Firefox */
1045
+ scrollbar-width: thin;
1046
+ scrollbar-color: ${(props) => props.theme.colors.palette.primary.normal} transparent;
597
1047
  `;
598
1048
 
599
1049
  // src/components/MessageBalloon/index.tsx
600
- import React7 from "react";
1050
+ import React7, { useState as useState4, useRef as useRef3, useEffect as useEffect3 } from "react";
601
1051
 
602
1052
  // src/components/MessageBalloon/styles.ts
1053
+ import { mix } from "polished";
603
1054
  import { css as css2, styled as styled5 } from "styled-components";
604
1055
  var Time = styled5.span`
605
1056
  font-size: ${(props) => props.theme.fontSizes.small};
1057
+ color: ${(props) => props.theme.colors.content.detail};
1058
+ white-space: nowrap;
1059
+
1060
+ ${({ $type }) => {
1061
+ switch ($type) {
1062
+ case "received":
1063
+ return css2`
1064
+ text-align: left;
1065
+ `;
1066
+ default:
1067
+ case "sent":
1068
+ return css2`
1069
+ text-align: right;
1070
+ `;
1071
+ }
1072
+ }}
606
1073
  `;
607
1074
  var Balloon = styled5.div`
608
1075
  padding: ${(props) => props.theme.spacing.components.small};
@@ -627,6 +1094,7 @@ var Balloon = styled5.div`
627
1094
  case "received":
628
1095
  return css2`
629
1096
  background: ${(props) => props.theme.colors.layers[2].background};
1097
+ color: ${(props) => props.theme.colors.content.text};
630
1098
  border-top-left-radius: 0;
631
1099
 
632
1100
  ${$error && css2`
@@ -645,8 +1113,9 @@ var Balloon = styled5.div`
645
1113
  case "sent":
646
1114
  return css2`
647
1115
  background: ${(props) => props.theme.colors.palette.primary.normal};
1116
+ color: white;
648
1117
  border-top-right-radius: 0;
649
- margin-left: auto;
1118
+ text-align: left;
650
1119
 
651
1120
  &::after {
652
1121
  border-width: 0px 0px 10px 10px;
@@ -664,9 +1133,19 @@ var Balloon = styled5.div`
664
1133
  flex-direction: column;
665
1134
  gap: 1.3em;
666
1135
 
1136
+ p {
1137
+ margin: 0;
1138
+ color: inherit;
1139
+ }
1140
+
667
1141
  a {
668
1142
  color: ${(props) => props.theme.colors.palette.primary.normal};
669
1143
  font-weight: semibold;
1144
+
1145
+ ${({ $type }) => $type === "sent" && css2`
1146
+ color: white;
1147
+ text-decoration: underline;
1148
+ `}
670
1149
  }
671
1150
 
672
1151
  ol,
@@ -674,16 +1153,220 @@ var Balloon = styled5.div`
674
1153
  display: flex;
675
1154
  flex-direction: column;
676
1155
  gap: 1.3em;
1156
+ margin: 0;
1157
+ padding-left: 1.5em;
1158
+ color: inherit;
1159
+ }
1160
+
1161
+ li {
1162
+ color: inherit;
1163
+ }
1164
+
1165
+ code {
1166
+ background: ${(props) => props.$type === "received" ? props.theme.colors.layers[1].background : "rgba(0, 0, 0, 0.2)"};
1167
+ color: inherit;
1168
+ padding: 0.2em 0.4em;
1169
+ border-radius: ${(props) => props.theme.borderRadius.small};
1170
+ font-size: 0.9em;
1171
+ }
1172
+
1173
+ pre {
1174
+ background: ${(props) => props.$type === "received" ? props.theme.colors.layers[1].background : "rgba(0, 0, 0, 0.2)"};
1175
+ color: inherit;
1176
+ padding: ${(props) => props.theme.spacing.components.small};
1177
+ border-radius: ${(props) => props.theme.borderRadius.small};
1178
+ overflow-x: auto;
1179
+ margin: 0;
1180
+
1181
+ /* Custom scrollbar styles */
1182
+ &::-webkit-scrollbar {
1183
+ height: 8px;
1184
+ }
1185
+
1186
+ &::-webkit-scrollbar-track {
1187
+ background: transparent;
1188
+ }
1189
+
1190
+ &::-webkit-scrollbar-thumb {
1191
+ background: ${(props) => props.theme.colors.palette.primary.normal};
1192
+ border-radius: 4px;
1193
+ }
1194
+
1195
+ &::-webkit-scrollbar-thumb:hover {
1196
+ opacity: 0.8;
1197
+ }
1198
+
1199
+ /* Firefox */
1200
+ scrollbar-width: thin;
1201
+ scrollbar-color: ${(props) => props.theme.colors.palette.primary.normal} transparent;
1202
+ }
1203
+
1204
+ pre code {
1205
+ background: transparent;
1206
+ padding: 0;
1207
+ }
1208
+
1209
+ strong,
1210
+ b {
1211
+ color: inherit;
1212
+ font-weight: ${(props) => props.theme.fontFamily.montserrat.weights.bold};
1213
+ }
1214
+
1215
+ em,
1216
+ i {
1217
+ color: inherit;
677
1218
  }
678
1219
  `;
679
1220
  var Label = styled5.span`
680
1221
  font-size: ${(props) => props.theme.fontSizes.small};
1222
+ color: ${(props) => props.theme.colors.content.detail};
1223
+ `;
1224
+ var BalloonAndActionsWrapper = styled5.div`
1225
+ display: flex;
1226
+ flex-direction: column;
1227
+ gap: 2px;
1228
+ margin-bottom: ${(props) => props.theme.spacing.components.medium};
1229
+
1230
+ ${({ $type }) => {
1231
+ switch ($type) {
1232
+ case "received":
1233
+ return css2``;
1234
+ default:
1235
+ case "sent":
1236
+ return css2`
1237
+ align-items: flex-end;
1238
+ `;
1239
+ }
1240
+ }}
1241
+ `;
1242
+ var BalloonContainer = styled5.div`
1243
+ position: relative;
1244
+ width: max-content;
1245
+ max-width: 100%;
1246
+
1247
+ ${({ $type }) => {
1248
+ switch ($type) {
1249
+ case "received":
1250
+ return css2`
1251
+ display: inline-block;
1252
+ `;
1253
+ default:
1254
+ case "sent":
1255
+ return css2`
1256
+ display: block;
1257
+ margin-left: auto;
1258
+ `;
1259
+ }
1260
+ }}
1261
+ `;
1262
+ var TimestampAndActionsRow = styled5.div`
1263
+ display: flex;
1264
+ align-items: center;
1265
+ gap: 10px;
1266
+ width: 100%;
1267
+
1268
+ ${({ $type }) => {
1269
+ switch ($type) {
1270
+ case "received":
1271
+ return css2`
1272
+ flex-direction: row-reverse;
1273
+ justify-content: flex-end;
1274
+ `;
1275
+ default:
1276
+ case "sent":
1277
+ return css2`
1278
+ flex-direction: row;
1279
+ justify-content: flex-end;
1280
+ `;
1281
+ }
1282
+ }}
1283
+ `;
1284
+ var ActionsContainer = styled5.div`
1285
+ display: flex;
1286
+ gap: ${(props) => props.theme.spacing.components.xsmall};
1287
+ align-items: center;
1288
+ z-index: 10;
1289
+ opacity: ${(props) => props.$isVisible ? 1 : 0};
1290
+ visibility: ${(props) => props.$isVisible ? "visible" : "hidden"};
1291
+ transition: opacity 0.2s ease, visibility 0.2s ease;
1292
+ pointer-events: ${(props) => props.$isVisible ? "auto" : "none"};
1293
+ margin: 0;
1294
+ padding: 0;
1295
+
1296
+ @keyframes fadeIn {
1297
+ from {
1298
+ opacity: 0;
1299
+ transform: translateY(-5px) scale(0.9);
1300
+ }
1301
+ to {
1302
+ opacity: 1;
1303
+ transform: translateY(0) scale(1);
1304
+ }
1305
+ }
1306
+
1307
+ ${({ $type }) => {
1308
+ switch ($type) {
1309
+ case "received":
1310
+ return css2`
1311
+ left: 0;
1312
+ `;
1313
+ default:
1314
+ case "sent":
1315
+ return css2`
1316
+ right: 0;
1317
+ `;
1318
+ }
1319
+ }}
1320
+ `;
1321
+ var ActionButton = styled5.button`
1322
+ display: flex;
1323
+ align-items: center;
1324
+ justify-content: center;
1325
+ width: 28px;
1326
+ height: 28px;
1327
+ padding: 0;
1328
+ border: none;
1329
+ background: ${(props) => props.theme.colors.layers[1].background};
1330
+ color: ${(props) => props.theme.colors.content.text};
1331
+ border-radius: ${(props) => props.theme.borderRadius.small};
1332
+ cursor: pointer;
1333
+ position: relative;
1334
+ transition: all 0.2s ease;
1335
+ opacity: 0.85;
1336
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
1337
+
1338
+ &:hover {
1339
+ opacity: 1;
1340
+ background: ${(props) => props.theme.colors.layers[2].background};
1341
+ transform: scale(1.1);
1342
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
1343
+ }
1344
+
1345
+ &:active {
1346
+ transform: scale(0.95);
1347
+ }
1348
+ `;
1349
+ var Tooltip = styled5.span`
1350
+ position: absolute;
1351
+ white-space: nowrap;
1352
+ padding: ${(props) => props.theme.spacing.components.xsmall}
1353
+ ${(props) => props.theme.spacing.components.small};
1354
+ background: ${(props) => mix(0.1, props.theme.colors.content.text, props.theme.colors.layers[2].background)};
1355
+ color: ${(props) => props.theme.colors.content.text};
1356
+ border-radius: ${(props) => props.theme.borderRadius.small};
1357
+ font-size: ${(props) => props.theme.fontSizes.xsmall};
1358
+ pointer-events: none;
1359
+ z-index: 30;
1360
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
1361
+ bottom: calc(100% + ${(props) => props.theme.spacing.components.xsmall});
1362
+ left: 50%;
1363
+ transform: translateX(-50%);
681
1364
  `;
682
1365
  var Wrapper5 = styled5.div`
683
1366
  font-size: ${(props) => props.theme.fontSizes.small};
684
1367
  display: flex;
685
1368
  flex-direction: column;
686
- gap: ${(props) => props.theme.spacing.components.xsmall};
1369
+ gap: 2px;
687
1370
 
688
1371
  ${(props) => {
689
1372
  switch (props.$type) {
@@ -707,6 +1390,7 @@ var formatMessageTime = /* @__PURE__ */ __name((dateUTC, currentDateUTC, locale)
707
1390
  // src/components/MessageBalloon/index.tsx
708
1391
  import Markdown from "react-markdown";
709
1392
  import { CircleExclamationIcon } from "@ibti-tech/ui";
1393
+ import { Copy, Share2, RotateCw } from "lucide-react";
710
1394
  var messageTypes = {
711
1395
  assistant: "received",
712
1396
  user: "sent"
@@ -716,16 +1400,204 @@ var MessageBalloon = /* @__PURE__ */ __name(({
716
1400
  currentDateUTC
717
1401
  }) => {
718
1402
  const messageType = messageTypes[data.role];
719
- const i18n2 = useI18n();
720
- const { locale } = useChatbot_default();
721
- return /* @__PURE__ */ React7.createElement(Wrapper5, { $type: messageType }, data.role == "assistant" && /* @__PURE__ */ React7.createElement(Label, null, i18n2.CHATBOT_NAME), /* @__PURE__ */ React7.createElement(Balloon, { $type: messageType, $error: data.error }, data.error && /* @__PURE__ */ React7.createElement(CircleExclamationIcon, null), /* @__PURE__ */ React7.createElement(Markdown, null, data.content)), /* @__PURE__ */ React7.createElement(Time, null, formatMessageTime(data.timestamp, currentDateUTC, locale)));
1403
+ const texts = useChatbotTexts();
1404
+ const { locale, makeQuestion } = useChatbot_default();
1405
+ const [isHovered, setIsHovered] = useState4(false);
1406
+ const [showTooltip, setShowTooltip] = useState4(null);
1407
+ const [copied, setCopied] = useState4(false);
1408
+ const copyTimeoutRef = useRef3(null);
1409
+ useEffect3(() => {
1410
+ return () => {
1411
+ if (copyTimeoutRef.current) {
1412
+ clearTimeout(copyTimeoutRef.current);
1413
+ }
1414
+ };
1415
+ }, []);
1416
+ const handleCopy = /* @__PURE__ */ __name(async () => {
1417
+ try {
1418
+ await navigator.clipboard.writeText(data.content);
1419
+ setCopied(true);
1420
+ setShowTooltip("copy");
1421
+ if (copyTimeoutRef.current) {
1422
+ clearTimeout(copyTimeoutRef.current);
1423
+ }
1424
+ copyTimeoutRef.current = setTimeout(() => {
1425
+ setCopied(false);
1426
+ setShowTooltip((prev) => prev === "copy" ? null : prev);
1427
+ }, 2e3);
1428
+ } catch (err) {
1429
+ console.error("Failed to copy:", err);
1430
+ }
1431
+ }, "handleCopy");
1432
+ const handleShare = /* @__PURE__ */ __name(async () => {
1433
+ if (navigator.share) {
1434
+ try {
1435
+ await navigator.share({
1436
+ title: texts.CHATBOT_NAME,
1437
+ text: data.content
1438
+ });
1439
+ } catch (err) {
1440
+ console.error("Failed to share:", err);
1441
+ }
1442
+ } else {
1443
+ handleCopy();
1444
+ }
1445
+ }, "handleShare");
1446
+ const handleResend = /* @__PURE__ */ __name(() => {
1447
+ if (data.role === "user") {
1448
+ makeQuestion(data.content);
1449
+ }
1450
+ }, "handleResend");
1451
+ return /* @__PURE__ */ React7.createElement(Wrapper5, { $type: messageType }, data.role == "assistant" && /* @__PURE__ */ React7.createElement(Label, null, texts.CHATBOT_NAME), data.role == "user" && /* @__PURE__ */ React7.createElement(Label, null, texts.USER_LABEL), /* @__PURE__ */ React7.createElement(
1452
+ BalloonAndActionsWrapper,
1453
+ {
1454
+ onMouseEnter: () => setIsHovered(true),
1455
+ onMouseLeave: () => setIsHovered(false),
1456
+ $type: messageType
1457
+ },
1458
+ /* @__PURE__ */ React7.createElement(BalloonContainer, { $type: messageType }, /* @__PURE__ */ React7.createElement(Balloon, { $type: messageType, $error: data.error }, data.error && /* @__PURE__ */ React7.createElement(CircleExclamationIcon, null), /* @__PURE__ */ React7.createElement(Markdown, null, data.content))),
1459
+ /* @__PURE__ */ React7.createElement(TimestampAndActionsRow, { $type: messageType }, /* @__PURE__ */ React7.createElement(
1460
+ ActionsContainer,
1461
+ {
1462
+ $type: messageType,
1463
+ $isVisible: isHovered,
1464
+ onMouseEnter: () => setIsHovered(true),
1465
+ onMouseLeave: () => setIsHovered(false)
1466
+ },
1467
+ data.role === "user" && /* @__PURE__ */ React7.createElement(
1468
+ ActionButton,
1469
+ {
1470
+ $messageType: messageType,
1471
+ onClick: handleResend,
1472
+ onMouseEnter: () => setShowTooltip("resend"),
1473
+ onMouseLeave: () => setShowTooltip(null),
1474
+ "aria-label": texts.MESSAGE_ACTIONS.RESEND
1475
+ },
1476
+ /* @__PURE__ */ React7.createElement(RotateCw, { size: 14 }),
1477
+ showTooltip === "resend" && /* @__PURE__ */ React7.createElement(Tooltip, { $messageType: messageType }, texts.MESSAGE_ACTIONS.RESEND)
1478
+ ),
1479
+ /* @__PURE__ */ React7.createElement(
1480
+ ActionButton,
1481
+ {
1482
+ $messageType: messageType,
1483
+ onClick: handleCopy,
1484
+ onMouseEnter: () => {
1485
+ if (!copied) {
1486
+ setShowTooltip("copy");
1487
+ }
1488
+ },
1489
+ onMouseLeave: () => {
1490
+ if (!copied) {
1491
+ setShowTooltip(null);
1492
+ }
1493
+ },
1494
+ "aria-label": texts.MESSAGE_ACTIONS.COPY
1495
+ },
1496
+ /* @__PURE__ */ React7.createElement(Copy, { size: 14 }),
1497
+ (showTooltip === "copy" || copied) && /* @__PURE__ */ React7.createElement(Tooltip, { $messageType: messageType }, copied ? locale === "pt-BR" ? "Copiado!" : "Copied!" : texts.MESSAGE_ACTIONS.COPY)
1498
+ ),
1499
+ /* @__PURE__ */ React7.createElement(
1500
+ ActionButton,
1501
+ {
1502
+ $messageType: messageType,
1503
+ onClick: handleShare,
1504
+ onMouseEnter: () => setShowTooltip("share"),
1505
+ onMouseLeave: () => setShowTooltip(null),
1506
+ "aria-label": texts.MESSAGE_ACTIONS.SHARE
1507
+ },
1508
+ /* @__PURE__ */ React7.createElement(Share2, { size: 14 }),
1509
+ showTooltip === "share" && /* @__PURE__ */ React7.createElement(Tooltip, { $messageType: messageType }, texts.MESSAGE_ACTIONS.SHARE)
1510
+ )
1511
+ ), /* @__PURE__ */ React7.createElement(Time, { $type: messageType }, formatMessageTime(data.timestamp, currentDateUTC, locale)))
1512
+ ));
722
1513
  }, "MessageBalloon");
723
1514
  var MessageBalloon_default = MessageBalloon;
724
1515
 
1516
+ // src/components/WritingIndicator/index.tsx
1517
+ import React8 from "react";
1518
+
1519
+ // src/components/WritingIndicator/styles.ts
1520
+ import { keyframes, styled as styled6 } from "styled-components";
1521
+ var dotAnimation = keyframes`
1522
+ 0%, 20% {
1523
+ opacity: 0;
1524
+ transform: scale(0.8);
1525
+ }
1526
+ 50% {
1527
+ opacity: 1;
1528
+ transform: scale(1);
1529
+ }
1530
+ 80%, 100% {
1531
+ opacity: 0.3;
1532
+ transform: scale(0.8);
1533
+ }
1534
+ `;
1535
+ var Dots = styled6.span`
1536
+ display: inline-flex;
1537
+ gap: 4px;
1538
+ align-items: center;
1539
+ margin-left: 4px;
1540
+ `;
1541
+ var Dot = styled6.span`
1542
+ width: 6px;
1543
+ height: 6px;
1544
+ border-radius: 50%;
1545
+ background: currentColor;
1546
+ animation: ${dotAnimation} 1.4s ease-in-out infinite;
1547
+ animation-delay: ${(props) => props.$delay}s;
1548
+ `;
1549
+ var Text = styled6.span`
1550
+ display: inline-block;
1551
+ `;
1552
+ var Balloon2 = styled6.div`
1553
+ padding: ${(props) => props.theme.spacing.components.small};
1554
+ width: max-content;
1555
+ max-width: 100%;
1556
+ border-radius: ${(props) => props.theme.borderRadius.medium};
1557
+ border-top-left-radius: 0;
1558
+ position: relative;
1559
+ line-height: 1.5em;
1560
+ display: flex;
1561
+ align-items: center;
1562
+ background: ${(props) => props.theme.colors.layers[2].background};
1563
+ color: ${(props) => props.theme.colors.content.text};
1564
+
1565
+ &::after {
1566
+ position: absolute;
1567
+ content: '';
1568
+ width: 0;
1569
+ height: 0;
1570
+ border-style: solid;
1571
+ border-width: 0px 10px 10px 0;
1572
+ border-color: transparent
1573
+ ${(props) => props.theme.colors.layers[2].background} transparent
1574
+ transparent;
1575
+ top: 0;
1576
+ left: -10px;
1577
+ }
1578
+ `;
1579
+ var Label2 = styled6.span`
1580
+ font-size: ${(props) => props.theme.fontSizes.small};
1581
+ `;
1582
+ var Wrapper6 = styled6.div`
1583
+ font-size: ${(props) => props.theme.fontSizes.small};
1584
+ display: flex;
1585
+ flex-direction: column;
1586
+ gap: ${(props) => props.theme.spacing.components.xsmall};
1587
+ `;
1588
+
1589
+ // src/components/WritingIndicator/index.tsx
1590
+ var WritingIndicator = /* @__PURE__ */ __name(() => {
1591
+ const texts = useChatbotTexts();
1592
+ const { locale } = useChatbot();
1593
+ return /* @__PURE__ */ React8.createElement(Wrapper6, null, /* @__PURE__ */ React8.createElement(Label2, null, texts.CHATBOT_NAME), /* @__PURE__ */ React8.createElement(Balloon2, null, /* @__PURE__ */ React8.createElement(Text, null, texts.WRITING_MESSAGE), /* @__PURE__ */ React8.createElement(Dots, null, /* @__PURE__ */ React8.createElement(Dot, { $delay: 0 }), /* @__PURE__ */ React8.createElement(Dot, { $delay: 0.2 }), /* @__PURE__ */ React8.createElement(Dot, { $delay: 0.4 }))));
1594
+ }, "WritingIndicator");
1595
+ var WritingIndicator_default = WritingIndicator;
1596
+
725
1597
  // src/hooks/useElementScroll.ts
726
- import { useEffect as useEffect3, useState as useState4 } from "react";
1598
+ import { useEffect as useEffect4, useState as useState5 } from "react";
727
1599
  var useElementScroll = /* @__PURE__ */ __name((elementRef) => {
728
- const [scrollProgress, setScrollProgress] = useState4(0);
1600
+ const [scrollProgress, setScrollProgress] = useState5(0);
729
1601
  const calculateScrollPercentage = /* @__PURE__ */ __name(() => {
730
1602
  if (!elementRef.current) return;
731
1603
  const element = elementRef.current;
@@ -737,7 +1609,7 @@ var useElementScroll = /* @__PURE__ */ __name((elementRef) => {
737
1609
  const progressValue = Math.min(100, Math.max(0, scrolled));
738
1610
  setScrollProgress(isNaN(progressValue) ? 100 : progressValue);
739
1611
  }, "calculateScrollPercentage");
740
- useEffect3(() => {
1612
+ useEffect4(() => {
741
1613
  if (!elementRef.current) return;
742
1614
  const handleIntersection = /* @__PURE__ */ __name(() => {
743
1615
  calculateScrollPercentage();
@@ -772,12 +1644,12 @@ var useElementScroll = /* @__PURE__ */ __name((elementRef) => {
772
1644
  }, "useElementScroll");
773
1645
 
774
1646
  // src/hooks/useUpdatedTime.ts
775
- import { useEffect as useEffect4, useState as useState5 } from "react";
1647
+ import { useEffect as useEffect5, useState as useState6 } from "react";
776
1648
  var useUpdatedTime = /* @__PURE__ */ __name(({
777
1649
  intervalInSeconds
778
1650
  }) => {
779
- const [date, setDate] = useState5(/* @__PURE__ */ new Date());
780
- useEffect4(() => {
1651
+ const [date, setDate] = useState6(/* @__PURE__ */ new Date());
1652
+ useEffect5(() => {
781
1653
  const time = 1e3 * intervalInSeconds;
782
1654
  const interval = setInterval(() => {
783
1655
  const now = /* @__PURE__ */ new Date();
@@ -791,40 +1663,40 @@ var useUpdatedTime = /* @__PURE__ */ __name(({
791
1663
  }, "useUpdatedTime");
792
1664
 
793
1665
  // src/components/SuggestedQuestions/index.tsx
794
- import React8 from "react";
1666
+ import React9 from "react";
795
1667
 
796
1668
  // src/components/SuggestedQuestions/styles.ts
797
1669
  import { Button as Button2 } from "@ibti-tech/ui";
798
- import { styled as styled6 } from "styled-components";
799
- var QuestionButton = styled6(Button2)`
1670
+ import { styled as styled7 } from "styled-components";
1671
+ var QuestionButton = styled7(Button2)`
800
1672
  text-align: left;
801
1673
  justify-content: flex-start;
802
1674
  height: auto;
803
1675
  padding: ${(props) => props.theme.spacing.components.small};
804
1676
  `;
805
- var QuestionListItem = styled6.li``;
806
- var QuestionList = styled6.ul`
1677
+ var QuestionListItem = styled7.li``;
1678
+ var QuestionList = styled7.ul`
807
1679
  display: flex;
808
1680
  flex-direction: column;
809
1681
  gap: ${(props) => props.theme.spacing.components.small};
810
1682
  `;
811
- var Title = styled6.span`
1683
+ var Title = styled7.span`
812
1684
  display: block;
813
1685
  font-size: ${(props) => props.theme.fontSizes.small};
814
1686
  font-weight: ${(props) => props.theme.fontFamily.montserrat.weights.bold};
815
1687
  margin-bottom: ${(props) => props.theme.spacing.components.medium};
816
1688
  /* text-align: center; */
817
1689
  `;
818
- var Wrapper6 = styled6.div`
1690
+ var Wrapper7 = styled7.div`
819
1691
  padding: ${(props) => props.theme.spacing.components.medium};
820
1692
  `;
821
1693
 
822
1694
  // src/components/SuggestedQuestions/index.tsx
823
1695
  var SuggestedQuestions = /* @__PURE__ */ __name(({}) => {
824
- const i18n2 = useI18n();
1696
+ const texts = useChatbotTexts();
825
1697
  const { suggestedQuestions, makeQuestion } = useChatbot_default();
826
1698
  if (suggestedQuestions.data.length === 0) return;
827
- 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(
1699
+ return /* @__PURE__ */ React9.createElement(Wrapper7, null, /* @__PURE__ */ React9.createElement(Title, null, texts.SUGGESTED_QUESTIONS.TITLE), /* @__PURE__ */ React9.createElement(QuestionList, null, suggestedQuestions.data.map((question) => /* @__PURE__ */ React9.createElement(QuestionListItem, { key: question }, /* @__PURE__ */ React9.createElement(
828
1700
  QuestionButton,
829
1701
  {
830
1702
  label: question,
@@ -843,11 +1715,11 @@ var SuggestedQuestions_default = SuggestedQuestions;
843
1715
 
844
1716
  // src/components/ChatbotBody/index.tsx
845
1717
  var ChatbotBody = /* @__PURE__ */ __name(({}) => {
846
- const { chatMessages, suggestedQuestions } = useChatbot();
847
- const scrollableElementRef = useRef3(null);
1718
+ const { chatMessages, suggestedQuestions, loading } = useChatbot();
1719
+ const scrollableElementRef = useRef5(null);
848
1720
  const { scrollProgress, scrollToEnd } = useElementScroll(scrollableElementRef);
849
1721
  const { dateUTC } = useUpdatedTime({ intervalInSeconds: 15 });
850
- useEffect5(() => {
1722
+ useEffect6(() => {
851
1723
  if (scrollProgress > 85) scrollToEnd();
852
1724
  document.addEventListener(
853
1725
  CUSTOM_EVENT_NAMES.ASSISTANT_ANSWER_FINISHED,
@@ -860,23 +1732,28 @@ var ChatbotBody = /* @__PURE__ */ __name(({}) => {
860
1732
  );
861
1733
  };
862
1734
  }, [chatMessages, suggestedQuestions]);
863
- return /* @__PURE__ */ React9.createElement(Wrapper4, { ref: scrollableElementRef }, /* @__PURE__ */ React9.createElement(MessagesList, null, chatMessages.map((msgData) => /* @__PURE__ */ React9.createElement(
1735
+ useEffect6(() => {
1736
+ if (loading) {
1737
+ scrollToEnd();
1738
+ }
1739
+ }, [loading, scrollToEnd]);
1740
+ return /* @__PURE__ */ React10.createElement(Wrapper4, { ref: scrollableElementRef }, /* @__PURE__ */ React10.createElement(MessagesList, null, chatMessages.map((msgData) => /* @__PURE__ */ React10.createElement(
864
1741
  MessageBalloon_default,
865
1742
  {
866
1743
  key: msgData.id,
867
1744
  data: msgData,
868
1745
  currentDateUTC: dateUTC
869
1746
  }
870
- ))), /* @__PURE__ */ React9.createElement(SuggestedQuestions_default, null));
1747
+ )), loading && /* @__PURE__ */ React10.createElement(WritingIndicator_default, null)), /* @__PURE__ */ React10.createElement(SuggestedQuestions_default, null));
871
1748
  }, "ChatbotBody");
872
1749
  var ChatbotBody_default = ChatbotBody;
873
1750
 
874
1751
  // src/components/ChatbotFooter/index.tsx
875
- import React10, { useState as useState6 } from "react";
1752
+ import React11, { useState as useState7, useRef as useRef6, useEffect as useEffect7 } from "react";
876
1753
 
877
1754
  // src/components/ChatbotFooter/styles.ts
878
- import { styled as styled7 } from "styled-components";
879
- var LoadingSuggestedQuestions = styled7.span`
1755
+ import { styled as styled8 } from "styled-components";
1756
+ var LoadingSuggestedQuestions = styled8.span`
880
1757
  display: flex;
881
1758
  align-items: center;
882
1759
  gap: ${(props) => props.theme.spacing.components.small};
@@ -884,11 +1761,84 @@ var LoadingSuggestedQuestions = styled7.span`
884
1761
  padding: ${(props) => props.theme.spacing.components.small}
885
1762
  ${(props) => props.theme.spacing.components.medium};
886
1763
  `;
887
- var Form = styled7.form`
1764
+ var Form = styled8.form`
888
1765
  display: flex;
889
1766
  gap: ${(props) => props.theme.spacing.components.small};
1767
+
1768
+ /* Remove border from submit button */
1769
+ button[type='submit'] {
1770
+ border: none !important;
1771
+ outline: none !important;
1772
+ box-shadow: none !important;
1773
+ }
1774
+
1775
+ /* Chatbot textarea styles */
1776
+ .textareaField {
1777
+ font-family: inherit !important;
1778
+
1779
+ &,
1780
+ *,
1781
+ textarea {
1782
+ font-size: ${(props) => props.theme.fontSizes.medium} !important;
1783
+ font-family: inherit !important;
1784
+ }
1785
+
1786
+ textarea {
1787
+ border-radius: ${(props) => props.theme.borderRadius.medium} !important;
1788
+ border: 1px solid ${(props) => props.theme.colors.layers[2].border} !important;
1789
+ padding: ${(props) => props.theme.spacing.components.small}
1790
+ ${(props) => props.theme.spacing.components.medium} !important;
1791
+ min-height: 40px !important;
1792
+ max-height: 200px !important;
1793
+ resize: none !important;
1794
+ line-height: 1.5 !important;
1795
+ width: 100% !important;
1796
+ box-sizing: border-box !important;
1797
+ font-family: inherit !important;
1798
+ overflow-y: auto !important;
1799
+ overflow-x: hidden !important;
1800
+
1801
+ /* Custom scrollbar styles */
1802
+ &::-webkit-scrollbar {
1803
+ width: 8px;
1804
+ }
1805
+
1806
+ &::-webkit-scrollbar-track {
1807
+ background: transparent;
1808
+ }
1809
+
1810
+ &::-webkit-scrollbar-thumb {
1811
+ background: ${(props) => props.theme.colors.palette.primary.normal} !important;
1812
+ border-radius: 4px;
1813
+ }
1814
+
1815
+ &::-webkit-scrollbar-thumb:hover {
1816
+ opacity: 0.8;
1817
+ }
1818
+
1819
+ /* Firefox */
1820
+ scrollbar-width: thin !important;
1821
+ scrollbar-color: ${(props) => props.theme.colors.palette.primary.normal} transparent !important;
1822
+
1823
+ &:focus {
1824
+ outline: none !important;
1825
+ border-color: ${(props) => props.theme.colors.palette.primary.normal} !important;
1826
+ }
1827
+
1828
+ &::placeholder {
1829
+ color: ${(props) => props.theme.colors.content.detail} !important;
1830
+ font-size: ${(props) => props.theme.fontSizes.medium} !important;
1831
+ font-family: inherit !important;
1832
+ }
1833
+ }
1834
+
1835
+ &:focus-within textarea {
1836
+ outline: none !important;
1837
+ border-color: ${(props) => props.theme.colors.palette.primary.normal} !important;
1838
+ }
1839
+ }
890
1840
  `;
891
- var Wrapper7 = styled7.div`
1841
+ var Wrapper8 = styled8.div`
892
1842
  padding: ${(props) => props.theme.spacing.components.small};
893
1843
  `;
894
1844
 
@@ -901,31 +1851,60 @@ import {
901
1851
  } from "@ibti-tech/ui";
902
1852
  var ChatbotFooter = /* @__PURE__ */ __name(({}) => {
903
1853
  const { makeQuestion, loading, writing, suggestedQuestions } = useChatbot_default();
904
- const [questionInput, setQuestionInput] = useState6("");
905
- const i18nTerms = useI18n();
1854
+ const [questionInput, setQuestionInput] = useState7("");
1855
+ const wrapperRef = useRef6(null);
1856
+ const texts = useChatbotTexts();
906
1857
  const handleSubmit = /* @__PURE__ */ __name(async (e) => {
907
1858
  e.preventDefault();
1859
+ if (!questionInput.trim() || loading || writing) {
1860
+ return;
1861
+ }
1862
+ const questionToSend = questionInput.trim();
908
1863
  setQuestionInput("");
909
- await makeQuestion(questionInput);
1864
+ await makeQuestion(questionToSend);
910
1865
  }, "handleSubmit");
911
- 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(
1866
+ useEffect7(() => {
1867
+ const textarea = wrapperRef.current?.querySelector(
1868
+ ".textareaField textarea"
1869
+ );
1870
+ if (textarea) {
1871
+ textarea.style.height = "auto";
1872
+ textarea.style.height = `${textarea.scrollHeight}px`;
1873
+ const handleKeyDown = /* @__PURE__ */ __name((e) => {
1874
+ if (e.key === "Enter" && !e.shiftKey) {
1875
+ e.preventDefault();
1876
+ if (questionInput.trim() && !loading && !writing) {
1877
+ const form = textarea.closest("form");
1878
+ if (form) {
1879
+ form.requestSubmit();
1880
+ }
1881
+ }
1882
+ }
1883
+ }, "handleKeyDown");
1884
+ textarea.addEventListener("keydown", handleKeyDown);
1885
+ return () => {
1886
+ textarea.removeEventListener("keydown", handleKeyDown);
1887
+ };
1888
+ }
1889
+ }, [questionInput, loading, writing]);
1890
+ return /* @__PURE__ */ React11.createElement(Wrapper8, { "data-testid": "chatbot-footer", ref: wrapperRef }, suggestedQuestions.loading && /* @__PURE__ */ React11.createElement(LoadingSuggestedQuestions, null, /* @__PURE__ */ React11.createElement(Spinner, { size: "extra-small" }), texts.SUGGESTED_QUESTIONS.LOADING), /* @__PURE__ */ React11.createElement(Form, { onSubmit: handleSubmit }, /* @__PURE__ */ React11.createElement(
912
1891
  TextArea,
913
1892
  {
914
1893
  fillWidth: true,
915
1894
  disabled: loading || writing,
916
1895
  value: questionInput,
917
1896
  onChange: (e) => setQuestionInput(e.target.value),
918
- placeholder: i18nTerms.INPUT_PLACEHOLDER,
1897
+ placeholder: texts.INPUT_PLACEHOLDER,
919
1898
  className: "textareaField",
920
1899
  rows: 1,
921
1900
  layer: 2
922
1901
  }
923
- ), /* @__PURE__ */ React10.createElement(
1902
+ ), /* @__PURE__ */ React11.createElement(
924
1903
  IconButton,
925
1904
  {
926
1905
  icon: ArrowUpIcon,
927
1906
  loading,
928
- disabled: loading || writing || !questionInput,
1907
+ disabled: loading || writing || !questionInput.trim(),
929
1908
  type: "submit"
930
1909
  }
931
1910
  )));
@@ -934,13 +1913,11 @@ var ChatbotFooter_default = ChatbotFooter;
934
1913
 
935
1914
  // src/components/ChatbotToggle/styles.ts
936
1915
  import { screens as screens2 } from "@ibti-tech/ui";
937
- import { styled as styled8 } from "styled-components";
938
- var ButtonWrapper = styled8.button`
1916
+ import { css as css4, styled as styled9 } from "styled-components";
1917
+ var ButtonWrapper = styled9.button`
939
1918
  height: 32px;
940
1919
  width: 100%;
941
1920
  background: transparent;
942
- /* background-color: ${(props) => props.theme.colors.layers[1].background};
943
- border: 1px solid ${(props) => props.theme.colors.layers[1].border}; */
944
1921
  color: ${(props) => props.theme.colors.palette.primary.normal};
945
1922
  padding: ${(props) => props.theme.spacing.components.small};
946
1923
  font-size: ${(props) => props.theme.fontSizes.small};
@@ -950,6 +1927,21 @@ var ButtonWrapper = styled8.button`
950
1927
  justify-content: center;
951
1928
  gap: ${(props) => props.theme.spacing.components.small};
952
1929
  border-radius: ${(props) => props.theme.borderRadius.medium};
1930
+ border: none !important;
1931
+ outline: none !important;
1932
+ box-shadow: none !important;
1933
+ flex-shrink: 0;
1934
+
1935
+ ${(props) => !props.$opened && props.$verticalPosition === "bottom" && css4`
1936
+ background-color: ${props.theme.colors.layers[1].background};
1937
+ cursor: pointer;
1938
+ min-height: 32px;
1939
+ height: 32px;
1940
+ width: 100%;
1941
+ flex-shrink: 0;
1942
+ box-sizing: border-box;
1943
+ border-radius: ${(props2) => props2.theme.borderRadius.medium} ${(props2) => props2.theme.borderRadius.medium} 0 0;
1944
+ `}
953
1945
 
954
1946
  ${screens2.tablet} {
955
1947
  transition: ${(props) => props.theme.transitionDurations.default};
@@ -962,50 +1954,116 @@ var ButtonWrapper = styled8.button`
962
1954
  `;
963
1955
 
964
1956
  // src/components/ChatbotToggle/index.tsx
965
- import React11 from "react";
1957
+ import React12 from "react";
966
1958
  import { ChevronDownIcon, ChevronUpIcon } from "@ibti-tech/ui/dist/icons";
967
- var ChatbotToggle = /* @__PURE__ */ __name(() => {
968
- const { opened, openedToggle } = useChatbot_default();
969
- const i18nTerms = useI18n();
970
- const toggleText = (opened ? i18nTerms.CHATBOT_BAR.CLOSE : i18nTerms.CHATBOT_BAR.OPEN).replace("{{CHATBOT_NAME}}", i18nTerms.CHATBOT_NAME);
971
- const Chevron = opened ? ChevronUpIcon : ChevronDownIcon;
972
- return /* @__PURE__ */ React11.createElement(ButtonWrapper, { onClick: openedToggle }, /* @__PURE__ */ React11.createElement(BotIcon, null), /* @__PURE__ */ React11.createElement("span", null, toggleText), /* @__PURE__ */ React11.createElement(Chevron, null));
1959
+ var ChatbotToggle = /* @__PURE__ */ __name(({
1960
+ verticalPosition = "bottom",
1961
+ opened: openedProp
1962
+ }) => {
1963
+ const { opened: openedContext, openedToggle } = useChatbot_default();
1964
+ const opened = openedProp ?? openedContext;
1965
+ const texts = useChatbotTexts();
1966
+ const toggleText = (opened ? texts.CHATBOT_BAR.CLOSE : texts.CHATBOT_BAR.OPEN).replace("{{CHATBOT_NAME}}", texts.CHATBOT_NAME);
1967
+ const Chevron = verticalPosition === "top" ? opened ? ChevronUpIcon : ChevronDownIcon : opened ? ChevronDownIcon : ChevronUpIcon;
1968
+ return /* @__PURE__ */ React12.createElement(
1969
+ ButtonWrapper,
1970
+ {
1971
+ onClick: openedToggle,
1972
+ $opened: opened,
1973
+ $verticalPosition: verticalPosition
1974
+ },
1975
+ /* @__PURE__ */ React12.createElement(BotIcon, null),
1976
+ /* @__PURE__ */ React12.createElement("span", null, toggleText),
1977
+ /* @__PURE__ */ React12.createElement(Chevron, null)
1978
+ );
973
1979
  }, "ChatbotToggle");
974
1980
  var ChatbotToggle_default = ChatbotToggle;
975
1981
 
976
1982
  // src/components/ChatUserFeedbackRating/index.tsx
977
- import React12, { useState as useState7 } from "react";
1983
+ import React13, { useState as useState8 } from "react";
978
1984
  import { Button as Button4, RatingStars, TextArea as TextArea2 } from "@ibti-tech/ui";
979
1985
 
980
1986
  // src/components/ChatUserFeedbackRating/styles.ts
981
1987
  import { CardBase } from "@ibti-tech/ui";
982
- import { css as css3, styled as styled9 } from "styled-components";
983
- var ActionButtons = styled9.div`
1988
+ import { css as css5, styled as styled10 } from "styled-components";
1989
+ var ActionButtons = styled10.div`
984
1990
  display: flex;
985
1991
  justify-content: flex-end;
986
1992
  gap: ${(props) => props.theme.spacing.components.small};
1993
+
1994
+ /* Remove border from submit button */
1995
+ button[type='submit'] {
1996
+ border: none !important;
1997
+ outline: none !important;
1998
+ box-shadow: none !important;
1999
+ }
987
2000
  `;
988
- var Form2 = styled9.form`
2001
+ var Form2 = styled10.form`
989
2002
  display: flex;
990
2003
  flex-direction: column;
991
2004
  gap: ${(props) => props.theme.spacing.components.medium};
2005
+
2006
+ /* Feedback textarea styles */
2007
+ .feedbackTextarea {
2008
+ font-family: inherit !important;
2009
+
2010
+ &,
2011
+ *,
2012
+ textarea {
2013
+ font-size: ${(props) => props.theme.fontSizes.medium} !important;
2014
+ font-family: inherit !important;
2015
+ }
2016
+
2017
+ textarea {
2018
+ border-radius: ${(props) => props.theme.borderRadius.medium} !important;
2019
+ border: 1px solid ${(props) => props.theme.colors.layers[2].border} !important;
2020
+ padding: ${(props) => props.theme.spacing.components.small}
2021
+ ${(props) => props.theme.spacing.components.medium} !important;
2022
+ min-height: 40px !important;
2023
+ resize: none !important;
2024
+ line-height: 1.5 !important;
2025
+ width: 100% !important;
2026
+ box-sizing: border-box !important;
2027
+ font-family: inherit !important;
2028
+ color: ${(props) => props.theme.colors.content.text} !important;
2029
+ background-color: ${(props) => props.theme.colors.layers[2].background} !important;
2030
+
2031
+ &:focus {
2032
+ outline: none !important;
2033
+ border-color: ${(props) => props.theme.colors.palette.primary.normal} !important;
2034
+ }
2035
+
2036
+ &::placeholder {
2037
+ color: ${(props) => props.theme.colors.content.detail} !important;
2038
+ font-size: ${(props) => props.theme.fontSizes.medium} !important;
2039
+ font-family: inherit !important;
2040
+ }
2041
+ }
2042
+
2043
+ &:focus-within textarea {
2044
+ outline: none !important;
2045
+ border-color: ${(props) => props.theme.colors.palette.primary.normal} !important;
2046
+ }
2047
+ }
992
2048
  `;
993
- var Description = styled9.div`
2049
+ var Description = styled10.div`
994
2050
  font-size: ${(props) => props.theme.fontSizes.small};
2051
+ color: ${(props) => props.theme.colors.content.text};
995
2052
  margin-bottom: ${(props) => props.theme.spacing.components.medium};
996
2053
  `;
997
- var Title2 = styled9.span`
2054
+ var Title2 = styled10.span`
998
2055
  display: block;
999
2056
  font-weight: ${(props) => props.theme.fontFamily.montserrat.weights.bold};
2057
+ color: ${(props) => props.theme.colors.content.text};
1000
2058
  margin-bottom: ${(props) => props.theme.spacing.components.small};
1001
2059
  `;
1002
- var Box = styled9(CardBase)`
2060
+ var Box = styled10(CardBase)`
1003
2061
  width: 100%;
1004
2062
  height: max-content;
1005
2063
  padding: ${(props) => props.theme.spacing.components.medium};
1006
2064
  transition: ${(props) => props.theme.transitionDurations.default};
1007
2065
  `;
1008
- var Wrapper8 = styled9.div`
2066
+ var Wrapper9 = styled10.div`
1009
2067
  position: absolute;
1010
2068
  z-index: 1;
1011
2069
  left: 0;
@@ -1018,11 +2076,11 @@ var Wrapper8 = styled9.div`
1018
2076
  padding: ${(props) => props.theme.spacing.components.medium};
1019
2077
  transition: ${(props) => props.theme.transitionDurations.default};
1020
2078
 
1021
- ${(props) => props.$opened ? css3`
2079
+ ${(props) => props.$opened ? css5`
1022
2080
  background-color: rgba(0, 0, 0, 0.15);
1023
2081
  backdrop-filter: blur(3px);
1024
2082
  opacity: 1;
1025
- ` : css3`
2083
+ ` : css5`
1026
2084
  pointer-events: none;
1027
2085
  opacity: 0;
1028
2086
 
@@ -1037,9 +2095,9 @@ var ChatUserFeedbackRating = /* @__PURE__ */ __name(({
1037
2095
  chatFeedbackBox
1038
2096
  }) => {
1039
2097
  const chatbot = useChatbot_default();
1040
- const [ratingValue, setRatingValue] = useState7(5);
1041
- const [descriptionValue, setDescriptionValue] = useState7("");
1042
- const i18nTerms = useI18n();
2098
+ const [ratingValue, setRatingValue] = useState8(5);
2099
+ const [descriptionValue, setDescriptionValue] = useState8("");
2100
+ const texts = useChatbotTexts();
1043
2101
  const handleSubmit = /* @__PURE__ */ __name((e) => {
1044
2102
  e.preventDefault();
1045
2103
  chatFeedbackBox.sendUserRating({
@@ -1047,35 +2105,36 @@ var ChatUserFeedbackRating = /* @__PURE__ */ __name(({
1047
2105
  ratingScore: ratingValue
1048
2106
  });
1049
2107
  }, "handleSubmit");
1050
- return /* @__PURE__ */ React12.createElement(Wrapper8, { $opened: chatFeedbackBox.opened && chatbot.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(
2108
+ return /* @__PURE__ */ React13.createElement(Wrapper9, { $opened: chatFeedbackBox.opened && chatbot.opened }, /* @__PURE__ */ React13.createElement(Box, null, /* @__PURE__ */ React13.createElement(Title2, null, texts.CHAT_FEEDBACK.TITLE), /* @__PURE__ */ React13.createElement(Description, null, texts.CHAT_FEEDBACK.DESCRIPTION), /* @__PURE__ */ React13.createElement(Form2, { onSubmit: handleSubmit }, /* @__PURE__ */ React13.createElement(
1051
2109
  RatingStars,
1052
2110
  {
1053
2111
  value: ratingValue,
1054
2112
  setValue: setRatingValue,
1055
2113
  layer: 2
1056
2114
  }
1057
- ), /* @__PURE__ */ React12.createElement(
2115
+ ), /* @__PURE__ */ React13.createElement(
1058
2116
  TextArea2,
1059
2117
  {
1060
2118
  layer: 2,
1061
- placeholder: i18nTerms.CHAT_FEEDBACK.MESSAGE_FIELD,
2119
+ placeholder: texts.CHAT_FEEDBACK.MESSAGE_FIELD,
1062
2120
  fillWidth: true,
1063
2121
  value: descriptionValue,
1064
- onChange: (e) => setDescriptionValue(e.target.value)
2122
+ onChange: (e) => setDescriptionValue(e.target.value),
2123
+ className: "feedbackTextarea"
1065
2124
  }
1066
- ), /* @__PURE__ */ React12.createElement(ActionButtons, null, /* @__PURE__ */ React12.createElement(
2125
+ ), /* @__PURE__ */ React13.createElement(ActionButtons, null, /* @__PURE__ */ React13.createElement(
1067
2126
  Button4,
1068
2127
  {
1069
2128
  onClick: chatFeedbackBox.close,
1070
- label: i18nTerms.CHAT_FEEDBACK.CLOSE_BUTTON,
2129
+ label: texts.CHAT_FEEDBACK.CLOSE_BUTTON,
1071
2130
  size: "small",
1072
2131
  variant: "layerBased"
1073
2132
  }
1074
- ), /* @__PURE__ */ React12.createElement(
2133
+ ), /* @__PURE__ */ React13.createElement(
1075
2134
  Button4,
1076
2135
  {
1077
2136
  type: "submit",
1078
- label: i18nTerms.CHAT_FEEDBACK.SUBMIT_BUTTON,
2137
+ label: texts.CHAT_FEEDBACK.SUBMIT_BUTTON,
1079
2138
  size: "small",
1080
2139
  loading: chatFeedbackBox.loading
1081
2140
  }
@@ -1083,15 +2142,12 @@ var ChatUserFeedbackRating = /* @__PURE__ */ __name(({
1083
2142
  }, "ChatUserFeedbackRating");
1084
2143
 
1085
2144
  // src/contexts/Chatbot/useChatFeedbackBox.ts
1086
- import { useEffect as useEffect6, useState as useState8 } from "react";
2145
+ import { useState as useState9 } from "react";
1087
2146
  var useChatFeedbackBox = /* @__PURE__ */ __name(() => {
1088
2147
  const { chatMessages, apiURL, locale } = useChatbot_default();
1089
- const [rated, setRated] = useState8(false);
1090
- const [opened, setOpened] = useState8(false);
1091
- const [loading, setLoading] = useState8(false);
1092
- useEffect6(() => {
1093
- if (chatMessages.length > 6 && !rated) setOpened(true);
1094
- }, [chatMessages]);
2148
+ const [rated, setRated] = useState9(false);
2149
+ const [opened, setOpened] = useState9(false);
2150
+ const [loading, setLoading] = useState9(false);
1095
2151
  const open = /* @__PURE__ */ __name(() => {
1096
2152
  setOpened(true);
1097
2153
  }, "open");
@@ -1131,42 +2187,93 @@ var useChatFeedbackBox = /* @__PURE__ */ __name(() => {
1131
2187
  }, "useChatFeedbackBox");
1132
2188
 
1133
2189
  // src/components/ChatbotDevice/index.tsx
1134
- var ChatbotDevice = /* @__PURE__ */ __name(({}) => {
2190
+ var ChatbotDevice = /* @__PURE__ */ __name(({
2191
+ verticalPosition = "bottom"
2192
+ }) => {
1135
2193
  const { opened } = useChatbot_default();
1136
2194
  const chatFeedbackBox = useChatFeedbackBox();
1137
- return /* @__PURE__ */ React13.createElement(Wrapper, { $opened: opened }, /* @__PURE__ */ React13.createElement(ChatUserFeedbackRating, { chatFeedbackBox }), /* @__PURE__ */ React13.createElement(ChatbotHeader_default, { chatFeedbackBox }), /* @__PURE__ */ React13.createElement(ChatbotBody_default, null), /* @__PURE__ */ React13.createElement(ChatbotFooter_default, null), /* @__PURE__ */ React13.createElement(ChatbotToggle_default, null));
2195
+ return /* @__PURE__ */ React14.createElement(Wrapper, { $opened: opened, $verticalPosition: verticalPosition }, /* @__PURE__ */ React14.createElement(ChatUserFeedbackRating, { chatFeedbackBox }), /* @__PURE__ */ React14.createElement(ChatbotHeader_default, { chatFeedbackBox }), /* @__PURE__ */ React14.createElement(ChatbotBody_default, null), /* @__PURE__ */ React14.createElement(ChatbotFooter_default, null), /* @__PURE__ */ React14.createElement(ChatbotToggle_default, { verticalPosition, opened }));
1138
2196
  }, "ChatbotDevice");
1139
2197
  var ChatbotDevice_default = ChatbotDevice;
1140
2198
 
1141
2199
  // src/components/ChatbotBar/styles.ts
1142
2200
  import { breakpoints as breakpoints3, screens as screens3 } from "@ibti-tech/ui";
1143
2201
  import { Container } from "@ibti-tech/ui/dist/components/Container";
1144
- import { styled as styled10 } from "styled-components";
1145
- var BarContainer = styled10(Container)`
2202
+ import { css as css6, styled as styled11 } from "styled-components";
2203
+ var BarContainer = styled11(Container)`
1146
2204
  @media screen and (max-width: ${breakpoints3.tablet}px) {
1147
2205
  padding: 0;
1148
2206
  }
1149
2207
 
1150
2208
  ${screens3.tablet} {
1151
2209
  display: flex;
1152
- justify-content: flex-end;
2210
+ justify-content: ${(props) => props.$horizontalPosition === "right" ? "flex-end" : "flex-start"};
2211
+
2212
+ ${(props) => !props.$opened && props.$verticalPosition === "bottom" ? css6`
2213
+ width: ${breakpoints3.mobileL}px;
2214
+ max-width: ${breakpoints3.mobileL}px;
2215
+ min-width: ${breakpoints3.mobileL}px;
2216
+ padding-left: 0;
2217
+ ` : ""}
1153
2218
  }
1154
2219
  `;
1155
- var Wrapper9 = styled10.div`
2220
+ var Wrapper10 = styled11.div`
2221
+ position: fixed;
1156
2222
  width: 100%;
1157
- padding-bottom: ${(props) => props.theme.spacing.components.xsmall};
1158
- background-color: ${(props) => props.theme.colors.layers[0].background};
1159
- border-bottom: 1px solid ${(props) => props.theme.colors.layers[0].border};
1160
- height: 40px;
1161
- z-index: 2;
1162
- pointer-events: none;
1163
2223
  z-index: ${(props) => props.theme.zIndex.modals};
2224
+ pointer-events: none;
2225
+
2226
+ ${(props) => props.$verticalPosition === "top" ? css6`
2227
+ top: 0;
2228
+ padding-top: ${(props2) => props2.theme.spacing.components.xsmall};
2229
+ ` : css6`
2230
+ bottom: 0;
2231
+ padding-bottom: ${(props2) => props2.theme.spacing.components.xsmall};
2232
+
2233
+ @media screen and (max-width: ${breakpoints3.tablet}px) {
2234
+ ${props.$opened ? css6`
2235
+ padding-bottom: 0;
2236
+ ` : ""}
2237
+ }
2238
+ `}
2239
+
2240
+ ${screens3.tablet} {
2241
+ width: auto;
2242
+ background-color: transparent;
2243
+ border: none;
2244
+ padding: 0;
2245
+ ${(props) => props.$horizontalPosition === "right" ? css6`
2246
+ right: ${(props2) => props2.theme.spacing.components.medium};
2247
+ ` : css6`
2248
+ left: ${(props2) => props2.theme.spacing.components.medium};
2249
+ `}
2250
+ }
1164
2251
  `;
1165
2252
 
1166
2253
  // src/components/ChatbotBar/index.tsx
1167
- import React14 from "react";
1168
- var ChatbotBar = /* @__PURE__ */ __name(() => {
1169
- return /* @__PURE__ */ React14.createElement(Wrapper9, null, /* @__PURE__ */ React14.createElement(BarContainer, null, /* @__PURE__ */ React14.createElement(ChatbotDevice_default, null)));
2254
+ import React15 from "react";
2255
+ var ChatbotBar = /* @__PURE__ */ __name(({
2256
+ verticalPosition = "bottom",
2257
+ horizontalPosition = "right"
2258
+ }) => {
2259
+ const { opened } = useChatbot_default();
2260
+ return /* @__PURE__ */ React15.createElement(
2261
+ Wrapper10,
2262
+ {
2263
+ $verticalPosition: verticalPosition,
2264
+ $horizontalPosition: horizontalPosition,
2265
+ $opened: opened
2266
+ },
2267
+ /* @__PURE__ */ React15.createElement(
2268
+ BarContainer,
2269
+ {
2270
+ $horizontalPosition: horizontalPosition,
2271
+ $opened: opened,
2272
+ $verticalPosition: verticalPosition
2273
+ },
2274
+ /* @__PURE__ */ React15.createElement(ChatbotDevice_default, { verticalPosition })
2275
+ )
2276
+ );
1170
2277
  }, "ChatbotBar");
1171
2278
  export {
1172
2279
  ChatbotBar,