💄 feat(playground): Enhance the fade-in animation for the chat

This commit is contained in:
Apple\Apple
2025-06-02 20:15:00 +08:00
parent bafb0078e2
commit 3a5013b876
4 changed files with 95 additions and 8 deletions

View File

@@ -4,8 +4,12 @@ import { visit } from 'unist-util-visit';
* rehype 插件:将段落等文本节点拆分为逐词 <span>,并添加淡入动画 class。
* 仅在流式渲染阶段使用,避免已渲染文字重复动画。
*/
export function rehypeSplitWordsIntoSpans() {
export function rehypeSplitWordsIntoSpans(options = {}) {
const { previousContentLength = 0 } = options;
return (tree) => {
let currentCharCount = 0; // 当前已处理的字符数
visit(tree, 'element', (node) => {
if (
['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'strong'].includes(node.tagName) &&
@@ -18,22 +22,49 @@ export function rehypeSplitWordsIntoSpans() {
// 使用 Intl.Segmenter 精准拆分中英文及标点
const segmenter = new Intl.Segmenter('zh', { granularity: 'word' });
const segments = segmenter.segment(child.value);
Array.from(segments)
.map((seg) => seg.segment)
.filter(Boolean)
.forEach((word) => {
const wordStartPos = currentCharCount;
const wordEndPos = currentCharCount + word.length;
// 判断这个词是否是新增的(在 previousContentLength 之后)
const isNewContent = wordStartPos >= previousContentLength;
newChildren.push({
type: 'element',
tagName: 'span',
properties: {
className: ['animate-fade-in'],
className: isNewContent ? ['animate-fade-in'] : [],
},
children: [{ type: 'text', value: word }],
});
currentCharCount = wordEndPos;
});
} catch (_) {
// Fallback如果浏览器不支持 Segmenter,直接输出原文本
newChildren.push(child);
// Fallback如果浏览器不支持 Segmenter
const textStartPos = currentCharCount;
const isNewContent = textStartPos >= previousContentLength;
if (isNewContent) {
// 新内容,添加动画
newChildren.push({
type: 'element',
tagName: 'span',
properties: {
className: ['animate-fade-in'],
},
children: [{ type: 'text', value: child.value }],
});
} else {
// 旧内容,不添加动画
newChildren.push(child);
}
currentCharCount += child.value.length;
}
} else {
newChildren.push(child);