llm-message-react 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -10
- package/dist/highlighters/shiki.d.cts +1 -1
- package/dist/highlighters/shiki.d.ts +1 -1
- package/dist/highlighters/shikiWeb.d.cts +1 -1
- package/dist/highlighters/shikiWeb.d.ts +1 -1
- package/dist/index.cjs +887 -330
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -8
- package/dist/index.d.ts +35 -8
- package/dist/index.js +893 -336
- package/dist/index.js.map +1 -1
- package/dist/styles.css +43 -0
- package/dist/{types-Dz7GupgB.d.cts → types-DETvxTAd.d.cts} +1 -0
- package/dist/{types-Dz7GupgB.d.ts → types-DETvxTAd.d.ts} +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -14,8 +14,6 @@ A single React component that renders LLM markdown output the way a polished cha
|
|
|
14
14
|
npm install llm-message-react
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
`react` and `react-dom` (>=18) are peer dependencies.
|
|
18
|
-
|
|
19
17
|
## Usage
|
|
20
18
|
|
|
21
19
|
```tsx
|
|
@@ -114,6 +112,38 @@ Notes:
|
|
|
114
112
|
|
|
115
113
|
`LLMMessage` repairs partially-streamed markdown/LaTeX by default, so unterminated tokens (`**bold`, `` `code ``, `[label](http`, `$E = mc^2`, `\[ ... `) don't flash as raw delimiters while a response streams in. Disable it with `completePartialTokens={false}`.
|
|
116
114
|
|
|
115
|
+
### Turn repair off once streaming is done
|
|
116
|
+
|
|
117
|
+
The repair only helps _while_ text is still arriving. On a finished message it can do harm: a single trailing `$` (or a stray `\`) that is really part of the content gets read as the start of an unterminated math token and "completed", so a final string like `Pay $12\at the door` is mistaken for LaTeX and mangled.
|
|
118
|
+
|
|
119
|
+
If you know when a message has finished streaming, gate `completePartialTokens` on that. Keep it `true` while streaming, then flip it to `false` once the stream closes so the final, complete text is rendered verbatim:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { useEffect, useState } from "react";
|
|
123
|
+
import { LLMMessage } from "llm-message-react";
|
|
124
|
+
import "llm-message-react/styles.css";
|
|
125
|
+
|
|
126
|
+
export function StreamedMessage({ messageId }: { messageId: string }) {
|
|
127
|
+
const [content, setContent] = useState("");
|
|
128
|
+
const [isStreaming, setIsStreaming] = useState(true);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const stream = subscribeToMessage(messageId, {
|
|
132
|
+
onToken: (token) => setContent((prev) => prev + token),
|
|
133
|
+
onDone: () => setIsStreaming(false), // stream finished
|
|
134
|
+
});
|
|
135
|
+
return () => stream.close();
|
|
136
|
+
}, [messageId]);
|
|
137
|
+
|
|
138
|
+
// Repair partial tokens while streaming, render verbatim once done.
|
|
139
|
+
return (
|
|
140
|
+
<LLMMessage completePartialTokens={isStreaming}>{content}</LLMMessage>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This way half-streamed tokens are still repaired mid-stream, but the completed message is never "fixed" into something it isn't.
|
|
146
|
+
|
|
117
147
|
### Unfinished block math
|
|
118
148
|
|
|
119
149
|
By default, unterminated **block** math (`\[ ... `, `$$ ... `) is rendered _progressively_: the open environments/braces are closed and the largest prefix KaTeX accepts is shown, so a long aligned block reveals itself row by row instead of popping in only once the closing delimiter arrives.
|
|
@@ -124,15 +154,32 @@ This convenience has a cost: it runs a synchronous KaTeX parse on every streamed
|
|
|
124
154
|
<LLMMessage showUnfinishedLatexBlocks={false}>{content}</LLMMessage>
|
|
125
155
|
```
|
|
126
156
|
|
|
127
|
-
|
|
157
|
+
### Smooth reveal
|
|
128
158
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
159
|
+
By default new text pops in as each chunk arrives. Set `smoothReveal` to fade it in instead — character by character for prose, and as a single unit for complex blocks (code, tables, images, rules). Box decorations (a blockquote's border, list bullets, an inline-code background) fade in together with the content they belong to. Block math (`$$ … $$`, `\[ … \]`) is the one exception: KaTeX only produces output once the whole formula has streamed in, so a fade would just be a flash — instead the block appears instantly the moment the wave reaches it:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
<LLMMessage smoothReveal>{content}</LLMMessage>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The reveal is purely visual: text is always in the DOM the moment it streams in, it just eases up from transparent as a single travelling wave. The wave is sized so it sweeps through the not-yet-revealed text within a short window (`smoothRevealDuration`, default `300` ms). When a new chunk arrives while the previous one is still fading, the leftover (not-yet-revealed) characters and the new ones reveal together over one fresh window, so the animation stays a single coherent wave that keeps pace with the stream instead of many overlapping fades.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<LLMMessage smoothReveal smoothRevealDuration={200}>
|
|
169
|
+
{content}
|
|
170
|
+
</LLMMessage>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Opacity is computed purely from each character's position relative to the reveal point, so it only ever increases (no flicker) regardless of how the stream re-renders, and it is disabled automatically for users who prefer reduced motion.
|
|
174
|
+
|
|
175
|
+
### Block memoization
|
|
176
|
+
|
|
177
|
+
While a long message streams in, each new chunk would otherwise re-parse and re-render the entire message — re-running KaTeX and code highlighting over text that has not changed. By default `LLMMessage` splits the message into top-level markdown blocks (paragraphs, headings, code fences, math blocks, lists, tables, …) and memoizes each one, so only the last (currently growing) block re-renders on each chunk. Earlier blocks stay mounted and untouched, which keeps streaming smooth regardless of message length.
|
|
178
|
+
|
|
179
|
+
This is on by default and needs no configuration. The one trade-off is that constructs which resolve across blocks — footnotes and link reference definitions — cannot be split (a definition in one block could not be seen by a reference in another). Those are detected automatically and kept in a single block, but if you render content that relies on them and want to be certain, disable splitting:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<LLMMessage blockMemoization={false}>{content}</LLMMessage>
|
|
136
183
|
```
|
|
137
184
|
|
|
138
185
|
## Theming
|
|
@@ -209,6 +256,9 @@ import { MyCheckbox, MyCodeBlock, MyLink } from "./ui";
|
|
|
209
256
|
- `highlighter?: CodeHighlighter` — opt-in syntax highlighter for fenced code blocks (see [Syntax highlighting](#syntax-highlighting)). Omitted by default, so no highlighter bundle is pulled in.
|
|
210
257
|
- `completePartialTokens?: boolean` — repair partially-streamed markdown/LaTeX. Defaults to `true`.
|
|
211
258
|
- `showUnfinishedLatexBlocks?: boolean` — progressively render unterminated block math while it streams (costs a synchronous KaTeX parse per chunk); set to `false` to hide unfinished blocks until they close and skip that work. Defaults to `true`. Only relevant while `completePartialTokens` is enabled.
|
|
259
|
+
- `smoothReveal?: boolean` — fade newly-streamed text in (per character for prose, whole-unit for code/tables/images; block math snaps in instantly since it can't fade progressively) instead of popping it in. Purely visual and respects `prefers-reduced-motion`. Defaults to `false`.
|
|
260
|
+
- `smoothRevealDuration?: number` — reveal window in milliseconds for each freshly-arrived chunk. Defaults to `300`. Only relevant while `smoothReveal` is enabled.
|
|
261
|
+
- `blockMemoization?: boolean` — split the message into top-level blocks and memoize each so a streaming chunk only re-renders the last block (see [Block memoization](#block-memoization)). Defaults to `true`.
|
|
212
262
|
- All other `div` props are spread onto the root element.
|
|
213
263
|
|
|
214
264
|
> Pass stable references for `classNames`, `components`, and `highlighter` (define them outside render or memoize them). They are dependencies of an internal `useMemo`, so new object/identity on every render defeats it.
|