mini-chat-bot-widget 0.8.0 → 0.11.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/index.html CHANGED
@@ -41,7 +41,7 @@
41
41
  try {
42
42
  // Initialize the widget
43
43
  new ChatWidget({
44
- title: "Agricultural Assistant",
44
+ title: "PUCAR Assistant",
45
45
  placeholder: "Ask me anything…",
46
46
  primaryColor: "#1a5c4b",
47
47
 
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.11.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",
@@ -381,6 +381,9 @@ class AudioChatScreen {
381
381
  const text = this.audioQueue.shift();
382
382
 
383
383
  try {
384
+ // Use selected language for TTS (hi for Hindi, en for English, etc.)
385
+ // Bhashini will fallback to English if language is not supported
386
+ console.log("TTS Language:", this.selectedLanguage);
384
387
  const audioContent = await this.bhashini.textToSpeech(text, this.selectedLanguage);
385
388
  if (audioContent) {
386
389
  const audioSrc = `data:audio/wav;base64,${audioContent}`;
@@ -464,7 +467,9 @@ class AudioChatScreen {
464
467
  // Update existing message
465
468
  const bubble = existingMessage.querySelector(".message-bubble");
466
469
  if (bubble) {
467
- bubble.innerHTML = this._escapeHtml(messageData.content || text);
470
+ // Parse markdown for assistant messages, escape for user messages
471
+ const content = messageData.content || text;
472
+ bubble.innerHTML = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
468
473
  }
469
474
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
470
475
  return;
@@ -526,10 +531,13 @@ class AudioChatScreen {
526
531
  const timestamp = messageData?.timestamp ? new Date(messageData.timestamp) : new Date();
527
532
  const formattedTime = this._formatTime(timestamp);
528
533
 
534
+ // Parse markdown for assistant messages, escape for user messages
535
+ const renderedContent = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
536
+
529
537
  messageEl.innerHTML = `
530
538
  <div class="message-content">
531
539
  ${attachmentsHTML}
532
- <div class="message-bubble">${this._escapeHtml(content)}</div>
540
+ <div class="message-bubble">${renderedContent}</div>
533
541
  <div class="message-time">${formattedTime}</div>
534
542
  </div>
535
543
  `;
@@ -586,7 +594,10 @@ class AudioChatScreen {
586
594
  if (existingEl) {
587
595
  const bubble = existingEl.querySelector(".message-bubble");
588
596
  if (bubble) {
589
- bubble.innerHTML = this._escapeHtml(msg.content || "");
597
+ // Parse markdown for assistant messages, escape for user messages
598
+ const content = msg.content || "";
599
+ const isUserMessage = msg.sender === "user";
600
+ bubble.innerHTML = !isUserMessage ? this._parseMarkdown(content) : this._escapeHtml(content);
590
601
  }
591
602
 
592
603
  // Update attachments if they exist
@@ -687,6 +698,106 @@ class AudioChatScreen {
687
698
  return div.innerHTML;
688
699
  }
689
700
 
701
+ // Parse markdown to HTML
702
+ _parseMarkdown(text) {
703
+ if (!text || typeof text !== 'string') return '';
704
+
705
+ let html = text;
706
+
707
+ // Escape HTML first to prevent XSS
708
+ html = html
709
+ .replace(/&/g, '&amp;')
710
+ .replace(/</g, '&lt;')
711
+ .replace(/>/g, '&gt;');
712
+
713
+ // Split into lines for processing
714
+ const lines = html.split('\n');
715
+ const processedLines = [];
716
+ let inList = false;
717
+
718
+ for (let i = 0; i < lines.length; i++) {
719
+ let line = lines[i];
720
+
721
+ // Check for headers first (#, ##, ###, etc.)
722
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
723
+ if (headerMatch) {
724
+ if (inList) {
725
+ processedLines.push('</ul>');
726
+ inList = false;
727
+ }
728
+ const level = headerMatch[1].length;
729
+ const headerText = headerMatch[2];
730
+ // Process markdown inside header
731
+ let processedHeader = headerText;
732
+ processedHeader = processedHeader.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
733
+ processedHeader = processedHeader.replace(/__([^_]+)__/g, '<strong>$1</strong>');
734
+ processedHeader = processedHeader.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
735
+ processedHeader = processedHeader.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
736
+ processedLines.push(`<h${level}>${processedHeader}</h${level}>`);
737
+ continue;
738
+ }
739
+
740
+ // Check if this is a list item (before processing bold/italic)
741
+ const listMatch = line.match(/^[\s]*[-*]\s+(.+)$/);
742
+
743
+ if (listMatch) {
744
+ // Process markdown inside list item
745
+ let listContent = listMatch[1];
746
+
747
+ // Bold: **text** (must be processed before italic)
748
+ listContent = listContent.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
749
+ listContent = listContent.replace(/__([^_]+)__/g, '<strong>$1</strong>');
750
+
751
+ // Italic: *text* (single asterisk, avoid matching **text**)
752
+ listContent = listContent.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
753
+ listContent = listContent.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
754
+
755
+ if (!inList) {
756
+ processedLines.push('<ul>');
757
+ inList = true;
758
+ }
759
+ processedLines.push(`<li>${listContent}</li>`);
760
+ } else {
761
+ if (inList) {
762
+ processedLines.push('</ul>');
763
+ inList = false;
764
+ }
765
+
766
+ // Skip empty lines (they'll become <br> later)
767
+ if (line.trim() === '') {
768
+ processedLines.push('');
769
+ continue;
770
+ }
771
+
772
+ // Process markdown in non-list lines
773
+ // Bold: **text** (must be processed before italic)
774
+ line = line.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
775
+ line = line.replace(/__([^_]+)__/g, '<strong>$1</strong>');
776
+
777
+ // Italic: *text* (single asterisk, avoid matching **text**)
778
+ line = line.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
779
+ line = line.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
780
+
781
+ // Inline code: `code`
782
+ line = line.replace(/`([^`]+)`/g, '<code>$1</code>');
783
+
784
+ // Links: [text](url)
785
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
786
+
787
+ processedLines.push(line);
788
+ }
789
+ }
790
+
791
+ if (inList) {
792
+ processedLines.push('</ul>');
793
+ }
794
+
795
+ // Join lines with <br> and return
796
+ html = processedLines.join('<br>');
797
+
798
+ return html;
799
+ }
800
+
690
801
  // Show expanded image modal (mirrors TextChatScreen)
691
802
  showExpandedImage(src, alt) {
692
803
  const existingModal = document.querySelector(".expanded-image-modal");
@@ -1102,6 +1213,88 @@ class AudioChatScreen {
1102
1213
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
1103
1214
  }
1104
1215
 
1216
+ /* Markdown styles */
1217
+ .audio-chat-screen .message-bubble strong {
1218
+ font-weight: 600;
1219
+ color: inherit;
1220
+ }
1221
+
1222
+ .audio-chat-screen .message-bubble em {
1223
+ font-style: italic;
1224
+ }
1225
+
1226
+ .audio-chat-screen .message-bubble ul {
1227
+ margin: 0px 0;
1228
+ padding-left: 20px;
1229
+ list-style-type: disc;
1230
+ margin-bottom:0px;
1231
+ margin-top:5px;
1232
+ }
1233
+
1234
+ .audio-chat-screen .message-bubble li {
1235
+ margin: 0;
1236
+ line-height: 1.1;
1237
+ padding-left: 3px;
1238
+ }
1239
+
1240
+ .audio-chat-screen .message-bubble h1,
1241
+ .audio-chat-screen .message-bubble h2,
1242
+ .audio-chat-screen .message-bubble h3,
1243
+ .audio-chat-screen .message-bubble h4,
1244
+ .audio-chat-screen .message-bubble h5,
1245
+ .audio-chat-screen .message-bubble h6 {
1246
+ margin: 0px 0 0px 0;
1247
+ font-weight: 600;
1248
+ color: inherit;
1249
+ line-height: 1.3;
1250
+ }
1251
+
1252
+ .audio-chat-screen .message-bubble h1 {
1253
+ font-size: 1.5em;
1254
+ }
1255
+
1256
+ .audio-chat-screen .message-bubble h2 {
1257
+ font-size: 1.3em;
1258
+ }
1259
+
1260
+ .audio-chat-screen .message-bubble h3 {
1261
+ font-size: 1.15em;
1262
+ }
1263
+
1264
+ .audio-chat-screen .message-bubble h4 {
1265
+ font-size: 1.05em;
1266
+ }
1267
+
1268
+ .audio-chat-screen .message-bubble h5 {
1269
+ font-size: 1em;
1270
+ }
1271
+
1272
+ .audio-chat-screen .message-bubble h6 {
1273
+ font-size: 0.95em;
1274
+ }
1275
+
1276
+ .audio-chat-screen .message-bubble code {
1277
+ background: rgba(0, 0, 0, 0.05);
1278
+ padding: 2px 6px;
1279
+ border-radius: 4px;
1280
+ font-family: 'Courier New', Courier, monospace;
1281
+ font-size: 0.9em;
1282
+ }
1283
+
1284
+ .audio-chat-screen .message-bubble a {
1285
+ color: ${this.primaryColor};
1286
+ text-decoration: underline;
1287
+ }
1288
+
1289
+ .audio-chat-screen .message-bubble a:hover {
1290
+ opacity: 0.8;
1291
+ }
1292
+
1293
+ .audio-chat-screen .message-bubble br {
1294
+ line-height: 0.1; /* Adjust between 0 and 1 */
1295
+ }
1296
+
1297
+
1105
1298
  .audio-chat-screen .message-time {
1106
1299
  font-size: 10px;
1107
1300
  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() {
@@ -929,7 +934,7 @@ class ChatWidget {
929
934
  <div class="welcome-text">
930
935
  <p class="greeting">Hello!</p>
931
936
  <p class="intro">
932
- We are your Krishi Vigyan Sahayak—KVS, a companion to help with every farming question! Tell us, what do you want to know today?
937
+ This is PUCAR agent how can I help you today.
933
938
  </p>
934
939
  </div>
935
940
 
@@ -1038,6 +1043,11 @@ class ChatWidget {
1038
1043
  const value = e.currentTarget.dataset.value;
1039
1044
  this.selectedLanguage = value;
1040
1045
 
1046
+ // Update audio chat screen language for TTS
1047
+ if (this.audioChatScreen) {
1048
+ this.audioChatScreen.selectedLanguage = value;
1049
+ }
1050
+
1041
1051
  // Update UI
1042
1052
  this._updateSelectedLanguageDisplay();
1043
1053
  optionsFn.querySelectorAll(".select-option").forEach(o => o.classList.remove("selected"));
@@ -1262,6 +1272,7 @@ class ChatWidget {
1262
1272
  return;
1263
1273
  }
1264
1274
 
1275
+
1265
1276
  console.log(
1266
1277
  "📤 Sending text message - Language:",
1267
1278
  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;