diff --git a/src/ui/UserPortal/components/ChatMessage.vue b/src/ui/UserPortal/components/ChatMessage.vue index a55ef3de35..6c9bbf7466 100644 --- a/src/ui/UserPortal/components/ChatMessage.vue +++ b/src/ui/UserPortal/components/ChatMessage.vue @@ -36,7 +36,7 @@ - {{ displayText }} +
${highlighted}
`;
+};
+marked.use({ renderer });
+
export default {
name: 'ChatMessage',
@@ -149,34 +165,44 @@ export default {
return {
prompt: {} as CompletionPrompt,
viewPrompt: false,
- displayText: '',
+ displayHtml: '',
+ currentWordIndex: 0,
primaryButtonBg: this.$appConfigStore.primaryButtonBg,
- primaryButtonText: this.$appConfigStore.primaryButtonText
+ primaryButtonText: this.$appConfigStore.primaryButtonText,
};
},
+ computed: {
+ compiledMarkdown() {
+ return DOMPurify.sanitize(marked(this.message.text));
+ }
+ },
+
created() {
if (this.showWordAnimation) {
this.displayWordByWord();
} else {
- this.displayText = this.message.text;
+ this.displayHtml = this.compiledMarkdown;
}
},
methods: {
displayWordByWord() {
- const words = this.message.text.split(' ');
- let index = 0;
-
- const displayNextWord = () => {
- if (index < words.length) {
- this.displayText += words[index] + ' ';
- index++;
- setTimeout(displayNextWord, 10);
- }
- };
-
- displayNextWord();
+ if (this.currentWordIndex >= this.compiledMarkdown.split(/\s+/).length) return;
+
+ this.currentWordIndex += 1;
+ this.displayHtml = truncate(this.compiledMarkdown, this.currentWordIndex, {
+ byWords: true,
+ stripTags: false,
+ ellipsis: '',
+ decodeEntities: false,
+ keepWhitespaces: true,
+ excludes: '',
+ reserveLastWord: false,
+ keepWhitespaces: true
+ });
+
+ setTimeout(this.displayWordByWord, 10);
},
formatTimeStamp(timeStamp: string) {
@@ -260,7 +286,7 @@ export default {
}
.message__body {
- white-space: pre-wrap;
+ // white-space: pre-wrap;
overflow-wrap: break-word;
padding-left: 12px;
padding-right: 12px;
diff --git a/src/ui/UserPortal/package-lock.json b/src/ui/UserPortal/package-lock.json
index 6d83ddda52..40f09efdaf 100644
--- a/src/ui/UserPortal/package-lock.json
+++ b/src/ui/UserPortal/package-lock.json
@@ -10,10 +10,15 @@
"@azure/app-configuration": "^1.4.1",
"@azure/msal-browser": "^3.2.0",
"@pinia/nuxt": "^0.5.1",
+ "dompurify": "^3.1.6",
+ "highlight.js": "^11.10.0",
"javascript-time-ago": "^2.5.9",
+ "marked": "^13.0.2",
+ "marked-highlight": "^2.1.3",
"pinia": "^2.1.7",
"primeicons": "^6.0.1",
"primevue": "^3.35.0",
+ "truncate-html": "^1.1.1",
"vue-mention": "^2.0.0-alpha.3"
},
"devDependencies": {
@@ -5588,8 +5593,7 @@
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
- "dev": true
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"node_modules/brace-expansion": {
"version": "2.0.1",
@@ -5886,6 +5890,42 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/cheerio": {
+ "version": "1.0.0-rc.12",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
+ "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
+ "dependencies": {
+ "cheerio-select": "^2.1.0",
+ "dom-serializer": "^2.0.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1",
+ "htmlparser2": "^8.0.1",
+ "parse5": "^7.0.0",
+ "parse5-htmlparser2-tree-adapter": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
+ }
+ },
+ "node_modules/cheerio-select": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-select": "^5.1.0",
+ "css-what": "^6.1.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -6316,7 +6356,6 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
- "dev": true,
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
@@ -6345,7 +6384,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
- "dev": true,
"engines": {
"node": ">= 6"
},
@@ -6759,7 +6797,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
- "dev": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
@@ -6773,7 +6810,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -6785,7 +6821,6 @@
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
- "dev": true,
"dependencies": {
"domelementtype": "^2.3.0"
},
@@ -6796,11 +6831,15 @@
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
+ "node_modules/dompurify": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
+ "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
+ },
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
- "dev": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
@@ -8639,6 +8678,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/highlight.js": {
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz",
+ "integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
@@ -8677,6 +8724,24 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/htmlparser2": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
+ "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1",
+ "entities": "^4.4.0"
+ }
+ },
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
@@ -9874,6 +9939,25 @@
"node": "^16.14.0 || >=18.0.0"
}
},
+ "node_modules/marked": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.2.tgz",
+ "integrity": "sha512-J6CPjP8pS5sgrRqxVRvkCIkZ6MFdRIjDkwUwgJ9nL2fbmM6qGQeB2C16hi8Cc9BOzj6xXzy0jyi0iPIfnMHYzA==",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/marked-highlight": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/marked-highlight/-/marked-highlight-2.1.3.tgz",
+ "integrity": "sha512-t35JWm2u8HanOJ+gSJBAYQ0Jgr3vy+gl7ORAXN8bSEQFHl5FYXH0A7YXVMrfhmKaSuBSy6LidXECn3U9Qv/dHA==",
+ "peerDependencies": {
+ "marked": ">=4 <14"
+ }
+ },
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@@ -10709,7 +10793,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
- "dev": true,
"dependencies": {
"boolbase": "^1.0.0"
},
@@ -11294,6 +11377,29 @@
"parse-path": "^7.0.0"
}
},
+ "node_modules/parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "dependencies": {
+ "entities": "^4.4.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5-htmlparser2-tree-adapter": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
+ "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
+ "dependencies": {
+ "domhandler": "^5.0.2",
+ "parse5": "^7.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -13764,6 +13870,14 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"dev": true
},
+ "node_modules/truncate-html": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/truncate-html/-/truncate-html-1.1.1.tgz",
+ "integrity": "sha512-8U5jgta8uapbnTId/h95a5EVFGld94V7pZ2iLH18lRppjx8+r/Zx0VdFYThRQEVjBhbG7W2Goiv+b1+kceeb7A==",
+ "dependencies": {
+ "cheerio": "^1.0.0-rc.12"
+ }
+ },
"node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
diff --git a/src/ui/UserPortal/package.json b/src/ui/UserPortal/package.json
index 4eae1add77..2b6943bcb8 100644
--- a/src/ui/UserPortal/package.json
+++ b/src/ui/UserPortal/package.json
@@ -31,10 +31,15 @@
"@azure/app-configuration": "^1.4.1",
"@azure/msal-browser": "^3.2.0",
"@pinia/nuxt": "^0.5.1",
+ "dompurify": "^3.1.6",
+ "highlight.js": "^11.10.0",
"javascript-time-ago": "^2.5.9",
+ "marked": "^13.0.2",
+ "marked-highlight": "^2.1.3",
"pinia": "^2.1.7",
"primeicons": "^6.0.1",
"primevue": "^3.35.0",
+ "truncate-html": "^1.1.1",
"vue-mention": "^2.0.0-alpha.3"
}
}
diff --git a/src/ui/UserPortal/plugins/primevue.ts b/src/ui/UserPortal/plugins/primevue.ts
index b8d7f839fb..b18aed897f 100644
--- a/src/ui/UserPortal/plugins/primevue.ts
+++ b/src/ui/UserPortal/plugins/primevue.ts
@@ -16,7 +16,6 @@ import OverlayPanel from 'primevue/overlaypanel';
import Badge from 'primevue/badge';
import BadgeDirective from 'primevue/badgedirective';
-
import { defineNuxtPlugin } from '#app';
export default defineNuxtPlugin((nuxtApp) => {