这个是测试工具中的一个完成 KPI 任务的功能...
目的是在测试工具中提供一个共享 AI Prompts 的功能,大家可以在这里分享自己好的 prompts,即查即用,抹平使用 chatGPT 帐号、网络等因素。
这里需要解决一个问题就是 AI 接口响应的 mermaid 代码能渲染成对应图表功能:
react 前端都是依葫芦画瓢 (我对 vue 更熟悉),这次遇到很多坑,而且这个平台的 react、antd 版本有点老了,找到合适的组件版本非常费时间。
实现的效果是 ai 返回的 markdown 内容中如果有 mermaid 代码,则提供渲染按钮,点击后渲染出对应的图表.
这个是取巧的做法,另一种做法是直接替换 mermaid 代码块,但这样破坏了 ai 接口响应的内容且不够美观;
1、直接从响应文本中匹配 mermaid 代码的正则很难写的完善:等 markdown 的组件中获取到 mermaid 代码段中的代码则不会有太多的异常情况。
这里用 useState, useEffect 处理等待组件渲染完成处理
2、承接上个处理:markdown 中 code 段有时会有行号,从 code 中取 innerText 需要清理掉行号,所以利用replaceAll(/(?<=^|\n|\r)\d+/g,'')
即可
3、另外 mermaid 的这个 react 版本遇到中文符号会报错,一种方法是把所有文本用""包裹(情况多不好处理),另一种方法就是转换为为没有;结尾 html 实体码,使用利用正则匹配处理即可replaceAll( /\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5/g,
function(m){
return "&#" + m.charCodeAt()
}
)
代码参考(实现了代码高亮及 mermaid 代码提供渲染功能)
import React, { useState, useEffect } from "react";
import {Modal,Button } from 'antd'
import ReactMarkdown from 'react-markdown';
import { toPng } from 'html-to-image';
import remarkGfm from 'remark-gfm';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
import styles from './index.less';
import Mermaid from './Mermaid'
function AiRstComponent({aiRst,onAiRstComponentDone,showCodeLineNum}){
useEffect(() => {
onAiRstComponentDone();
}, []);
return (
<ReactMarkdown
plugins={[remarkGfm]}
className={styles.mdTable}
components={{
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
showLineNumbers={showCodeLineNum}
style={oneDark}
language={match[1]}
PreTag='div'
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{aiRst}
</ReactMarkdown>
)
};
function replaceCnMark(s){
// 针对mermaid报错,替换中文符号为实体码,但没有;号结尾
return s.replaceAll( /\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5/g,
function(m){
return "&#" + m.charCodeAt()
}
)
}
function MarkDown({aiResult,mermaidModalState,handleMaidModalState,handleSaveImg,showCodeLineNum}) {
const [codeStrList, setCodeStrList] = useState(null);
const handleMarkDownMainDone = () => {
// 当ComponentA完成渲染时,将componentADone状态设为true
setMermaidcode();
};
const setMermaidcode= ()=>{
const tmp=[]
const merMaidCodeElems=document.querySelectorAll(".language-mermaid")
if(merMaidCodeElems!==null && merMaidCodeElems.length>0){
for(const codeElm of merMaidCodeElems){
let codeStr=codeElm.innerText
if(showCodeLineNum){
codeStr=codeStr.replaceAll(/(?<=^|\n|\r)\d+/g,'')
}
tmp.push(replaceCnMark(codeStr)) //去除行号
}
setCodeStrList(tmp)
}
}
return (
<div>
<div>
<AiRstComponent
aiRst={aiResult}
onAiRstComponentDone={handleMarkDownMainDone}
showCodeLineNum={showCodeLineNum}
/>
{codeStrList?.length>0?
<a onClick={() => handleMaidModalState(true)}>查看第1个mermaid代码渲染图</a>
:null
}
</div>
<Modal
title="查看mermaid代码渲染图"
width='60%'
className={styles.mermaidChart}
visible={mermaidModalState}
onCancel={() => handleMaidModalState(false)}
footer={[
<Button key="submit" type="primary" onClick={() => handleSaveImg()}>
Save
</Button>,
<Button onClick={() => handleMaidModalState(false)}>
Close
</Button>,
]}
>
<Mermaid chart={codeStrList?.length>0?codeStrList[0]:''} />
</Modal>
</div>
);
}
export default class MarkDownComponent extends React.Component {
state ={
mermaidModalState:false,
showCodeLineNum:true,
}
handleMaidModalState=(state)=>{
this.setState({
mermaidModalState:state,
})
}
handleSaveImg=()=>{ //提供保存图表的方法
toPng(document.querySelector('.mermaid svg')).then(
(dataUrl) => {
const link = document.createElement('a')
link.download = 'mermaid_img.png'
link.href = dataUrl
link.click()
});
}
render() {
return(
<MarkDown
aiResult={this.props.aiResult}
mermaidModalState={this.state.mermaidModalState}
handleMaidModalState={this.handleMaidModalState}
handleSaveImg={this.handleSaveImg}
showCodeLineNum={this.state.showCodeLineNum}
/>
)}
}
另外还有个 Mermaid 组件,直接照抄其官网例子:
import React from "react";
import mermaid from "mermaid";
mermaid.initialize({
startOnLoad: true,
theme: "default",
securityLevel: "loose",
});
export default class Mermaid extends React.Component {
componentDidMount() {
mermaid.contentLoaded();
}
render() {
return <div className="mermaid" style={{textAlign:'center',overflow:'auto'}}>{this.props.chart}</div>;
}
}
调用 AI 接口获取响应内容
渲染 mermaid 图
上面的方法只渲染第 1 个 mermaid 的图,如果能要渲染 markdown 中多个图表,需要优化。
后来想了想,其实可以直接在 markdown 的内容中显示图表。
这个方法其实更加简单,在以下地方加上段代码即可,其他等待组件渲染处理、弹框啊等等代码都可以删掉。
这不过这样原来的 mermaid 代码就不显示,而且生成的图片很大情况下不够美观。
再次优化:
可以上述将 markdown 中 code 中判断 mermaid 后return <Mermaid/>
组件再次封装一个组件,这个组件中提供 mermaid 代码和点击查看其对应图表功能。
这样就更加完美了。