From 0e055b34cad1159bf46178a7d12822f7fade0a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=BB=BA=E6=AD=A6?= Date: Sat, 3 May 2025 02:24:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(MarkdownViewer):=E5=A2=9E=E5=BC=BA=20Markd?= =?UTF-8?q?own=20=E8=A7=A3=E6=9E=90=E5=92=8C=E6=B8=B2=E6=9F=93=E8=83=BD?= =?UTF-8?q?=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加对 GFM (GitHub Flavored Markdown) 的支持 - 增加数学公式渲染功能 - 实现加粗编号标题的特殊处理 - 优化代码块样式 - 添加图片缩放功能 --- BillNote_frontend/package.json | 10 ++- .../HomePage/components/MarkdownViewer.tsx | 78 +++++++++++++++++-- backend/app/gpt/prompt.py | 14 +++- backend/app/routers/note.py | 2 +- 4 files changed, 93 insertions(+), 11 deletions(-) diff --git a/BillNote_frontend/package.json b/BillNote_frontend/package.json index f38bae3..0f3eaa8 100644 --- a/BillNote_frontend/package.json +++ b/BillNote_frontend/package.json @@ -30,19 +30,23 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "github-markdown-css": "^5.8.1", - "katex": "^0.16.21", + "katex": "^0.16.22", "lottie-react": "^2.4.1", "lucide-react": "^0.487.0", + "markdown-navbar": "^1.4.3", "next-themes": "^0.4.6", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hook-form": "^7.55.0", "react-hot-toast": "^2.5.2", - "react-markdown": "^10.1.0", + "react-markdown": "^8.0.7", + "react-medium-image-zoom": "^5.2.14", "react-resizable-panels": "^2.1.8", "react-router-dom": "^7.5.1", "react-syntax-highlighter": "^15.6.1", - "remark-gfm": "1.0.0", + "rehype-katex": "^6.0.2", + "remark-gfm": "3.0.1", + "remark-math": "^5.1.1", "sonner": "^2.0.3", "tailwind-merge": "^3.1.0", "tailwindcss": "^4.1.3", diff --git a/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx b/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx index 8c47e47..0102f40 100644 --- a/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx +++ b/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx @@ -2,16 +2,27 @@ import { useState } from 'react' import ReactMarkdown from 'react-markdown' import { Button } from '@/components/ui/button.tsx' import { Copy, Download, FileText, ArrowRight } from 'lucide-react' -import { toast } from 'sonner' // 你可以换成自己的通知组件 +import { toast } from 'react-hot-toast' // 你可以换成自己的通知组件 import Error from '@/components/Lottie/error.tsx' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' -import { solarizedlight as codeStyle } from 'react-syntax-highlighter/dist/cjs/styles/prism' +import { atomDark as codeStyle } from 'react-syntax-highlighter/dist/esm/styles/prism' +import Zoom from 'react-medium-image-zoom' +import 'react-medium-image-zoom/dist/styles.css' + import 'github-markdown-css/github-markdown-light.css' import { FC } from 'react' import Loading from '@/components/Lottie/Loading.tsx' import Idle from '@/components/Lottie/Idle.tsx' import { useTaskStore } from '@/store/taskStore' import StepBar from '@/pages/HomePage/components/StepBar.tsx' +import gfm from 'remark-gfm' +import remarkMath from 'remark-math' + +import 'katex/dist/katex.min.css' + +import rehypeKatex from 'rehype-katex' +import 'katex/dist/katex.min.css' + interface MarkdownViewerProps { content: string status: 'idle' | 'loading' | 'success' | 'failed' @@ -31,6 +42,8 @@ const MarkdownViewer: FC = ({ content, status }) => { const currentTask = useTaskStore(state => state.getCurrentTask()) const taskStatus = currentTask?.status || 'PENDING' const retryTask = useTaskStore.getState().retryTask + let firstHeadingRendered = false + const handleCopy = async () => { try { await navigator.clipboard.writeText(content) @@ -124,7 +137,58 @@ const MarkdownViewer: FC = ({ content, status }) => {
{' '} ( + + {props.alt + + ), + strong({ node, children, ...props }){ + return {children} + }, + li({ node, children, ...props }) { + const rawText = String(children) + + // 检测是否是“加粗的编号开头项”,比如 "**2. 算法摄影的兴起**" + const isFakeHeading = /^(\*\*.+\*\*)$/.test(rawText.trim()) + + if (isFakeHeading) { + return ( +

+ {children} +

+ ) + } + + return
  • {children}
  • + }, + h1({ node, children, ...props }) { + return ( +

    + {children} +

    + ) + }, + h2({ node, children, ...props }) { + return ( +

    + {children} +

    + ) + }, + code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || '') const codeContent = String(children).replace(/\n$/, '') @@ -133,15 +197,17 @@ const MarkdownViewer: FC = ({ content, status }) => { return (
    {codeContent} +