llm-message-react 0.1.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/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/chunk-S3LNINQ5.js +36 -0
- package/dist/chunk-S3LNINQ5.js.map +1 -0
- package/dist/highlighters/shiki.cjs +42 -0
- package/dist/highlighters/shiki.cjs.map +1 -0
- package/dist/highlighters/shiki.d.cts +14 -0
- package/dist/highlighters/shiki.d.ts +14 -0
- package/dist/highlighters/shiki.js +8 -0
- package/dist/highlighters/shiki.js.map +1 -0
- package/dist/highlighters/shikiWeb.cjs +42 -0
- package/dist/highlighters/shikiWeb.cjs.map +1 -0
- package/dist/highlighters/shikiWeb.d.cts +15 -0
- package/dist/highlighters/shikiWeb.d.ts +15 -0
- package/dist/highlighters/shikiWeb.js +8 -0
- package/dist/highlighters/shikiWeb.js.map +1 -0
- package/dist/index.cjs +746 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +111 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +703 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +309 -0
- package/dist/types-Dz7GupgB.d.cts +133 -0
- package/dist/types-Dz7GupgB.d.ts +133 -0
- package/package.json +103 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TODO: copyright holder
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# llm-message-react
|
|
2
|
+
|
|
3
|
+
A single React component that renders LLM markdown output the way a polished chat UI does:
|
|
4
|
+
|
|
5
|
+
- GitHub Flavored Markdown (tables, task lists, strikethrough, autolinks)
|
|
6
|
+
- Math via KaTeX (inline `$...$` and block `$$...$$`, plus `\(...\)` / `\[...\]`)
|
|
7
|
+
- Code blocks with a language label and a copy button, plus **opt-in** [Shiki](https://shiki.style) syntax highlighting (you choose the bundle, so it tree-shakes away when unused)
|
|
8
|
+
- Streaming-aware: partial markdown/LaTeX tokens are repaired so half-streamed responses don't flash raw delimiter junk
|
|
9
|
+
- A built-in light/dark theme you can opt into, with full per-element class **and** component overrides
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install llm-message-react
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`react` and `react-dom` (>=18) are peer dependencies.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { LLMMessage } from "llm-message-react";
|
|
23
|
+
import "llm-message-react/styles.css"; // built-in theme (optional)
|
|
24
|
+
|
|
25
|
+
export function Message({ content }: { content: string }) {
|
|
26
|
+
return <LLMMessage>{content}</LLMMessage>;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`content` can be passed as children or via the `content` prop:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
<LLMMessage content={content} />
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Dark mode
|
|
37
|
+
|
|
38
|
+
The built-in theme switches automatically when an ancestor has the `dark` class (the convention used by Tailwind / shadcn), or when you put `dark` directly on the component:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<LLMMessage className="dark">{content}</LLMMessage>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Syntax highlighting
|
|
45
|
+
|
|
46
|
+
Highlighting is **off by default**: fenced code blocks still render with a language label and copy button, but the code body is plain text. Because the default path never imports Shiki, it tree-shakes out of your bundle entirely. You opt in by passing a `highlighter`, and you choose which Shiki bundle you pay for.
|
|
47
|
+
|
|
48
|
+
### 1. No highlighting (default)
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { LLMMessage } from "llm-message-react";
|
|
52
|
+
import "llm-message-react/styles.css";
|
|
53
|
+
|
|
54
|
+
<LLMMessage>{content}</LLMMessage>;
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Web bundle (smaller — common web languages)
|
|
58
|
+
|
|
59
|
+
Install `shiki` (an optional peer dependency) and import the ready-made highlighter from the `/shiki-web` subpath:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { LLMMessage } from "llm-message-react";
|
|
63
|
+
import { ShikiWebHighlighter } from "llm-message-react/shiki-web";
|
|
64
|
+
import "llm-message-react/styles.css";
|
|
65
|
+
|
|
66
|
+
<LLMMessage highlighter={ShikiWebHighlighter}>{content}</LLMMessage>;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Full bundle (all languages and themes)
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { LLMMessage } from "llm-message-react";
|
|
73
|
+
import { ShikiHighlighter } from "llm-message-react/shiki";
|
|
74
|
+
import "llm-message-react/styles.css";
|
|
75
|
+
|
|
76
|
+
<LLMMessage highlighter={ShikiHighlighter}>{content}</LLMMessage>;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 4. Custom languages / themes (smallest bundle)
|
|
80
|
+
|
|
81
|
+
Build a minimal Shiki core highlighter with only the grammars and themes you need, then wrap its `codeToHtml` with `createShikiHighlighter`. The factory itself imports no Shiki, so nothing extra is pulled in:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { LLMMessage, createShikiHighlighter } from "llm-message-react";
|
|
85
|
+
import "llm-message-react/styles.css";
|
|
86
|
+
|
|
87
|
+
import { createHighlighterCore } from "shiki/core";
|
|
88
|
+
import { createOnigurumaEngine } from "shiki/engine/oniguruma";
|
|
89
|
+
|
|
90
|
+
const core = await createHighlighterCore({
|
|
91
|
+
langs: [
|
|
92
|
+
import("shiki/langs/typescript.mjs"),
|
|
93
|
+
import("shiki/langs/python.mjs"),
|
|
94
|
+
],
|
|
95
|
+
themes: [
|
|
96
|
+
import("shiki/themes/github-light.mjs"),
|
|
97
|
+
import("shiki/themes/github-dark.mjs"),
|
|
98
|
+
],
|
|
99
|
+
engine: createOnigurumaEngine(import("shiki/wasm")),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const MyHighlighter = createShikiHighlighter(core.codeToHtml);
|
|
103
|
+
|
|
104
|
+
<LLMMessage highlighter={MyHighlighter}>{content}</LLMMessage>;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Notes:
|
|
108
|
+
|
|
109
|
+
- `createShikiHighlighter(codeToHtml, options?)` accepts any function with the signature `(code, { lang, themes: { light, dark } }) => string | Promise<string>`, so a synchronous core `codeToHtml` works too.
|
|
110
|
+
- Themes default to `github-light` / `github-dark`; override them with the second argument: `createShikiHighlighter(fn, { themes: { light: "vitesse-light", dark: "vitesse-dark" } })` (and load the matching themes in your core highlighter).
|
|
111
|
+
- If a streamed language isn't loaded, the highlighter falls back to plain code (same as on any highlight error).
|
|
112
|
+
|
|
113
|
+
## Streaming
|
|
114
|
+
|
|
115
|
+
`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
|
+
|
|
117
|
+
### Unfinished block math
|
|
118
|
+
|
|
119
|
+
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.
|
|
120
|
+
|
|
121
|
+
This convenience has a cost: it runs a synchronous KaTeX parse on every streamed chunk that contains an open block. If you stream many large math blocks and want to avoid that work, set `showUnfinishedLatexBlocks={false}`. Unfinished blocks are then hidden until their closing delimiter arrives (no KaTeX parsing happens for them mid-stream):
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<LLMMessage showUnfinishedLatexBlocks={false}>{content}</LLMMessage>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The repair function is also exported if you need it directly, alongside the LaTeX preprocessing helpers:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import {
|
|
131
|
+
completePartialTokens,
|
|
132
|
+
preprocessLaTeX,
|
|
133
|
+
escapeBrackets,
|
|
134
|
+
escapeMhchem,
|
|
135
|
+
} from "llm-message-react";
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Theming
|
|
139
|
+
|
|
140
|
+
You have three independent ways to control the look, from lightest to fullest:
|
|
141
|
+
|
|
142
|
+
### 1. Built-in theme
|
|
143
|
+
|
|
144
|
+
Import the stylesheet once and you're done:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import "llm-message-react/styles.css";
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
It targets stable semantic class names (`llm-message`, `llm-p`, `llm-code`, `llm-code-block`, `llm-table`, `llm-blockquote`, `llm-a`, `llm-checkbox`, ...) and exposes CSS custom properties you can override:
|
|
151
|
+
|
|
152
|
+
```css
|
|
153
|
+
.llm-message {
|
|
154
|
+
--llm-foreground: #1a1a1a;
|
|
155
|
+
--llm-primary: #2563eb;
|
|
156
|
+
--llm-muted: #f4f4f5;
|
|
157
|
+
--llm-border: #e4e4e7;
|
|
158
|
+
--llm-radius: 0.5rem;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 2. `classNames` — restyle while keeping native elements
|
|
163
|
+
|
|
164
|
+
Pass per-element classes (Tailwind or your own). They are merged with the built-in class names, so you can extend or override:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
<LLMMessage
|
|
168
|
+
className="prose"
|
|
169
|
+
classNames={{
|
|
170
|
+
p: "text-lg",
|
|
171
|
+
codeBlock: "rounded-2xl shadow",
|
|
172
|
+
a: "text-blue-500",
|
|
173
|
+
}}
|
|
174
|
+
>
|
|
175
|
+
{content}
|
|
176
|
+
</LLMMessage>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
If you only use `classNames` / your own CSS, you can skip importing `styles.css` entirely.
|
|
180
|
+
|
|
181
|
+
### 3. `components` — replace the markup entirely
|
|
182
|
+
|
|
183
|
+
Override how any element is rendered with your own component. Everything not overridden falls back to the built-in native renderer.
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { LLMMessage } from "llm-message-react";
|
|
187
|
+
import { MyCheckbox, MyCodeBlock, MyLink } from "./ui";
|
|
188
|
+
|
|
189
|
+
<LLMMessage
|
|
190
|
+
components={{
|
|
191
|
+
checkbox: ({ checked }) => <MyCheckbox checked={checked} />,
|
|
192
|
+
codeBlock: ({ code, language }) => (
|
|
193
|
+
<MyCodeBlock language={language}>{code}</MyCodeBlock>
|
|
194
|
+
),
|
|
195
|
+
a: ({ href, children }) => <MyLink href={href}>{children}</MyLink>,
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
{content}
|
|
199
|
+
</LLMMessage>;
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Props
|
|
203
|
+
|
|
204
|
+
- `children?: string` — the markdown content.
|
|
205
|
+
- `content?: string` — alias for `children`.
|
|
206
|
+
- `className?: string` — class for the root element (merged with `llm-message`).
|
|
207
|
+
- `classNames?: LLMMessageClassNames` — per-element class overrides (look only).
|
|
208
|
+
- `components?: LLMMessageComponents` — per-element component overrides (full markup control).
|
|
209
|
+
- `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
|
+
- `completePartialTokens?: boolean` — repair partially-streamed markdown/LaTeX. Defaults to `true`.
|
|
211
|
+
- `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.
|
|
212
|
+
- All other `div` props are spread onto the root element.
|
|
213
|
+
|
|
214
|
+
> 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.
|
|
215
|
+
|
|
216
|
+
## Known limitations
|
|
217
|
+
|
|
218
|
+
- **Currency vs. inline math with a leading digit.** A `$` directly before a digit is treated as currency and escaped (so `$5` renders literally). This means inline math that starts with a digit, e.g. `$5x$`, is ambiguous and may be escaped rather than rendered as math. Use `\( ... \)` (or `$$ ... $$`) for such expressions.
|
|
219
|
+
- **KaTeX stylesheet import.** `llm-message-react/styles.css` starts with `@import "katex/dist/katex.min.css";`. This resolves automatically with bundlers that handle bare CSS imports (Vite, webpack + css-loader, etc.). If you load the stylesheet via a plain `<link>` instead, import KaTeX's CSS separately.
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/createShikiHighlighter.tsx
|
|
5
|
+
var DEFAULT_THEMES = { light: "github-light", dark: "github-dark" };
|
|
6
|
+
function createShikiHighlighter(codeToHtml, options) {
|
|
7
|
+
const themes = options?.themes ?? DEFAULT_THEMES;
|
|
8
|
+
return function ShikiHighlighter({ code, language, className }) {
|
|
9
|
+
const [html, setHtml] = useState(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
Promise.resolve(codeToHtml(code, { lang: language, themes })).then((result) => {
|
|
13
|
+
if (!cancelled) setHtml(result);
|
|
14
|
+
}).catch(() => {
|
|
15
|
+
if (!cancelled) setHtml(null);
|
|
16
|
+
});
|
|
17
|
+
return () => {
|
|
18
|
+
cancelled = true;
|
|
19
|
+
};
|
|
20
|
+
}, [code, language]);
|
|
21
|
+
if (html) {
|
|
22
|
+
return /* @__PURE__ */ jsx(
|
|
23
|
+
"div",
|
|
24
|
+
{
|
|
25
|
+
className: className ?? "llm-shiki",
|
|
26
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return /* @__PURE__ */ jsx("code", { className: "llm-code-plain", children: code });
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { createShikiHighlighter };
|
|
35
|
+
//# sourceMappingURL=chunk-S3LNINQ5.js.map
|
|
36
|
+
//# sourceMappingURL=chunk-S3LNINQ5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/createShikiHighlighter.tsx"],"names":[],"mappings":";;;;AASA,IAAM,cAAA,GAAiB,EAAE,KAAA,EAAO,cAAA,EAAgB,MAAM,aAAA,EAAc;AAe7D,SAAS,sBAAA,CACd,YACA,OAAA,EACiB;AACjB,EAAA,MAAM,MAAA,GAAS,SAAS,MAAA,IAAU,cAAA;AAElC,EAAA,OAAO,SAAS,gBAAA,CAAiB,EAAE,IAAA,EAAM,QAAA,EAAU,WAAU,EAAG;AAC9D,IAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAwB,IAAI,CAAA;AAEpD,IAAA,SAAA,CAAU,MAAM;AACd,MAAA,IAAI,SAAA,GAAY,KAAA;AAChB,MAAA,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,CAAC,CAAA,CACzD,IAAA,CAAK,CAAC,MAAA,KAAW;AAChB,QAAA,IAAI,CAAC,SAAA,EAAW,OAAA,CAAQ,MAAM,CAAA;AAAA,MAChC,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAEX,QAAA,IAAI,CAAC,SAAA,EAAW,OAAA,CAAQ,IAAI,CAAA;AAAA,MAC9B,CAAC,CAAA;AACH,MAAA,OAAO,MAAM;AACX,QAAA,SAAA,GAAY,IAAA;AAAA,MACd,CAAA;AAAA,IAIF,CAAA,EAAG,CAAC,IAAA,EAAM,QAAQ,CAAC,CAAA;AAEnB,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,uBACE,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,WAAW,SAAA,IAAa,WAAA;AAAA,UAExB,uBAAA,EAAyB,EAAE,MAAA,EAAQ,IAAA;AAAK;AAAA,OAC1C;AAAA,IAEJ;AAEA,IAAA,uBAAO,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gBAAA,EAAkB,QAAA,EAAA,IAAA,EAAK,CAAA;AAAA,EAChD,CAAA;AACF","file":"chunk-S3LNINQ5.js","sourcesContent":["import { useEffect, useState } from \"react\";\n\nimport type { CodeHighlighter, CodeToHtml } from \"./types\";\n\ninterface CreateShikiHighlighterOptions {\n /** Theme pair forwarded to `codeToHtml`. Defaults to GitHub light/dark. */\n themes?: { light: string; dark: string };\n}\n\nconst DEFAULT_THEMES = { light: \"github-light\", dark: \"github-dark\" };\n\n/**\n * Builds a {@link CodeHighlighter} from any `codeToHtml`-compatible function.\n *\n * This factory imports no Shiki bundle itself, so importing it never drags\n * highlighting code into your app. You decide which bundle to pay for by\n * passing the `codeToHtml` from `shiki` (full), `shiki/bundle/web` (web), or a\n * minimal `createHighlighterCore` instance.\n *\n * @example\n * import { codeToHtml } from \"shiki/bundle/web\";\n * const Highlighter = createShikiHighlighter(codeToHtml);\n * <LLMMessage highlighter={Highlighter}>{content}</LLMMessage>\n */\nexport function createShikiHighlighter(\n codeToHtml: CodeToHtml,\n options?: CreateShikiHighlighterOptions,\n): CodeHighlighter {\n const themes = options?.themes ?? DEFAULT_THEMES;\n\n return function ShikiHighlighter({ code, language, className }) {\n const [html, setHtml] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n Promise.resolve(codeToHtml(code, { lang: language, themes }))\n .then((result) => {\n if (!cancelled) setHtml(result);\n })\n .catch(() => {\n // Fallback: if the language is unsupported, just show plain code.\n if (!cancelled) setHtml(null);\n });\n return () => {\n cancelled = true;\n };\n // `codeToHtml` and `themes` are fixed for the lifetime of a highlighter\n // instance (captured from the factory closure), so they are intentionally\n // not listed: they never change between renders of this component.\n }, [code, language]);\n\n if (html) {\n return (\n <div\n className={className ?? \"llm-shiki\"}\n // The HTML is generated by Shiki, which is safe.\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n }\n\n return <code className=\"llm-code-plain\">{code}</code>;\n };\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var shiki = require('shiki');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/highlighters/shiki.tsx
|
|
8
|
+
var DEFAULT_THEMES = { light: "github-light", dark: "github-dark" };
|
|
9
|
+
function createShikiHighlighter(codeToHtml2, options) {
|
|
10
|
+
const themes = DEFAULT_THEMES;
|
|
11
|
+
return function ShikiHighlighter2({ code, language, className }) {
|
|
12
|
+
const [html, setHtml] = react.useState(null);
|
|
13
|
+
react.useEffect(() => {
|
|
14
|
+
let cancelled = false;
|
|
15
|
+
Promise.resolve(codeToHtml2(code, { lang: language, themes })).then((result) => {
|
|
16
|
+
if (!cancelled) setHtml(result);
|
|
17
|
+
}).catch(() => {
|
|
18
|
+
if (!cancelled) setHtml(null);
|
|
19
|
+
});
|
|
20
|
+
return () => {
|
|
21
|
+
cancelled = true;
|
|
22
|
+
};
|
|
23
|
+
}, [code, language]);
|
|
24
|
+
if (html) {
|
|
25
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
26
|
+
"div",
|
|
27
|
+
{
|
|
28
|
+
className: className ?? "llm-shiki",
|
|
29
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return /* @__PURE__ */ jsxRuntime.jsx("code", { className: "llm-code-plain", children: code });
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/highlighters/shiki.tsx
|
|
38
|
+
var ShikiHighlighter = createShikiHighlighter(shiki.codeToHtml);
|
|
39
|
+
|
|
40
|
+
exports.ShikiHighlighter = ShikiHighlighter;
|
|
41
|
+
//# sourceMappingURL=shiki.cjs.map
|
|
42
|
+
//# sourceMappingURL=shiki.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/createShikiHighlighter.tsx","../../src/highlighters/shiki.tsx"],"names":["codeToHtml","ShikiHighlighter","useState","useEffect","jsx"],"mappings":";;;;;;;AASA,IAAM,cAAA,GAAiB,EAAE,KAAA,EAAO,cAAA,EAAgB,MAAM,aAAA,EAAc;AAe7D,SAAS,sBAAA,CACdA,aACA,OAAA,EACiB;AACjB,EAAA,MAAM,MAAA,GAA4B,cAAA;AAElC,EAAA,OAAO,SAASC,iBAAAA,CAAiB,EAAE,IAAA,EAAM,QAAA,EAAU,WAAU,EAAG;AAC9D,IAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIC,eAAwB,IAAI,CAAA;AAEpD,IAAAC,eAAA,CAAU,MAAM;AACd,MAAA,IAAI,SAAA,GAAY,KAAA;AAChB,MAAA,OAAA,CAAQ,OAAA,CAAQH,WAAAA,CAAW,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,CAAC,CAAA,CACzD,IAAA,CAAK,CAAC,MAAA,KAAW;AAChB,QAAA,IAAI,CAAC,SAAA,EAAW,OAAA,CAAQ,MAAM,CAAA;AAAA,MAChC,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAEX,QAAA,IAAI,CAAC,SAAA,EAAW,OAAA,CAAQ,IAAI,CAAA;AAAA,MAC9B,CAAC,CAAA;AACH,MAAA,OAAO,MAAM;AACX,QAAA,SAAA,GAAY,IAAA;AAAA,MACd,CAAA;AAAA,IAIF,CAAA,EAAG,CAAC,IAAA,EAAM,QAAQ,CAAC,CAAA;AAEnB,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,uBACEI,cAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,WAAW,SAAA,IAAa,WAAA;AAAA,UAExB,uBAAA,EAAyB,EAAE,MAAA,EAAQ,IAAA;AAAK;AAAA,OAC1C;AAAA,IAEJ;AAEA,IAAA,uBAAOA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gBAAA,EAAkB,QAAA,EAAA,IAAA,EAAK,CAAA;AAAA,EAChD,CAAA;AACF;;;ACnDO,IAAM,gBAAA,GAAmB,uBAAuBJ,gBAAU","file":"shiki.cjs","sourcesContent":["import { useEffect, useState } from \"react\";\n\nimport type { CodeHighlighter, CodeToHtml } from \"./types\";\n\ninterface CreateShikiHighlighterOptions {\n /** Theme pair forwarded to `codeToHtml`. Defaults to GitHub light/dark. */\n themes?: { light: string; dark: string };\n}\n\nconst DEFAULT_THEMES = { light: \"github-light\", dark: \"github-dark\" };\n\n/**\n * Builds a {@link CodeHighlighter} from any `codeToHtml`-compatible function.\n *\n * This factory imports no Shiki bundle itself, so importing it never drags\n * highlighting code into your app. You decide which bundle to pay for by\n * passing the `codeToHtml` from `shiki` (full), `shiki/bundle/web` (web), or a\n * minimal `createHighlighterCore` instance.\n *\n * @example\n * import { codeToHtml } from \"shiki/bundle/web\";\n * const Highlighter = createShikiHighlighter(codeToHtml);\n * <LLMMessage highlighter={Highlighter}>{content}</LLMMessage>\n */\nexport function createShikiHighlighter(\n codeToHtml: CodeToHtml,\n options?: CreateShikiHighlighterOptions,\n): CodeHighlighter {\n const themes = options?.themes ?? DEFAULT_THEMES;\n\n return function ShikiHighlighter({ code, language, className }) {\n const [html, setHtml] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n Promise.resolve(codeToHtml(code, { lang: language, themes }))\n .then((result) => {\n if (!cancelled) setHtml(result);\n })\n .catch(() => {\n // Fallback: if the language is unsupported, just show plain code.\n if (!cancelled) setHtml(null);\n });\n return () => {\n cancelled = true;\n };\n // `codeToHtml` and `themes` are fixed for the lifetime of a highlighter\n // instance (captured from the factory closure), so they are intentionally\n // not listed: they never change between renders of this component.\n }, [code, language]);\n\n if (html) {\n return (\n <div\n className={className ?? \"llm-shiki\"}\n // The HTML is generated by Shiki, which is safe.\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n }\n\n return <code className=\"llm-code-plain\">{code}</code>;\n };\n}\n","import { codeToHtml } from \"shiki\";\n\nimport { createShikiHighlighter } from \"../createShikiHighlighter\";\n\n/**\n * A ready-to-use highlighter backed by the full Shiki bundle (all languages\n * and themes). Importing this entry opts the full bundle into your build.\n *\n * @example\n * import { ShikiHighlighter } from \"llm-message-react/shiki\";\n * <LLMMessage highlighter={ShikiHighlighter}>{content}</LLMMessage>\n */\nexport const ShikiHighlighter = createShikiHighlighter(codeToHtml);\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { C as CodeHighlighter } from '../types-Dz7GupgB.cjs';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A ready-to-use highlighter backed by the full Shiki bundle (all languages
|
|
6
|
+
* and themes). Importing this entry opts the full bundle into your build.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { ShikiHighlighter } from "llm-message-react/shiki";
|
|
10
|
+
* <LLMMessage highlighter={ShikiHighlighter}>{content}</LLMMessage>
|
|
11
|
+
*/
|
|
12
|
+
declare const ShikiHighlighter: CodeHighlighter;
|
|
13
|
+
|
|
14
|
+
export { ShikiHighlighter };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { C as CodeHighlighter } from '../types-Dz7GupgB.js';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A ready-to-use highlighter backed by the full Shiki bundle (all languages
|
|
6
|
+
* and themes). Importing this entry opts the full bundle into your build.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { ShikiHighlighter } from "llm-message-react/shiki";
|
|
10
|
+
* <LLMMessage highlighter={ShikiHighlighter}>{content}</LLMMessage>
|
|
11
|
+
*/
|
|
12
|
+
declare const ShikiHighlighter: CodeHighlighter;
|
|
13
|
+
|
|
14
|
+
export { ShikiHighlighter };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createShikiHighlighter } from '../chunk-S3LNINQ5.js';
|
|
2
|
+
import { codeToHtml } from 'shiki';
|
|
3
|
+
|
|
4
|
+
var ShikiHighlighter = createShikiHighlighter(codeToHtml);
|
|
5
|
+
|
|
6
|
+
export { ShikiHighlighter };
|
|
7
|
+
//# sourceMappingURL=shiki.js.map
|
|
8
|
+
//# sourceMappingURL=shiki.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/highlighters/shiki.tsx"],"names":[],"mappings":";;;AAYO,IAAM,gBAAA,GAAmB,uBAAuB,UAAU","file":"shiki.js","sourcesContent":["import { codeToHtml } from \"shiki\";\n\nimport { createShikiHighlighter } from \"../createShikiHighlighter\";\n\n/**\n * A ready-to-use highlighter backed by the full Shiki bundle (all languages\n * and themes). Importing this entry opts the full bundle into your build.\n *\n * @example\n * import { ShikiHighlighter } from \"llm-message-react/shiki\";\n * <LLMMessage highlighter={ShikiHighlighter}>{content}</LLMMessage>\n */\nexport const ShikiHighlighter = createShikiHighlighter(codeToHtml);\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var web = require('shiki/bundle/web');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/highlighters/shikiWeb.tsx
|
|
8
|
+
var DEFAULT_THEMES = { light: "github-light", dark: "github-dark" };
|
|
9
|
+
function createShikiHighlighter(codeToHtml2, options) {
|
|
10
|
+
const themes = DEFAULT_THEMES;
|
|
11
|
+
return function ShikiHighlighter({ code, language, className }) {
|
|
12
|
+
const [html, setHtml] = react.useState(null);
|
|
13
|
+
react.useEffect(() => {
|
|
14
|
+
let cancelled = false;
|
|
15
|
+
Promise.resolve(codeToHtml2(code, { lang: language, themes })).then((result) => {
|
|
16
|
+
if (!cancelled) setHtml(result);
|
|
17
|
+
}).catch(() => {
|
|
18
|
+
if (!cancelled) setHtml(null);
|
|
19
|
+
});
|
|
20
|
+
return () => {
|
|
21
|
+
cancelled = true;
|
|
22
|
+
};
|
|
23
|
+
}, [code, language]);
|
|
24
|
+
if (html) {
|
|
25
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
26
|
+
"div",
|
|
27
|
+
{
|
|
28
|
+
className: className ?? "llm-shiki",
|
|
29
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return /* @__PURE__ */ jsxRuntime.jsx("code", { className: "llm-code-plain", children: code });
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/highlighters/shikiWeb.tsx
|
|
38
|
+
var ShikiWebHighlighter = createShikiHighlighter(web.codeToHtml);
|
|
39
|
+
|
|
40
|
+
exports.ShikiWebHighlighter = ShikiWebHighlighter;
|
|
41
|
+
//# sourceMappingURL=shikiWeb.cjs.map
|
|
42
|
+
//# sourceMappingURL=shikiWeb.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/createShikiHighlighter.tsx","../../src/highlighters/shikiWeb.tsx"],"names":["codeToHtml","useState","useEffect","jsx"],"mappings":";;;;;;;AASA,IAAM,cAAA,GAAiB,EAAE,KAAA,EAAO,cAAA,EAAgB,MAAM,aAAA,EAAc;AAe7D,SAAS,sBAAA,CACdA,aACA,OAAA,EACiB;AACjB,EAAA,MAAM,MAAA,GAA4B,cAAA;AAElC,EAAA,OAAO,SAAS,gBAAA,CAAiB,EAAE,IAAA,EAAM,QAAA,EAAU,WAAU,EAAG;AAC9D,IAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIC,eAAwB,IAAI,CAAA;AAEpD,IAAAC,eAAA,CAAU,MAAM;AACd,MAAA,IAAI,SAAA,GAAY,KAAA;AAChB,MAAA,OAAA,CAAQ,OAAA,CAAQF,WAAAA,CAAW,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,CAAC,CAAA,CACzD,IAAA,CAAK,CAAC,MAAA,KAAW;AAChB,QAAA,IAAI,CAAC,SAAA,EAAW,OAAA,CAAQ,MAAM,CAAA;AAAA,MAChC,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAEX,QAAA,IAAI,CAAC,SAAA,EAAW,OAAA,CAAQ,IAAI,CAAA;AAAA,MAC9B,CAAC,CAAA;AACH,MAAA,OAAO,MAAM;AACX,QAAA,SAAA,GAAY,IAAA;AAAA,MACd,CAAA;AAAA,IAIF,CAAA,EAAG,CAAC,IAAA,EAAM,QAAQ,CAAC,CAAA;AAEnB,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,uBACEG,cAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,WAAW,SAAA,IAAa,WAAA;AAAA,UAExB,uBAAA,EAAyB,EAAE,MAAA,EAAQ,IAAA;AAAK;AAAA,OAC1C;AAAA,IAEJ;AAEA,IAAA,uBAAOA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gBAAA,EAAkB,QAAA,EAAA,IAAA,EAAK,CAAA;AAAA,EAChD,CAAA;AACF;;;AClDO,IAAM,mBAAA,GAAsB,uBAAuBH,cAAU","file":"shikiWeb.cjs","sourcesContent":["import { useEffect, useState } from \"react\";\n\nimport type { CodeHighlighter, CodeToHtml } from \"./types\";\n\ninterface CreateShikiHighlighterOptions {\n /** Theme pair forwarded to `codeToHtml`. Defaults to GitHub light/dark. */\n themes?: { light: string; dark: string };\n}\n\nconst DEFAULT_THEMES = { light: \"github-light\", dark: \"github-dark\" };\n\n/**\n * Builds a {@link CodeHighlighter} from any `codeToHtml`-compatible function.\n *\n * This factory imports no Shiki bundle itself, so importing it never drags\n * highlighting code into your app. You decide which bundle to pay for by\n * passing the `codeToHtml` from `shiki` (full), `shiki/bundle/web` (web), or a\n * minimal `createHighlighterCore` instance.\n *\n * @example\n * import { codeToHtml } from \"shiki/bundle/web\";\n * const Highlighter = createShikiHighlighter(codeToHtml);\n * <LLMMessage highlighter={Highlighter}>{content}</LLMMessage>\n */\nexport function createShikiHighlighter(\n codeToHtml: CodeToHtml,\n options?: CreateShikiHighlighterOptions,\n): CodeHighlighter {\n const themes = options?.themes ?? DEFAULT_THEMES;\n\n return function ShikiHighlighter({ code, language, className }) {\n const [html, setHtml] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n Promise.resolve(codeToHtml(code, { lang: language, themes }))\n .then((result) => {\n if (!cancelled) setHtml(result);\n })\n .catch(() => {\n // Fallback: if the language is unsupported, just show plain code.\n if (!cancelled) setHtml(null);\n });\n return () => {\n cancelled = true;\n };\n // `codeToHtml` and `themes` are fixed for the lifetime of a highlighter\n // instance (captured from the factory closure), so they are intentionally\n // not listed: they never change between renders of this component.\n }, [code, language]);\n\n if (html) {\n return (\n <div\n className={className ?? \"llm-shiki\"}\n // The HTML is generated by Shiki, which is safe.\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n }\n\n return <code className=\"llm-code-plain\">{code}</code>;\n };\n}\n","import { codeToHtml } from \"shiki/bundle/web\";\n\nimport { createShikiHighlighter } from \"../createShikiHighlighter\";\n\n/**\n * A ready-to-use highlighter backed by the smaller Shiki \"web\" bundle, which\n * covers the common web languages. Importing this entry opts the web bundle\n * into your build.\n *\n * @example\n * import { ShikiWebHighlighter } from \"llm-message-react/shiki-web\";\n * <LLMMessage highlighter={ShikiWebHighlighter}>{content}</LLMMessage>\n */\nexport const ShikiWebHighlighter = createShikiHighlighter(codeToHtml);\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { C as CodeHighlighter } from '../types-Dz7GupgB.cjs';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A ready-to-use highlighter backed by the smaller Shiki "web" bundle, which
|
|
6
|
+
* covers the common web languages. Importing this entry opts the web bundle
|
|
7
|
+
* into your build.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { ShikiWebHighlighter } from "llm-message-react/shiki-web";
|
|
11
|
+
* <LLMMessage highlighter={ShikiWebHighlighter}>{content}</LLMMessage>
|
|
12
|
+
*/
|
|
13
|
+
declare const ShikiWebHighlighter: CodeHighlighter;
|
|
14
|
+
|
|
15
|
+
export { ShikiWebHighlighter };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { C as CodeHighlighter } from '../types-Dz7GupgB.js';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A ready-to-use highlighter backed by the smaller Shiki "web" bundle, which
|
|
6
|
+
* covers the common web languages. Importing this entry opts the web bundle
|
|
7
|
+
* into your build.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { ShikiWebHighlighter } from "llm-message-react/shiki-web";
|
|
11
|
+
* <LLMMessage highlighter={ShikiWebHighlighter}>{content}</LLMMessage>
|
|
12
|
+
*/
|
|
13
|
+
declare const ShikiWebHighlighter: CodeHighlighter;
|
|
14
|
+
|
|
15
|
+
export { ShikiWebHighlighter };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createShikiHighlighter } from '../chunk-S3LNINQ5.js';
|
|
2
|
+
import { codeToHtml } from 'shiki/bundle/web';
|
|
3
|
+
|
|
4
|
+
var ShikiWebHighlighter = createShikiHighlighter(codeToHtml);
|
|
5
|
+
|
|
6
|
+
export { ShikiWebHighlighter };
|
|
7
|
+
//# sourceMappingURL=shikiWeb.js.map
|
|
8
|
+
//# sourceMappingURL=shikiWeb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/highlighters/shikiWeb.tsx"],"names":[],"mappings":";;;AAaO,IAAM,mBAAA,GAAsB,uBAAuB,UAAU","file":"shikiWeb.js","sourcesContent":["import { codeToHtml } from \"shiki/bundle/web\";\n\nimport { createShikiHighlighter } from \"../createShikiHighlighter\";\n\n/**\n * A ready-to-use highlighter backed by the smaller Shiki \"web\" bundle, which\n * covers the common web languages. Importing this entry opts the web bundle\n * into your build.\n *\n * @example\n * import { ShikiWebHighlighter } from \"llm-message-react/shiki-web\";\n * <LLMMessage highlighter={ShikiWebHighlighter}>{content}</LLMMessage>\n */\nexport const ShikiWebHighlighter = createShikiHighlighter(codeToHtml);\n"]}
|