mini-chat-bot-widget 0.8.0 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mini-chat-bot-widget",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "A tiny chat bot widget fixed at bottom right, distributable via npm and usable with a <script> tag.",
5
5
  "main": "dist/chat-widget.umd.js",
6
6
  "module": "dist/chat-widget.esm.js",
@@ -464,7 +464,9 @@ class AudioChatScreen {
464
464
  // Update existing message
465
465
  const bubble = existingMessage.querySelector(".message-bubble");
466
466
  if (bubble) {
467
- bubble.innerHTML = this._escapeHtml(messageData.content || text);
467
+ // Parse markdown for assistant messages, escape for user messages
468
+ const content = messageData.content || text;
469
+ bubble.innerHTML = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
468
470
  }
469
471
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
470
472
  return;
@@ -526,10 +528,13 @@ class AudioChatScreen {
526
528
  const timestamp = messageData?.timestamp ? new Date(messageData.timestamp) : new Date();
527
529
  const formattedTime = this._formatTime(timestamp);
528
530
 
531
+ // Parse markdown for assistant messages, escape for user messages
532
+ const renderedContent = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
533
+
529
534
  messageEl.innerHTML = `
530
535
  <div class="message-content">
531
536
  ${attachmentsHTML}
532
- <div class="message-bubble">${this._escapeHtml(content)}</div>
537
+ <div class="message-bubble">${renderedContent}</div>
533
538
  <div class="message-time">${formattedTime}</div>
534
539
  </div>
535
540
  `;
@@ -586,7 +591,10 @@ class AudioChatScreen {
586
591
  if (existingEl) {
587
592
  const bubble = existingEl.querySelector(".message-bubble");
588
593
  if (bubble) {
589
- bubble.innerHTML = this._escapeHtml(msg.content || "");
594
+ // Parse markdown for assistant messages, escape for user messages
595
+ const content = msg.content || "";
596
+ const isUserMessage = msg.sender === "user";
597
+ bubble.innerHTML = !isUserMessage ? this._parseMarkdown(content) : this._escapeHtml(content);
590
598
  }
591
599
 
592
600
  // Update attachments if they exist
@@ -687,6 +695,106 @@ class AudioChatScreen {
687
695
  return div.innerHTML;
688
696
  }
689
697
 
698
+ // Parse markdown to HTML
699
+ _parseMarkdown(text) {
700
+ if (!text || typeof text !== 'string') return '';
701
+
702
+ let html = text;
703
+
704
+ // Escape HTML first to prevent XSS
705
+ html = html
706
+ .replace(/&/g, '&amp;')
707
+ .replace(/</g, '&lt;')
708
+ .replace(/>/g, '&gt;');
709
+
710
+ // Split into lines for processing
711
+ const lines = html.split('\n');
712
+ const processedLines = [];
713
+ let inList = false;
714
+
715
+ for (let i = 0; i < lines.length; i++) {
716
+ let line = lines[i];
717
+
718
+ // Check for headers first (#, ##, ###, etc.)
719
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
720
+ if (headerMatch) {
721
+ if (inList) {
722
+ processedLines.push('</ul>');
723
+ inList = false;
724
+ }
725
+ const level = headerMatch[1].length;
726
+ const headerText = headerMatch[2];
727
+ // Process markdown inside header
728
+ let processedHeader = headerText;
729
+ processedHeader = processedHeader.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
730
+ processedHeader = processedHeader.replace(/__([^_]+)__/g, '<strong>$1</strong>');
731
+ processedHeader = processedHeader.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
732
+ processedHeader = processedHeader.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
733
+ processedLines.push(`<h${level}>${processedHeader}</h${level}>`);
734
+ continue;
735
+ }
736
+
737
+ // Check if this is a list item (before processing bold/italic)
738
+ const listMatch = line.match(/^[\s]*[-*]\s+(.+)$/);
739
+
740
+ if (listMatch) {
741
+ // Process markdown inside list item
742
+ let listContent = listMatch[1];
743
+
744
+ // Bold: **text** (must be processed before italic)
745
+ listContent = listContent.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
746
+ listContent = listContent.replace(/__([^_]+)__/g, '<strong>$1</strong>');
747
+
748
+ // Italic: *text* (single asterisk, avoid matching **text**)
749
+ listContent = listContent.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
750
+ listContent = listContent.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
751
+
752
+ if (!inList) {
753
+ processedLines.push('<ul>');
754
+ inList = true;
755
+ }
756
+ processedLines.push(`<li>${listContent}</li>`);
757
+ } else {
758
+ if (inList) {
759
+ processedLines.push('</ul>');
760
+ inList = false;
761
+ }
762
+
763
+ // Skip empty lines (they'll become <br> later)
764
+ if (line.trim() === '') {
765
+ processedLines.push('');
766
+ continue;
767
+ }
768
+
769
+ // Process markdown in non-list lines
770
+ // Bold: **text** (must be processed before italic)
771
+ line = line.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
772
+ line = line.replace(/__([^_]+)__/g, '<strong>$1</strong>');
773
+
774
+ // Italic: *text* (single asterisk, avoid matching **text**)
775
+ line = line.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
776
+ line = line.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
777
+
778
+ // Inline code: `code`
779
+ line = line.replace(/`([^`]+)`/g, '<code>$1</code>');
780
+
781
+ // Links: [text](url)
782
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
783
+
784
+ processedLines.push(line);
785
+ }
786
+ }
787
+
788
+ if (inList) {
789
+ processedLines.push('</ul>');
790
+ }
791
+
792
+ // Join lines with <br> and return
793
+ html = processedLines.join('<br>');
794
+
795
+ return html;
796
+ }
797
+
690
798
  // Show expanded image modal (mirrors TextChatScreen)
691
799
  showExpandedImage(src, alt) {
692
800
  const existingModal = document.querySelector(".expanded-image-modal");
@@ -1102,6 +1210,88 @@ class AudioChatScreen {
1102
1210
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
1103
1211
  }
1104
1212
 
1213
+ /* Markdown styles */
1214
+ .audio-chat-screen .message-bubble strong {
1215
+ font-weight: 600;
1216
+ color: inherit;
1217
+ }
1218
+
1219
+ .audio-chat-screen .message-bubble em {
1220
+ font-style: italic;
1221
+ }
1222
+
1223
+ .audio-chat-screen .message-bubble ul {
1224
+ margin: 0px 0;
1225
+ padding-left: 20px;
1226
+ list-style-type: disc;
1227
+ margin-bottom:0px;
1228
+ margin-top:5px;
1229
+ }
1230
+
1231
+ .audio-chat-screen .message-bubble li {
1232
+ margin: 0;
1233
+ line-height: 1.1;
1234
+ padding-left: 3px;
1235
+ }
1236
+
1237
+ .audio-chat-screen .message-bubble h1,
1238
+ .audio-chat-screen .message-bubble h2,
1239
+ .audio-chat-screen .message-bubble h3,
1240
+ .audio-chat-screen .message-bubble h4,
1241
+ .audio-chat-screen .message-bubble h5,
1242
+ .audio-chat-screen .message-bubble h6 {
1243
+ margin: 0px 0 0px 0;
1244
+ font-weight: 600;
1245
+ color: inherit;
1246
+ line-height: 1.3;
1247
+ }
1248
+
1249
+ .audio-chat-screen .message-bubble h1 {
1250
+ font-size: 1.5em;
1251
+ }
1252
+
1253
+ .audio-chat-screen .message-bubble h2 {
1254
+ font-size: 1.3em;
1255
+ }
1256
+
1257
+ .audio-chat-screen .message-bubble h3 {
1258
+ font-size: 1.15em;
1259
+ }
1260
+
1261
+ .audio-chat-screen .message-bubble h4 {
1262
+ font-size: 1.05em;
1263
+ }
1264
+
1265
+ .audio-chat-screen .message-bubble h5 {
1266
+ font-size: 1em;
1267
+ }
1268
+
1269
+ .audio-chat-screen .message-bubble h6 {
1270
+ font-size: 0.95em;
1271
+ }
1272
+
1273
+ .audio-chat-screen .message-bubble code {
1274
+ background: rgba(0, 0, 0, 0.05);
1275
+ padding: 2px 6px;
1276
+ border-radius: 4px;
1277
+ font-family: 'Courier New', Courier, monospace;
1278
+ font-size: 0.9em;
1279
+ }
1280
+
1281
+ .audio-chat-screen .message-bubble a {
1282
+ color: ${this.primaryColor};
1283
+ text-decoration: underline;
1284
+ }
1285
+
1286
+ .audio-chat-screen .message-bubble a:hover {
1287
+ opacity: 0.8;
1288
+ }
1289
+
1290
+ .audio-chat-screen .message-bubble br {
1291
+ line-height: 0.1; /* Adjust between 0 and 1 */
1292
+ }
1293
+
1294
+
1105
1295
  .audio-chat-screen .message-time {
1106
1296
  font-size: 10px;
1107
1297
  color: #94a3b8;
@@ -22,9 +22,10 @@ class ChatWidget {
22
22
  this.threadId = options.threadId || null;
23
23
  this.assistantId = options.assistantId || null;
24
24
  this.selectedLanguage = options.selectedLanguage || "en";
25
- this.accessToken = options.accessToken || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhpZHdnbHl5emRqc2dyaW92bWdtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDczOTE5NjEsImV4cCI6MjA2Mjk2Nzk2MX0.jAdwoGNbwK";
26
- this.supabaseToken = options.supabaseToken || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhpZHdnbHl5emRqc2dyaW92bWdtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDczOTE5NjEsImV4cCI6MjA2Mjk2Nzk2MX0.jAdwoGNbwK";
25
+ this.accessToken = options.accessToken || "";
26
+ this.supabaseToken = options.supabaseToken || "";
27
27
  this.userInfo = options.userInfo || {};
28
+ this.mcpServerUrl = options.mcpServerUrl || "http://localhost:8010/mcp";
28
29
  // Store the custom getHeaders function or use default
29
30
  this._customGetHeaders = options.getHeaders;
30
31
 
@@ -106,6 +107,9 @@ class ChatWidget {
106
107
  }
107
108
  }
108
109
 
110
+
111
+
112
+
109
113
  _init() {
110
114
  // Create container
111
115
  this.container = document.createElement("div");
@@ -157,6 +161,7 @@ class ChatWidget {
157
161
  this._renderScreen();
158
162
  this._populateLanguageOptions();
159
163
  this._bindEvents();
164
+
160
165
  }
161
166
 
162
167
  _applyStyles() {
@@ -1262,6 +1267,7 @@ class ChatWidget {
1262
1267
  return;
1263
1268
  }
1264
1269
 
1270
+
1265
1271
  console.log(
1266
1272
  "📤 Sending text message - Language:",
1267
1273
  this.selectedLanguage,
@@ -421,7 +421,9 @@ class TextChatScreen {
421
421
  // Update existing message
422
422
  const bubble = existingMessage.querySelector(".message-bubble");
423
423
  if (bubble) {
424
- bubble.innerHTML = this._escapeHtml(messageData.content || text);
424
+ // Parse markdown for assistant messages, escape for user messages
425
+ const content = messageData.content || text;
426
+ bubble.innerHTML = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
425
427
  }
426
428
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
427
429
  return;
@@ -483,11 +485,14 @@ class TextChatScreen {
483
485
  const timestamp = messageData?.timestamp ? new Date(messageData.timestamp) : new Date();
484
486
  const formattedTime = this._formatTime(timestamp);
485
487
 
488
+ // Parse markdown for assistant messages, escape for user messages
489
+ const renderedContent = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
490
+
486
491
  messageEl.innerHTML = `
487
492
  <div class="message-content">
488
493
  ${attachmentsHTML}
489
494
  <div class="message-bubble">
490
- ${this._escapeHtml(content)}
495
+ ${renderedContent}
491
496
  </div>
492
497
  <div class="message-time">${formattedTime}</div>
493
498
  </div>
@@ -537,7 +542,10 @@ class TextChatScreen {
537
542
  if (existingEl) {
538
543
  const bubble = existingEl.querySelector(".message-bubble");
539
544
  if (bubble) {
540
- bubble.innerHTML = this._escapeHtml(msg.content || "");
545
+ // Parse markdown for assistant messages, escape for user messages
546
+ const content = msg.content || "";
547
+ const isUserMessage = msg.sender === "user";
548
+ bubble.innerHTML = !isUserMessage ? this._parseMarkdown(content) : this._escapeHtml(content);
541
549
  }
542
550
 
543
551
  // Update attachments if they exist
@@ -676,6 +684,106 @@ class TextChatScreen {
676
684
  return div.innerHTML;
677
685
  }
678
686
 
687
+ // Parse markdown to HTML
688
+ _parseMarkdown(text) {
689
+ if (!text || typeof text !== 'string') return '';
690
+
691
+ let html = text;
692
+
693
+ // Escape HTML first to prevent XSS
694
+ html = html
695
+ .replace(/&/g, '&amp;')
696
+ .replace(/</g, '&lt;')
697
+ .replace(/>/g, '&gt;');
698
+
699
+ // Split into lines for processing
700
+ const lines = html.split('\n');
701
+ const processedLines = [];
702
+ let inList = false;
703
+
704
+ for (let i = 0; i < lines.length; i++) {
705
+ let line = lines[i];
706
+
707
+ // Check for headers first (#, ##, ###, etc.)
708
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
709
+ if (headerMatch) {
710
+ if (inList) {
711
+ processedLines.push('</ul>');
712
+ inList = false;
713
+ }
714
+ const level = headerMatch[1].length;
715
+ const headerText = headerMatch[2];
716
+ // Process markdown inside header
717
+ let processedHeader = headerText;
718
+ processedHeader = processedHeader.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
719
+ processedHeader = processedHeader.replace(/__([^_]+)__/g, '<strong>$1</strong>');
720
+ processedHeader = processedHeader.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
721
+ processedHeader = processedHeader.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
722
+ processedLines.push(`<h${level}>${processedHeader}</h${level}>`);
723
+ continue;
724
+ }
725
+
726
+ // Check if this is a list item (before processing bold/italic)
727
+ const listMatch = line.match(/^[\s]*[-*]\s+(.+)$/);
728
+
729
+ if (listMatch) {
730
+ // Process markdown inside list item
731
+ let listContent = listMatch[1];
732
+
733
+ // Bold: **text** (must be processed before italic)
734
+ listContent = listContent.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
735
+ listContent = listContent.replace(/__([^_]+)__/g, '<strong>$1</strong>');
736
+
737
+ // Italic: *text* (single asterisk, avoid matching **text**)
738
+ listContent = listContent.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
739
+ listContent = listContent.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
740
+
741
+ if (!inList) {
742
+ processedLines.push('<ul>');
743
+ inList = true;
744
+ }
745
+ processedLines.push(`<li>${listContent}</li>`);
746
+ } else {
747
+ if (inList) {
748
+ processedLines.push('</ul>');
749
+ inList = false;
750
+ }
751
+
752
+ // Skip empty lines (they'll become <br> later)
753
+ if (line.trim() === '') {
754
+ processedLines.push('');
755
+ continue;
756
+ }
757
+
758
+ // Process markdown in non-list lines
759
+ // Bold: **text** (must be processed before italic)
760
+ line = line.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
761
+ line = line.replace(/__([^_]+)__/g, '<strong>$1</strong>');
762
+
763
+ // Italic: *text* (single asterisk, avoid matching **text**)
764
+ line = line.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
765
+ line = line.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
766
+
767
+ // Inline code: `code`
768
+ line = line.replace(/`([^`]+)`/g, '<code>$1</code>');
769
+
770
+ // Links: [text](url)
771
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
772
+
773
+ processedLines.push(line);
774
+ }
775
+ }
776
+
777
+ if (inList) {
778
+ processedLines.push('</ul>');
779
+ }
780
+
781
+ // Join lines with <br> and return
782
+ html = processedLines.join('<br>');
783
+
784
+ return html;
785
+ }
786
+
679
787
  // Show expanded image modal
680
788
  showExpandedImage(src, alt) {
681
789
  // Remove existing modal if any
@@ -898,6 +1006,79 @@ class TextChatScreen {
898
1006
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
899
1007
  }
900
1008
 
1009
+ /* ============================================
1010
+ Text Chat Message Bubble Styles
1011
+ ============================================ */
1012
+
1013
+ /* Typography - Bold */
1014
+ .text-chat-screen .message-bubble strong {
1015
+ font-weight: 600;
1016
+ color: inherit;
1017
+ }
1018
+
1019
+ /* Typography - Italic */
1020
+ .text-chat-screen .message-bubble em {
1021
+ font-style: italic;
1022
+ }
1023
+
1024
+ /* Lists */
1025
+ .text-chat-screen .message-bubble ul {
1026
+ margin: 0px 0;
1027
+ padding-left: 20px;
1028
+ list-style-type: disc;
1029
+ margin-bottom:0px;
1030
+ margin-top:5px;
1031
+ }
1032
+
1033
+ .text-chat-screen .message-bubble li {
1034
+ margin: 0;
1035
+ line-height: 1.1;
1036
+ padding-left: 3px;
1037
+ }
1038
+
1039
+ /* Headings - Shared Styles */
1040
+ .text-chat-screen .message-bubble h1,
1041
+ .text-chat-screen .message-bubble h2,
1042
+ .text-chat-screen .message-bubble h3,
1043
+ .text-chat-screen .message-bubble h4,
1044
+ .text-chat-screen .message-bubble h5,
1045
+ .text-chat-screen .message-bubble h6 {
1046
+ margin: 0px 0 0px 0;
1047
+ font-weight: 600;
1048
+ color: inherit;
1049
+ line-height: 1.3;
1050
+ }
1051
+
1052
+ /* Heading Sizes */
1053
+ .text-chat-screen .message-bubble h1 { font-size: 1.5em; }
1054
+ .text-chat-screen .message-bubble h2 { font-size: 1.3em; }
1055
+ .text-chat-screen .message-bubble h3 { font-size: 1.15em; }
1056
+ .text-chat-screen .message-bubble h4 { font-size: 1.05em; }
1057
+ .text-chat-screen .message-bubble h5 { font-size: 1em; }
1058
+ .text-chat-screen .message-bubble h6 { font-size: 0.95em; }
1059
+
1060
+ /* Inline Code */
1061
+ .text-chat-screen .message-bubble code {
1062
+ background: rgba(0, 0, 0, 0.05);
1063
+ padding: 2px 6px;
1064
+ border-radius: 4px;
1065
+ font-family: 'Courier New', Courier, monospace;
1066
+ font-size: 0.9em;
1067
+ }
1068
+
1069
+ /* Links */
1070
+ .text-chat-screen .message-bubble a {
1071
+ color: ${this.primaryColor};
1072
+ text-decoration: underline;
1073
+ }
1074
+
1075
+ .text-chat-screen .message-bubble a:hover {
1076
+ opacity: 0.8;
1077
+ }
1078
+ .text-chat-screen .message-bubble br {
1079
+ line-height: 0.1; /* Adjust between 0 and 1 */
1080
+ }
1081
+
901
1082
  .text-chat-screen .message-time {
902
1083
  font-size: 10px;
903
1084
  color: #94a3b8;