基于react-markdown实现对大模型输出展示——2:自定义标签及事件触发

背景

上一篇文章 我们基于 react-markdown 并引用 remark-gfm rehype-raw github-markdown-css来处理复杂md格式,处理html标签 和美化样式,本篇章我们来实现自定义标签和事件触发

场景

举例:例如后端跟我们约定,他们流式返回数据,我们接到后拼装展示,但是有一些特殊标识,比如说引用,类似于参考文献,一般是右上角上标,点击会跳转详情或者其他操作

这里我们就需要,自定义标签,并需要触发事件,暂定 我们约定 后端返回的数据特殊格式如下, 第一个是引用的顺序,第二个是引用的id

js 复制代码
[[1,'引用1的id']]

我们需要判定此格式,转为 sup标签,并自定义样式,点击的时候,需要将id传过去,触发事件

实现方案

1:原始效果及展示

以上规则,demo代码及展示如下

js 复制代码
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import "github-markdown-css"

const richMarkdownContent = `
# 一级标题:Markdown 丰富示例 [[1,'我是1的id']]

## 二级标题:Markdown 丰富示例 [[1,'我是1的id']]

### 三级标题:Markdown 丰富示例 [[1,'我是1的id']]
`;

const App = () => {
  return (
    <div className="markdown-body">
      <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
        {richMarkdownContent}
      </ReactMarkdown>
    </div>
  );
};

export default App;

效果:

如果我们不处理,则按照字符串展示

现在我们按需截取特殊约定字符

2: 正则截取

正则截取,我们将顺序回显,id作为自定义属性添加到元素上,借助rehype-raw渲染出来自定义的标签,注意自定义属性data-supid

js 复制代码
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import "github-markdown-css"

const richMarkdownContent = `
# 一级标题:Markdown 丰富示例 [[1,'我是1的id']]

## 二级标题:Markdown 丰富示例 [[1,'我是1的id']]

### 三级标题:Markdown 丰富示例 [[1,'我是1的id']]
`;

const replaceReferences = (str)=> {
  // 定义正则表达式
  const regex = /\[\[(\d+),'(.*?)'\]\]/g;

  // 使用 replace 方法进行全局替换
  return str.replace(regex, (match, num, id) => {
      return `<sup className="text-active cursor-pointer" data-supid="${id}">[${num}]</sup>`;
  });
}

const regStr = replaceReferences(richMarkdownContent)

const App = () => {
  return (
    <div className="markdown-body">
      <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
        {regStr}
      </ReactMarkdown>
    </div>
  );
};

export default App;

此时效果如下,

3:美化样式

对于创建的元素,可以使用tailwindcss 修改样式,或者其他自定义样式均可,和普通html样式样式无异

js 复制代码
const replaceReferences = (str)=> {
  // 定义正则表达式
  const regex = /\[\[(\d+),'(.*?)'\]\]/g;

  // 使用 replace 方法进行全局替换
  return str.replace(regex, (match, num, id) => {
      return `<sup className="text-blue-600 cursor-pointer" data-supid="${id}">[${num}]</sup>`;
  });
}

注意text-blue-600颜色设置,现在效果展示如下

4:自定义事件触发

借助react-markdown的 components来自定义标签

js 复制代码
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import "github-markdown-css"

const richMarkdownContent = `
# 一级标题:Markdown 丰富示例 [[1,'我是1的id']]

## 二级标题:Markdown 丰富示例 [[2,'我是2的id']]

### 三级标题:Markdown 丰富示例 [[3,'我是3的id']]
`;

const replaceReferences = (str) => {
  // 定义正则表达式
  const regex = /\[\[(\d+),'(.*?)'\]\]/g;

  // 使用 replace 方法进行全局替换
  return str.replace(regex, (match, num, id) => {
    return `<sup className="text-blue-600 cursor-pointer" data-supid="${id}">[${num}]</sup>`;
  });
}

const regStr = replaceReferences(richMarkdownContent)

// 自定义渲染器
const components = {
  sup: ({ children, ...rest }) => {
    return (
      <sup className="text-active" onClick={(event) => handleSupClick(event)} {...rest}>
        {children}
      </sup>
    );
  },
};

// 点击事件处理函数
const handleSupClick = (event) => {
  const supid = event.target.dataset.supid;
  console.log("Clicked sup data-supid:", supid);
  // 你可以在这里进行其他操作,比如将内容传递给父组件等
};

const App = () => {
  return (
    <div className="markdown-body">
      <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} components={components}>
        {regStr}
      </ReactMarkdown>
    </div>
  );
};

export default App;
  1. components 对象中的 sup 组件:

    • 在 onClick 事件处理函数中,将事件对象 event 传递给 handleSupClick 函数。
    • 使用扩展运算符 ...rest 传递其他属性,确保 data-supid 属性被正确传递。
  2. handleSupClick 函数:

    • 通过 event.target.dataset.supid 获取自定义属性 data-supid 的值。

    • 打印获取到的值,你可以在这里进行其他操作,比如将内容传递给父组件等。

这样,当你点击 sup 元素时,就可以获取到自定义属性 data-supid 的值了。

此时的展示及展示效果如下

总结

这篇文章 我们基于自定义的sup标签,借助自定义components,结合正则来实现了文章前期的需求,可以发现功能很强大,对于多数其他标签有更特殊的需求,可以自己来实现,一家之言,有需要的按需取舍,你也可以下载项目项目地址 dev2分支

未完待续。。。

相关推荐
用户270507772524几秒前
MCP 最小 Server 实测:3 个 JSON-RPC 报错与修法
前端
用户499335025073 分钟前
一人VIP账号多人共用:Web 代理里的会话共享怎么实现
前端
用户老李4 分钟前
无线电自学教程 05:第一台收音机——从矿石机到 SDR
前端
IPIPD11 分钟前
Node.js请求遇到503怎么重试?先解析Retry-After,再做有上限退避
前端
葡萄城技术团队18 分钟前
dsh-spreadjs-editor 的集成链路:把 SpreadJS Designer 嵌进 Agent 工作台
前端
FlyWIHTSKY1 小时前
vue2和vue3有什么区别,用法上有哪些区别
前端·javascript·vue.js
码云之上1 小时前
10 颗一模一样的「Edit」:中后台 Web Agent 怎么找到对的那一行
前端·人工智能·浏览器
量化分析码农2 小时前
【Python量化多因子实战 04】因子暴露不是 alpha:市值与行业中性化实战
前端·后端
不可能片场2 小时前
Electron 发布 正文写入的三条通道与读回口径
前端·electron
程序员大阳2 小时前
副队长JavaScript教程(66)-本地存储实战:待办事项网页的开发
javascript·json·实战·待办事项·本地存储·案例