@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
106 lines (105 loc) • 3.45 kB
JavaScript
"use client";
import { useStreamHighlight } from "../../hooks/useStreamHighlight.mjs";
import { createTokenFadeStore, markTokenBirths, resolveTokenFadeStyle } from "./tokenFade.mjs";
import { memo, useRef } from "react";
import { jsx } from "react/jsx-runtime";
import { cx } from "antd-style";
import { getTokenStyleObject } from "@shikijs/core";
//#region src/Highlighter/SyntaxHighlighter/StreamRenderer.tsx
const normalizeStyleKeys = (style) => {
const normalized = {};
Object.entries(style).forEach(([key, value]) => {
const normalizedKey = key.replaceAll(/-([a-z])/g, (_, char) => char.toUpperCase());
normalized[normalizedKey] = value;
});
return normalized;
};
const getTokenInlineStyle = (token) => {
const rawStyle = token.htmlStyle || getTokenStyleObject(token);
return {
...normalizeStyleKeys(rawStyle),
whiteSpace: "pre"
};
};
const TokenSpan = memo(({ fadeStyle, token }) => {
const style = fadeStyle ? {
...getTokenInlineStyle(token),
...fadeStyle
} : getTokenInlineStyle(token);
return /* @__PURE__ */ jsx("span", {
className: fadeStyle ? "stream-char" : void 0,
style,
children: token.content
});
}, (prev, next) => prev.token === next.token && prev.fadeStyle === next.fadeStyle);
const TokenLine = memo(({ fade, line, now, start }) => {
if (!line.length) return /* @__PURE__ */ jsx("span", {
className: "line",
children: /* @__PURE__ */ jsx("span", {
style: { whiteSpace: "pre" },
children: "\xA0"
})
});
let offset = start;
return /* @__PURE__ */ jsx("span", {
className: "line",
children: line.map((token) => {
const tokenOffset = offset;
offset += token.content.length;
return /* @__PURE__ */ jsx(TokenSpan, {
fadeStyle: resolveTokenFadeStyle(fade, tokenOffset, now),
token
}, tokenOffset);
})
});
}, (prev, next) => prev.line === next.line && prev.start === next.start);
const StreamRenderer = memo(({ children, className, enableTransformer, fallbackClassName, language, style, theme }) => {
const safeChildren = children ?? "";
const streaming = useStreamHighlight(safeChildren, {
enableTransformer,
language,
streaming: true,
theme
});
const lines = streaming?.lines;
const preStyle = streaming?.preStyle;
const fadeRef = useRef(createTokenFadeStore());
const previousTextRef = useRef("");
if (!safeChildren.startsWith(previousTextRef.current)) fadeRef.current = createTokenFadeStore();
previousTextRef.current = safeChildren;
const now = performance.now();
const lineStarts = lines ? markTokenBirths(fadeRef.current, lines, now) : [];
if (!lines || lines.length === 0) return /* @__PURE__ */ jsx("div", {
className: fallbackClassName,
dir: "ltr",
style,
children: /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: safeChildren }) })
});
return /* @__PURE__ */ jsx("div", {
className,
dir: "ltr",
style,
children: /* @__PURE__ */ jsx("pre", {
className: cx("shiki", theme),
style: preStyle,
tabIndex: 0,
children: /* @__PURE__ */ jsx("code", {
style: {
display: "flex",
flexDirection: "column",
whiteSpace: "pre"
},
children: lines.map((line, index) => /* @__PURE__ */ jsx(TokenLine, {
fade: fadeRef.current,
line,
now,
start: lineStarts[index]
}, `line-${index}`))
})
})
});
});
StreamRenderer.displayName = "StreamRenderer";
//#endregion
export { StreamRenderer as default };
//# sourceMappingURL=StreamRenderer.mjs.map