diff --git a/package.json b/package.json index b8f8324b..66182197 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,11 @@ "axios": "1.14.0", "chart.js": "^4.5.1", "date-fns": "^4.1.0", + "dompurify": "^3.3.3", "guid": "^0.0.12", "laravel-vite-plugin": "^3.0.0", "lodash": "^4.17.21", + "marked": "^18.0.0", "maska": "^3.0.4", "mini-svg-data-uri": "^1.4.4", "moment": "^2.30.1", diff --git a/resources/scripts/features/company/ai/components/AiChatMessage.vue b/resources/scripts/features/company/ai/components/AiChatMessage.vue index 7b2ee2ae..a02fba56 100644 --- a/resources/scripts/features/company/ai/components/AiChatMessage.vue +++ b/resources/scripts/features/company/ai/components/AiChatMessage.vue @@ -1,12 +1,21 @@ diff --git a/resources/scripts/utils/markdown.ts b/resources/scripts/utils/markdown.ts new file mode 100644 index 00000000..374102da --- /dev/null +++ b/resources/scripts/utils/markdown.ts @@ -0,0 +1,42 @@ +import { marked } from 'marked' +import DOMPurify from 'dompurify' + +/** + * Render a markdown string to safe, sanitized HTML. + * + * Used by the AI chat drawer to render assistant responses. Even though + * the AI provider controls the immediate source of the content, the model + * can echo anything it's fed — including user input from earlier in the + * conversation or tool results from the database. We therefore parse + * markdown → HTML via marked and then sanitize the result with DOMPurify + * before handing it to Vue's v-html. + * + * Marked is configured with: + * - gfm: true — GitHub-flavored markdown (tables, fenced code, + * strikethrough, task lists). Matches what users + * already expect from any modern chat UI. + * - breaks: true — newlines become
so a single user-typed line + * break renders as a visual break without needing + * two trailing spaces. + * - async: false — force synchronous parsing so the caller doesn't + * have to await; marked defaults to returning a + * Promise when extensions are registered. + * + * DOMPurify is run in its default browser profile which strips