@meetelise/chat 1.20.83 → 1.20.85

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.
@@ -13,6 +13,7 @@ import {
13
13
  FeatureFlagsShowDropdown,
14
14
  fetchFeatureFlagShowMarketingSourceDropdown,
15
15
  fetchFeatureFlagUsePhoneNumberBySource,
16
+ fetchFeatureFlagUsePubnub,
16
17
  } from "../fetchFeatureFlag";
17
18
  import fetchWebchatPreferences, {
18
19
  DesignConcepts,
@@ -37,10 +38,16 @@ import addMinutes from "date-fns/addMinutes";
37
38
  import formatISO from "date-fns/formatISO";
38
39
  import fetchLeadSources from "../fetchLeadSources";
39
40
  import { formatPhoneNumber } from "./actions/formatPhoneNumber";
41
+ import MyPubnub from "../MyPubnub";
40
42
 
41
43
  import "./actions/minimize-expand-button";
42
44
  import "./launcher/mobile-launcher";
45
+ import "./pubnub-chat";
43
46
 
47
+ enum ChatProviders {
48
+ TALKJS = "talkjs",
49
+ PUBNUB = "pubnub",
50
+ }
44
51
  @customElement("me-chat")
45
52
  export class MEChat extends LitElement {
46
53
  static styles = css`
@@ -66,7 +73,8 @@ export class MEChat extends LitElement {
66
73
  justify-content: space-between;
67
74
  gap: 32px;
68
75
  }
69
- #chatAdditionalActions {
76
+ #chatAdditionalActionsTalkjs,
77
+ #chatAdditionalActionsPubnub {
70
78
  position: fixed;
71
79
  box-sizing: border-box;
72
80
 
@@ -78,7 +86,8 @@ export class MEChat extends LitElement {
78
86
 
79
87
  z-index: 1000000000;
80
88
  }
81
- #chatAdditionalActions::after {
89
+ #chatAdditionalActionsPubnub::after,
90
+ #chatAdditionalActionsTalkjs::after {
82
91
  content: "";
83
92
  position: absolute;
84
93
  top: -6px;
@@ -88,6 +97,10 @@ export class MEChat extends LitElement {
88
97
  border-bottom: 22px solid transparent;
89
98
  border-right: 30px solid black;
90
99
  }
100
+ #pubnub-bottom {
101
+ position: fixed;
102
+ bottom: 0;
103
+ }
91
104
  `;
92
105
  @property({ type: String })
93
106
  private buildingSlug = "";
@@ -147,6 +160,15 @@ export class MEChat extends LitElement {
147
160
  @state()
148
161
  private talkjsPopupElement: Element | null = null;
149
162
 
163
+ @state()
164
+ private chatProvider: ChatProviders = ChatProviders.TALKJS;
165
+
166
+ @state()
167
+ private displayPubnubChat = false;
168
+
169
+ @state()
170
+ private myPubnub: MyPubnub | null = null;
171
+
150
172
  private yardiDNIScriptInterval: NodeJS.Timer | null = null;
151
173
  launcherRef: Ref<Launcher> = createRef();
152
174
 
@@ -378,11 +400,7 @@ export class MEChat extends LitElement {
378
400
  (talkjsPopupElement as HTMLElement).style.zIndex = "99999999999";
379
401
  this.popup = popup;
380
402
 
381
- const autoOpenedTimestamp = sessionStorage.getItem("autoOpenedTimestamp");
382
- const shouldAutoOpen =
383
- !autoOpenedTimestamp ||
384
- (autoOpenedTimestamp && isPast(parseISO(autoOpenedTimestamp)));
385
- if (building.autoOpenChatWidget && shouldAutoOpen && !isMobile()) {
403
+ if (this.shouldAutoOpenChatWidget(building.autoOpenChatWidget)) {
386
404
  this.popup.show();
387
405
  this.hideLauncher = true;
388
406
  this.hasMounted = true;
@@ -394,6 +412,18 @@ export class MEChat extends LitElement {
394
412
  }
395
413
  };
396
414
 
415
+ private shouldAutoOpenChatWidget = (
416
+ buildingHasAutoOpen: boolean
417
+ ): boolean => {
418
+ const autoOpenedTimestamp = sessionStorage.getItem("autoOpenedTimestamp");
419
+ const shouldAutoOpen =
420
+ !autoOpenedTimestamp ||
421
+ (autoOpenedTimestamp && isPast(parseISO(autoOpenedTimestamp)));
422
+ if (buildingHasAutoOpen && shouldAutoOpen && !isMobile()) {
423
+ return true;
424
+ }
425
+ return false;
426
+ };
397
427
  /**
398
428
  * Remove the instance from the screen.
399
429
  *
@@ -432,26 +462,70 @@ export class MEChat extends LitElement {
432
462
  this.launcher.style.display = "none";
433
463
  }
434
464
 
435
- firstUpdated = (): void => {
436
- this.initializeInstanceVariables();
465
+ firstUpdated = async (): Promise<void> => {
466
+ const usePubnubProviderFlag = await fetchFeatureFlagUsePubnub(
467
+ this.buildingSlug
468
+ );
469
+
470
+ if (usePubnubProviderFlag) {
471
+ this.chatProvider = ChatProviders.PUBNUB;
472
+ this.initializePubnubVariables();
473
+ } else {
474
+ this.initializeInstanceVariables();
475
+ }
476
+ };
477
+
478
+ initializePubnubVariables = async (): Promise<void> => {
479
+ await this.setBuildingDerivedInfo();
480
+ if (!this.building) return;
481
+
482
+ const pubnubRaw = new MyPubnub(
483
+ this.orgSlug,
484
+ this.buildingSlug,
485
+ this.building
486
+ );
487
+ await pubnubRaw.initializePubnub();
488
+ this.myPubnub = pubnubRaw;
489
+ this.attachOnClickToLauncher();
490
+ if (this.shouldAutoOpenChatWidget(this.building.autoOpenChatWidget)) {
491
+ this.displayPubnubChat = true;
492
+ this.hideLauncher = true;
493
+ this.hasMounted = true;
494
+ sessionStorage.setItem(
495
+ "autoOpenedTimestamp",
496
+ formatISO(addMinutes(new Date(), 15))
497
+ );
498
+ }
499
+ this.isLoading = false;
437
500
  };
438
501
 
439
502
  handleContactClicked = (e: MouseEvent): void => {
440
- this.popup?.hide();
503
+ if (this.chatProvider === ChatProviders.TALKJS) {
504
+ this.popup?.hide();
505
+ } else if (this.chatProvider === ChatProviders.PUBNUB) {
506
+ this.displayPubnubChat = false;
507
+ }
441
508
  this.hideLauncher = false;
442
509
 
443
510
  this.launcherRef.value?.onClickEmailOption(e);
444
511
  };
445
512
 
446
513
  handleTourClicked = (e: MouseEvent): void => {
447
- this.popup?.hide();
514
+ if (this.chatProvider === ChatProviders.TALKJS) {
515
+ this.popup?.hide();
516
+ } else if (this.chatProvider === ChatProviders.PUBNUB) {
517
+ this.displayPubnubChat = false;
518
+ }
448
519
  this.hideLauncher = false;
449
-
450
520
  this.launcherRef.value?.onClickSSTOption(e);
451
521
  };
452
522
 
453
523
  handleContactTabClicked = (e: MouseEvent): void => {
454
- this.popup?.hide();
524
+ if (this.chatProvider === ChatProviders.TALKJS) {
525
+ this.popup?.hide();
526
+ } else if (this.chatProvider === ChatProviders.PUBNUB) {
527
+ this.displayPubnubChat = false;
528
+ }
455
529
  this.hideLauncher = false;
456
530
 
457
531
  this.launcherRef.value?.onClickPhoneOption(e);
@@ -461,14 +535,25 @@ export class MEChat extends LitElement {
461
535
  // For the top header contact, we change its pos based on the current x and y
462
536
  // of the talkjs popup - so we must adjust its coords on resize and mount
463
537
  adjustTopHeaderContactCoords = (): void => {
464
- if (this.talkjsPopupElement) {
538
+ const headerRef = this.shadowRoot?.getElementById(
539
+ this.chatProvider === ChatProviders.TALKJS
540
+ ? "chatAdditionalActionsTalkjs"
541
+ : "chatAdditionalActionsPubnub"
542
+ );
543
+ if (!headerRef) return;
544
+ if (this.chatProvider === ChatProviders.TALKJS && this.talkjsPopupElement) {
465
545
  const talkjsPopupCoords = this.talkjsPopupElement.getBoundingClientRect();
466
- const headerRef = this.shadowRoot?.getElementById(
467
- "chatAdditionalActions"
468
- );
469
- if (!headerRef) return;
470
546
  headerRef.style.left = `${talkjsPopupCoords.left + 20}px`;
471
547
  headerRef.style.top = `${talkjsPopupCoords.bottom - 24}px`;
548
+ } else if (this.chatProvider === ChatProviders.PUBNUB) {
549
+ const pubnubContainerElement = this.shadowRoot
550
+ ?.getElementById("pubnub-chat")
551
+ ?.shadowRoot?.getElementById("pubnub-chat-container");
552
+
553
+ const pubnubPopupCoords = pubnubContainerElement?.getBoundingClientRect();
554
+ if (!pubnubPopupCoords) return;
555
+ headerRef.style.left = `${pubnubPopupCoords.left}px`;
556
+ headerRef.style.top = `${pubnubPopupCoords.bottom}px`;
472
557
  }
473
558
  };
474
559
  connectedCallback(): void {
@@ -476,6 +561,7 @@ export class MEChat extends LitElement {
476
561
  window.addEventListener("resize", this.adjustTopHeaderContactCoords);
477
562
  }
478
563
  disconnectedCallback(): void {
564
+ this.myPubnub?.removeChatListeners();
479
565
  window.removeEventListener("resize", this.adjustTopHeaderContactCoords);
480
566
  super.disconnectedCallback();
481
567
  }
@@ -501,6 +587,34 @@ export class MEChat extends LitElement {
501
587
  EliseAI widget that allows you to chat with a virtual assistant, book
502
588
  a tour, contact the leasing office, and more.
503
589
  </div>
590
+
591
+ ${
592
+ this.chatProvider === ChatProviders.PUBNUB &&
593
+ this.displayPubnubChat &&
594
+ this.myPubnub
595
+ ? html`
596
+ <pubnub-chat
597
+ id="pubnub-chat"
598
+ .channel=${this.myPubnub.channel}
599
+ .myPubnub=${this.myPubnub}
600
+ .buildingSlug=${this.buildingSlug}
601
+ .building=${this.building}
602
+ .brandColor=${this.brandColor}
603
+ .onClickExit=${() => {
604
+ this.displayPubnubChat = false;
605
+ this.hideLauncher = false;
606
+ }}
607
+ .onMount=${() => {
608
+ this.adjustTopHeaderContactCoords();
609
+ }}
610
+ ></pubnub-chat>
611
+ ${this.renderChatAdditionalActions(
612
+ "chatAdditionalActionsPubnub",
613
+ showChatAdditionalActions
614
+ )}
615
+ `
616
+ : ""
617
+ }
504
618
  <div
505
619
  class=${classMap({
506
620
  launcherContainer: true,
@@ -534,6 +648,7 @@ export class MEChat extends LitElement {
534
648
  phoneNumber="${this.phoneNumberForSource?.number ??
535
649
  this.building?.phoneNumber ??
536
650
  ""}"
651
+ buildingName=${this.building?.name ?? ""}
537
652
  textColor="${this.theme.chatHeader.textColor}"
538
653
  backgroundColor="${this.theme.chatPaneBackgroundColor}"
539
654
  orgSlug="${this.orgSlug}"
@@ -567,54 +682,65 @@ export class MEChat extends LitElement {
567
682
  }
568
683
 
569
684
  </div>
570
- <div
571
- id="chatAdditionalActions"
572
- class=${classMap({
573
- ["showTab"]: showChatAdditionalActions,
574
- ["hideTab"]: !showChatAdditionalActions,
575
- })}
576
- >
577
- <mobile-launcher
578
- .onClickMinimize=${this.onClickMinimize}
579
- .onClickEmailOption=${this.handleContactClicked}
580
- .onClickPhoneOption=${this.handleContactTabClicked}
581
- .onClickSSTOption=${this.handleTourClicked}
582
- .isMobile=${this.isMobile}
583
- .brandColor=${this.brandColor}
584
- .hideChat=${true}
585
- .hasChatEnabled=${
586
- !this.building?.chatWidgets
587
- ? true
588
- : this.building?.chatWidgets.includes("CHAT")
589
- }
590
- .hasEmailEnabled=${
591
- !this.building?.chatWidgets
592
- ? true
593
- : this.building?.chatWidgets.includes("EMAIL")
594
- }
595
- .hasCallUsEnabled=${
596
- !this.building?.chatWidgets
597
- ? true
598
- : this.building?.chatWidgets.includes("CALL")
599
- }
600
- .hasSSTEnabled=${
601
- !this.building?.chatWidgets
602
- ? true
603
- : this.building?.chatWidgets.includes("SST")
604
- }
605
- ></mobile-launcher>
606
- <minimize-expand-button
607
- .brandColor=${this.brandColor}
608
- .onClick=${() => {
609
- this.popup?.hide();
610
- this.hideLauncher = false;
611
- }}
612
- ></minimize-expand-button>
613
- </div>
685
+ ${
686
+ this.chatProvider === ChatProviders.TALKJS
687
+ ? this.renderChatAdditionalActions(
688
+ "chatAdditionalActionsTalkjs",
689
+ showChatAdditionalActions
690
+ )
691
+ : ""
692
+ }
693
+
614
694
  </meta>
615
695
  `;
616
696
  }
617
697
 
698
+ private renderChatAdditionalActions = (
699
+ id: string,
700
+ showChatAdditionalActions: boolean
701
+ ): TemplateResult => {
702
+ return html` <div
703
+ id=${id}
704
+ class=${classMap({
705
+ ["showTab"]: showChatAdditionalActions,
706
+ ["hideTab"]: !showChatAdditionalActions,
707
+ })}
708
+ >
709
+ <mobile-launcher
710
+ .onClickMinimize=${this.onClickMinimize}
711
+ .onClickEmailOption=${this.handleContactClicked}
712
+ .onClickPhoneOption=${this.handleContactTabClicked}
713
+ .onClickSSTOption=${this.handleTourClicked}
714
+ .isMobile=${this.isMobile}
715
+ .brandColor=${this.brandColor}
716
+ .hideChat=${true}
717
+ .hasChatEnabled=${!this.building?.chatWidgets
718
+ ? true
719
+ : this.building?.chatWidgets.includes("CHAT")}
720
+ .hasEmailEnabled=${!this.building?.chatWidgets
721
+ ? true
722
+ : this.building?.chatWidgets.includes("EMAIL")}
723
+ .hasCallUsEnabled=${!this.building?.chatWidgets
724
+ ? true
725
+ : this.building?.chatWidgets.includes("CALL")}
726
+ .hasSSTEnabled=${!this.building?.chatWidgets
727
+ ? true
728
+ : this.building?.chatWidgets.includes("SST")}
729
+ ></mobile-launcher>
730
+ <minimize-expand-button
731
+ .brandColor=${this.brandColor}
732
+ .onClick=${() => {
733
+ if (this.chatProvider === ChatProviders.PUBNUB) {
734
+ this.displayPubnubChat = false;
735
+ } else if (this.chatProvider === ChatProviders.TALKJS) {
736
+ this.popup?.hide();
737
+ }
738
+ this.hideLauncher = false;
739
+ }}
740
+ ></minimize-expand-button>
741
+ </div>`;
742
+ };
743
+
618
744
  private attachOnClickToLauncher = () => {
619
745
  const launcher = this.launcherRef.value;
620
746
  if (!launcher) {
@@ -622,12 +748,13 @@ export class MEChat extends LitElement {
622
748
  }
623
749
 
624
750
  launcher.onChatTapped = async () => {
625
- if (!this.popup) {
626
- return;
751
+ if (this.chatProvider === ChatProviders.PUBNUB) {
752
+ this.displayPubnubChat = true;
753
+ } else if (this.chatProvider === ChatProviders.TALKJS) {
754
+ if (!this.popup) return;
755
+ this.popup.show();
756
+ this.analytics?.ping("open");
627
757
  }
628
-
629
- this.popup.show();
630
- this.analytics?.ping("open");
631
758
  this.hideLauncher = true;
632
759
  this.hasMounted = true;
633
760
  };
@@ -0,0 +1,172 @@
1
+ import { css } from "lit";
2
+
3
+ export const pubnubChatStyles = css`
4
+ #pubnub-chat-container {
5
+ position: fixed;
6
+ right: 0px;
7
+ bottom: 110px;
8
+ z-index: 100000;
9
+ display: flex;
10
+ align-items: center;
11
+
12
+ width: 340px;
13
+ height: 600px;
14
+
15
+ display: flex;
16
+ flex-direction: column;
17
+ border-radius: 24px 24px 0px 24px;
18
+ overflow: hidden;
19
+
20
+ box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,
21
+ rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
22
+ }
23
+ #header {
24
+ height: 70px;
25
+ width: 100%;
26
+ background: white;
27
+ overflow: hidden;
28
+
29
+ box-sizing: border-box;
30
+
31
+ display: flex;
32
+ align-items: center;
33
+ padding: 24px;
34
+ justify-content: space-between;
35
+ }
36
+
37
+ #header-text {
38
+ font-size: 14px;
39
+ font-weight: bold;
40
+ font-family: Poppins, "Open Sans", "Helvetica", sans-serif;
41
+ }
42
+ #exit-chat-bttn {
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ background: none;
47
+ border: none;
48
+ cursor: pointer;
49
+ }
50
+
51
+ #conversation-body {
52
+ flex: 1;
53
+ width: 100%;
54
+ height: 100%;
55
+ background: rgba(193, 193, 193, 0.5);
56
+ backdrop-filter: blur(4px);
57
+ overflow-y: scroll;
58
+
59
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
60
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
61
+ }
62
+
63
+ #message-thread-list {
64
+ display: flex;
65
+ flex-direction: column;
66
+ position: relative;
67
+ padding: 0px 16px;
68
+ gap: 16px;
69
+ }
70
+
71
+ .message-container {
72
+ display: flex;
73
+ padding: 6px;
74
+ width: fit-content;
75
+ max-width: calc(100% - 60px);
76
+ box-sizing: border-box;
77
+ }
78
+
79
+ .lead-message {
80
+ background: black;
81
+ border-radius: 8px 8px 0px 8px;
82
+
83
+ align-self: flex-end;
84
+ color: white;
85
+ }
86
+ .ai-message {
87
+ background: white;
88
+ border-radius: 10px 10px 10px 0px;
89
+ color: black;
90
+ }
91
+ .message-text {
92
+ font-size: 14px;
93
+ word-break: break-word;
94
+ margin: 0;
95
+ padding: 6px 12px;
96
+ line-height: 150%;
97
+ font-family: Poppins, "Open Sans", "Helvetica", sans-serif;
98
+ }
99
+
100
+ .loading-dot {
101
+ width: 6px;
102
+ height: 6px;
103
+ border-radius: 50%;
104
+ margin: 5px;
105
+ animation: pulse 4s linear infinite;
106
+ }
107
+
108
+ .dot-1 {
109
+ background-color: #7d7d7d;
110
+ animation-delay: 0s;
111
+ }
112
+
113
+ .dot-2 {
114
+ background-color: #7d7d7d;
115
+ animation-delay: 1s;
116
+ }
117
+
118
+ .dot-3 {
119
+ background-color: #7d7d7d;
120
+ animation-delay: 2s;
121
+ }
122
+
123
+ @keyframes pulse {
124
+ 0% {
125
+ background-color: #7d7d7d;
126
+ transform: scale(1);
127
+ }
128
+ 50% {
129
+ background-color: #c7c7c7;
130
+ transform: scale(2);
131
+ }
132
+ 100% {
133
+ background-color: #7d7d7d;
134
+ transform: scale(1);
135
+ }
136
+ }
137
+
138
+ #footer {
139
+ height: 70px;
140
+ width: 100%;
141
+ background: black;
142
+
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: space-between;
146
+ box-sizing: border-box;
147
+ gap: 16px;
148
+ padding: 24px;
149
+ }
150
+ #message-input {
151
+ height: 40px;
152
+ width: 100%;
153
+ box-sizing: border-box;
154
+ font-weight: 300;
155
+ font-size: 14px;
156
+ line-height: 150%;
157
+ border: none;
158
+ color: white;
159
+ background: none;
160
+ font-family: Poppins, "Open Sans", "Helvetica", sans-serif;
161
+ }
162
+ #message-input:focus {
163
+ outline: none;
164
+ -webkit-box-shadow: none;
165
+ box-shadow: none;
166
+ }
167
+ #send-message-bttn {
168
+ background: none;
169
+ border: none;
170
+ cursor: pointer;
171
+ }
172
+ `;
@@ -0,0 +1,155 @@
1
+ import { html, LitElement, TemplateResult } from "lit";
2
+ import { customElement, property, query, state } from "lit/decorators.js";
3
+ import { styleMap } from "lit/directives/style-map.js";
4
+ import { Building } from "../fetchBuildingInfo";
5
+ import MyPubnub, { ChatMessage } from "../MyPubnub";
6
+ import { SendMessageIconWhite, XBlackOutlineIcon } from "../svgIcons";
7
+ import { defaultBrandColor } from "../themes";
8
+ import { hexToAlmostWhite } from "../utils";
9
+ import { pubnubChatStyles } from "./pubnub-chat-styles";
10
+
11
+ @customElement("pubnub-chat")
12
+ export class PubnubChat extends LitElement {
13
+ static styles = [pubnubChatStyles];
14
+
15
+ @property({ attribute: true })
16
+ channel: string | undefined;
17
+
18
+ @property({ attribute: true })
19
+ myPubnub: MyPubnub | undefined;
20
+
21
+ @property({ attribute: true })
22
+ buildingSlug: string | undefined;
23
+
24
+ @property({ attribute: true })
25
+ building: Building | undefined;
26
+
27
+ @property({ attribute: true })
28
+ brandColor: string = defaultBrandColor;
29
+
30
+ @property({ attribute: true })
31
+ onClickExit: () => void = () => ({});
32
+
33
+ @property({ attribute: true })
34
+ onMount: () => void = () => ({});
35
+
36
+ @query("#message-input", true)
37
+ messageInput!: HTMLInputElement;
38
+
39
+ @query("#conversation-body", true)
40
+ messageBody!: HTMLDivElement;
41
+
42
+ @state()
43
+ messages: ChatMessage[] = [];
44
+
45
+ @state()
46
+ isLoadingMessages = false;
47
+
48
+ sendMessage = async (message: string): Promise<void> => {
49
+ this.messageInput.value = "";
50
+ await this.myPubnub?.sendMessage(message);
51
+ };
52
+ firstUpdated(): void {
53
+ this.messages = this.myPubnub?.messages || [];
54
+ this.myPubnub?.addChatListener(
55
+ (response: { messages: ChatMessage[]; isLoading: boolean }) => {
56
+ this.messages = response.messages;
57
+ this.isLoadingMessages = response.isLoading;
58
+ }
59
+ );
60
+ this.onMount();
61
+ }
62
+ scrollToChatBottom = (): void => {
63
+ this.messageBody.scrollTo({
64
+ top: this.messageBody.scrollHeight - this.messageBody.clientHeight,
65
+ behavior: "smooth",
66
+ });
67
+ };
68
+
69
+ updated(): void {
70
+ this.scrollToChatBottom();
71
+ }
72
+
73
+ render(): TemplateResult {
74
+ if (
75
+ !this.channel ||
76
+ !this.myPubnub ||
77
+ !this.buildingSlug ||
78
+ !this.building
79
+ ) {
80
+ return html``; // error here
81
+ }
82
+ return html`
83
+ <div id="pubnub-chat-container">
84
+ <div
85
+ id="header"
86
+ style=${styleMap({
87
+ background:
88
+ this.brandColor !== defaultBrandColor
89
+ ? hexToAlmostWhite(this.brandColor, 0.6)
90
+ : this.brandColor,
91
+ })}
92
+ >
93
+ <div id="header-text">${this.building.name}</div>
94
+ <button id="exit-chat-bttn" @click=${this.onClickExit}>
95
+ ${XBlackOutlineIcon}
96
+ </button>
97
+ </div>
98
+ <div id="conversation-body">
99
+ <ul id="message-thread-list">
100
+ ${this.building.welcomeMessage
101
+ ? html` <li class="message-container ai-message">
102
+ <p class="message-text">${this.building.welcomeMessage}</p>
103
+ </li>`
104
+ : ""}
105
+ ${this.messages.map((message) => {
106
+ return html`
107
+ <li
108
+ class=${this.myPubnub?.isLeadMessage(message)
109
+ ? "message-container lead-message"
110
+ : "message-container ai-message"}
111
+ key=${message.timetoken}
112
+ style=${styleMap({
113
+ background: this.myPubnub?.isLeadMessage(message)
114
+ ? this.brandColor
115
+ : undefined,
116
+ })}
117
+ >
118
+ <p class="message-text">
119
+ ${message.message.text
120
+ .split("\n")
121
+ .map((line) => html`${line}<br />`)}
122
+ </p>
123
+ </li>
124
+ `;
125
+ })}
126
+ ${this.isLoadingMessages
127
+ ? html`
128
+ <li id="loading-message" class="message-container ai-message">
129
+ <div class="loading-dot dot-1"></div>
130
+ <div class="loading-dot dot-2"></div>
131
+ <div class="loading-dot dot-3"></div>
132
+ </li>
133
+ `
134
+ : ""}
135
+ </ul>
136
+ </div>
137
+ <div id="footer">
138
+ <input
139
+ id="message-input"
140
+ placeholder="Ask a question..."
141
+ @keydown=${(e: KeyboardEvent) => {
142
+ if (e.key === "Enter") this.sendMessage(this.messageInput.value);
143
+ }}
144
+ />
145
+ <button
146
+ id="send-message-bttn"
147
+ @click=${() => this.sendMessage(this.messageInput.value)}
148
+ >
149
+ ${SendMessageIconWhite}
150
+ </button>
151
+ </div>
152
+ </div>
153
+ `;
154
+ }
155
+ }