From 02704a4b204ec2b8003c1d96601c73d089354d55 Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski Date: Sat, 11 Apr 2026 20:58:41 +0200 Subject: [PATCH] feat(ai): render assistant chat messages as sanitized markdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AI chat drawer was rendering assistant responses as plain text, so code blocks, lists, tables and inline formatting came through as literal asterisks and backticks — noisy and hard to scan. Adds a shared renderMarkdown() helper in resources/scripts/utils/ markdown.ts that parses GFM markdown via marked and sanitizes the result with DOMPurify before handing it to Vue's v-html. AiChatMessage uses the helper for assistant messages only; user messages stay as plain text since markdown syntax in their own typed input would be surprising. Assistant bubbles get the Tailwind `prose prose-sm` classes from the already-enabled @tailwindcss/typography plugin so headings, lists and code blocks inherit sensible defaults without per-element styling. Security: DOMPurify runs in its default browser profile, which strips 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