diff --git a/app/src/server.js b/app/src/server.js index e266c9c0..e8acec24 100755 --- a/app/src/server.js +++ b/app/src/server.js @@ -39,7 +39,7 @@ dependencies: { * @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.4.21 + * @version 1.4.22 * */ diff --git a/package.json b/package.json index d04da038..39b20a4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalk", - "version": "1.4.21", + "version": "1.4.22", "description": "A free WebRTC browser-based video call", "main": "server.js", "scripts": { diff --git a/public/js/client.js b/public/js/client.js index 0ecd2eca..825e5fa3 100644 --- a/public/js/client.js +++ b/public/js/client.js @@ -15,7 +15,7 @@ * @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.4.21 + * @version 1.4.22 * */ @@ -7741,7 +7741,7 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) { const getPrivateMsg = filterXSS(privateMsg); const getMsgId = filterXSS(msgId); - // collect chat msges to save it later + // collect chat messages to save it later chatMessages.push({ time: time, from: getFrom, @@ -7754,8 +7754,6 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) { const isValidPrivateMessage = getPrivateMsg && getMsgId != null && getMsgId != myPeerId; - const message = getFrom === 'ChatGPT' ? `
${getMsg}
` : getMsg; - let msgHTML = `
@@ -7764,7 +7762,8 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) {
${getFrom}
${time}
-
${message} +
+

`; // add btn direct reply to private message @@ -7788,7 +7787,7 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) { id="msg-copy-${chatMessagesId}" class="${className.copy}" style="color:#fff; border:none; background:transparent;" - onclick="copyToClipboard('${chatMessagesId}')" + onclick="copyToClipboard('message-${chatMessagesId}')" >`; if (isSpeechSynthesisSupported) { msgHTML += ` @@ -7796,7 +7795,7 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) { id="msg-speech-${chatMessagesId}" class="${className.speech}" style="color:#fff; border:none; background:transparent;" - onclick="speechMessage(false, '${getFrom}', '${checkMsg(message)}')" + onclick="speechElementText(false, '${getFrom}', 'message-${chatMessagesId}')" >`; } msgHTML += ` @@ -7804,7 +7803,21 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) {
`; + msgerChat.insertAdjacentHTML('beforeend', msgHTML); + + const message = getId(`message-${chatMessagesId}`); + if (message) { + if (getFrom === 'ChatGPT') { + // Stream the message for ChatGPT + streamMessage(message, getMsg, 100); + } else { + // Process the message for other senders + message.innerHTML = processMessage(getMsg); + hljs.highlightAll(); + } + } + msgerChat.scrollTop += 500; if (!isMobileDevice) { setTippy(getId('msg-delete-' + chatMessagesId), 'Delete', 'top'); @@ -7817,6 +7830,104 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) { chatMessagesId++; } +/** + * Process Messages + * @param {string} message + * @returns + */ +function processMessage(message) { + const codeBlockRegex = /```([a-zA-Z0-9]+)?\n([\s\S]*?)```/g; + let parts = []; + let lastIndex = 0; + + message.replace(codeBlockRegex, (match, lang, code, offset) => { + if (offset > lastIndex) { + parts.push({ type: 'text', value: message.slice(lastIndex, offset) }); + } + parts.push({ type: 'code', lang, value: code }); + lastIndex = offset + match.length; + }); + + if (lastIndex < message.length) { + parts.push({ type: 'text', value: message.slice(lastIndex) }); + } + + return parts + .map((part) => { + if (part.type === 'text') { + return part.value; + } else if (part.type === 'code') { + return `
${part.value}
`; + } + }) + .join(''); +} + +/** + * Stream message + * @param {string} element + * @param {string} message + * @param {integer} speed + */ +function streamMessage(element, message, speed = 100) { + const codeBlockRegex = /```([a-zA-Z0-9]+)?\n([\s\S]*?)```/g; + const parts = []; + + let lastIndex = 0; + + message.replace(codeBlockRegex, (match, lang, code, offset) => { + if (offset > lastIndex) { + parts.push({ type: 'text', value: message.slice(lastIndex, offset) }); + } + parts.push({ type: 'code', lang, value: code }); + lastIndex = offset + match.length; + }); + + if (lastIndex < message.length) { + parts.push({ type: 'text', value: message.slice(lastIndex) }); + } + + let index = 0; + let textBuffer = ''; + let wordIndex = 0; + + const interval = setInterval(() => { + if (index < parts.length) { + const part = parts[index]; + + if (part.type === 'text') { + const words = part.value.split(' '); + if (wordIndex < words.length) { + textBuffer += words[wordIndex] + ' '; + wordIndex++; + element.innerHTML = textBuffer; + } else { + wordIndex = 0; + index++; + } + } else if (part.type === 'code') { + textBuffer += `
${part.value}
`; + element.innerHTML = textBuffer; + index++; + } + + if (index % 5 === 0 || index === parts.length) { + highlightCodeBlocks(element); + } + } else { + clearInterval(interval); + highlightCodeBlocks(element); + } + }, speed); + + function highlightCodeBlocks(element) { + const codeBlocks = element.querySelectorAll('pre code'); + codeBlocks.forEach((block) => { + hljs.highlightElement(block); + }); + } +} + /** * Speech message * https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance @@ -7832,6 +7943,17 @@ function speechMessage(newMsg = true, from, msg) { window.speechSynthesis.speak(speech); } +/** + * Speech element text + * @param {boolean} newMsg true/false + * @param {string} from peer_name + * @param {string} elemId + */ +function speechElementText(newMsg = true, from, elemId) { + const element = getId(elemId); + speechMessage(newMsg, from, element.innerText); +} + /** * Delete message * @param {string} id msg id @@ -10698,7 +10820,7 @@ function showAbout() { Swal.fire({ background: swBg, position: 'center', - title: 'WebRTC P2P v1.4.21', + title: 'WebRTC P2P v1.4.22', imageAlt: 'mirotalk-about', imageUrl: images.about, customClass: { image: 'img-about' }, diff --git a/public/views/client.html b/public/views/client.html index db66d10d..7faa50a2 100755 --- a/public/views/client.html +++ b/public/views/client.html @@ -61,6 +61,13 @@ + + + + @@ -1025,6 +1032,7 @@ access to use this app. - https://simonwep.github.io/pickr/ (https://github.com/simonwep/pickr) - https://uaparser.dev/ (https://github.com/faisalman/ua-parser-js) - https://www.npmjs.com/package/axios (https://github.com/axios/axios) + - https://highlightjs.org/ (https://github.com/highlightjs/highlight.js) --> @@ -1043,6 +1051,7 @@ access to use this app. +