Files
InvoiceShelf/package.json
Darko Gjorgjijoski 02704a4b20 feat(ai): render assistant chat messages as sanitized markdown
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
<script>, event handlers, javascript: URLs and every other XSS vector.
The AI provider isn't a trusted source — it can echo arbitrary user
input and tool-call results from the database — so sanitization is
non-negotiable even though the immediate source is our own backend.
2026-04-11 20:58:41 +02:00

68 lines
1.8 KiB
JSON

{
"private": true,
"engines": {
"node": ">=24"
},
"type": "module",
"scripts": {
"dev": "vite",
"watch": "vite build --watch",
"build": "vite build",
"serve": "vite preview",
"test": "eslint ./resources/scripts"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.0.0",
"@types/lodash": "^4.17.24",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/compiler-sfc": "^3.5.13",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-vue": "^10.0.0",
"prettier": "^3.4.2",
"tailwind-scrollbar": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^6.0.2",
"vue-tsc": "^3.2.6"
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.2.0",
"@popperjs/core": "^2.11.8",
"@stripe/stripe-js": "^2.4.0",
"@tiptap/core": "^3.0.0",
"@tiptap/extension-text-align": "^3.0.0",
"@tiptap/pm": "^3.0.0",
"@tiptap/starter-kit": "^3.0.0",
"@tiptap/vue-3": "^3.0.0",
"@types/node": "^24.0.0",
"@vuelidate/components": "^1.2.6",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/core": "^14.0.0",
"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",
"path": "^0.12.7",
"pinia": "^3.0.0",
"v-money3": "^3.24.1",
"v-tooltip": "^4.0.0-beta.17",
"vite": "^8.0.0",
"vue": "^3.5",
"vue-flatpickr-component": "^12.0.0",
"vue-i18n": "^11.0.1",
"vue-router": "^5.0.0",
"vuedraggable": "^4.1.0"
}
}