在与大模型交户的过程中,一般采用sse的方式与后端交互,返回流式字符串,前端需要处理渲染这些内容,在使用markdown渲染这些内容时,一般会存在以下问题:分片不完整、字符截断、语法中途断裂、增量刷新闪烁、代码块 / 表格 / 公式渲染异常、安全风险。如果语法不完整,markdown会渲染成普通文本,并不会报错,后续内容补全后会正常渲染,但是会出现内容闪烁问题。那么如何解决呢,一般会采用全量缓存+分片渲染的方案。
演示环境是用的marked.js渲染markdown
先看一段演示

上面的示例中,官方开源仓库的超链接和项目预览图在没有输出完全的是当成普通文本渲染,还有下面核心功能参数的表格,在表格头没有输出完的时候也是当成普通文本。这种情况会出现内容闪烁的问题,从普通文本编程器超链接或者图片。如何解决这个问题呢,一般会采用内容占位来解决,如果当前chunk不能渲染成有效内容时,会采用其他内容来占位(图片或者其他的html内容均可)
优化后效果如下:

对项目预览图和表格用图片进行了占位,等有内容可以渲染后会正常渲染原来的内容。
部分js如下
javascript
function handleIncompleteMarkdown (cache) {
const { token, pending } = cache;
// 如果当前是普通文本,不需要特殊处理
if (token === StreamCacheTokenType.Text) return;
/**
* 场景1:图片标签以 '!' 开头,如果当前只接收到了一个 '!',说明是不完整的,应当直接剥离不渲染。
* !
* ^
*/
if (token === StreamCacheTokenType.Image && pending === '!') return undefined;
/**
* 场景2:表格如果已经有两行以上(包含表头和分隔线如 | -- | --|\n),则认为它足够完整,
* 不会被替换为占位符,可以正常渲染出表格雏形。
* | column1 | column2 |\n| -- | --|\n
* ^
*/
if (token === StreamCacheTokenType.Table && pending.split('\n').length > 2) {
return pending;
}
// 对于其他未完成的语法,尝试映射为自定义组件
const componentMap = _config.incompleteMarkdownComponentMap || {};
// 获取映射的组件名,如果没有配置映射,则使用默认的 incomplete-{tokenType} 格式
const componentName = componentMap[token] || `incomplete-${token}`;
// 对未完成的内容进行 URI 编码,防止其中的引号等特殊字符破坏 HTML 标签属性
const encodedPending = safeEncodeURIComponent(pending);
// 如果开发者提供了对应的自定义组件,则渲染该组件,并将原始未完成内容通过 data-raw 传递进去
return _config.components[componentName] ? _config.components[componentName] : undefined; // 如果没有提供组件,则返回 undefined,意味着这部分未完成内容将被直接截断不显示
}
jsconst incomplete_map = {
'incomplete-image': '<incomplete-image></incomplete-image>',
'incomplete-table': '<incomplete-table></incomplete-table>',
'xmd-tail': `<span className="xmd-tail">▋</span>`,
}
<incomplete-image></incomplete-image>这些都是自定义的html元素标签


本文作者:繁星
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!