Add typography to webui and fix markdown render problem

This commit is contained in:
yangdx 2025-04-22 17:21:24 +08:00
parent 9a2b6caac9
commit 64f5bb0243
5 changed files with 313 additions and 9 deletions

View File

@ -55,6 +55,7 @@
"sonner": "^1.7.4",
"tailwind-merge": "^3.0.2",
"tailwind-scrollbar": "^4.0.1",
"typography": "^0.16.24",
"zustand": "^5.0.3",
},
"devDependencies": {
@ -659,10 +660,16 @@
"commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"compass-vertical-rhythm": ["compass-vertical-rhythm@1.4.5", "", { "dependencies": { "convert-css-length": "^1.0.1", "object-assign": "^4.1.0", "parse-unit": "^1.0.1" } }, "sha512-bJo3IYX7xmmZCDYjrT2XolaiNjGZ4E2JvUGxpdU0ecbH4ZLK786wvc8aHKVrGrKct9JlkmJbUi8YLrQWvOc+uA=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
"console-polyfill": ["console-polyfill@0.1.2", "", {}, "sha512-oHLGQmf0q2yuuqfTXuzAB5UMqgPH1cHdwLkjfCqRTG2eupc52jbXT1OtOlREv+yXmXRi3wqywAevz3qMSk90Hg=="],
"convert-css-length": ["convert-css-length@1.0.2", "", { "dependencies": { "console-polyfill": "^0.1.2", "parse-unit": "^1.0.1" } }, "sha512-ecV7j3hXyXN1X2XfJBzhMR0o1Obv0v3nHmn0UiS3ACENrzbxE/EknkiunS/fCwQva0U62X1GChi8GaPh4oTlLg=="],
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
"cookie": ["cookie@1.0.2", "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
@ -757,6 +764,8 @@
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
"decode-named-character-reference": ["decode-named-character-reference@1.0.2", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
@ -925,6 +934,8 @@
"graphology-utils": ["graphology-utils@2.5.2", "", { "peerDependencies": { "graphology-types": ">=0.23.0" } }, "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ=="],
"gray-percentage": ["gray-percentage@2.0.0", "", {}, "sha512-T0i4bwJoXbweuBM7bJwil9iHVAwXxmS9IFsEy27cXvRYxHwR2YVSBSXBjJw4EDKUvLpfjANeT5PrvTuAH1XnTw=="],
"hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
@ -1101,8 +1112,12 @@
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
"lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
@ -1229,6 +1244,8 @@
"mnemonist": ["mnemonist@0.39.8", "", { "dependencies": { "obliterator": "^2.0.1" } }, "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ=="],
"modularscale": ["modularscale@1.0.2", "", { "dependencies": { "lodash.isnumber": "^3.0.0" } }, "sha512-xfu46hZcAL9xU8S/RihHE49rmDYRAg36lQez49pcO6/aLBJ7cfVr5DH7Obo2PGKTCSAyy4iTAsWZa9apECK9mQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
@ -1269,6 +1286,8 @@
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
"parse-unit": ["parse-unit@1.0.1", "", {}, "sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg=="],
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
@ -1491,6 +1510,10 @@
"typescript-eslint": ["typescript-eslint@8.24.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.24.1", "@typescript-eslint/parser": "8.24.1", "@typescript-eslint/utils": "8.24.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA=="],
"typography": ["typography@0.16.24", "", { "dependencies": { "compass-vertical-rhythm": "^1.4.5", "decamelize": "^1.2.0", "gray-percentage": "^2.0.0", "lodash": "^4.13.1", "modularscale": "^1.0.2", "object-assign": "^4.1.0", "typography-normalize": "^0.16.19" } }, "sha512-o5jNctzGoJm2XgdqivJdpkF6lQkcQo8v1biMGY+rLSpBHhpCKdQv5em9S3R6igApxVYtbhNBJbV95vK9oPwRKQ=="],
"typography-normalize": ["typography-normalize@0.16.19", "", {}, "sha512-vtnSv/uGBZVbd4e/ZhZB9HKBgKKlWQUXw74+ADIHHxzKp27CEf8PSR8TX1zF2qSyQ9/qMdqLwXYz8yRQFq9JLQ=="],
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],

View File

@ -64,6 +64,7 @@
"sonner": "^1.7.4",
"tailwind-merge": "^3.0.2",
"tailwind-scrollbar": "^4.0.1",
"typography": "^0.16.24",
"zustand": "^5.0.3"
},
"devDependencies": {

View File

@ -24,9 +24,6 @@ export type MessageWithError = Message & {
export const ChatMessage = ({ message }: { message: MessageWithError }) => {
const { t } = useTranslation()
// Remove extra spaces around bold text
message.content = message.content.replace(/\* {3}/g, '').replace(/ {4}\*\*/g, '**')
const handleCopyMarkdown = useCallback(async () => {
if (message.content) {
try {
@ -47,14 +44,22 @@ export const ChatMessage = ({ message }: { message: MessageWithError }) => {
: 'bg-muted'
}`}
>
<pre className="relative break-words whitespace-pre-wrap">
<div className="relative">
<ReactMarkdown
className="dark:prose-invert max-w-none text-base text-sm"
className="prose dark:prose-invert max-w-none text-sm break-words prose-headings:mt-4 prose-headings:mb-2 prose-p:my-2 prose-ul:my-2 prose-ol:my-2 prose-li:my-1"
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeReact]}
skipHtml={false}
components={{
code: CodeHighlight
code: CodeHighlight,
p: ({ children }) => <p className="my-2">{children}</p>,
h1: ({ children }) => <h1 className="text-xl font-bold mt-4 mb-2">{children}</h1>,
h2: ({ children }) => <h2 className="text-lg font-bold mt-4 mb-2">{children}</h2>,
h3: ({ children }) => <h3 className="text-base font-bold mt-3 mb-2">{children}</h3>,
h4: ({ children }) => <h4 className="text-base font-semibold mt-3 mb-2">{children}</h4>,
ul: ({ children }) => <ul className="list-disc pl-5 my-2">{children}</ul>,
ol: ({ children }) => <ol className="list-decimal pl-5 my-2">{children}</ol>,
li: ({ children }) => <li className="my-1">{children}</li>
}}
>
{message.content}
@ -70,7 +75,7 @@ export const ChatMessage = ({ message }: { message: MessageWithError }) => {
<CopyIcon className="size-4" /> {/* Explicit size */}
</Button>
)}
</pre>
</div>
{message.content === '' && <LoaderIcon className="animate-spin duration-2000" />} {/* Check for empty string specifically */}
</div>
)
@ -246,7 +251,7 @@ const CodeHighlight = ({ className, children, node, ...props }: CodeHighlightPro
) : (
// Handle inline code
<code
className={cn(className, 'mx-1 rounded-sm bg-muted px-1 py-0.5 text-sm')} // Adjusted styling for inline code
className={cn(className, 'mx-1 rounded-sm bg-muted px-1 py-0.5 font-mono text-sm')} // 添加 font-mono 确保使用等宽字体
{...props}
>
{children}

View File

@ -0,0 +1,272 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ['class'],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px',
},
},
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
keyframes: {
'accordion-down': {
from: { height: 0 },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: 0 },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
typography: {
DEFAULT: {
css: {
maxWidth: '100%',
color: 'var(--tw-prose-body)',
'[class~="lead"]': {
color: 'var(--tw-prose-lead)',
},
a: {
color: 'var(--tw-prose-links)',
textDecoration: 'underline',
fontWeight: '500',
},
strong: {
color: 'var(--tw-prose-bold)',
fontWeight: '600',
},
'ol[type="A"]': {
listStyleType: 'upper-alpha',
},
'ol[type="a"]': {
listStyleType: 'lower-alpha',
},
'ol[type="A" s]': {
listStyleType: 'upper-alpha',
},
'ol[type="a" s]': {
listStyleType: 'lower-alpha',
},
'ol[type="I"]': {
listStyleType: 'upper-roman',
},
'ol[type="i"]': {
listStyleType: 'lower-roman',
},
'ol[type="I" s]': {
listStyleType: 'upper-roman',
},
'ol[type="i" s]': {
listStyleType: 'lower-roman',
},
'ol[type="1"]': {
listStyleType: 'decimal',
},
'ol > li': {
position: 'relative',
paddingLeft: '1.75em',
},
'ol > li::before': {
content: 'counter(list-item, var(--list-counter-style, decimal)) "."',
position: 'absolute',
fontWeight: '400',
color: 'var(--tw-prose-counters)',
left: '0',
},
'ul > li': {
position: 'relative',
paddingLeft: '1.75em',
},
'ul > li::before': {
content: '""',
position: 'absolute',
backgroundColor: 'var(--tw-prose-bullets)',
borderRadius: '50%',
width: '0.375em',
height: '0.375em',
top: 'calc(0.875em - 0.1875em)',
left: '0.25em',
},
hr: {
borderColor: 'var(--tw-prose-hr)',
borderTopWidth: 1,
marginTop: '3em',
marginBottom: '3em',
},
blockquote: {
fontWeight: '500',
fontStyle: 'italic',
color: 'var(--tw-prose-quotes)',
borderLeftWidth: '0.25rem',
borderLeftColor: 'var(--tw-prose-quote-borders)',
quotes: '"\\201C""\\201D""\\2018""\\2019"',
marginTop: '1.6em',
marginBottom: '1.6em',
paddingLeft: '1em',
},
h1: {
color: 'var(--tw-prose-headings)',
fontWeight: '800',
fontSize: '2.25em',
marginTop: '0',
marginBottom: '0.8888889em',
lineHeight: '1.1111111',
},
h2: {
color: 'var(--tw-prose-headings)',
fontWeight: '700',
fontSize: '1.5em',
marginTop: '2em',
marginBottom: '1em',
lineHeight: '1.3333333',
},
h3: {
color: 'var(--tw-prose-headings)',
fontWeight: '600',
fontSize: '1.25em',
marginTop: '1.6em',
marginBottom: '0.6em',
lineHeight: '1.6',
},
h4: {
color: 'var(--tw-prose-headings)',
fontWeight: '600',
marginTop: '1.5em',
marginBottom: '0.5em',
lineHeight: '1.5',
},
'figure > *': {
margin: '0',
},
figcaption: {
color: 'var(--tw-prose-captions)',
fontSize: '0.875em',
lineHeight: '1.4285714',
marginTop: '0.8571429em',
},
code: {
color: 'var(--tw-prose-code)',
fontWeight: '600',
fontSize: '0.875em',
},
'code::before': {
content: '""',
},
'code::after': {
content: '""',
},
'a code': {
color: 'var(--tw-prose-links)',
},
'h1 code': {
color: 'inherit',
},
'h2 code': {
color: 'inherit',
fontSize: '0.875em',
},
'h3 code': {
color: 'inherit',
fontSize: '0.9em',
},
'h4 code': {
color: 'inherit',
},
'blockquote code': {
color: 'inherit',
},
'thead': {
color: 'var(--tw-prose-headings)',
fontWeight: '600',
borderBottomWidth: '1px',
borderBottomColor: 'var(--tw-prose-th-borders)',
},
'thead th': {
verticalAlign: 'bottom',
paddingRight: '0.5714286em',
paddingBottom: '0.5714286em',
paddingLeft: '0.5714286em',
},
'tbody tr': {
borderBottomWidth: '1px',
borderBottomColor: 'var(--tw-prose-td-borders)',
},
'tbody tr:last-child': {
borderBottomWidth: '0',
},
'tbody td': {
verticalAlign: 'baseline',
paddingTop: '0.5714286em',
paddingRight: '0.5714286em',
paddingBottom: '0.5714286em',
paddingLeft: '0.5714286em',
},
p: {
marginTop: '1.25em',
marginBottom: '1.25em',
},
},
},
},
},
},
plugins: [
// Using ES module imports
// Note: This assumes these packages support ES module imports
// If issues occur, may need to fallback to require() and disable ESLint rules
import('tailwindcss-animate').then(module => module.default),
import('@tailwindcss/typography').then(module => module.default),
],
}

View File

@ -37,6 +37,9 @@ export default defineConfig({
// Mermaid-related modules
'mermaid-vendor': ['mermaid'],
// Typography-related modules
'typography-vendor': ['typography'],
// Markdown-related modules
'markdown-vendor': [
'react-markdown',