@kroonen-ai/librebot-widget 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -148,6 +148,13 @@ const getStyles = (primaryColor = '#14b8a6') => `
148
148
  background: var(--lb-bg-secondary);
149
149
  }
150
150
 
151
+ .librebot-chat-container {
152
+ flex: 1;
153
+ display: flex;
154
+ flex-direction: column;
155
+ overflow: hidden;
156
+ }
157
+
151
158
  .librebot-messages {
152
159
  flex: 1;
153
160
  overflow-y: auto;
@@ -157,6 +164,13 @@ const getStyles = (primaryColor = '#14b8a6') => `
157
164
  gap: 12px;
158
165
  }
159
166
 
167
+ .librebot-booking-container {
168
+ flex: 1;
169
+ display: flex;
170
+ flex-direction: column;
171
+ overflow: hidden;
172
+ }
173
+
160
174
  .librebot-message {
161
175
  max-width: 85%;
162
176
  padding: 10px 14px;
@@ -467,11 +481,616 @@ const getStyles = (primaryColor = '#14b8a6') => `
467
481
  .librebot-powered a:hover {
468
482
  text-decoration: underline;
469
483
  }
484
+
485
+ /* RTL Support */
486
+ .librebot-widget.rtl {
487
+ direction: rtl;
488
+ text-align: right;
489
+ }
490
+
491
+ .librebot-widget.rtl .librebot-message.user {
492
+ align-self: flex-start;
493
+ border-bottom-right-radius: 16px;
494
+ border-bottom-left-radius: 4px;
495
+ }
496
+
497
+ .librebot-widget.rtl .librebot-message.assistant {
498
+ align-self: flex-end;
499
+ border-bottom-left-radius: 16px;
500
+ border-bottom-right-radius: 4px;
501
+ }
502
+
503
+ .librebot-widget.rtl .librebot-typing {
504
+ align-self: flex-end;
505
+ border-bottom-left-radius: 16px;
506
+ border-bottom-right-radius: 4px;
507
+ }
508
+
509
+ .librebot-widget.rtl .librebot-header-content {
510
+ flex-direction: row-reverse;
511
+ }
512
+
513
+ .librebot-widget.rtl .librebot-input {
514
+ text-align: right;
515
+ }
516
+
517
+ .librebot-widget.rtl .librebot-input-area {
518
+ flex-direction: row-reverse;
519
+ }
520
+
521
+ .librebot-widget.rtl .librebot-ul,
522
+ .librebot-widget.rtl .librebot-ol {
523
+ padding-left: 0;
524
+ padding-right: 20px;
525
+ }
526
+
527
+ .librebot-widget.rtl .librebot-blockquote {
528
+ border-left: none;
529
+ border-right: 3px solid var(--lb-primary);
530
+ border-radius: 6px 0 0 6px;
531
+ }
532
+
533
+ /* Booking Mode Styles */
534
+ .librebot-booking {
535
+ flex: 1;
536
+ display: flex;
537
+ flex-direction: column;
538
+ overflow: hidden;
539
+ }
540
+
541
+ .librebot-booking-header {
542
+ padding: 12px 16px;
543
+ border-bottom: 1px solid var(--lb-border);
544
+ display: flex;
545
+ align-items: center;
546
+ gap: 12px;
547
+ }
548
+
549
+ .librebot-booking-back {
550
+ width: 32px;
551
+ height: 32px;
552
+ border-radius: 8px;
553
+ border: none;
554
+ background: transparent;
555
+ cursor: pointer;
556
+ display: flex;
557
+ align-items: center;
558
+ justify-content: center;
559
+ color: var(--lb-text-secondary);
560
+ transition: background 0.2s;
561
+ }
562
+
563
+ .librebot-booking-back:hover {
564
+ background: var(--lb-bg-secondary);
565
+ }
566
+
567
+ .librebot-booking-back svg {
568
+ width: 20px;
569
+ height: 20px;
570
+ }
571
+
572
+ .librebot-booking-title {
573
+ font-size: 16px;
574
+ font-weight: 600;
575
+ color: var(--lb-text);
576
+ margin: 0;
577
+ }
578
+
579
+ .librebot-booking-content {
580
+ flex: 1;
581
+ overflow-y: auto;
582
+ padding: 16px;
583
+ }
584
+
585
+ .librebot-booking-section {
586
+ margin-bottom: 20px;
587
+ }
588
+
589
+ .librebot-booking-label {
590
+ display: block;
591
+ font-size: 13px;
592
+ font-weight: 600;
593
+ color: var(--lb-text);
594
+ margin-bottom: 8px;
595
+ }
596
+
597
+ .librebot-date-input {
598
+ width: 100%;
599
+ padding: 10px 14px;
600
+ border: 1px solid var(--lb-border);
601
+ border-radius: 8px;
602
+ background: var(--lb-bg-secondary);
603
+ color: var(--lb-text);
604
+ font-size: 14px;
605
+ outline: none;
606
+ cursor: pointer;
607
+ }
608
+
609
+ .librebot-date-input:focus {
610
+ border-color: var(--lb-primary);
611
+ }
612
+
613
+ .librebot-slots {
614
+ display: grid;
615
+ grid-template-columns: repeat(3, 1fr);
616
+ gap: 8px;
617
+ }
618
+
619
+ .librebot-slot {
620
+ padding: 10px 8px;
621
+ border: 1px solid var(--lb-border);
622
+ border-radius: 8px;
623
+ background: var(--lb-bg-secondary);
624
+ color: var(--lb-text);
625
+ font-size: 13px;
626
+ cursor: pointer;
627
+ transition: all 0.2s;
628
+ text-align: center;
629
+ }
630
+
631
+ .librebot-slot:hover {
632
+ border-color: var(--lb-primary);
633
+ }
634
+
635
+ .librebot-slot.selected {
636
+ background: var(--lb-primary);
637
+ border-color: var(--lb-primary);
638
+ color: white;
639
+ }
640
+
641
+ .librebot-slot:disabled {
642
+ opacity: 0.5;
643
+ cursor: not-allowed;
644
+ }
645
+
646
+ .librebot-slots-loading,
647
+ .librebot-slots-empty {
648
+ text-align: center;
649
+ padding: 20px;
650
+ color: var(--lb-text-secondary);
651
+ font-size: 13px;
652
+ }
653
+
654
+ .librebot-form-field {
655
+ margin-bottom: 16px;
656
+ }
657
+
658
+ .librebot-form-label {
659
+ display: block;
660
+ font-size: 13px;
661
+ font-weight: 500;
662
+ color: var(--lb-text);
663
+ margin-bottom: 6px;
664
+ }
665
+
666
+ .librebot-form-label .required {
667
+ color: #ef4444;
668
+ margin-left: 2px;
669
+ }
670
+
671
+ .librebot-form-input,
672
+ .librebot-form-textarea,
673
+ .librebot-form-select {
674
+ width: 100%;
675
+ padding: 10px 14px;
676
+ border: 1px solid var(--lb-border);
677
+ border-radius: 8px;
678
+ background: var(--lb-bg-secondary);
679
+ color: var(--lb-text);
680
+ font-size: 14px;
681
+ outline: none;
682
+ font-family: inherit;
683
+ }
684
+
685
+ .librebot-form-input:focus,
686
+ .librebot-form-textarea:focus,
687
+ .librebot-form-select:focus {
688
+ border-color: var(--lb-primary);
689
+ }
690
+
691
+ .librebot-form-textarea {
692
+ min-height: 80px;
693
+ resize: vertical;
694
+ }
695
+
696
+ .librebot-booking-submit {
697
+ width: 100%;
698
+ padding: 12px;
699
+ border: none;
700
+ border-radius: 8px;
701
+ background: var(--lb-primary);
702
+ color: white;
703
+ font-size: 14px;
704
+ font-weight: 600;
705
+ cursor: pointer;
706
+ transition: background 0.2s;
707
+ margin-top: 12px;
708
+ }
709
+
710
+ .librebot-booking-submit:hover {
711
+ background: var(--lb-primary-hover);
712
+ }
713
+
714
+ .librebot-booking-submit:disabled {
715
+ opacity: 0.5;
716
+ cursor: not-allowed;
717
+ }
718
+
719
+ /* Booking Success */
720
+ .librebot-booking-success {
721
+ text-align: center;
722
+ padding: 24px 16px;
723
+ }
724
+
725
+ .librebot-booking-success-icon {
726
+ width: 60px;
727
+ height: 60px;
728
+ margin: 0 auto 16px;
729
+ background: var(--lb-primary);
730
+ border-radius: 50%;
731
+ display: flex;
732
+ align-items: center;
733
+ justify-content: center;
734
+ }
735
+
736
+ .librebot-booking-success-icon svg {
737
+ width: 32px;
738
+ height: 32px;
739
+ fill: white;
740
+ }
741
+
742
+ .librebot-booking-success h3 {
743
+ font-size: 18px;
744
+ font-weight: 600;
745
+ color: var(--lb-text);
746
+ margin: 0 0 16px;
747
+ }
748
+
749
+ .librebot-booking-details {
750
+ background: var(--lb-bg-secondary);
751
+ border-radius: 8px;
752
+ padding: 16px;
753
+ margin-bottom: 16px;
754
+ text-align: left;
755
+ }
756
+
757
+ .librebot-booking-detail {
758
+ display: flex;
759
+ justify-content: space-between;
760
+ padding: 8px 0;
761
+ border-bottom: 1px solid var(--lb-border);
762
+ font-size: 13px;
763
+ }
764
+
765
+ .librebot-booking-detail:last-child {
766
+ border-bottom: none;
767
+ }
768
+
769
+ .librebot-booking-detail-label {
770
+ color: var(--lb-text-secondary);
771
+ }
772
+
773
+ .librebot-booking-detail-value {
774
+ color: var(--lb-text);
775
+ font-weight: 500;
776
+ }
777
+
778
+ .librebot-meeting-link {
779
+ display: inline-block;
780
+ padding: 12px 24px;
781
+ background: var(--lb-primary);
782
+ color: white;
783
+ text-decoration: none;
784
+ border-radius: 8px;
785
+ font-weight: 600;
786
+ margin-bottom: 16px;
787
+ transition: background 0.2s;
788
+ }
789
+
790
+ .librebot-meeting-link:hover {
791
+ background: var(--lb-primary-hover);
792
+ }
793
+
794
+ /* RTL Booking Styles */
795
+ .librebot-widget.rtl .librebot-booking-header {
796
+ flex-direction: row-reverse;
797
+ }
798
+
799
+ .librebot-widget.rtl .librebot-form-label .required {
800
+ margin-left: 0;
801
+ margin-right: 2px;
802
+ }
803
+
804
+ .librebot-widget.rtl .librebot-form-input,
805
+ .librebot-widget.rtl .librebot-form-textarea {
806
+ text-align: right;
807
+ }
808
+
809
+ .librebot-widget.rtl .librebot-booking-detail {
810
+ flex-direction: row-reverse;
811
+ }
812
+
813
+ .librebot-widget.rtl .librebot-booking-details {
814
+ text-align: right;
815
+ }
470
816
  `;
471
817
 
472
818
  const chatIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>`;
473
819
  const closeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
474
820
  const sendIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>`;
821
+ const backIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>`;
822
+ const checkIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
823
+
824
+ const WIDGET_TRANSLATIONS = {
825
+ en: {
826
+ placeholder: 'Ask a question...',
827
+ welcomeMessage: 'Hi! I can help you find answers in the documentation. What would you like to know?',
828
+ errorPrefix: 'Sorry, I encountered an error:',
829
+ connectionError: 'Sorry, I had trouble connecting. Please try again.',
830
+ streamingNotSupported: 'Sorry, streaming is not supported.',
831
+ poweredBy: 'Powered by',
832
+ bookingTitle: 'Book an Appointment',
833
+ selectDate: 'Select a date',
834
+ selectTime: 'Select a time',
835
+ bookAppointment: 'Book Appointment',
836
+ noSlotsAvailable: 'No time slots available for this date',
837
+ loadingSlots: 'Loading available times...',
838
+ bookingSuccess: 'Your appointment has been confirmed!',
839
+ bookingError: 'Failed to book appointment. Please try again.',
840
+ backToChat: 'Back to chat',
841
+ yourDetails: 'Your Details',
842
+ confirmBooking: 'Confirm Booking',
843
+ meetingWith: 'Meeting',
844
+ duration: 'Duration',
845
+ minutes: 'minutes',
846
+ joinMeeting: 'Join Meeting',
847
+ },
848
+ fr: {
849
+ placeholder: 'Posez une question...',
850
+ welcomeMessage: 'Bonjour ! Je peux vous aider à trouver des réponses dans la documentation. Que souhaitez-vous savoir ?',
851
+ errorPrefix: 'Désolé, j\'ai rencontré une erreur :',
852
+ connectionError: 'Désolé, j\'ai eu du mal à me connecter. Veuillez réessayer.',
853
+ streamingNotSupported: 'Désolé, le streaming n\'est pas pris en charge.',
854
+ poweredBy: 'Propulsé par',
855
+ bookingTitle: 'Prendre rendez-vous',
856
+ selectDate: 'Choisir une date',
857
+ selectTime: 'Choisir une heure',
858
+ bookAppointment: 'Prendre rendez-vous',
859
+ noSlotsAvailable: 'Aucun créneau disponible pour cette date',
860
+ loadingSlots: 'Chargement des horaires...',
861
+ bookingSuccess: 'Votre rendez-vous a été confirmé !',
862
+ bookingError: 'Échec de la réservation. Veuillez réessayer.',
863
+ backToChat: 'Retour au chat',
864
+ yourDetails: 'Vos informations',
865
+ confirmBooking: 'Confirmer la réservation',
866
+ meetingWith: 'Réunion',
867
+ duration: 'Durée',
868
+ minutes: 'minutes',
869
+ joinMeeting: 'Rejoindre la réunion',
870
+ },
871
+ de: {
872
+ placeholder: 'Stellen Sie eine Frage...',
873
+ welcomeMessage: 'Hallo! Ich kann Ihnen helfen, Antworten in der Dokumentation zu finden. Was möchten Sie wissen?',
874
+ errorPrefix: 'Entschuldigung, ein Fehler ist aufgetreten:',
875
+ connectionError: 'Entschuldigung, ich hatte Verbindungsprobleme. Bitte versuchen Sie es erneut.',
876
+ streamingNotSupported: 'Entschuldigung, Streaming wird nicht unterstützt.',
877
+ poweredBy: 'Betrieben von',
878
+ bookingTitle: 'Termin buchen',
879
+ selectDate: 'Datum auswählen',
880
+ selectTime: 'Uhrzeit auswählen',
881
+ bookAppointment: 'Termin buchen',
882
+ noSlotsAvailable: 'Keine Termine für dieses Datum verfügbar',
883
+ loadingSlots: 'Verfügbare Zeiten werden geladen...',
884
+ bookingSuccess: 'Ihr Termin wurde bestätigt!',
885
+ bookingError: 'Terminbuchung fehlgeschlagen. Bitte versuchen Sie es erneut.',
886
+ backToChat: 'Zurück zum Chat',
887
+ yourDetails: 'Ihre Angaben',
888
+ confirmBooking: 'Buchung bestätigen',
889
+ meetingWith: 'Meeting',
890
+ duration: 'Dauer',
891
+ minutes: 'Minuten',
892
+ joinMeeting: 'An Meeting teilnehmen',
893
+ },
894
+ nl: {
895
+ placeholder: 'Stel een vraag...',
896
+ welcomeMessage: 'Hallo! Ik kan je helpen antwoorden te vinden in de documentatie. Wat wil je weten?',
897
+ errorPrefix: 'Sorry, er is een fout opgetreden:',
898
+ connectionError: 'Sorry, ik had moeite met verbinden. Probeer het opnieuw.',
899
+ streamingNotSupported: 'Sorry, streaming wordt niet ondersteund.',
900
+ poweredBy: 'Mogelijk gemaakt door',
901
+ bookingTitle: 'Afspraak maken',
902
+ selectDate: 'Selecteer een datum',
903
+ selectTime: 'Selecteer een tijd',
904
+ bookAppointment: 'Afspraak maken',
905
+ noSlotsAvailable: 'Geen tijdsloten beschikbaar voor deze datum',
906
+ loadingSlots: 'Beschikbare tijden laden...',
907
+ bookingSuccess: 'Je afspraak is bevestigd!',
908
+ bookingError: 'Afspraak maken mislukt. Probeer het opnieuw.',
909
+ backToChat: 'Terug naar chat',
910
+ yourDetails: 'Je gegevens',
911
+ confirmBooking: 'Boeking bevestigen',
912
+ meetingWith: 'Vergadering',
913
+ duration: 'Duur',
914
+ minutes: 'minuten',
915
+ joinMeeting: 'Deelnemen aan vergadering',
916
+ },
917
+ es: {
918
+ placeholder: 'Haz una pregunta...',
919
+ welcomeMessage: '¡Hola! Puedo ayudarte a encontrar respuestas en la documentación. ¿Qué te gustaría saber?',
920
+ errorPrefix: 'Lo siento, encontré un error:',
921
+ connectionError: 'Lo siento, tuve problemas para conectarme. Por favor, inténtalo de nuevo.',
922
+ streamingNotSupported: 'Lo siento, el streaming no está soportado.',
923
+ poweredBy: 'Desarrollado por',
924
+ bookingTitle: 'Reservar cita',
925
+ selectDate: 'Seleccionar fecha',
926
+ selectTime: 'Seleccionar hora',
927
+ bookAppointment: 'Reservar cita',
928
+ noSlotsAvailable: 'No hay horarios disponibles para esta fecha',
929
+ loadingSlots: 'Cargando horarios disponibles...',
930
+ bookingSuccess: '¡Tu cita ha sido confirmada!',
931
+ bookingError: 'Error al reservar la cita. Por favor, inténtalo de nuevo.',
932
+ backToChat: 'Volver al chat',
933
+ yourDetails: 'Tus datos',
934
+ confirmBooking: 'Confirmar reserva',
935
+ meetingWith: 'Reunión',
936
+ duration: 'Duración',
937
+ minutes: 'minutos',
938
+ joinMeeting: 'Unirse a la reunión',
939
+ },
940
+ pt: {
941
+ placeholder: 'Faça uma pergunta...',
942
+ welcomeMessage: 'Olá! Posso ajudá-lo a encontrar respostas na documentação. O que você gostaria de saber?',
943
+ errorPrefix: 'Desculpe, encontrei um erro:',
944
+ connectionError: 'Desculpe, tive problemas para conectar. Por favor, tente novamente.',
945
+ streamingNotSupported: 'Desculpe, streaming não é suportado.',
946
+ poweredBy: 'Desenvolvido por',
947
+ bookingTitle: 'Agendar consulta',
948
+ selectDate: 'Selecionar data',
949
+ selectTime: 'Selecionar horário',
950
+ bookAppointment: 'Agendar consulta',
951
+ noSlotsAvailable: 'Nenhum horário disponível para esta data',
952
+ loadingSlots: 'Carregando horários disponíveis...',
953
+ bookingSuccess: 'Seu agendamento foi confirmado!',
954
+ bookingError: 'Falha ao agendar. Por favor, tente novamente.',
955
+ backToChat: 'Voltar ao chat',
956
+ yourDetails: 'Seus dados',
957
+ confirmBooking: 'Confirmar agendamento',
958
+ meetingWith: 'Reunião',
959
+ duration: 'Duração',
960
+ minutes: 'minutos',
961
+ joinMeeting: 'Entrar na reunião',
962
+ },
963
+ ko: {
964
+ placeholder: '질문하세요...',
965
+ welcomeMessage: '안녕하세요! 문서에서 답변을 찾는 데 도움을 드릴 수 있습니다. 무엇을 알고 싶으신가요?',
966
+ errorPrefix: '죄송합니다. 오류가 발생했습니다:',
967
+ connectionError: '죄송합니다. 연결에 문제가 있습니다. 다시 시도해 주세요.',
968
+ streamingNotSupported: '죄송합니다. 스트리밍이 지원되지 않습니다.',
969
+ poweredBy: '제공:',
970
+ bookingTitle: '예약하기',
971
+ selectDate: '날짜 선택',
972
+ selectTime: '시간 선택',
973
+ bookAppointment: '예약하기',
974
+ noSlotsAvailable: '선택한 날짜에 가능한 시간이 없습니다',
975
+ loadingSlots: '가능한 시간 로딩 중...',
976
+ bookingSuccess: '예약이 확정되었습니다!',
977
+ bookingError: '예약에 실패했습니다. 다시 시도해 주세요.',
978
+ backToChat: '채팅으로 돌아가기',
979
+ yourDetails: '정보 입력',
980
+ confirmBooking: '예약 확정',
981
+ meetingWith: '미팅',
982
+ duration: '소요 시간',
983
+ minutes: '분',
984
+ joinMeeting: '미팅 참가',
985
+ },
986
+ ja: {
987
+ placeholder: '質問してください...',
988
+ welcomeMessage: 'こんにちは!ドキュメントから回答を見つけるお手伝いができます。何をお知りになりたいですか?',
989
+ errorPrefix: '申し訳ありません。エラーが発生しました:',
990
+ connectionError: '申し訳ありません。接続に問題がありました。もう一度お試しください。',
991
+ streamingNotSupported: '申し訳ありません。ストリーミングはサポートされていません。',
992
+ poweredBy: '提供:',
993
+ bookingTitle: '予約する',
994
+ selectDate: '日付を選択',
995
+ selectTime: '時間を選択',
996
+ bookAppointment: '予約する',
997
+ noSlotsAvailable: 'この日は空き時間がありません',
998
+ loadingSlots: '空き時間を読み込み中...',
999
+ bookingSuccess: 'ご予約が確定しました!',
1000
+ bookingError: '予約に失敗しました。もう一度お試しください。',
1001
+ backToChat: 'チャットに戻る',
1002
+ yourDetails: 'お客様情報',
1003
+ confirmBooking: '予約を確定',
1004
+ meetingWith: 'ミーティング',
1005
+ duration: '所要時間',
1006
+ minutes: '分',
1007
+ joinMeeting: 'ミーティングに参加',
1008
+ },
1009
+ zh: {
1010
+ placeholder: '请提问...',
1011
+ welcomeMessage: '您好!我可以帮助您在文档中查找答案。您想了解什么?',
1012
+ errorPrefix: '抱歉,遇到了错误:',
1013
+ connectionError: '抱歉,连接出现问题。请重试。',
1014
+ streamingNotSupported: '抱歉,不支持流式传输。',
1015
+ poweredBy: '由以下提供支持:',
1016
+ bookingTitle: '预约',
1017
+ selectDate: '选择日期',
1018
+ selectTime: '选择时间',
1019
+ bookAppointment: '预约',
1020
+ noSlotsAvailable: '该日期没有可用时间段',
1021
+ loadingSlots: '加载可用时间...',
1022
+ bookingSuccess: '您的预约已确认!',
1023
+ bookingError: '预约失败。请重试。',
1024
+ backToChat: '返回聊天',
1025
+ yourDetails: '您的信息',
1026
+ confirmBooking: '确认预约',
1027
+ meetingWith: '会议',
1028
+ duration: '时长',
1029
+ minutes: '分钟',
1030
+ joinMeeting: '加入会议',
1031
+ },
1032
+ hi: {
1033
+ placeholder: 'कोई प्रश्न पूछें...',
1034
+ welcomeMessage: 'नमस्ते! मैं आपको दस्तावेज़ों में उत्तर खोजने में मदद कर सकता हूं। आप क्या जानना चाहते हैं?',
1035
+ errorPrefix: 'क्षमा करें, एक त्रुटि हुई:',
1036
+ connectionError: 'क्षमा करें, कनेक्ट करने में समस्या हुई। कृपया पुनः प्रयास करें।',
1037
+ streamingNotSupported: 'क्षमा करें, स्ट्रीमिंग समर्थित नहीं है।',
1038
+ poweredBy: 'द्वारा संचालित',
1039
+ bookingTitle: 'अपॉइंटमेंट बुक करें',
1040
+ selectDate: 'तिथि चुनें',
1041
+ selectTime: 'समय चुनें',
1042
+ bookAppointment: 'अपॉइंटमेंट बुक करें',
1043
+ noSlotsAvailable: 'इस तिथि के लिए कोई समय उपलब्ध नहीं है',
1044
+ loadingSlots: 'उपलब्ध समय लोड हो रहा है...',
1045
+ bookingSuccess: 'आपकी अपॉइंटमेंट की पुष्टि हो गई है!',
1046
+ bookingError: 'बुकिंग विफल। कृपया पुनः प्रयास करें।',
1047
+ backToChat: 'चैट पर वापस जाएं',
1048
+ yourDetails: 'आपका विवरण',
1049
+ confirmBooking: 'बुकिंग की पुष्टि करें',
1050
+ meetingWith: 'मीटिंग',
1051
+ duration: 'अवधि',
1052
+ minutes: 'मिनट',
1053
+ joinMeeting: 'मीटिंग में शामिल हों',
1054
+ },
1055
+ ar: {
1056
+ placeholder: 'اطرح سؤالاً...',
1057
+ welcomeMessage: 'مرحباً! يمكنني مساعدتك في العثور على إجابات في الوثائق. ماذا تريد أن تعرف؟',
1058
+ errorPrefix: 'عذراً، حدث خطأ:',
1059
+ connectionError: 'عذراً، واجهت مشكلة في الاتصال. يرجى المحاولة مرة أخرى.',
1060
+ streamingNotSupported: 'عذراً، البث غير مدعوم.',
1061
+ poweredBy: 'مدعوم من',
1062
+ bookingTitle: 'حجز موعد',
1063
+ selectDate: 'اختر التاريخ',
1064
+ selectTime: 'اختر الوقت',
1065
+ bookAppointment: 'حجز موعد',
1066
+ noSlotsAvailable: 'لا توجد أوقات متاحة لهذا التاريخ',
1067
+ loadingSlots: 'جاري تحميل الأوقات المتاحة...',
1068
+ bookingSuccess: 'تم تأكيد موعدك!',
1069
+ bookingError: 'فشل الحجز. يرجى المحاولة مرة أخرى.',
1070
+ backToChat: 'العودة إلى المحادثة',
1071
+ yourDetails: 'بياناتك',
1072
+ confirmBooking: 'تأكيد الحجز',
1073
+ meetingWith: 'اجتماع',
1074
+ duration: 'المدة',
1075
+ minutes: 'دقيقة',
1076
+ joinMeeting: 'انضم إلى الاجتماع',
1077
+ },
1078
+ };
1079
+ const RTL_LANGUAGES = ['ar'];
1080
+ function detectLanguage() {
1081
+ // 1. Check document's lang attribute
1082
+ const htmlLang = document.documentElement.lang?.toLowerCase().split('-')[0];
1083
+ if (htmlLang && htmlLang in WIDGET_TRANSLATIONS) {
1084
+ return htmlLang;
1085
+ }
1086
+ // 2. Check navigator.language
1087
+ const navLang = navigator.language?.toLowerCase().split('-')[0];
1088
+ if (navLang && navLang in WIDGET_TRANSLATIONS) {
1089
+ return navLang;
1090
+ }
1091
+ // 3. Default to English
1092
+ return 'en';
1093
+ }
475
1094
 
476
1095
  class LibreBotWidget {
477
1096
  constructor(config) {
@@ -483,6 +1102,17 @@ class LibreBotWidget {
483
1102
  this.messages = [];
484
1103
  this.isLoading = false;
485
1104
  this.sessionId = null;
1105
+ this.translations = WIDGET_TRANSLATIONS.en;
1106
+ this.isRtl = false;
1107
+ // Booking mode state
1108
+ this.mode = 'chat';
1109
+ this.bookingConfig = null;
1110
+ this.selectedDate = null;
1111
+ this.selectedSlot = null;
1112
+ this.availableSlots = [];
1113
+ this.bookingContainer = null;
1114
+ this.chatContainer = null;
1115
+ this.inputArea = null;
486
1116
  this.defaultConfig = {
487
1117
  apiUrl: 'https://librebot.io/api',
488
1118
  position: 'bottom-right',
@@ -490,17 +1120,29 @@ class LibreBotWidget {
490
1120
  primaryColor: '#14b8a6',
491
1121
  title: 'Libre Bot',
492
1122
  subtitle: 'AI Documentation Assistant',
493
- placeholder: 'Ask a question...',
494
- welcomeMessage: 'Hi! I can help you find answers in the documentation. What would you like to know?',
1123
+ placeholder: '', // Will be set from translations
1124
+ welcomeMessage: '', // Will be set from translations
495
1125
  streaming: true,
496
1126
  whiteLabel: false,
497
1127
  autoLoadConfig: true,
1128
+ lang: 'en',
498
1129
  };
499
1130
  if (!config.apiKey) {
500
1131
  console.error('LibreBot: API key is required');
501
1132
  return;
502
1133
  }
503
- this.config = { ...this.defaultConfig, ...config };
1134
+ // Detect language from config or host page
1135
+ const detectedLang = config.lang || detectLanguage();
1136
+ this.translations = WIDGET_TRANSLATIONS[detectedLang] || WIDGET_TRANSLATIONS.en;
1137
+ this.isRtl = RTL_LANGUAGES.includes(detectedLang);
1138
+ // Apply defaults with translations
1139
+ const configWithTranslations = {
1140
+ ...config,
1141
+ lang: detectedLang,
1142
+ placeholder: config.placeholder || this.translations.placeholder,
1143
+ welcomeMessage: config.welcomeMessage || this.translations.welcomeMessage,
1144
+ };
1145
+ this.config = { ...this.defaultConfig, ...configWithTranslations };
504
1146
  // Auto-load config from server if enabled and no custom settings provided
505
1147
  if (this.config.autoLoadConfig) {
506
1148
  this.loadRemoteConfig().then(() => this.init());
@@ -601,7 +1243,12 @@ class LibreBotWidget {
601
1243
  createWidget() {
602
1244
  // Create container
603
1245
  this.container = document.createElement('div');
604
- this.container.className = `librebot-widget ${this.config.theme === 'light' ? 'light' : ''}`;
1246
+ let className = 'librebot-widget';
1247
+ if (this.config.theme === 'light')
1248
+ className += ' light';
1249
+ if (this.isRtl)
1250
+ className += ' rtl';
1251
+ this.container.className = className;
605
1252
  // Create FAB button
606
1253
  const fab = document.createElement('button');
607
1254
  fab.className = `librebot-fab ${this.config.position === 'bottom-left' ? 'left' : ''}`;
@@ -618,6 +1265,9 @@ class LibreBotWidget {
618
1265
  // Get references
619
1266
  this.messagesContainer = this.modal.querySelector('.librebot-messages');
620
1267
  this.input = this.modal.querySelector('.librebot-input');
1268
+ this.chatContainer = this.modal.querySelector('.librebot-chat-container');
1269
+ this.bookingContainer = this.modal.querySelector('.librebot-booking-container');
1270
+ this.inputArea = this.modal.querySelector('.librebot-input-area');
621
1271
  // Add event listeners
622
1272
  const closeBtn = this.modal.querySelector('.librebot-close');
623
1273
  closeBtn?.addEventListener('click', () => this.close());
@@ -640,7 +1290,7 @@ class LibreBotWidget {
640
1290
  const poweredByHtml = this.config.whiteLabel
641
1291
  ? ''
642
1292
  : `<div class="librebot-powered">
643
- Powered by <a href="https://librebot.io" target="_blank">Libre Bot</a>
1293
+ ${this.translations.poweredBy} <a href="https://librebot.io" target="_blank">Libre Bot</a>
644
1294
  </div>`;
645
1295
  return `
646
1296
  <div class="librebot-header">
@@ -653,7 +1303,10 @@ class LibreBotWidget {
653
1303
  </div>
654
1304
  <button class="librebot-close">${closeIcon}</button>
655
1305
  </div>
656
- <div class="librebot-messages"></div>
1306
+ <div class="librebot-chat-container">
1307
+ <div class="librebot-messages"></div>
1308
+ </div>
1309
+ <div class="librebot-booking-container" style="display: none;"></div>
657
1310
  <div class="librebot-input-area">
658
1311
  <input type="text" class="librebot-input" placeholder="${this.config.placeholder}" />
659
1312
  <button class="librebot-send">${sendIcon}</button>
@@ -800,16 +1453,18 @@ class LibreBotWidget {
800
1453
  const response = await this.callAPI(content);
801
1454
  typing.remove();
802
1455
  if (response.error) {
803
- this.addMessage('assistant', `Sorry, I encountered an error: ${response.error}`);
1456
+ this.addMessage('assistant', `${this.translations.errorPrefix} ${response.error}`);
804
1457
  }
805
1458
  else {
806
1459
  this.addMessage('assistant', response.response);
1460
+ // Check for booking intent
1461
+ this.handleBookingIntent(response);
807
1462
  }
808
1463
  }
809
1464
  }
810
1465
  catch (error) {
811
1466
  typing.remove();
812
- this.addMessage('assistant', 'Sorry, I had trouble connecting. Please try again.');
1467
+ this.addMessage('assistant', this.translations.connectionError);
813
1468
  console.error('LibreBot error:', error);
814
1469
  }
815
1470
  finally {
@@ -833,7 +1488,7 @@ class LibreBotWidget {
833
1488
  if (!response.ok) {
834
1489
  typing.remove();
835
1490
  const error = await response.json().catch(() => ({ error: 'Request failed' }));
836
- this.addMessage('assistant', `Sorry, I encountered an error: ${error.error || 'Request failed'}`);
1491
+ this.addMessage('assistant', `${this.translations.errorPrefix} ${error.error || 'Request failed'}`);
837
1492
  return;
838
1493
  }
839
1494
  // Remove typing indicator and create streaming message element
@@ -843,11 +1498,12 @@ class LibreBotWidget {
843
1498
  this.messagesContainer?.appendChild(messageEl);
844
1499
  const reader = response.body?.getReader();
845
1500
  if (!reader) {
846
- this.addMessage('assistant', 'Sorry, streaming is not supported.');
1501
+ this.addMessage('assistant', this.translations.streamingNotSupported);
847
1502
  return;
848
1503
  }
849
1504
  const decoder = new TextDecoder();
850
1505
  let fullContent = '';
1506
+ let bookingIntentData = null;
851
1507
  try {
852
1508
  while (true) {
853
1509
  const { done, value } = await reader.read();
@@ -871,6 +1527,10 @@ class LibreBotWidget {
871
1527
  if (parsed.sessionId) {
872
1528
  this.saveSession(parsed.sessionId);
873
1529
  }
1530
+ // Check for booking intent in metadata
1531
+ if (parsed.booking_intent && parsed.booking_config) {
1532
+ bookingIntentData = parsed;
1533
+ }
874
1534
  }
875
1535
  catch {
876
1536
  // Skip malformed JSON
@@ -889,6 +1549,10 @@ class LibreBotWidget {
889
1549
  content: fullContent,
890
1550
  timestamp: new Date(),
891
1551
  });
1552
+ // Handle booking intent if detected
1553
+ if (bookingIntentData) {
1554
+ this.handleBookingIntent(bookingIntentData);
1555
+ }
892
1556
  }
893
1557
  async callAPI(message) {
894
1558
  const response = await fetch(`${this.config.apiUrl}/chat`, {
@@ -942,7 +1606,318 @@ class LibreBotWidget {
942
1606
  // localStorage may not be available
943
1607
  }
944
1608
  }
1609
+ // Booking Mode Methods
1610
+ switchToBookingMode(config) {
1611
+ this.bookingConfig = config;
1612
+ this.mode = 'booking';
1613
+ this.selectedDate = null;
1614
+ this.selectedSlot = null;
1615
+ this.availableSlots = [];
1616
+ // Hide chat, show booking
1617
+ if (this.chatContainer)
1618
+ this.chatContainer.style.display = 'none';
1619
+ if (this.inputArea)
1620
+ this.inputArea.style.display = 'none';
1621
+ if (this.bookingContainer) {
1622
+ this.bookingContainer.style.display = 'flex';
1623
+ this.renderBookingUI();
1624
+ }
1625
+ }
1626
+ switchToChatMode() {
1627
+ this.mode = 'chat';
1628
+ this.bookingConfig = null;
1629
+ this.selectedDate = null;
1630
+ this.selectedSlot = null;
1631
+ this.availableSlots = [];
1632
+ // Show chat, hide booking
1633
+ if (this.chatContainer)
1634
+ this.chatContainer.style.display = 'flex';
1635
+ if (this.inputArea)
1636
+ this.inputArea.style.display = 'flex';
1637
+ if (this.bookingContainer) {
1638
+ this.bookingContainer.style.display = 'none';
1639
+ this.bookingContainer.innerHTML = '';
1640
+ }
1641
+ }
1642
+ renderBookingUI() {
1643
+ if (!this.bookingContainer || !this.bookingConfig)
1644
+ return;
1645
+ const minDate = new Date();
1646
+ minDate.setDate(minDate.getDate() + 1);
1647
+ const maxDate = new Date();
1648
+ maxDate.setDate(maxDate.getDate() + (this.bookingConfig.advance_booking_days || 30));
1649
+ const formFieldsHtml = this.bookingConfig.form_fields.map(field => {
1650
+ const requiredMark = field.required ? '<span class="required">*</span>' : '';
1651
+ const requiredAttr = field.required ? 'required' : '';
1652
+ if (field.type === 'textarea') {
1653
+ return `
1654
+ <div class="librebot-form-field">
1655
+ <label class="librebot-form-label">${field.label}${requiredMark}</label>
1656
+ <textarea
1657
+ class="librebot-form-textarea"
1658
+ name="${field.name}"
1659
+ placeholder="${field.placeholder || ''}"
1660
+ ${requiredAttr}
1661
+ ></textarea>
1662
+ </div>
1663
+ `;
1664
+ }
1665
+ if (field.type === 'select' && field.options) {
1666
+ const optionsHtml = field.options.map(opt => `<option value="${opt}">${opt}</option>`).join('');
1667
+ return `
1668
+ <div class="librebot-form-field">
1669
+ <label class="librebot-form-label">${field.label}${requiredMark}</label>
1670
+ <select
1671
+ class="librebot-form-select"
1672
+ name="${field.name}"
1673
+ ${requiredAttr}
1674
+ >
1675
+ <option value="">${field.placeholder || 'Select...'}</option>
1676
+ ${optionsHtml}
1677
+ </select>
1678
+ </div>
1679
+ `;
1680
+ }
1681
+ return `
1682
+ <div class="librebot-form-field">
1683
+ <label class="librebot-form-label">${field.label}${requiredMark}</label>
1684
+ <input
1685
+ type="${field.type}"
1686
+ class="librebot-form-input"
1687
+ name="${field.name}"
1688
+ placeholder="${field.placeholder || ''}"
1689
+ ${requiredAttr}
1690
+ />
1691
+ </div>
1692
+ `;
1693
+ }).join('');
1694
+ this.bookingContainer.innerHTML = `
1695
+ <div class="librebot-booking">
1696
+ <div class="librebot-booking-header">
1697
+ <button class="librebot-booking-back">${backIcon}</button>
1698
+ <h3 class="librebot-booking-title">${this.translations.bookingTitle}</h3>
1699
+ </div>
1700
+ <div class="librebot-booking-content">
1701
+ <div class="librebot-booking-section">
1702
+ <label class="librebot-booking-label">${this.translations.selectDate}</label>
1703
+ <input
1704
+ type="date"
1705
+ class="librebot-date-input"
1706
+ min="${minDate.toISOString().split('T')[0]}"
1707
+ max="${maxDate.toISOString().split('T')[0]}"
1708
+ />
1709
+ </div>
1710
+ <div class="librebot-booking-section">
1711
+ <label class="librebot-booking-label">${this.translations.selectTime}</label>
1712
+ <div class="librebot-slots-container">
1713
+ <div class="librebot-slots-empty">${this.translations.selectDate}</div>
1714
+ </div>
1715
+ </div>
1716
+ <div class="librebot-booking-section librebot-form-section" style="display: none;">
1717
+ <label class="librebot-booking-label">${this.translations.yourDetails}</label>
1718
+ <form class="librebot-booking-form">
1719
+ ${formFieldsHtml}
1720
+ <button type="submit" class="librebot-booking-submit" disabled>
1721
+ ${this.translations.confirmBooking}
1722
+ </button>
1723
+ </form>
1724
+ </div>
1725
+ </div>
1726
+ </div>
1727
+ `;
1728
+ // Add event listeners
1729
+ const backBtn = this.bookingContainer.querySelector('.librebot-booking-back');
1730
+ backBtn?.addEventListener('click', () => this.switchToChatMode());
1731
+ const dateInput = this.bookingContainer.querySelector('.librebot-date-input');
1732
+ dateInput?.addEventListener('change', (e) => {
1733
+ const target = e.target;
1734
+ this.selectedDate = target.value;
1735
+ this.selectedSlot = null;
1736
+ this.loadAvailableSlots(target.value);
1737
+ });
1738
+ const form = this.bookingContainer.querySelector('.librebot-booking-form');
1739
+ form?.addEventListener('submit', (e) => {
1740
+ e.preventDefault();
1741
+ this.submitBooking(form);
1742
+ });
1743
+ }
1744
+ async loadAvailableSlots(date) {
1745
+ const slotsContainer = this.bookingContainer?.querySelector('.librebot-slots-container');
1746
+ if (!slotsContainer)
1747
+ return;
1748
+ slotsContainer.innerHTML = `<div class="librebot-slots-loading">${this.translations.loadingSlots}</div>`;
1749
+ try {
1750
+ const response = await fetch(`${this.config.apiUrl}/bookings/slots?date=${date}&timezone=${Intl.DateTimeFormat().resolvedOptions().timeZone}`, {
1751
+ headers: {
1752
+ Authorization: `Bearer ${this.config.apiKey}`,
1753
+ },
1754
+ });
1755
+ if (!response.ok) {
1756
+ throw new Error('Failed to load slots');
1757
+ }
1758
+ const data = await response.json();
1759
+ this.availableSlots = data.slots || [];
1760
+ if (this.availableSlots.length === 0) {
1761
+ slotsContainer.innerHTML = `<div class="librebot-slots-empty">${this.translations.noSlotsAvailable}</div>`;
1762
+ return;
1763
+ }
1764
+ const slotsHtml = this.availableSlots.map(slot => {
1765
+ const disabled = !slot.available ? 'disabled' : '';
1766
+ return `
1767
+ <button
1768
+ type="button"
1769
+ class="librebot-slot"
1770
+ data-start="${slot.start}"
1771
+ data-end="${slot.end}"
1772
+ ${disabled}
1773
+ >${slot.start}</button>
1774
+ `;
1775
+ }).join('');
1776
+ slotsContainer.innerHTML = `<div class="librebot-slots">${slotsHtml}</div>`;
1777
+ // Add click handlers to slot buttons
1778
+ const slotButtons = slotsContainer.querySelectorAll('.librebot-slot');
1779
+ slotButtons.forEach(btn => {
1780
+ btn.addEventListener('click', () => {
1781
+ const start = btn.getAttribute('data-start') || '';
1782
+ const end = btn.getAttribute('data-end') || '';
1783
+ this.selectSlot({ start, end, available: true }, btn);
1784
+ });
1785
+ });
1786
+ }
1787
+ catch (err) {
1788
+ console.error('Failed to load slots:', err);
1789
+ slotsContainer.innerHTML = `<div class="librebot-slots-empty">${this.translations.bookingError}</div>`;
1790
+ }
1791
+ }
1792
+ selectSlot(slot, buttonEl) {
1793
+ this.selectedSlot = slot;
1794
+ // Update button styles
1795
+ const allSlots = this.bookingContainer?.querySelectorAll('.librebot-slot');
1796
+ allSlots?.forEach(el => el.classList.remove('selected'));
1797
+ buttonEl.classList.add('selected');
1798
+ // Show form section
1799
+ const formSection = this.bookingContainer?.querySelector('.librebot-form-section');
1800
+ if (formSection) {
1801
+ formSection.style.display = 'block';
1802
+ }
1803
+ // Enable submit button
1804
+ const submitBtn = this.bookingContainer?.querySelector('.librebot-booking-submit');
1805
+ if (submitBtn) {
1806
+ submitBtn.disabled = false;
1807
+ }
1808
+ // Scroll to form
1809
+ formSection?.scrollIntoView({ behavior: 'smooth', block: 'start' });
1810
+ }
1811
+ async submitBooking(form) {
1812
+ if (!this.selectedDate || !this.selectedSlot || !this.bookingConfig)
1813
+ return;
1814
+ const submitBtn = form.querySelector('.librebot-booking-submit');
1815
+ if (submitBtn) {
1816
+ submitBtn.disabled = true;
1817
+ submitBtn.textContent = '...';
1818
+ }
1819
+ // Collect form data
1820
+ const formData = new FormData(form);
1821
+ const formDataObj = {};
1822
+ formData.forEach((value, key) => {
1823
+ formDataObj[key] = value.toString();
1824
+ });
1825
+ try {
1826
+ const response = await fetch(`${this.config.apiUrl}/bookings/submit`, {
1827
+ method: 'POST',
1828
+ headers: {
1829
+ 'Content-Type': 'application/json',
1830
+ Authorization: `Bearer ${this.config.apiKey}`,
1831
+ },
1832
+ body: JSON.stringify({
1833
+ date: this.selectedDate,
1834
+ slot_start: this.selectedSlot.start,
1835
+ slot_end: this.selectedSlot.end,
1836
+ form_data: formDataObj,
1837
+ session_id: this.sessionId,
1838
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1839
+ }),
1840
+ });
1841
+ const result = await response.json();
1842
+ if (result.success && result.booking) {
1843
+ this.renderBookingSuccess(result.booking);
1844
+ }
1845
+ else {
1846
+ throw new Error(result.error || 'Booking failed');
1847
+ }
1848
+ }
1849
+ catch (err) {
1850
+ console.error('Booking submission failed:', err);
1851
+ if (submitBtn) {
1852
+ submitBtn.disabled = false;
1853
+ submitBtn.textContent = this.translations.confirmBooking;
1854
+ }
1855
+ alert(this.translations.bookingError);
1856
+ }
1857
+ }
1858
+ renderBookingSuccess(booking) {
1859
+ if (!this.bookingContainer)
1860
+ return;
1861
+ // Format the date and time
1862
+ const startDate = new Date(booking.scheduled_start);
1863
+ const formattedDate = startDate.toLocaleDateString(undefined, {
1864
+ weekday: 'long',
1865
+ year: 'numeric',
1866
+ month: 'long',
1867
+ day: 'numeric',
1868
+ });
1869
+ const formattedTime = startDate.toLocaleTimeString(undefined, {
1870
+ hour: 'numeric',
1871
+ minute: '2-digit',
1872
+ });
1873
+ const meetingLinkHtml = booking.meeting_link
1874
+ ? `<a href="${booking.meeting_link}" target="_blank" class="librebot-meeting-link">${this.translations.joinMeeting}</a>`
1875
+ : '';
1876
+ this.bookingContainer.innerHTML = `
1877
+ <div class="librebot-booking">
1878
+ <div class="librebot-booking-header">
1879
+ <button class="librebot-booking-back">${backIcon}</button>
1880
+ <h3 class="librebot-booking-title">${this.translations.bookingTitle}</h3>
1881
+ </div>
1882
+ <div class="librebot-booking-content">
1883
+ <div class="librebot-booking-success">
1884
+ <div class="librebot-booking-success-icon">${checkIcon}</div>
1885
+ <h3>${this.translations.bookingSuccess}</h3>
1886
+ <div class="librebot-booking-details">
1887
+ <div class="librebot-booking-detail">
1888
+ <span class="librebot-booking-detail-label">${this.translations.selectDate}</span>
1889
+ <span class="librebot-booking-detail-value">${formattedDate}</span>
1890
+ </div>
1891
+ <div class="librebot-booking-detail">
1892
+ <span class="librebot-booking-detail-label">${this.translations.selectTime}</span>
1893
+ <span class="librebot-booking-detail-value">${formattedTime}</span>
1894
+ </div>
1895
+ <div class="librebot-booking-detail">
1896
+ <span class="librebot-booking-detail-label">${this.translations.duration}</span>
1897
+ <span class="librebot-booking-detail-value">${booking.duration_minutes} ${this.translations.minutes}</span>
1898
+ </div>
1899
+ </div>
1900
+ ${meetingLinkHtml}
1901
+ <button class="librebot-booking-submit" type="button">${this.translations.backToChat}</button>
1902
+ </div>
1903
+ </div>
1904
+ </div>
1905
+ `;
1906
+ // Add event listeners
1907
+ const backBtn = this.bookingContainer.querySelector('.librebot-booking-back');
1908
+ backBtn?.addEventListener('click', () => this.switchToChatMode());
1909
+ const chatBtn = this.bookingContainer.querySelector('.librebot-booking-submit');
1910
+ chatBtn?.addEventListener('click', () => this.switchToChatMode());
1911
+ }
1912
+ handleBookingIntent(data) {
1913
+ if (data.booking_intent && data.booking_config) {
1914
+ // Add a brief delay so user can see the AI response before switching
1915
+ setTimeout(() => {
1916
+ this.switchToBookingMode(data.booking_config);
1917
+ }, 1500);
1918
+ }
1919
+ }
945
1920
  }
946
1921
 
947
- export { LibreBotWidget };
1922
+ export { LibreBotWidget, RTL_LANGUAGES, WIDGET_TRANSLATIONS, detectLanguage };
948
1923
  //# sourceMappingURL=librebot.esm.js.map