Update Katex styles and rendering in markdown content
This commit is contained in:
parent
6ec4100e50
commit
00f9923ed7
@ -19,6 +19,33 @@ body {
|
|||||||
inset 0 1px 0 0 #ffffff52;
|
inset 0 1px 0 0 #ffffff52;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-body .katex {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .katex-display {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .katex-display > .katex {
|
||||||
|
font-size: 1.21em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .katex-display > .katex > .katex-html {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .katex-display > .katex > .katex-html > .tag {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|
||||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import 'katex/dist/katex.min.css';
|
||||||
import { Metadata, Viewport } from "next";
|
import { Metadata, Viewport } from "next";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
import { Inter, Instrument_Serif, IBM_Plex_Mono } from 'next/font/google';
|
import { Inter, Instrument_Serif, IBM_Plex_Mono } from 'next/font/google';
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
"use client";
|
"use client";
|
||||||
|
import 'katex/dist/katex.min.css';
|
||||||
|
|
||||||
import
|
import
|
||||||
React,
|
React,
|
||||||
@ -15,9 +16,9 @@ React,
|
|||||||
import ReactMarkdown, { Components } from 'react-markdown';
|
import ReactMarkdown, { Components } from 'react-markdown';
|
||||||
import Marked, { ReactRenderer } from 'marked-react';
|
import Marked, { ReactRenderer } from 'marked-react';
|
||||||
import katex from 'katex';
|
import katex from 'katex';
|
||||||
|
import Latex from 'react-latex-next';
|
||||||
import { track } from '@vercel/analytics';
|
import { track } from '@vercel/analytics';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import 'katex/dist/katex.min.css';
|
|
||||||
import { useChat } from 'ai/react';
|
import { useChat } from 'ai/react';
|
||||||
import { ToolInvocation } from 'ai';
|
import { ToolInvocation } from 'ai';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@ -1624,21 +1625,12 @@ The o1-mini is a new OpenAI model that is optimized for reasoning tasks. Current
|
|||||||
return metadata;
|
return metadata;
|
||||||
}, [metadataCache]);
|
}, [metadataCache]);
|
||||||
|
|
||||||
const renderEquation = (equation: string): string => {
|
const preprocessContent = (text: string) => {
|
||||||
try {
|
// Replace block-level LaTeX
|
||||||
return katex.renderToString(equation, {
|
text = text.replace(/\$\$(.*?)\$\$/g, '\\[$1\\]');
|
||||||
throwOnError: false,
|
// Replace bracket-enclosed LaTeX
|
||||||
displayMode: true,
|
text = text.replace(/\[(.*?)\]/g, '\\[$1\\]');
|
||||||
strict: false,
|
return text;
|
||||||
trust: true,
|
|
||||||
macros: {
|
|
||||||
"\\,": "\\:"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("KaTeX rendering error:", error);
|
|
||||||
return equation;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CodeBlock = ({ language, children }: { language: string | undefined; children: string }) => {
|
const CodeBlock = ({ language, children }: { language: string | undefined; children: string }) => {
|
||||||
@ -1721,10 +1713,6 @@ The o1-mini is a new OpenAI model that is optimized for reasoning tasks. Current
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between px-3 py-2 bg-gray-50 text-xs text-gray-500">
|
|
||||||
<span className="truncate max-w-[80%]">{href}</span>
|
|
||||||
<ExternalLink size={14} className="flex-shrink-0" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1753,27 +1741,50 @@ The o1-mini is a new OpenAI model that is optimized for reasoning tasks. Current
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const latexMacros = {
|
||||||
|
"\\display": "\\displaystyle",
|
||||||
|
};
|
||||||
|
|
||||||
const renderer: Partial<ReactRenderer> = {
|
const renderer: Partial<ReactRenderer> = {
|
||||||
paragraph(children) {
|
paragraph(children) {
|
||||||
if (typeof children === 'string') {
|
return (
|
||||||
const parts = children.split(/(\[.*?\])/g);
|
<p className="my-4">
|
||||||
return (
|
{React.Children.map(children, (child) => {
|
||||||
<p>
|
if (typeof child === 'string') {
|
||||||
{parts.map((part, index) => {
|
// Split the string to handle inline and display equations separately
|
||||||
if (part.startsWith('[') && part.endsWith(']')) {
|
const parts = child.split(/(\\\[.*?\\\]|\$.*?\$)/gs);
|
||||||
const equation = part.slice(1, -1);
|
return parts.map((part, index) => {
|
||||||
return (
|
if (part.startsWith('\\[') && part.endsWith('\\]')) {
|
||||||
<span key={index} dangerouslySetInnerHTML={{
|
// Display mode equation
|
||||||
__html: renderEquation(equation)
|
return (
|
||||||
}} />
|
<Latex key={index} macros={latexMacros}>
|
||||||
);
|
{part}
|
||||||
}
|
</Latex>
|
||||||
return <span key={index}>{part}</span>;
|
);
|
||||||
})}
|
} else if (part.startsWith('$') && part.endsWith('$')) {
|
||||||
</p>
|
// Inline equation
|
||||||
);
|
return (
|
||||||
}
|
<Latex key={index} macros={latexMacros}>
|
||||||
return <p>{children}</p>;
|
{part}
|
||||||
|
</Latex>
|
||||||
|
);
|
||||||
|
} // add $$ for display mode equations
|
||||||
|
else if (part.startsWith('$$') && part.endsWith('$$')) {
|
||||||
|
// Display mode equation
|
||||||
|
return (
|
||||||
|
<Latex key={index} macros={latexMacros}>
|
||||||
|
{part}
|
||||||
|
</Latex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Regular text
|
||||||
|
return part;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
code(children, language) {
|
code(children, language) {
|
||||||
return <CodeBlock language={language}>{String(children)}</CodeBlock>;
|
return <CodeBlock language={language}>{String(children)}</CodeBlock>;
|
||||||
@ -1787,7 +1798,7 @@ The o1-mini is a new OpenAI model that is optimized for reasoning tasks. Current
|
|||||||
</sup>
|
</sup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return isValidUrl(href) ? renderHoverCard(href, text) : <a href={href}>{text}</a>;
|
return isValidUrl(href) ? renderHoverCard(href, text) : <a href={href} className="text-blue-600 hover:underline">{text}</a>;
|
||||||
},
|
},
|
||||||
heading(children, level) {
|
heading(children, level) {
|
||||||
const HeadingTag = `h${level}` as keyof JSX.IntrinsicElements;
|
const HeadingTag = `h${level}` as keyof JSX.IntrinsicElements;
|
||||||
@ -1796,19 +1807,21 @@ The o1-mini is a new OpenAI model that is optimized for reasoning tasks. Current
|
|||||||
},
|
},
|
||||||
list(children, ordered) {
|
list(children, ordered) {
|
||||||
const ListTag = ordered ? 'ol' : 'ul';
|
const ListTag = ordered ? 'ol' : 'ul';
|
||||||
return <ListTag className="list-inside list-disc my-2">{children}</ListTag>;
|
return <ListTag className="list-inside list-disc my-4 pl-4">{children}</ListTag>;
|
||||||
},
|
},
|
||||||
listItem(children) {
|
listItem(children) {
|
||||||
return <li className="my-1">{children}</li>;
|
return <li className="my-2">{children}</li>;
|
||||||
},
|
},
|
||||||
blockquote(children) {
|
blockquote(children) {
|
||||||
return <blockquote className="border-l-4 border-gray-300 pl-4 italic my-4">{children}</blockquote>;
|
return <blockquote className="border-l-4 border-gray-300 pl-4 italic my-4">{children}</blockquote>;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const preprocessedContent = useMemo(() => preprocessContent(content), [content]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="prose prose-sm sm:prose-base max-w-none">
|
<div className="markdown-body">
|
||||||
<Marked renderer={renderer}>{content}</Marked>
|
<Marked renderer={renderer}>{preprocessedContent}</Marked>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"openai": "^4.56.0",
|
"openai": "^4.56.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-latex-next": "^3.0.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"react-tweet": "^3.2.1",
|
"react-tweet": "^3.2.1",
|
||||||
|
|||||||
@ -85,7 +85,7 @@ dependencies:
|
|||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
ai:
|
ai:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 3.3.40(openai@4.56.0)(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8)
|
version: 3.3.41(openai@4.56.0)(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8)
|
||||||
anthropic-vertex-ai:
|
anthropic-vertex-ai:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0(zod@3.23.8)
|
version: 1.0.0(zod@3.23.8)
|
||||||
@ -137,6 +137,9 @@ dependencies:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18
|
specifier: ^18
|
||||||
version: 18.3.1(react@18.3.1)
|
version: 18.3.1(react@18.3.1)
|
||||||
|
react-latex-next:
|
||||||
|
specifier: ^3.0.0
|
||||||
|
version: 3.0.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react-markdown:
|
react-markdown:
|
||||||
specifier: ^9.0.1
|
specifier: ^9.0.1
|
||||||
version: 9.0.1(@types/react@18.3.3)(react@18.3.1)
|
version: 9.0.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
@ -424,8 +427,8 @@ packages:
|
|||||||
zod-to-json-schema: 3.23.2(zod@3.23.8)
|
zod-to-json-schema: 3.23.2(zod@3.23.8)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@ai-sdk/vue@0.0.49(vue@3.4.35)(zod@3.23.8):
|
/@ai-sdk/vue@0.0.50(vue@3.4.35)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-GLjk5uhn0dA8iXpqdF91NyOw+VCgDIo22zrdkRtDg+nLaqkFSjgdDLAp7CL+ihW4F0/IkpZym3j0lFi9LiCjZA==}
|
resolution: {integrity: sha512-eIWfxqpKwRdL3rxJMg1HDJcjfugFJGg4P934Tl69S7UCot2/U4BPZoESVJQFroS1elbKHaMRgv0ZJt1ddWQPjQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.3.4
|
vue: ^3.3.4
|
||||||
@ -1921,8 +1924,8 @@ packages:
|
|||||||
humanize-ms: 1.2.1
|
humanize-ms: 1.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/ai@3.3.40(openai@4.56.0)(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8):
|
/ai@3.3.41(openai@4.56.0)(react@18.3.1)(svelte@4.2.18)(vue@3.4.35)(zod@3.23.8):
|
||||||
resolution: {integrity: sha512-EqCrKHKO0TIsZANJEO3QY9uNXiZC9owhpE/jn9Yt83ET3qPrdIi3zbx/MXX8F6ivmKt2vzoYzR4a5Z7l9kJLJQ==}
|
resolution: {integrity: sha512-unWUqw0hnZo0irhdedTv8ef7IEiySBCO3zjPxx1/k0kI1G0whKYq8l83k/LzqShLekc2Qg3gyyhdEO+39ptegw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
openai: ^4.42.0
|
openai: ^4.42.0
|
||||||
@ -1948,7 +1951,7 @@ packages:
|
|||||||
'@ai-sdk/solid': 0.0.47(zod@3.23.8)
|
'@ai-sdk/solid': 0.0.47(zod@3.23.8)
|
||||||
'@ai-sdk/svelte': 0.0.49(svelte@4.2.18)(zod@3.23.8)
|
'@ai-sdk/svelte': 0.0.49(svelte@4.2.18)(zod@3.23.8)
|
||||||
'@ai-sdk/ui-utils': 0.0.44(zod@3.23.8)
|
'@ai-sdk/ui-utils': 0.0.44(zod@3.23.8)
|
||||||
'@ai-sdk/vue': 0.0.49(vue@3.4.35)(zod@3.23.8)
|
'@ai-sdk/vue': 0.0.50(vue@3.4.35)(zod@3.23.8)
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
eventsource-parser: 1.1.2
|
eventsource-parser: 1.1.2
|
||||||
json-schema: 0.4.0
|
json-schema: 0.4.0
|
||||||
@ -5347,6 +5350,18 @@ packages:
|
|||||||
/react-is@16.13.1:
|
/react-is@16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
|
|
||||||
|
/react-latex-next@3.0.0(react-dom@18.3.1)(react@18.3.1):
|
||||||
|
resolution: {integrity: sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g==}
|
||||||
|
engines: {node: '>=12', npm: '>=5'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
katex: 0.16.11
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-markdown@9.0.1(@types/react@18.3.3)(react@18.3.1):
|
/react-markdown@9.0.1(@types/react@18.3.3)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==}
|
resolution: {integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|||||||
@ -84,6 +84,11 @@ const config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
|
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
|
||||||
|
safelist: [
|
||||||
|
{
|
||||||
|
pattern: /katex-.*/,
|
||||||
|
},
|
||||||
|
],
|
||||||
} satisfies Config
|
} satisfies Config
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
Loading…
Reference in New Issue
Block a user