开发日志没啥先后顺序,想到哪个就先记录哪个,主要目的是为了给对这个东西感兴趣的伙伴分享心得,供我和大家一起参考,内容不一定是最优解,但能跑(要不然那个网页也用不了)。
开发日志应该都会开启评论,欢迎看到的小伙伴参与讨论吧。
下面记录的ai聊天功能是基于通义千问的模型搭建,如果您使用的是其他品牌,信息的收发以及API调用部分可能会有差别,还请留意。
1 仅渲染页面固有的公式
在没有添加LaTeX数学公式渲染库之前,AI的回复内容会显示成这样:
$x^2 + y^2 = z^2$
目前最流行且轻量级的选择是 MathJax,它可以在浏览器中动态将LaTeX代码转换为美观的数学公式,一般渲染网站已经存在的公式很简单,只需要在网站的HTML加入MathJax的js库然后在body标签里面调用就可以了,大概的结构就是:
在HTML文件的<head>标签中添加以下代码,即可完成配置。
<!-- 引入KaTeX核心CSS样式 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css">
<!-- 引入KaTeX核心JS库 -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js"></script>
<!-- 引入自动渲染扩展 -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js"></script>
然后在<body>标签的任意位置插入下面的代码进行初始化与配置,公式分隔符参考设置如下(设置 display: true 表示独立成行的块级公式,display: false则为行内公式):
<script>
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.body, {
delimiters: [
{left: '$$', right: '$$', display: true}, // 例如:$$ E = mc^2 $$
{left: '\\[', right: '\\]', display: true}, // 例如:\[ F = ma \]
{left: '\\(', right: '\\)', display: false}, // 例如:\( E = mc^2 \)
{left: '$', right: '$', display: false} // 注意:可能与美元符号冲突
],
throwOnError: false // 设为false可在公式错误时显示错误信息,避免页面崩溃[reference:6]
});
});
</script>
这样子整个页面的LaTeX代码都会变成好康的公式了,不过这不是重点。
2 将AI的生成的LaTeX代码转换成公式
由于AI生成的内容是外来接入的,第1节中的代码没法对中途产生的内容进行转换,以我们雨之庭的aiChat为例,只好将上面的内容插入到它的script.js里面去,并且小修一下js里的文本处理程序。
2.1 动态加载 MathJax 数学公式库
将第1节中的代码整合改成下列形式,放到js的开头。
// --- 动态加载 MathJax 数学公式库 ---
(function () {
if (window.MathJax) return;
const script = document.createElement('script');
script.src = 'https://polyfill.io/v3/polyfill.min.js?features=es6';
script.async = true;
document.head.appendChild(script);
const script2 = document.createElement('script');
script2.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
script2.async = true;
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']]
},
options: { skipHtmlTags: ['script', 'noscript', 'style', 'textarea'] }
};
document.head.appendChild(script2);
})();
2.2 定义进行公式渲染的函数
这个是执行渲染的核心,位置倒是能随便摆,不过一般放在2.3的两个函数之后。
// --- 新增:MathJax 渲染函数 ---
function renderMathInElement(element) {
if (window.MathJax && window.MathJax.typeset) {
try {
window.MathJax.typesetClear();
window.MathJax.typeset([element]); // 渲染这个特定元素
} catch (err) {
console.warn('MathJax 渲染失败:', err);
}
} else {
// 如果库还没加载好,稍微等一下再试
setTimeout(() => renderMathInElement(element), 100);
}
}
2.3 修改 displayMessage 和 typeWriterEffect 函数
按理来讲,AI应该在生成一条公式的代码后,无论后面是否有内容生成,都应该先将这个公式代码进行渲染,但是搞这个又要多加一堆条件,什么异步与同步的,索性不干了,因此,我是将这个显示AI信息的函数displayMessage的最底部的 if (role === 'bot') 里加入一个回调函数,这样子的话,就只有当AI结果生成结束后,才会进行公式的渲染(虽然过程不好看,但是结果好看就行了)。
if (role === 'bot') {
// 👇 修改这里:清空内容,并在打字机完成后触发公式渲染
messageContent.innerHTML = ''; typeWriterEffect(messageContent, message, () => { renderMathInElement(messageContent); // 打字完成后渲染公式 }); }
然后是typeWriterEffect函数,这是处理打字机特效的,主要关注要修改的部分,就是这一块运行结束后callback执行renderMathInElement函数,这样就能完成AI生成内容里的公式渲染了。
// 3. 打字机效果核心函数
function typeWriterEffect(element, text, callback) { // 👈 增加 callback 参数
...
...
function type() {
if (i < text.length) {
element.innerHTML = formatMessage(text.substring(0, i + 1)); i++; setTimeout(type, speed); element.scrollIntoView({ behavior: 'smooth', block: 'end' });
} else {
// 👇 增加这里:打字结束后,执行回调函数
if (callback) callback(); } } type(); }
3 结果展示
来看看效果,随便写个让ai会冒出公式的问题,在ai生成内容的过程中,公式未经过渲染。

内容全部生成后,如下图所示,大功告成。






