在React中利用markdown-it处理大模型流式输出的代码和公式

221 阅读1分钟

使用的包

markdown-it highlight.js katex @mdit/plugin-katex clipboard

相关代码

import MarkdownIt from 'markdown-it';
import hljs from 'highlight.js';
import 'katex/dist/katex.min.css'; // 导入KaTeX的样式
import { useEffect } from 'react';
import Clipboard from 'clipboard'
import { katex } from "@mdit/plugin-katex";

const copySvg = `<svg width="13px" height="12px" viewBox="0 0 13 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <g id=" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="" transform="translate(-82.000000, -298.000000)" fill="#898989">
            <g id="" transform="translate(82.333221, 298.000000)">
                <path d="M7.895,9.789 C7.895,9.527 8.107,9.315 8.369,9.315 C8.63,9.315 8.842,9.527 8.842,9.789 L8.842,10.894 C8.842,11.505 8.348,12 7.737,12 L1.106,12 C0.495,12 0,11.505 0,10.894 L0,4.263 C0,3.652 0.495,3.157 1.106,3.157 L2.211,3.157 C2.472,3.157 2.684,3.37 2.684,3.631 C2.684,3.893 2.472,4.105 2.211,4.105 L1.106,4.105 C1.018,4.105 0.948,4.176 0.948,4.263 L0.948,10.894 C0.948,10.981 1.018,11.052 1.106,11.052 L7.737,11.052 C7.824,11.052 7.895,10.981 7.895,10.894 L7.895,9.789 Z M4.263,0.947 C4.176,0.947 4.106,1.018 4.106,1.105 L4.106,7.736 C4.106,7.824 4.176,7.894 4.263,7.894 L10.895,7.894 C10.982,7.894 11.053,7.824 11.053,7.736 L11.053,1.105 C11.053,1.018 10.982,0.947 10.895,0.947 L4.263,0.947 Z M4.263,0 L10.895,0 C11.505,0 12,0.494 12,1.105 L12,7.736 C12,8.347 11.505,8.842 10.895,8.842 L4.263,8.842 C3.653,8.842 3.158,8.347 3.158,7.736 L3.158,1.105 C3.158,0.494 3.653,0 4.263,0 L4.263,0 Z" id="Fill-1"></path>
            </g>
        </g>
    </g>
</svg>`

const md = new MarkdownIt({
  html: true,
  linkify: true,
  typographer: true,
  highlight: function (str, lang) {
    const codeIndex = Date.now() + Math.floor(Math.random() * 10000000);
    let headerHtml = `<div class="code-header">`;
    if (lang) {
      headerHtml += `<span class="lang-name">${lang}</span>`;
    }
    headerHtml += `<button class="copy-btn" type="button" data-clipboard-action="copy" data-clipboard-target="#copy${codeIndex}"><span>${copySvg}</span>复制代码</button></div>`;

    let preCode = '';
    if (lang && hljs.getLanguage(lang)) {
      try {
        // 使用新的 highlight.js API
        preCode = hljs.highlight(str, { language: lang, ignoreIllegals: true }).value;
      } catch (error) {
        console.error('代码高亮失败:', error);
        preCode = md.utils.escapeHtml(str);
      }
    } else {
      preCode = md.utils.escapeHtml(str);
    }

    return `<div class="code-container">${headerHtml}<pre class="hljs"><code>${preCode}</code></pre><textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy${codeIndex}">${str.replace(/<\/textarea>/g, '&lt;/textarea>')}</textarea></div>`;
  }
}).use(katex, { delimiters: 'all' });

const MarkdownRenderer: React.FC<{ text: string }> = ({ text }) => {
  useEffect(() => {
    const clipboard = new Clipboard('.copy-btn');

    clipboard.on('success', (e) => {
      console.log('复制成功:', e.text);
      e.clearSelection();
    });

    clipboard.on('error', (e) => {
      console.error('复制失败');
    });

    return () => {
      clipboard.destroy();
    };
  }, [text]);

  return (
    <div dangerouslySetInnerHTML={{__html: md.render(text)}} />
  );
};

export default MarkdownRenderer;

代码高亮样式

highlight.js/src/styles …

参考

markdown-it代码块渲染、自定义行号、复制代码功能之前写过一篇关于代码块渲染添加自定义行号的文章:markdow - 掘金

@mdit/plugin-katex | Markdown It 插件