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/dist/chat-widget.esm.js +216 -11
- package/dist/chat-widget.esm.min.js +1 -1
- package/dist/chat-widget.umd.js +216 -11
- package/dist/chat-widget.umd.min.js +1 -1
- package/index.html +1 -1
- package/package.json +1 -1
- package/src/audio-chat-screen.js +196 -3
- package/src/chat-widget.js +14 -3
- package/src/text-chat-screen.js +184 -3
package/index.html
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mini-chat-bot-widget",
|
|
3
|
-
"version": "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",
|
package/src/audio-chat-screen.js
CHANGED
|
@@ -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
|
-
|
|
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">${
|
|
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
|
-
|
|
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, '&')
|
|
710
|
+
.replace(/</g, '<')
|
|
711
|
+
.replace(/>/g, '>');
|
|
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;
|
package/src/chat-widget.js
CHANGED
|
@@ -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 || "
|
|
26
|
-
this.supabaseToken = options.supabaseToken || "
|
|
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
|
-
|
|
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,
|
package/src/text-chat-screen.js
CHANGED
|
@@ -421,7 +421,9 @@ class TextChatScreen {
|
|
|
421
421
|
// Update existing message
|
|
422
422
|
const bubble = existingMessage.querySelector(".message-bubble");
|
|
423
423
|
if (bubble) {
|
|
424
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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, '&')
|
|
696
|
+
.replace(/</g, '<')
|
|
697
|
+
.replace(/>/g, '>');
|
|
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;
|