diff --git a/apps/dashboard/src/lib/features/ui/html-render.svelte b/apps/dashboard/src/lib/features/ui/html-render.svelte
index 7c482e09..78160278 100644
--- a/apps/dashboard/src/lib/features/ui/html-render.svelte
+++ b/apps/dashboard/src/lib/features/ui/html-render.svelte
@@ -1,8 +1,45 @@
diff --git a/packages/ui/src/tools/index.ts b/packages/ui/src/tools/index.ts
index ee339984..179370e1 100644
--- a/packages/ui/src/tools/index.ts
+++ b/packages/ui/src/tools/index.ts
@@ -1,5 +1,6 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
+export * from './render-math';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
diff --git a/packages/ui/src/tools/render-math.ts b/packages/ui/src/tools/render-math.ts
new file mode 100644
index 00000000..22d62876
--- /dev/null
+++ b/packages/ui/src/tools/render-math.ts
@@ -0,0 +1,24 @@
+import katex from 'katex';
+import 'katex/dist/katex.min.css';
+
+const MATH_NODE_SELECTOR = '[data-type="inline-math"], [data-type="block-math"]';
+
+export function renderMathInElement(root: ParentNode): void {
+ const mathNodes = root.querySelectorAll(MATH_NODE_SELECTOR);
+
+ for (const mathNode of mathNodes) {
+ if (mathNode.dataset.mathRendered === 'true') continue;
+
+ const latex = mathNode.getAttribute('data-latex');
+ const mathType = mathNode.getAttribute('data-type');
+
+ if (!latex || (mathType !== 'inline-math' && mathType !== 'block-math')) continue;
+
+ katex.render(latex, mathNode, {
+ displayMode: mathType === 'block-math',
+ throwOnError: false
+ });
+
+ mathNode.dataset.mathRendered = 'true';
+ }
+}
diff --git a/packages/ui/src/tools/sanitize.ts b/packages/ui/src/tools/sanitize.ts
index 917e4cc9..3f8508f4 100644
--- a/packages/ui/src/tools/sanitize.ts
+++ b/packages/ui/src/tools/sanitize.ts
@@ -45,7 +45,7 @@ export function sanitizeHtml(html: string): string {
'img'
],
// Allow safe attributes
- ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'style'],
+ ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'style', 'data-type', 'data-latex'],
// Forbid dangerous attributes
FORBID_ATTR: [
'onerror',