@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.
- package/dist/icons.d.ts +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/librebot.esm.js +986 -11
- package/dist/librebot.esm.js.map +1 -1
- package/dist/librebot.js +1 -1
- package/dist/librebot.js.map +1 -1
- package/dist/translations.d.ts +27 -0
- package/dist/types.d.ts +51 -0
- package/dist/widget.d.ts +18 -0
- package/package.json +1 -1
package/dist/librebot.esm.js
CHANGED
|
@@ -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: '
|
|
494
|
-
welcomeMessage: '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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
|