@ai-react-markdown/core 1.0.0 → 1.0.1

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/dist/index.js CHANGED
@@ -21,18 +21,18 @@ var AIMarkdownRenderDisplayOptimizeAbility = /* @__PURE__ */ ((AIMarkdownRenderD
21
21
  AIMarkdownRenderDisplayOptimizeAbility2["PANGU"] = "PANGU";
22
22
  return AIMarkdownRenderDisplayOptimizeAbility2;
23
23
  })(AIMarkdownRenderDisplayOptimizeAbility || {});
24
- var defaultMRMarkdownRenderConfig = {
25
- extraSyntaxSupported: [
24
+ var defaultAIMarkdownRenderConfig = Object.freeze({
25
+ extraSyntaxSupported: Object.freeze([
26
26
  "HIGHLIGHT" /* HIGHLIGHT */,
27
27
  "DEFINITION_LIST" /* DEFINITION_LIST */,
28
28
  "SUBSCRIPT" /* SUBSCRIPT */
29
- ],
30
- displayOptimizeAbilities: [
29
+ ]),
30
+ displayOptimizeAbilities: Object.freeze([
31
31
  "REMOVE_COMMENTS" /* REMOVE_COMMENTS */,
32
32
  "SMARTYPANTS" /* SMARTYPANTS */,
33
33
  "PANGU" /* PANGU */
34
- ]
35
- };
34
+ ])
35
+ });
36
36
 
37
37
  // src/context.tsx
38
38
  import { jsx } from "react/jsx-runtime";
@@ -57,11 +57,11 @@ var AIMarkdownRenderStateProvider = ({
57
57
  children
58
58
  }) => {
59
59
  const mergedConfig = useMemo(
60
- () => config ? mergeWith(cloneDeep(defaultMRMarkdownRenderConfig), config, configMergeCustomizer) : defaultMRMarkdownRenderConfig,
60
+ () => config ? mergeWith(cloneDeep(defaultAIMarkdownRenderConfig), config, configMergeCustomizer) : defaultAIMarkdownRenderConfig,
61
61
  [config]
62
62
  );
63
63
  const state = useMemo(
64
- () => ({
64
+ () => Object.freeze({
65
65
  streaming,
66
66
  fontSize,
67
67
  config: mergedConfig,
@@ -74,21 +74,27 @@ var AIMarkdownRenderStateProvider = ({
74
74
  var context_default = AIMarkdownRenderStateProvider;
75
75
 
76
76
  // src/preprocessors/latex.ts
77
- function escapeMhchemCommands(text) {
78
- return text.replaceAll("$\\ce{", "$\\\\ce{").replaceAll("$\\pu{", "$\\\\pu{");
79
- }
80
- function findCodeBlockRegions(content) {
81
- const regions = [];
77
+ function splitByProtectedRegions(content) {
78
+ const segments = [];
79
+ let lastIndex = 0;
82
80
  let inlineStart = -1;
83
81
  let multilineStart = -1;
82
+ function pushProtected(start, end) {
83
+ if (start > lastIndex) {
84
+ segments.push({ text: content.substring(lastIndex, start), isCode: false });
85
+ }
86
+ segments.push({ text: content.substring(start, end), isCode: true });
87
+ lastIndex = end;
88
+ }
84
89
  for (let i = 0; i < content.length; i++) {
85
90
  const char = content[i];
86
91
  if (char === "`" && i + 2 < content.length && content[i + 1] === "`" && content[i + 2] === "`") {
87
92
  if (multilineStart === -1) {
93
+ inlineStart = -1;
88
94
  multilineStart = i;
89
95
  i += 2;
90
96
  } else {
91
- regions.push([multilineStart, i + 2]);
97
+ pushProtected(multilineStart, i + 3);
92
98
  multilineStart = -1;
93
99
  i += 2;
94
100
  }
@@ -96,43 +102,71 @@ function findCodeBlockRegions(content) {
96
102
  if (inlineStart === -1) {
97
103
  inlineStart = i;
98
104
  } else {
99
- regions.push([inlineStart, i]);
105
+ pushProtected(inlineStart, i + 1);
100
106
  inlineStart = -1;
101
107
  }
108
+ } else if (char === "<" && multilineStart === -1 && inlineStart === -1) {
109
+ const rest = content.substring(i);
110
+ const tagMatch = rest.match(
111
+ /^<\/?(span|div|p|br|hr|img|a|em|strong|b|i|u|s|sub|sup|code|pre|table|tr|td|th|thead|tbody|tfoot|ul|ol|li|dl|dt|dd|h[1-6]|blockquote|details|summary|figure|figcaption|section|article|aside|nav|header|footer|main|mark|del|ins|small|abbr|cite|dfn|kbd|samp|var|ruby|rt|rp|bdo|wbr|input|button|select|textarea|label|fieldset|legend|output|iframe|video|audio|source|canvas|svg|math|time)(?:\s[^>]*)?\/?>/i
112
+ );
113
+ if (tagMatch) {
114
+ pushProtected(i, i + tagMatch[0].length);
115
+ i += tagMatch[0].length - 1;
116
+ }
102
117
  }
103
118
  }
104
- return regions;
105
- }
106
- function isInCodeBlock(position, codeRegions) {
107
- let left = 0;
108
- let right = codeRegions.length - 1;
109
- while (left <= right) {
110
- const mid = Math.floor((left + right) / 2);
111
- const [start, end] = codeRegions[mid];
112
- if (position >= start && position <= end) {
113
- return true;
114
- } else if (position < start) {
115
- right = mid - 1;
116
- } else {
117
- left = mid + 1;
118
- }
119
+ if (lastIndex < content.length) {
120
+ segments.push({ text: content.substring(lastIndex), isCode: false });
119
121
  }
120
- return false;
122
+ return segments;
123
+ }
124
+ function escapeMhchemCommands(text) {
125
+ return text.replaceAll("$\\ce{", "$\\\\ce{").replaceAll("$\\pu{", "$\\\\pu{");
121
126
  }
122
127
  var CURRENCY_REGEX = /(?<![\\$])\$(?!\$)(?=\d+(?:,\d{3})*(?:\.\d+)?(?:[KMBkmb])?(?:\s|$|[^a-zA-Z\d]))/g;
123
128
  var NO_ESCAPED_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)/g;
124
- var DELIMITERS_REGEX = /\\\[([\S\s]*?[^\\])\\]|\\\((.*?)\\\)/g;
125
- var UNESCAPED_PIPES_REGEX = /(?<!\\)\|/g;
126
- var LATEX_BLOCK_REGEX = /\$\$([\S\s]*?)\$\$|(?<![\\$])\$(?!\$)((?:.|\n)*?)(?<![\\`])\$(?!\$)/g;
129
+ var DELIMITERS_REGEX = /(?<!!)\\\[([\S\s]*?[^\\])\\](?!\()|\\\((.*?)\\\)/g;
130
+ var ARRAY_COL_SPEC_OR_PIPE_REGEX = /(\\begin\{(?:array|tabular[x*]?)\}\{[^}]*\})|(?<!\\)\|/g;
131
+ var LATEX_BLOCK_REGEX = /\$\$([\S\s]*?)\$\$|(?<![\\$])\$(?!\$)((?:[^$\n]|\\\$)*?)(?<![\\`])\$(?!\$)/g;
127
132
  var ESCAPE_TEXT_UNDERSCORES_REGEX = /\\text{([^}]*)}/g;
128
133
  var SINGLE_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)((?:[^$\n]|\\[$])+?)(?<!\\)(?<!`)\$(?!\$)/g;
129
- function convertLatexDelimiters(text, codeRegions) {
134
+ function escapeCurrencyDollarSigns(text) {
135
+ const parts = [];
136
+ let lastIndex = 0;
137
+ const currencyMatches = Array.from(text.matchAll(CURRENCY_REGEX));
138
+ for (let i = 0; i < currencyMatches.length; i++) {
139
+ const match = currencyMatches[i];
140
+ parts.push(text.substring(lastIndex, match.index));
141
+ let needEscape = true;
142
+ let restBeforeNextMatchOrEnd = "";
143
+ if (i < currencyMatches.length - 1) {
144
+ const nextMatch = currencyMatches[i + 1];
145
+ if (nextMatch.index - match.index > 1) {
146
+ restBeforeNextMatchOrEnd = text.substring(match.index + 1, nextMatch.index);
147
+ }
148
+ } else {
149
+ restBeforeNextMatchOrEnd = text.substring(match.index + 1);
150
+ }
151
+ const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\r\n|\r|\n/g)[0];
152
+ if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
153
+ const previousNewContent = parts.join("");
154
+ const previousLastLineContent = previousNewContent.split(/\r\n|\r|\n/g).pop();
155
+ const wholeLineBeforeNextMatchWithoutCurrentDollar = previousLastLineContent + firstLineBeforeNextMatch;
156
+ if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
157
+ needEscape = false;
158
+ }
159
+ }
160
+ parts.push(needEscape ? "\\$" : "$");
161
+ lastIndex = match.index + 1;
162
+ }
163
+ parts.push(text.substring(lastIndex));
164
+ return parts.join("");
165
+ }
166
+ function convertLatexDelimiters(text) {
130
167
  return text.replaceAll(
131
168
  DELIMITERS_REGEX,
132
- (match, squareBracket, roundBracket, index) => {
133
- if (isInCodeBlock(index, codeRegions)) {
134
- return match;
135
- }
169
+ (match, squareBracket, roundBracket) => {
136
170
  if (squareBracket !== void 0) {
137
171
  return `$$${squareBracket}$$`;
138
172
  } else if (roundBracket !== void 0) {
@@ -143,83 +177,43 @@ function convertLatexDelimiters(text, codeRegions) {
143
177
  );
144
178
  }
145
179
  var replaceUnescapedPipes = (formula) => (
146
- // Use \vert{} so the control sequence terminates before the next token
147
- formula.replaceAll(UNESCAPED_PIPES_REGEX, "\\vert{}")
180
+ // Use \vert{} so the control sequence terminates before the next token.
181
+ // Preserve `|` inside \begin{array}{...} / \begin{tabular}{...} column specifiers.
182
+ formula.replaceAll(
183
+ ARRAY_COL_SPEC_OR_PIPE_REGEX,
184
+ (match, colSpec) => colSpec !== void 0 ? match : "\\vert{}"
185
+ )
148
186
  );
149
- function escapeLatexPipes(text, codeRegions) {
150
- return text.replaceAll(LATEX_BLOCK_REGEX, (match, display, inline, index) => {
151
- if (isInCodeBlock(index, codeRegions)) {
152
- return match;
153
- }
187
+ function escapeLatexPipes(text) {
188
+ return text.replaceAll(LATEX_BLOCK_REGEX, (match, display, inline) => {
154
189
  if (display !== void 0) return `$$${replaceUnescapedPipes(display)}$$`;
155
190
  if (inline !== void 0) return `$${replaceUnescapedPipes(inline)}$`;
156
191
  return match;
157
192
  });
158
193
  }
159
- function escapeTextUnderscores(text, codeRegions) {
160
- return text.replaceAll(ESCAPE_TEXT_UNDERSCORES_REGEX, (match, textContent, index) => {
161
- if (isInCodeBlock(index, codeRegions)) {
162
- return match;
163
- }
194
+ function escapeTextUnderscores(text) {
195
+ return text.replaceAll(ESCAPE_TEXT_UNDERSCORES_REGEX, (_match, textContent) => {
164
196
  const escapedTextContent = textContent.replaceAll(/(?<!\\)_/g, "\\_");
165
197
  return `\\text{${escapedTextContent}}`;
166
198
  });
167
199
  }
200
+ function convertSingleToDoubleDollar(text) {
201
+ return text.replaceAll(SINGLE_DOLLAR_REGEX, (_match, content) => `$$${content}$$`);
202
+ }
168
203
  function preprocessLaTeX(str) {
169
204
  if (!str.includes("$") && !str.includes("\\[") && !str.includes("\\(")) return str;
170
- let processed = str;
171
- processed = escapeMhchemCommands(processed);
172
- const codeRegions = findCodeBlockRegions(processed);
173
- const parts = [];
174
- let lastIndex = 0;
175
- const currencyMatchesIterator = processed.matchAll(CURRENCY_REGEX);
176
- const currencyMatches = Array.from(currencyMatchesIterator);
177
- for (let i = 0; i < currencyMatches.length; i++) {
178
- const match = currencyMatches[i];
179
- parts.push(processed.substring(lastIndex, match.index));
180
- let needEscape = true;
181
- if (!isInCodeBlock(match.index, codeRegions)) {
182
- let restBeforeNextMatchOrEnd = "";
183
- if (i < currencyMatches.length - 1) {
184
- const nextMatch = currencyMatches[i + 1];
185
- if (nextMatch.index - match.index > 1) {
186
- restBeforeNextMatchOrEnd = processed.substring(match.index + 1, nextMatch.index);
187
- }
188
- } else {
189
- restBeforeNextMatchOrEnd = processed.substring(match.index + 1, processed.length);
190
- }
191
- const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\r\n|\r|\n/g)[0];
192
- if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
193
- const previousNewCotent = parts.join("");
194
- const previousLastLineContent = previousNewCotent.split(/\r\n|\r|\n/g).pop();
195
- const wholeLineBeforeNextMatchWithoutCurrentDollar = previousLastLineContent + firstLineBeforeNextMatch;
196
- if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
197
- needEscape = false;
198
- }
199
- }
200
- } else {
201
- needEscape = false;
202
- }
203
- parts.push(needEscape ? "\\$" : "$");
204
- lastIndex = match.index + 1;
205
- }
206
- parts.push(processed.substring(lastIndex));
207
- processed = parts.join("");
208
- console.log("processed", processed);
209
- processed = convertLatexDelimiters(processed, codeRegions);
210
- processed = escapeLatexPipes(processed, codeRegions);
211
- processed = escapeTextUnderscores(processed, codeRegions);
212
- const result = [];
213
- lastIndex = 0;
214
- const singleDollarMatchesIterator = processed.matchAll(SINGLE_DOLLAR_REGEX);
215
- for (const match of singleDollarMatchesIterator) {
216
- if (!isInCodeBlock(match.index, codeRegions)) {
217
- result.push(processed.substring(lastIndex, match.index));
218
- result.push(`$$${match[1]}$$`);
219
- lastIndex = match.index + match[0].length;
220
- }
221
- }
222
- result.push(processed.substring(lastIndex));
205
+ const segments = splitByProtectedRegions(str);
206
+ const result = segments.map((segment) => {
207
+ if (segment.isCode) return segment.text;
208
+ let text = segment.text;
209
+ text = escapeMhchemCommands(text);
210
+ text = escapeCurrencyDollarSigns(text);
211
+ text = convertLatexDelimiters(text);
212
+ text = escapeLatexPipes(text);
213
+ text = escapeTextUnderscores(text);
214
+ text = convertSingleToDoubleDollar(text);
215
+ return text;
216
+ });
223
217
  return result.join("");
224
218
  }
225
219
 
@@ -283,28 +277,36 @@ var AIMarkdownContent = memo(({ content, customComponents }) => {
283
277
  ReactMarkdown,
284
278
  {
285
279
  remarkPlugins: [
280
+ // --- Core plugins (always active) ---
286
281
  remarkGfm,
287
282
  [
288
283
  remarkMath,
289
284
  {
285
+ // Disable single-dollar inline math to avoid conflicts with currency
286
+ // signs and other dollar usages; the preprocessor converts $...$ to $$...$$.
290
287
  singleDollarTextMath: false
291
288
  }
292
289
  ],
290
+ // --- Configurable extra syntax plugins ---
293
291
  ...extraSyntaxRemarkPlugins,
292
+ // --- Formatting & normalization ---
294
293
  remarkBreaks,
295
294
  remarkEmoji,
296
295
  remarkSqueezeParagraphs,
297
296
  remarkCjkFriendly,
298
297
  remarkCjkFriendlyGfmStrikethrough,
298
+ // --- Configurable display optimizations ---
299
299
  ...displayOptimizeRemarkPlugins
300
300
  ],
301
301
  rehypePlugins: [
302
+ // Allow raw HTML through so rehype-sanitize can handle it.
302
303
  [
303
304
  rehypeRaw,
304
305
  {
305
306
  passThrough: []
306
307
  }
307
308
  ],
309
+ // Sanitize HTML while allowing <mark> (highlight) and KaTeX class names.
308
310
  [
309
311
  rehypeSanitize,
310
312
  {
@@ -323,6 +325,7 @@ var AIMarkdownContent = memo(({ content, customComponents }) => {
323
325
  remarkRehypeOptions: {
324
326
  allowDangerousHtml: true,
325
327
  handlers: {
328
+ // Inject definition-list HAST handlers when the extension is active.
326
329
  ...enableDefinitionList ? defListHastHandlers : {}
327
330
  }
328
331
  },
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx","../src/context.tsx","../src/defs.ts","../src/preprocessors/latex.ts","../src/preprocessors/index.ts","../src/components/MarkdownContent.tsx","../src/hooks/useStableValue.ts","../src/components/typography/Default.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo, memo } from 'react';\nimport AIMarkdownRenderStateProvider, { AIMarkdownRenderStateProviderProps } from './context';\nimport { AIMDContentPreprocessor } from './preprocessors/defs';\nimport preprocessAIMDContent from './preprocessors';\nimport AIMarkdownContent from './components/MarkdownContent';\nimport { Components } from 'react-markdown';\nimport {\n AIMarkdownRenderConfig,\n AIMarkdownMetadata,\n AIMarkdownTypographyComponent,\n AIMarkdownExtraStyleComponent,\n AIMarkdownVariant,\n AIMarkdownColorScheme,\n} from './defs';\nimport useStableValue from './hooks/useStableValue';\nimport DefaultTypography from './components/typography/Default';\n\nexport interface AIMarkdownProps<\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TRenderData extends AIMarkdownMetadata = AIMarkdownMetadata,\n> extends Omit<AIMarkdownRenderStateProviderProps<TConfig, TRenderData>, 'fontSize'> {\n fontSize?: number | string;\n content: string;\n contentPreprocessors?: AIMDContentPreprocessor[];\n customComponents?: Components;\n typography?: AIMarkdownTypographyComponent;\n extraStyle?: AIMarkdownExtraStyleComponent;\n variant?: AIMarkdownVariant;\n colorScheme?: AIMarkdownColorScheme;\n}\n\nconst AIMarkdownComponent = <\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TRenderData extends AIMarkdownMetadata = AIMarkdownMetadata,\n>({\n streaming = false,\n content,\n fontSize,\n contentPreprocessors,\n customComponents,\n config,\n metadata,\n typography: Typography = DefaultTypography,\n extraStyle: ExtraStyle,\n variant = 'default',\n colorScheme = 'light',\n}: AIMarkdownProps<TConfig, TRenderData>) => {\n const usedFontSize = fontSize ? (typeof fontSize === 'number' ? `${fontSize}px` : fontSize) : '0.875rem';\n\n const stableConfig = useStableValue(config);\n const stablePreprocessors = useStableValue(contentPreprocessors);\n const stableCustomComponents = useStableValue(customComponents);\n\n const usedContent = useMemo(\n () => (content ? preprocessAIMDContent(content, stablePreprocessors) : content),\n [content, stablePreprocessors]\n );\n\n return (\n <AIMarkdownRenderStateProvider<TConfig, TRenderData>\n streaming={streaming}\n fontSize={usedFontSize}\n config={stableConfig}\n metadata={metadata}\n >\n <Typography fontSize={usedFontSize} variant={variant} colorScheme={colorScheme}>\n {ExtraStyle ? (\n <ExtraStyle>\n <AIMarkdownContent content={usedContent} customComponents={stableCustomComponents} />\n </ExtraStyle>\n ) : (\n <AIMarkdownContent content={usedContent} customComponents={stableCustomComponents} />\n )}\n </Typography>\n </AIMarkdownRenderStateProvider>\n );\n};\n\nconst AIMarkdown = memo(AIMarkdownComponent);\nAIMarkdown.displayName = 'AIMarkdown';\n\nexport default AIMarkdown as typeof AIMarkdownComponent;\n\n// Types\nexport type { AIMDContentPreprocessor };\nexport type {\n AIMarkdownRenderConfig,\n AIMarkdownRenderState,\n AIMarkdownMetadata,\n AIMarkdownTypographyProps,\n AIMarkdownTypographyComponent,\n AIMarkdownExtraStyleProps,\n AIMarkdownExtraStyleComponent,\n AIMarkdownVariant,\n AIMarkdownColorScheme,\n} from './defs';\n\n// Enums\nexport { AIMarkdownRenderExtraSyntax, AIMarkdownRenderDisplayOptimizeAbility } from './defs';\n\n// Hook — for custom components to access render state\nexport { useAIMarkdownRenderState } from './context';\n","import { PropsWithChildren, createContext, useContext, useMemo } from 'react';\nimport cloneDeep from 'lodash/cloneDeep';\nimport mergeWith from 'lodash/mergeWith';\nimport {\n AIMarkdownRenderConfig,\n AIMarkdownMetadata,\n AIMarkdownRenderState,\n defaultMRMarkdownRenderConfig,\n} from './defs';\nimport { DeepPartial } from './utils/ts-util';\n\nconst AIMarkdownRenderStateContext = createContext<AIMarkdownRenderState<\n AIMarkdownRenderConfig,\n AIMarkdownMetadata\n> | null>(null);\n\nexport function useAIMarkdownRenderState<\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TMetadata extends AIMarkdownMetadata = AIMarkdownMetadata,\n>() {\n const context = useContext(AIMarkdownRenderStateContext) as AIMarkdownRenderState<TConfig, TMetadata>;\n\n if (!context) {\n throw new Error('useAIMarkdownRenderState must be used within an <AIMarkdown /> component.');\n }\n\n return context;\n}\n\nexport interface AIMarkdownRenderStateProviderProps<\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TMetadata extends AIMarkdownMetadata = AIMarkdownMetadata,\n> extends PropsWithChildren {\n streaming: boolean;\n fontSize: string;\n config?: DeepPartial<TConfig>;\n metadata?: TMetadata;\n}\n\n/**\n * When merging config, arrays from the source (user config) should fully replace\n * the target (default config) instead of being merged by index.\n */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nconst configMergeCustomizer = (\n _objValue: any,\n srcValue: any,\n _key: string,\n _object: any,\n _source: any,\n _stack: any\n) => {\n if (Array.isArray(srcValue)) {\n return srcValue;\n }\n};\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\nconst AIMarkdownRenderStateProvider = <\n RCT extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n RDT extends AIMarkdownMetadata = AIMarkdownMetadata,\n>({\n streaming,\n fontSize,\n config,\n metadata,\n children,\n}: AIMarkdownRenderStateProviderProps<RCT, RDT>) => {\n const mergedConfig = useMemo(\n () =>\n config\n ? mergeWith(cloneDeep(defaultMRMarkdownRenderConfig), config, configMergeCustomizer)\n : defaultMRMarkdownRenderConfig,\n [config]\n );\n\n const state = useMemo(\n () => ({\n streaming,\n fontSize,\n config: mergedConfig,\n metadata,\n }),\n [streaming, fontSize, mergedConfig, metadata]\n );\n\n return <AIMarkdownRenderStateContext.Provider value={state}>{children}</AIMarkdownRenderStateContext.Provider>;\n};\n\nexport default AIMarkdownRenderStateProvider;\n","import { ComponentType, PropsWithChildren } from 'react';\n\nexport enum AIMarkdownRenderExtraSyntax {\n HIGHLIGHT = 'HIGHLIGHT', // support ==Highlight==\n DEFINITION_LIST = 'DEFINITION_LIST', // support Definition List https://michelf.ca/projects/php-markdown/extra/#def-list\n SUBSCRIPT = 'SUBSCRIPT', // support superscript and subscript\n}\n\nexport enum AIMarkdownRenderDisplayOptimizeAbility {\n REMOVE_COMMENTS = 'REMOVE_COMMENTS', // remove comments\n SMARTYPANTS = 'SMARTYPANTS', // make conent more typographic by SmartyPants https://www.npmjs.com/package/smartypants\n PANGU = 'PANGU', // auto add space between CJK and English\n}\n\nexport interface AIMarkdownRenderConfig {\n extraSyntaxSupported: AIMarkdownRenderExtraSyntax[];\n displayOptimizeAbilities: AIMarkdownRenderDisplayOptimizeAbility[];\n}\n\nexport const defaultMRMarkdownRenderConfig: AIMarkdownRenderConfig = {\n extraSyntaxSupported: [\n AIMarkdownRenderExtraSyntax.HIGHLIGHT,\n AIMarkdownRenderExtraSyntax.DEFINITION_LIST,\n AIMarkdownRenderExtraSyntax.SUBSCRIPT,\n ],\n displayOptimizeAbilities: [\n AIMarkdownRenderDisplayOptimizeAbility.REMOVE_COMMENTS,\n AIMarkdownRenderDisplayOptimizeAbility.SMARTYPANTS,\n AIMarkdownRenderDisplayOptimizeAbility.PANGU,\n ],\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface AIMarkdownMetadata extends Record<string, any> {}\n\nexport type AIMarkdownVariant = 'default' | (string & {});\nexport type AIMarkdownColorScheme = 'light' | 'dark' | (string & {});\n\nexport interface AIMarkdownTypographyProps extends PropsWithChildren {\n fontSize: string;\n variant?: AIMarkdownVariant;\n colorScheme?: AIMarkdownColorScheme;\n}\nexport type AIMarkdownTypographyComponent = ComponentType<AIMarkdownTypographyProps>;\n\nexport interface AIMarkdownExtraStyleProps extends PropsWithChildren {}\nexport type AIMarkdownExtraStyleComponent = ComponentType<AIMarkdownExtraStyleProps>;\n\nexport interface AIMarkdownRenderState<\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TMetadata extends AIMarkdownMetadata = AIMarkdownMetadata,\n> {\n streaming: boolean;\n fontSize: string;\n config: TConfig;\n metadata?: TMetadata;\n}\n","/**\n * LaTeX preprocess functions\n * Thanks the implementations from the following repositories:\n * - https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n * - https://github.com/danny-avila/LibreChat/blob/main/client/src/utils/latex.ts\n */\n\n/**\n * Escape mhchem commands in LaTeX expressions to ensure proper rendering.\n *\n * @param text Input string containing LaTeX expressions with mhchem commands\n * @returns String with escaped mhchem commands\n * @from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nfunction escapeMhchemCommands(text: string) {\n return text.replaceAll('$\\\\ce{', '$\\\\\\\\ce{').replaceAll('$\\\\pu{', '$\\\\\\\\pu{');\n}\n\n/**\n * Efficiently finds all code block regions in the content\n * @param content The content to analyze\n * @returns Array of code block regions [start, end]\n * @from https://github.com/danny-avila/LibreChat/blob/main/client/src/utils/latex.ts\n */\nfunction findCodeBlockRegions(content: string): Array<[number, number]> {\n const regions: Array<[number, number]> = [];\n let inlineStart = -1;\n let multilineStart = -1;\n\n for (let i = 0; i < content.length; i++) {\n const char = content[i];\n\n // Check for multiline code blocks\n if (char === '`' && i + 2 < content.length && content[i + 1] === '`' && content[i + 2] === '`') {\n if (multilineStart === -1) {\n multilineStart = i;\n i += 2; // Skip the next two backticks\n } else {\n regions.push([multilineStart, i + 2]);\n multilineStart = -1;\n i += 2;\n }\n }\n // Check for inline code blocks (only if not in multiline)\n else if (char === '`' && multilineStart === -1) {\n if (inlineStart === -1) {\n inlineStart = i;\n } else {\n regions.push([inlineStart, i]);\n inlineStart = -1;\n }\n }\n }\n\n return regions;\n}\n\n/**\n * Checks if a position is inside any code block region using binary search\n * @param position The position to check\n * @param codeRegions Array of code block regions\n * @returns True if position is inside a code block\n * @from https://github.com/danny-avila/LibreChat/blob/main/client/src/utils/latex.ts\n */\nfunction isInCodeBlock(position: number, codeRegions: Array<[number, number]>): boolean {\n let left = 0;\n let right = codeRegions.length - 1;\n\n while (left <= right) {\n const mid = Math.floor((left + right) / 2);\n const [start, end] = codeRegions[mid];\n\n if (position >= start && position <= end) {\n return true;\n } else if (position < start) {\n right = mid - 1;\n } else {\n left = mid + 1;\n }\n }\n\n return false;\n}\n\nconst CURRENCY_REGEX = /(?<![\\\\$])\\$(?!\\$)(?=\\d+(?:,\\d{3})*(?:\\.\\d+)?(?:[KMBkmb])?(?:\\s|$|[^a-zA-Z\\d]))/g;\nconst NO_ESCAPED_DOLLAR_REGEX = /(?<![\\\\$])\\$(?!\\$)/g;\nconst DELIMITERS_REGEX = /\\\\\\[([\\S\\s]*?[^\\\\])\\\\]|\\\\\\((.*?)\\\\\\)/g;\nconst UNESCAPED_PIPES_REGEX = /(?<!\\\\)\\|/g;\nconst LATEX_BLOCK_REGEX = /\\$\\$([\\S\\s]*?)\\$\\$|(?<![\\\\$])\\$(?!\\$)((?:.|\\n)*?)(?<![\\\\`])\\$(?!\\$)/g;\nconst ESCAPE_TEXT_UNDERSCORES_REGEX = /\\\\text{([^}]*)}/g;\nconst SINGLE_DOLLAR_REGEX = /(?<![\\\\$])\\$(?!\\$)((?:[^$\\n]|\\\\[$])+?)(?<!\\\\)(?<!`)\\$(?!\\$)/g;\n\n/**\n * Convert LaTeX bracket delimiters to dollar sign delimiters.\n * Converts \\[...\\] to $$...$$ and \\(...\\) to $...$\n * Preserves code blocks during conversion.\n *\n * @param text Input string containing LaTeX expressions\n * @returns String with LaTeX bracket delimiters converted to dollar sign delimiters\n * @modified from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nfunction convertLatexDelimiters(text: string, codeRegions: Array<[number, number]>): string {\n return text.replaceAll(\n DELIMITERS_REGEX,\n (match: string, squareBracket: string | undefined, roundBracket: string | undefined, index: number): string => {\n if (isInCodeBlock(index, codeRegions)) {\n return match;\n }\n if (squareBracket !== undefined) {\n return `$$${squareBracket}$$`;\n } else if (roundBracket !== undefined) {\n return `$${roundBracket}$`;\n }\n return match;\n }\n );\n}\n\n/**\n * Helper function: replace unescaped pipes with \\vert in LaTeX math fragments\n * @from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nconst replaceUnescapedPipes = (formula: string): string =>\n // Use \\vert{} so the control sequence terminates before the next token\n formula.replaceAll(UNESCAPED_PIPES_REGEX, '\\\\vert{}');\n/**\n * Escape pipes in LaTeX expressions to prevent them from being interpreted as\n * column separators in markdown tables.\n *\n * @param text Input string containing LaTeX expressions\n * @returns String with pipes escaped in LaTeX expressions\n * @modified from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nfunction escapeLatexPipes(text: string, codeRegions: Array<[number, number]>): string {\n // Replace unescaped '|' with '\\vert' within LaTeX math ranges so that\n // remark-gfm table parsing doesn't treat them as column separators.\n // Keep code blocks/inline code unchanged.\n return text.replaceAll(LATEX_BLOCK_REGEX, (match, display, inline, index) => {\n if (isInCodeBlock(index, codeRegions)) {\n return match;\n }\n if (display !== undefined) return `$$${replaceUnescapedPipes(display)}$$`;\n if (inline !== undefined) return `$${replaceUnescapedPipes(inline)}$`;\n return match;\n });\n}\n\n/**\n * Escape unescaped underscores within \\text{...} commands in LaTeX expressions.\n * For example, \\text{node_domain} becomes \\text{node\\_domain},\n * but \\text{node\\_domain} remains \\text{node\\_domain}.\n *\n * @param text Input string that may contain LaTeX expressions\n * @returns String with unescaped underscores escaped within \\text{...} commands\n * @modified from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nfunction escapeTextUnderscores(text: string, codeRegions: Array<[number, number]>): string {\n return text.replaceAll(ESCAPE_TEXT_UNDERSCORES_REGEX, (match, textContent: string, index: number) => {\n if (isInCodeBlock(index, codeRegions)) {\n return match;\n }\n // textContent is the content within the braces, e.g., \"node_domain\" or \"already\\_escaped\"\n // Replace '_' with '\\_' only when the underscore '_' is not preceded by a backslash '\\'.\n // (?<!\\\\) is a negative lookbehind assertion that ensures the character before '_' is not '\\'.\n const escapedTextContent = textContent.replaceAll(/(?<!\\\\)_/g, '\\\\_');\n return `\\\\text{${escapedTextContent}}`;\n });\n}\n\nexport function preprocessLaTeX(str: string): string {\n // Step 0: return early if no LaTeX patterns are found\n if (!str.includes('$') && !str.includes('\\\\[') && !str.includes('\\\\(')) return str;\n\n let processed = str;\n\n // Step 1: escape mhchem commands\n processed = escapeMhchemCommands(processed);\n\n // Step 2: find code block regions\n const codeRegions = findCodeBlockRegions(processed);\n\n // Step 3: escape currency dollar signs\n const parts = [];\n let lastIndex = 0;\n const currencyMatchesIterator = processed.matchAll(CURRENCY_REGEX);\n const currencyMatches = Array.from(currencyMatchesIterator);\n for (let i = 0; i < currencyMatches.length; i++) {\n const match = currencyMatches[i];\n parts.push(processed.substring(lastIndex, match.index));\n let needEscape = true;\n if (!isInCodeBlock(match.index, codeRegions)) {\n let restBeforeNextMatchOrEnd = '';\n if (i < currencyMatches.length - 1) {\n const nextMatch = currencyMatches[i + 1];\n if (nextMatch.index - match.index > 1) {\n restBeforeNextMatchOrEnd = processed.substring(match.index + 1, nextMatch.index);\n }\n } else {\n restBeforeNextMatchOrEnd = processed.substring(match.index + 1, processed.length);\n }\n const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\\r\\n|\\r|\\n/g)[0];\n if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {\n const previousNewCotent = parts.join('');\n const previousLastLineContent = previousNewCotent.split(/\\r\\n|\\r|\\n/g).pop();\n const wholeLineBeforeNextMatchWithoutCurrentDollar = previousLastLineContent + firstLineBeforeNextMatch;\n if (\n Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !==\n 0\n ) {\n needEscape = false;\n }\n }\n } else {\n needEscape = false;\n }\n parts.push(needEscape ? '\\\\$' : '$');\n lastIndex = match.index + 1;\n }\n parts.push(processed.substring(lastIndex));\n processed = parts.join('');\n console.log('processed', processed);\n\n // Step 4: convert LaTeX delimiters\n processed = convertLatexDelimiters(processed, codeRegions);\n\n // Step 5: escape LaTeX pipes\n processed = escapeLatexPipes(processed, codeRegions);\n\n // Step 6: escape text underscores\n processed = escapeTextUnderscores(processed, codeRegions);\n\n // Step 7: convert single dollar delimiters to double dollars\n const result: string[] = [];\n lastIndex = 0;\n const singleDollarMatchesIterator = processed.matchAll(SINGLE_DOLLAR_REGEX);\n for (const match of singleDollarMatchesIterator) {\n if (!isInCodeBlock(match.index, codeRegions)) {\n result.push(processed.substring(lastIndex, match.index));\n result.push(`$$${match[1]}$$`);\n lastIndex = match.index + match[0].length;\n }\n }\n result.push(processed.substring(lastIndex));\n\n return result.join('');\n}\n","import { AIMDContentPreprocessor } from './defs';\nimport { preprocessLaTeX } from './latex';\n\nfunction applyPreprocessors(value: string, ...fns: Array<AIMDContentPreprocessor>): string {\n return fns.reduce((result, fn) => fn(result), value);\n}\n\nconst defaultExtraPreprocessors: AIMDContentPreprocessor[] = [];\n\nexport default function preprocessAIMDContent(\n content: string,\n extraPreprocessors: AIMDContentPreprocessor[] = defaultExtraPreprocessors\n) {\n return applyPreprocessors(content, preprocessLaTeX, ...extraPreprocessors);\n}\n","import { memo, useMemo } from 'react';\nimport ReactMarkdown, { Components } from 'react-markdown';\nimport rehypeKatex from 'rehype-katex';\nimport rehypeRaw from 'rehype-raw';\nimport rehypeUnwrapImages from 'rehype-unwrap-images';\nimport rehypeSanitize, { defaultSchema } from 'rehype-sanitize';\nimport remarkBreaks from 'remark-breaks';\nimport remarkCjkFriendly from 'remark-cjk-friendly';\nimport remarkCjkFriendlyGfmStrikethrough from 'remark-cjk-friendly-gfm-strikethrough';\nimport remarkEmoji from 'remark-emoji';\nimport remarkGfm from 'remark-gfm';\nimport remarkMath from 'remark-math';\nimport { remarkDefinitionList, defListHastHandlers } from 'remark-definition-list';\nimport remarkSupersub from 'remark-supersub';\nimport { remarkMark as remarkMarkHighlight } from 'remark-mark-highlight';\nimport remarkSqueezeParagraphs from 'remark-squeeze-paragraphs';\nimport remarkSmartypants from 'remark-smartypants';\nimport remarkPangu from 'remark-pangu';\nimport remarkRemoveComments from 'remark-remove-comments';\nimport { useAIMarkdownRenderState } from '../context';\nimport { AIMarkdownRenderDisplayOptimizeAbility, AIMarkdownRenderExtraSyntax } from '../defs';\n\nconst DisplayOptimizeRemarkPluginMap = {\n [AIMarkdownRenderDisplayOptimizeAbility.REMOVE_COMMENTS]: remarkRemoveComments,\n [AIMarkdownRenderDisplayOptimizeAbility.SMARTYPANTS]: remarkSmartypants,\n [AIMarkdownRenderDisplayOptimizeAbility.PANGU]: remarkPangu,\n};\n\nconst ExtraSyntaxRemarkPluginMap = {\n [AIMarkdownRenderExtraSyntax.HIGHLIGHT]: remarkMarkHighlight,\n [AIMarkdownRenderExtraSyntax.DEFINITION_LIST]: remarkDefinitionList,\n [AIMarkdownRenderExtraSyntax.SUBSCRIPT]: remarkSupersub,\n};\n\nconst DefaultCustomComponents: Components = {};\n\ninterface AIMarkdownContentProps {\n content: string;\n customComponents?: Components;\n}\n\nconst AIMarkdownContent = memo(({ content, customComponents }: AIMarkdownContentProps) => {\n const { config } = useAIMarkdownRenderState();\n\n const { extraSyntaxRemarkPlugins, enableDefinitionList } = useMemo(\n () => ({\n extraSyntaxRemarkPlugins: config.extraSyntaxSupported.map((syntax) => ExtraSyntaxRemarkPluginMap[syntax]),\n enableDefinitionList: config.extraSyntaxSupported.includes(AIMarkdownRenderExtraSyntax.DEFINITION_LIST),\n }),\n [config.extraSyntaxSupported]\n );\n\n const displayOptimizeRemarkPlugins = useMemo(() => {\n return config.displayOptimizeAbilities.map((ability) => DisplayOptimizeRemarkPluginMap[ability]);\n }, [config.displayOptimizeAbilities]);\n\n const usedComponents = useMemo(() => {\n return customComponents ? { ...DefaultCustomComponents, ...customComponents } : DefaultCustomComponents;\n }, [customComponents]);\n\n return (\n <ReactMarkdown\n remarkPlugins={[\n remarkGfm,\n [\n remarkMath,\n {\n singleDollarTextMath: false,\n },\n ],\n ...extraSyntaxRemarkPlugins,\n remarkBreaks,\n remarkEmoji,\n remarkSqueezeParagraphs,\n remarkCjkFriendly,\n remarkCjkFriendlyGfmStrikethrough,\n ...displayOptimizeRemarkPlugins,\n ]}\n rehypePlugins={[\n [\n rehypeRaw,\n {\n passThrough: [],\n },\n ],\n [\n rehypeSanitize,\n {\n ...defaultSchema,\n tagNames: [...(defaultSchema.tagNames || []), 'mark'],\n attributes: {\n ...defaultSchema.attributes,\n // The `language-*` regex is allowed by default.\n code: [['className', /^language-./, 'math-inline', 'math-display']],\n },\n },\n ],\n rehypeKatex,\n rehypeUnwrapImages,\n ]}\n remarkRehypeOptions={{\n allowDangerousHtml: true,\n handlers: {\n ...(enableDefinitionList ? defListHastHandlers : {}),\n },\n }}\n components={usedComponents}\n // By default, the defaultUrlTransform function in Windows environments treats local paths, such as those starting with C:/, as unsafe and replaces them with an empty string. Hence, in this case, it simply returns the URL that it has identified without any modification.\n // urlTransform={(url: string) => url}\n >\n {content}\n </ReactMarkdown>\n );\n});\n\nAIMarkdownContent.displayName = 'AIMarkdownContent';\n\nexport default AIMarkdownContent;\n","import { useRef, useEffect } from 'react';\nimport isEqual from 'lodash/isEqual';\n\nexport default function useStableValue<T>(value: T): T {\n const ref = useRef(value);\n\n // eslint-disable-next-line react-hooks/refs\n const prev = ref.current;\n const stableValue = isEqual(prev, value) ? prev : value;\n\n useEffect(() => {\n ref.current = stableValue;\n }, [stableValue]);\n\n return stableValue;\n}\n","import { memo } from 'react';\nimport type { AIMarkdownTypographyProps } from '../../defs';\n\nconst DefaultTypography = memo(({ children, fontSize, variant, colorScheme }: AIMarkdownTypographyProps) => (\n <div\n className={`aim-typography-root ${variant ?? ''} ${colorScheme ?? ''}`.trim()}\n style={{ width: '100%', fontSize }}\n >\n {children}\n </div>\n));\n\nDefaultTypography.displayName = 'DefaultTypography';\n\nexport default DefaultTypography;\n"],"mappings":"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","names":["useMemo","memo","AIMarkdownRenderExtraSyntax","AIMarkdownRenderDisplayOptimizeAbility","useMemo","jsx","useMemo","memo","jsx","jsx","useMemo","memo"]}
1
+ {"version":3,"sources":["../src/index.tsx","../src/context.tsx","../src/defs.ts","../src/preprocessors/latex.ts","../src/preprocessors/index.ts","../src/components/MarkdownContent.tsx","../src/hooks/useStableValue.ts","../src/components/typography/Default.tsx"],"sourcesContent":["/**\n * @ai-react-markdown/core\n *\n * A batteries-included React component for rendering AI-generated markdown\n * with first-class support for LaTeX math, GFM, CJK text, syntax highlighting,\n * and streaming content.\n *\n * ## Quick Start\n *\n * ```tsx\n * import AIMarkdown from '@ai-react-markdown/core';\n * import '@ai-react-markdown/core/typography/default.css';\n *\n * function App() {\n * return <AIMarkdown content=\"Hello **world**!\" />;\n * }\n * ```\n *\n * @module @ai-react-markdown/core\n */\n\n'use client';\n\nimport { useMemo, memo } from 'react';\nimport AIMarkdownRenderStateProvider, { AIMarkdownRenderStateProviderProps } from './context';\nimport { AIMDContentPreprocessor } from './preprocessors/defs';\nimport preprocessAIMDContent from './preprocessors';\nimport AIMarkdownContent from './components/MarkdownContent';\nimport { Components } from 'react-markdown';\nimport {\n AIMarkdownRenderConfig,\n AIMarkdownMetadata,\n AIMarkdownTypographyComponent,\n AIMarkdownExtraStyleComponent,\n AIMarkdownVariant,\n AIMarkdownColorScheme,\n} from './defs';\nimport useStableValue from './hooks/useStableValue';\nimport DefaultTypography from './components/typography/Default';\n\n/**\n * Props for the `<AIMarkdown>` component.\n *\n * @typeParam TConfig - Custom render configuration type (extends {@link AIMarkdownRenderConfig}).\n * @typeParam TRenderData - Custom metadata type (extends {@link AIMarkdownMetadata}).\n */\nexport interface AIMarkdownProps<\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TRenderData extends AIMarkdownMetadata = AIMarkdownMetadata,\n> extends Omit<AIMarkdownRenderStateProviderProps<TConfig, TRenderData>, 'fontSize'> {\n /**\n * Base font size for the rendered output.\n * Accepts a CSS length string (e.g. `'14px'`, `'0.875rem'`) or a number\n * which is treated as pixels. Defaults to `'0.875rem'`.\n */\n fontSize?: number | string;\n /** Raw markdown content to render. */\n content: string;\n /**\n * Additional preprocessors to run on the raw markdown before rendering.\n * These run *after* the built-in LaTeX preprocessor.\n */\n contentPreprocessors?: AIMDContentPreprocessor[];\n /**\n * Custom `react-markdown` component overrides.\n * Use this to replace the default renderers for specific HTML elements\n * (e.g. code blocks, links, images).\n */\n customComponents?: Components;\n /**\n * Typography wrapper component. Receives `fontSize`, `variant`, and `colorScheme`.\n * Defaults to the built-in {@link DefaultTypography}.\n */\n typography?: AIMarkdownTypographyComponent;\n /**\n * Optional extra style wrapper component rendered between the typography\n * wrapper and the markdown content. Useful for injecting additional\n * CSS scope or theme providers.\n */\n extraStyle?: AIMarkdownExtraStyleComponent;\n /** Typography variant name. Defaults to `'default'`. */\n variant?: AIMarkdownVariant;\n /** Color scheme name. Defaults to `'light'`. */\n colorScheme?: AIMarkdownColorScheme;\n}\n\n/**\n * Root component that preprocesses markdown content and renders it through\n * a configurable remark/rehype pipeline wrapped in typography and style layers.\n */\nconst AIMarkdownComponent = <\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TRenderData extends AIMarkdownMetadata = AIMarkdownMetadata,\n>({\n streaming = false,\n content,\n fontSize,\n contentPreprocessors,\n customComponents,\n config,\n metadata,\n typography: Typography = DefaultTypography,\n extraStyle: ExtraStyle,\n variant = 'default',\n colorScheme = 'light',\n}: AIMarkdownProps<TConfig, TRenderData>) => {\n // Normalize fontSize: number -> px string, undefined -> default rem value.\n const usedFontSize = fontSize ? (typeof fontSize === 'number' ? `${fontSize}px` : fontSize) : '0.875rem';\n\n // Stabilize object/array props to prevent unnecessary re-renders\n // when the consumer creates new references on each render.\n const stableConfig = useStableValue(config);\n const stablePreprocessors = useStableValue(contentPreprocessors);\n const stableCustomComponents = useStableValue(customComponents);\n\n // Run the preprocessing pipeline (LaTeX normalization + user preprocessors).\n const usedContent = useMemo(\n () => (content ? preprocessAIMDContent(content, stablePreprocessors) : content),\n [content, stablePreprocessors]\n );\n\n return (\n <AIMarkdownRenderStateProvider<TConfig, TRenderData>\n streaming={streaming}\n fontSize={usedFontSize}\n config={stableConfig}\n metadata={metadata}\n >\n <Typography fontSize={usedFontSize} variant={variant} colorScheme={colorScheme}>\n {ExtraStyle ? (\n <ExtraStyle>\n <AIMarkdownContent content={usedContent} customComponents={stableCustomComponents} />\n </ExtraStyle>\n ) : (\n <AIMarkdownContent content={usedContent} customComponents={stableCustomComponents} />\n )}\n </Typography>\n </AIMarkdownRenderStateProvider>\n );\n};\n\n/**\n * A React component for rendering AI-generated markdown with rich formatting support.\n *\n * Features:\n * - GFM (tables, strikethrough, task lists, autolinks)\n * - LaTeX math rendering via KaTeX\n * - Emoji shortcodes\n * - CJK-friendly line breaking and spacing\n * - Configurable syntax extensions (highlight, definition lists, super/subscript)\n * - Configurable display optimizations (SmartyPants, pangu, comment removal)\n * - Streaming-aware rendering\n * - Customizable typography, color scheme, and component overrides\n *\n * @example\n * ```tsx\n * <AIMarkdown\n * content={markdownString}\n * streaming={isStreaming}\n * colorScheme=\"dark\"\n * config={{ extraSyntaxSupported: [AIMarkdownRenderExtraSyntax.HIGHLIGHT] }}\n * />\n * ```\n */\nconst AIMarkdown = memo(AIMarkdownComponent);\nAIMarkdown.displayName = 'AIMarkdown';\n\nexport default AIMarkdown as typeof AIMarkdownComponent;\n\n// ── Public API re-exports ───────────────────────────────────────────────────\n\n// Types\nexport type { AIMDContentPreprocessor };\nexport type {\n AIMarkdownRenderConfig,\n AIMarkdownRenderState,\n AIMarkdownMetadata,\n AIMarkdownTypographyProps,\n AIMarkdownTypographyComponent,\n AIMarkdownExtraStyleProps,\n AIMarkdownExtraStyleComponent,\n AIMarkdownVariant,\n AIMarkdownColorScheme,\n} from './defs';\n\n// Enums\nexport { AIMarkdownRenderExtraSyntax, AIMarkdownRenderDisplayOptimizeAbility } from './defs';\n\n// Hook -- for custom components to access render state\nexport { useAIMarkdownRenderState } from './context';\n","/**\n * React context for the AIMarkdown render state.\n *\n * Provides an immutable {@link AIMarkdownRenderState} object to all descendant\n * components. The provider deep-merges user-supplied partial configuration with\n * the built-in defaults so that consumers always receive a complete config.\n *\n * @module context\n */\n\nimport { PropsWithChildren, createContext, useContext, useMemo } from 'react';\nimport cloneDeep from 'lodash/cloneDeep';\nimport mergeWith from 'lodash/mergeWith';\nimport {\n AIMarkdownRenderConfig,\n AIMarkdownMetadata,\n AIMarkdownRenderState,\n defaultAIMarkdownRenderConfig,\n} from './defs';\nimport { DeepPartial } from './utils/ts-util';\n\nconst AIMarkdownRenderStateContext = createContext<AIMarkdownRenderState<\n AIMarkdownRenderConfig,\n AIMarkdownMetadata\n> | null>(null);\n\n/**\n * Access the current {@link AIMarkdownRenderState} from within the `<AIMarkdown>` tree.\n *\n * Must be called inside a component rendered as a descendant of `<AIMarkdown>`.\n * Throws if called outside the provider boundary.\n *\n * @typeParam TConfig - Expected configuration shape (defaults to {@link AIMarkdownRenderConfig}).\n * @typeParam TMetadata - Expected metadata shape (defaults to {@link AIMarkdownMetadata}).\n * @returns The current render state.\n *\n * @example\n * ```tsx\n * function CustomCodeBlock({ children }: PropsWithChildren) {\n * const { streaming, config } = useAIMarkdownRenderState();\n * // ...\n * }\n * ```\n */\nexport function useAIMarkdownRenderState<\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TMetadata extends AIMarkdownMetadata = AIMarkdownMetadata,\n>() {\n const context = useContext(AIMarkdownRenderStateContext) as AIMarkdownRenderState<TConfig, TMetadata>;\n\n if (!context) {\n throw new Error('useAIMarkdownRenderState must be used within an <AIMarkdown /> component.');\n }\n\n return context;\n}\n\n/** Props for {@link AIMarkdownRenderStateProvider}. */\nexport interface AIMarkdownRenderStateProviderProps<\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TMetadata extends AIMarkdownMetadata = AIMarkdownMetadata,\n> extends PropsWithChildren {\n streaming: boolean;\n fontSize: string;\n /** Partial config that will be deep-merged with {@link defaultAIMarkdownRenderConfig}. */\n config?: DeepPartial<TConfig>;\n metadata?: TMetadata;\n}\n\n/**\n * Custom lodash `mergeWith` handler: arrays from the source (user config)\n * fully replace the target (default config) instead of being merged by index.\n */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nconst configMergeCustomizer = (\n _objValue: any,\n srcValue: any,\n _key: string,\n _object: any,\n _source: any,\n _stack: any\n) => {\n if (Array.isArray(srcValue)) {\n return srcValue;\n }\n};\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\n/**\n * Internal provider that deep-merges user config with defaults and exposes\n * the resulting {@link AIMarkdownRenderState} to the component tree.\n */\nconst AIMarkdownRenderStateProvider = <\n RCT extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n RDT extends AIMarkdownMetadata = AIMarkdownMetadata,\n>({\n streaming,\n fontSize,\n config,\n metadata,\n children,\n}: AIMarkdownRenderStateProviderProps<RCT, RDT>) => {\n // Deep-merge user config with defaults; clone first to avoid mutating the frozen default.\n const mergedConfig = useMemo(\n () =>\n config\n ? mergeWith(cloneDeep(defaultAIMarkdownRenderConfig), config, configMergeCustomizer)\n : defaultAIMarkdownRenderConfig,\n [config]\n );\n\n // Freeze the state object to enforce immutability downstream.\n const state = useMemo(\n () =>\n Object.freeze({\n streaming,\n fontSize,\n config: mergedConfig,\n metadata,\n }),\n [streaming, fontSize, mergedConfig, metadata]\n );\n\n return <AIMarkdownRenderStateContext.Provider value={state}>{children}</AIMarkdownRenderStateContext.Provider>;\n};\n\nexport default AIMarkdownRenderStateProvider;\n","/**\n * Core type definitions, enums, and default configuration for ai-react-markdown.\n *\n * This module defines the public API surface for configuring the renderer,\n * including extra markdown syntax extensions, display optimization abilities,\n * typography theming, and the shared render state shape.\n *\n * @module defs\n */\n\nimport { ComponentType, PropsWithChildren } from 'react';\n\n/**\n * Extra markdown syntax extensions beyond standard GFM.\n * Enable or disable these via {@link AIMarkdownRenderConfig.extraSyntaxSupported}.\n */\nexport enum AIMarkdownRenderExtraSyntax {\n /** `==Highlight==` syntax support. */\n HIGHLIGHT = 'HIGHLIGHT',\n /** Definition list syntax. @see https://michelf.ca/projects/php-markdown/extra/#def-list */\n DEFINITION_LIST = 'DEFINITION_LIST',\n /** Superscript (`^text^`) and subscript (`~text~`) syntax. */\n SUBSCRIPT = 'SUBSCRIPT',\n}\n\n/**\n * Display optimization abilities applied during markdown processing.\n * Enable or disable these via {@link AIMarkdownRenderConfig.displayOptimizeAbilities}.\n */\nexport enum AIMarkdownRenderDisplayOptimizeAbility {\n /** Strip HTML comments from the content. */\n REMOVE_COMMENTS = 'REMOVE_COMMENTS',\n /** Typographic enhancements via SmartyPants (curly quotes, em-dashes, etc.). @see https://www.npmjs.com/package/smartypants */\n SMARTYPANTS = 'SMARTYPANTS',\n /** Automatically insert spaces between CJK and half-width characters. */\n PANGU = 'PANGU',\n}\n\n/**\n * Configuration object controlling which markdown extensions and\n * display optimizations are active during rendering.\n */\nexport interface AIMarkdownRenderConfig {\n /** Extra syntax extensions to enable. */\n extraSyntaxSupported: AIMarkdownRenderExtraSyntax[];\n /** Display optimization abilities to enable. */\n displayOptimizeAbilities: AIMarkdownRenderDisplayOptimizeAbility[];\n}\n\n/**\n * Sensible default configuration with all extensions and optimizations enabled.\n * Frozen to prevent accidental mutation.\n */\nexport const defaultAIMarkdownRenderConfig: AIMarkdownRenderConfig = Object.freeze({\n extraSyntaxSupported: Object.freeze([\n AIMarkdownRenderExtraSyntax.HIGHLIGHT,\n AIMarkdownRenderExtraSyntax.DEFINITION_LIST,\n AIMarkdownRenderExtraSyntax.SUBSCRIPT,\n ]),\n displayOptimizeAbilities: Object.freeze([\n AIMarkdownRenderDisplayOptimizeAbility.REMOVE_COMMENTS,\n AIMarkdownRenderDisplayOptimizeAbility.SMARTYPANTS,\n AIMarkdownRenderDisplayOptimizeAbility.PANGU,\n ]),\n}) as AIMarkdownRenderConfig;\n\n/**\n * Arbitrary metadata that consumers can pass through the render context.\n * Custom renderers can access this via {@link AIMarkdownRenderState.metadata}.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface AIMarkdownMetadata extends Record<string, any> {}\n\n/**\n * Typography variant identifier. Built-in variant is `'default'`;\n * consumers may define additional variants via custom typography components.\n */\nexport type AIMarkdownVariant = 'default' | (string & {});\n\n/**\n * Color scheme identifier. Built-in schemes are `'light'` and `'dark'`;\n * consumers may define additional schemes via custom typography CSS.\n */\nexport type AIMarkdownColorScheme = 'light' | 'dark' | (string & {});\n\n/** Props accepted by a typography wrapper component. */\nexport interface AIMarkdownTypographyProps extends PropsWithChildren {\n /** Resolved CSS font-size value (e.g. `'14px'`, `'0.875rem'`). */\n fontSize: string;\n /** Active typography variant. */\n variant?: AIMarkdownVariant;\n /** Active color scheme. */\n colorScheme?: AIMarkdownColorScheme;\n}\n\n/** React component type for the typography wrapper. */\nexport type AIMarkdownTypographyComponent = ComponentType<AIMarkdownTypographyProps>;\n\n/** Props accepted by an optional extra style wrapper component. */\nexport interface AIMarkdownExtraStyleProps extends PropsWithChildren {}\n\n/** React component type for an optional extra style wrapper. */\nexport type AIMarkdownExtraStyleComponent = ComponentType<AIMarkdownExtraStyleProps>;\n\n/**\n * Immutable render state exposed to all descendant components via React context.\n * Access this with the {@link useAIMarkdownRenderState} hook.\n *\n * @typeParam TConfig - Render configuration type (defaults to {@link AIMarkdownRenderConfig}).\n * @typeParam TMetadata - Metadata type (defaults to {@link AIMarkdownMetadata}).\n */\nexport interface AIMarkdownRenderState<\n TConfig extends AIMarkdownRenderConfig = AIMarkdownRenderConfig,\n TMetadata extends AIMarkdownMetadata = AIMarkdownMetadata,\n> {\n /** Whether the content is currently being streamed (e.g. from an LLM). */\n streaming: boolean;\n /** Resolved CSS font-size value. */\n fontSize: string;\n /** Active render configuration. */\n config: TConfig;\n /** Optional consumer-provided metadata. */\n metadata?: TMetadata;\n}\n","/**\n * LaTeX preprocess functions\n * Thanks the implementations from the following repositories:\n * - https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n * - https://github.com/danny-avila/LibreChat/blob/main/client/src/utils/latex.ts\n */\n\ninterface Segment {\n text: string;\n isCode: boolean;\n}\n\n/**\n * Split content into alternating text and protected segments.\n * Protected segments (isCode: true) are excluded from LaTeX processing:\n * - ``` multiline code blocks\n * - ` inline code\n * - HTML tags (e.g. <span>$</span> where $ should not be treated as LaTeX)\n */\nfunction splitByProtectedRegions(content: string): Segment[] {\n const segments: Segment[] = [];\n let lastIndex = 0;\n let inlineStart = -1;\n let multilineStart = -1;\n\n function pushProtected(start: number, end: number) {\n if (start > lastIndex) {\n segments.push({ text: content.substring(lastIndex, start), isCode: false });\n }\n segments.push({ text: content.substring(start, end), isCode: true });\n lastIndex = end;\n }\n\n for (let i = 0; i < content.length; i++) {\n const char = content[i];\n\n // Check for multiline code blocks\n if (char === '`' && i + 2 < content.length && content[i + 1] === '`' && content[i + 2] === '`') {\n if (multilineStart === -1) {\n // Cancel any pending inline code — ``` takes priority over `\n inlineStart = -1;\n multilineStart = i;\n i += 2;\n } else {\n pushProtected(multilineStart, i + 3);\n multilineStart = -1;\n i += 2;\n }\n }\n // Check for inline code (only if not in multiline)\n else if (char === '`' && multilineStart === -1) {\n if (inlineStart === -1) {\n inlineStart = i;\n } else {\n pushProtected(inlineStart, i + 1);\n inlineStart = -1;\n }\n }\n // Check for HTML tags (only if not in code block)\n else if (char === '<' && multilineStart === -1 && inlineStart === -1) {\n // Only match known HTML tags to avoid false positives with angle brackets\n // in markdown links (<Slides Demo>), math comparisons ($a < b$), etc.\n const rest = content.substring(i);\n const tagMatch = rest.match(\n /^<\\/?(span|div|p|br|hr|img|a|em|strong|b|i|u|s|sub|sup|code|pre|table|tr|td|th|thead|tbody|tfoot|ul|ol|li|dl|dt|dd|h[1-6]|blockquote|details|summary|figure|figcaption|section|article|aside|nav|header|footer|main|mark|del|ins|small|abbr|cite|dfn|kbd|samp|var|ruby|rt|rp|bdo|wbr|input|button|select|textarea|label|fieldset|legend|output|iframe|video|audio|source|canvas|svg|math|time)(?:\\s[^>]*)?\\/?>/i\n );\n if (tagMatch) {\n pushProtected(i, i + tagMatch[0].length);\n i += tagMatch[0].length - 1; // -1 because loop does i++\n }\n }\n }\n\n // Push remaining text\n if (lastIndex < content.length) {\n segments.push({ text: content.substring(lastIndex), isCode: false });\n }\n\n return segments;\n}\n\n/**\n * Escape mhchem commands in LaTeX expressions to ensure proper rendering.\n *\n * @param text Input string containing LaTeX expressions with mhchem commands\n * @returns String with escaped mhchem commands\n * @from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nfunction escapeMhchemCommands(text: string) {\n return text.replaceAll('$\\\\ce{', '$\\\\\\\\ce{').replaceAll('$\\\\pu{', '$\\\\\\\\pu{');\n}\n\nconst CURRENCY_REGEX = /(?<![\\\\$])\\$(?!\\$)(?=\\d+(?:,\\d{3})*(?:\\.\\d+)?(?:[KMBkmb])?(?:\\s|$|[^a-zA-Z\\d]))/g;\nconst NO_ESCAPED_DOLLAR_REGEX = /(?<![\\\\$])\\$(?!\\$)/g;\n// Match \\[...\\] and \\(...\\) as LaTeX delimiters, but exclude:\n// - !\\[...\\] (markdown image)\n// - \\[...\\]( (markdown link)\nconst DELIMITERS_REGEX = /(?<!!)\\\\\\[([\\S\\s]*?[^\\\\])\\\\](?!\\()|\\\\\\((.*?)\\\\\\)/g;\nconst ARRAY_COL_SPEC_OR_PIPE_REGEX = /(\\\\begin\\{(?:array|tabular[x*]?)\\}\\{[^}]*\\})|(?<!\\\\)\\|/g;\n// Display $$ allows multiline; inline $ forbids newlines (consistent with SINGLE_DOLLAR_REGEX)\nconst LATEX_BLOCK_REGEX = /\\$\\$([\\S\\s]*?)\\$\\$|(?<![\\\\$])\\$(?!\\$)((?:[^$\\n]|\\\\\\$)*?)(?<![\\\\`])\\$(?!\\$)/g;\nconst ESCAPE_TEXT_UNDERSCORES_REGEX = /\\\\text{([^}]*)}/g;\nconst SINGLE_DOLLAR_REGEX = /(?<![\\\\$])\\$(?!\\$)((?:[^$\\n]|\\\\[$])+?)(?<!\\\\)(?<!`)\\$(?!\\$)/g;\n\n/**\n * Escape currency dollar signs (e.g. $100, $1,000.50) so they are not\n * misinterpreted as LaTeX delimiters.\n *\n * The tricky part: a `$` followed by digits might still be inside a LaTeX\n * expression (e.g. `$8.29 \\text{ B} \\times 4$`). We detect this by checking\n * whether there is an odd number of unescaped `$` on the same line after the\n * current match — if so, the current `$` is a LaTeX opener, not currency.\n */\nfunction escapeCurrencyDollarSigns(text: string): string {\n const parts: string[] = [];\n let lastIndex = 0;\n const currencyMatches = Array.from(text.matchAll(CURRENCY_REGEX));\n\n for (let i = 0; i < currencyMatches.length; i++) {\n const match = currencyMatches[i];\n parts.push(text.substring(lastIndex, match.index));\n\n let needEscape = true;\n let restBeforeNextMatchOrEnd = '';\n if (i < currencyMatches.length - 1) {\n const nextMatch = currencyMatches[i + 1];\n if (nextMatch.index - match.index > 1) {\n restBeforeNextMatchOrEnd = text.substring(match.index + 1, nextMatch.index);\n }\n } else {\n restBeforeNextMatchOrEnd = text.substring(match.index + 1);\n }\n const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\\r\\n|\\r|\\n/g)[0];\n if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {\n const previousNewContent = parts.join('');\n const previousLastLineContent = previousNewContent.split(/\\r\\n|\\r|\\n/g).pop();\n const wholeLineBeforeNextMatchWithoutCurrentDollar = previousLastLineContent + firstLineBeforeNextMatch;\n if (\n Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0\n ) {\n needEscape = false;\n }\n }\n\n parts.push(needEscape ? '\\\\$' : '$');\n lastIndex = match.index + 1;\n }\n parts.push(text.substring(lastIndex));\n return parts.join('');\n}\n\n/**\n * Convert LaTeX bracket delimiters to dollar sign delimiters.\n * Converts \\[...\\] to $$...$$ and \\(...\\) to $...$\n *\n * @param text Input string containing LaTeX expressions\n * @returns String with LaTeX bracket delimiters converted to dollar sign delimiters\n * @modified from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nfunction convertLatexDelimiters(text: string): string {\n return text.replaceAll(\n DELIMITERS_REGEX,\n (match: string, squareBracket: string | undefined, roundBracket: string | undefined): string => {\n if (squareBracket !== undefined) {\n return `$$${squareBracket}$$`;\n } else if (roundBracket !== undefined) {\n return `$${roundBracket}$`;\n }\n return match;\n }\n );\n}\n\n/**\n * Helper function: replace unescaped pipes with \\vert in LaTeX math fragments\n * @from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nconst replaceUnescapedPipes = (formula: string): string =>\n // Use \\vert{} so the control sequence terminates before the next token.\n // Preserve `|` inside \\begin{array}{...} / \\begin{tabular}{...} column specifiers.\n formula.replaceAll(ARRAY_COL_SPEC_OR_PIPE_REGEX, (match, colSpec: string | undefined) =>\n colSpec !== undefined ? match : '\\\\vert{}'\n );\n/**\n * Escape pipes in LaTeX expressions to prevent them from being interpreted as\n * column separators in markdown tables.\n *\n * @param text Input string containing LaTeX expressions\n * @returns String with pipes escaped in LaTeX expressions\n * @modified from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nfunction escapeLatexPipes(text: string): string {\n return text.replaceAll(LATEX_BLOCK_REGEX, (match, display, inline) => {\n if (display !== undefined) return `$$${replaceUnescapedPipes(display)}$$`;\n if (inline !== undefined) return `$${replaceUnescapedPipes(inline)}$`;\n return match;\n });\n}\n\n/**\n * Escape unescaped underscores within \\text{...} commands in LaTeX expressions.\n * For example, \\text{node_domain} becomes \\text{node\\_domain},\n * but \\text{node\\_domain} remains \\text{node\\_domain}.\n *\n * @param text Input string that may contain LaTeX expressions\n * @returns String with unescaped underscores escaped within \\text{...} commands\n * @modified from https://github.com/lobehub/lobe-ui/blob/master/src/hooks/useMarkdown/latex.ts\n */\nfunction escapeTextUnderscores(text: string): string {\n return text.replaceAll(ESCAPE_TEXT_UNDERSCORES_REGEX, (_match, textContent: string) => {\n const escapedTextContent = textContent.replaceAll(/(?<!\\\\)_/g, '\\\\_');\n return `\\\\text{${escapedTextContent}}`;\n });\n}\n\n/**\n * Convert single dollar delimiters to double dollar delimiters.\n * e.g. $x^2$ → $$x^2$$\n */\nfunction convertSingleToDoubleDollar(text: string): string {\n return text.replaceAll(SINGLE_DOLLAR_REGEX, (_match, content: string) => `$$${content}$$`);\n}\n\nexport function preprocessLaTeX(str: string): string {\n // Return early if no LaTeX patterns are found\n if (!str.includes('$') && !str.includes('\\\\[') && !str.includes('\\\\(')) return str;\n\n // Step 1: split by code blocks\n const segments = splitByProtectedRegions(str);\n\n // Step 2: process each non-code segment through the LaTeX pipeline\n const result = segments.map((segment) => {\n if (segment.isCode) return segment.text;\n\n let text = segment.text;\n text = escapeMhchemCommands(text);\n text = escapeCurrencyDollarSigns(text);\n text = convertLatexDelimiters(text);\n text = escapeLatexPipes(text);\n text = escapeTextUnderscores(text);\n text = convertSingleToDoubleDollar(text);\n return text;\n });\n\n return result.join('');\n}\n","/**\n * Content preprocessing pipeline.\n *\n * Runs all preprocessors (built-in + user-supplied) in sequence before\n * the markdown string is handed to react-markdown. The built-in LaTeX\n * preprocessor always runs first, followed by any extra preprocessors\n * provided by the consumer.\n *\n * @module preprocessors\n */\n\nimport { AIMDContentPreprocessor } from './defs';\nimport { preprocessLaTeX } from './latex';\n\n/** Sequentially apply an array of preprocessor functions via left-fold. */\nfunction applyPreprocessors(value: string, ...fns: Array<AIMDContentPreprocessor>): string {\n return fns.reduce((result, fn) => fn(result), value);\n}\n\n/** Stable empty array to avoid re-renders when no extra preprocessors are given. */\nconst defaultExtraPreprocessors: AIMDContentPreprocessor[] = [];\n\n/**\n * Run the full preprocessing pipeline on raw markdown content.\n *\n * @param content - Raw markdown string.\n * @param extraPreprocessors - Optional user-supplied preprocessors appended after the built-in ones.\n * @returns The preprocessed markdown string ready for rendering.\n */\nexport default function preprocessAIMDContent(\n content: string,\n extraPreprocessors: AIMDContentPreprocessor[] = defaultExtraPreprocessors\n) {\n return applyPreprocessors(content, preprocessLaTeX, ...extraPreprocessors);\n}\n","/**\n * Core markdown rendering component.\n *\n * Wraps `react-markdown` with a curated set of remark and rehype plugins\n * for GFM, math/LaTeX, emoji, CJK support, and configurable extra syntax\n * extensions and display optimizations. Plugin selection is driven by the\n * {@link AIMarkdownRenderConfig} from context.\n *\n * @module components/MarkdownContent\n */\n\nimport { memo, useMemo } from 'react';\nimport ReactMarkdown, { Components } from 'react-markdown';\nimport rehypeKatex from 'rehype-katex';\nimport rehypeRaw from 'rehype-raw';\nimport rehypeUnwrapImages from 'rehype-unwrap-images';\nimport rehypeSanitize, { defaultSchema } from 'rehype-sanitize';\nimport remarkBreaks from 'remark-breaks';\nimport remarkCjkFriendly from 'remark-cjk-friendly';\nimport remarkCjkFriendlyGfmStrikethrough from 'remark-cjk-friendly-gfm-strikethrough';\nimport remarkEmoji from 'remark-emoji';\nimport remarkGfm from 'remark-gfm';\nimport remarkMath from 'remark-math';\nimport { remarkDefinitionList, defListHastHandlers } from 'remark-definition-list';\nimport remarkSupersub from 'remark-supersub';\nimport { remarkMark as remarkMarkHighlight } from 'remark-mark-highlight';\nimport remarkSqueezeParagraphs from 'remark-squeeze-paragraphs';\nimport remarkSmartypants from 'remark-smartypants';\nimport remarkPangu from 'remark-pangu';\nimport remarkRemoveComments from 'remark-remove-comments';\nimport { useAIMarkdownRenderState } from '../context';\nimport { AIMarkdownRenderDisplayOptimizeAbility, AIMarkdownRenderExtraSyntax } from '../defs';\n\n/** Maps display optimization abilities to their corresponding remark plugins. */\nconst DisplayOptimizeRemarkPluginMap = {\n [AIMarkdownRenderDisplayOptimizeAbility.REMOVE_COMMENTS]: remarkRemoveComments,\n [AIMarkdownRenderDisplayOptimizeAbility.SMARTYPANTS]: remarkSmartypants,\n [AIMarkdownRenderDisplayOptimizeAbility.PANGU]: remarkPangu,\n};\n\n/** Maps extra syntax extensions to their corresponding remark plugins. */\nconst ExtraSyntaxRemarkPluginMap = {\n [AIMarkdownRenderExtraSyntax.HIGHLIGHT]: remarkMarkHighlight,\n [AIMarkdownRenderExtraSyntax.DEFINITION_LIST]: remarkDefinitionList,\n [AIMarkdownRenderExtraSyntax.SUBSCRIPT]: remarkSupersub,\n};\n\n/** Stable empty object to avoid unnecessary re-renders when no custom components are given. */\nconst DefaultCustomComponents: Components = {};\n\ninterface AIMarkdownContentProps {\n /** Preprocessed markdown string to render. */\n content: string;\n /** Optional react-markdown component overrides (e.g. custom code block renderer). */\n customComponents?: Components;\n}\n\n/**\n * Internal component that assembles the remark/rehype plugin chain based on\n * the current render config and delegates to `ReactMarkdown`.\n */\nconst AIMarkdownContent = memo(({ content, customComponents }: AIMarkdownContentProps) => {\n const { config } = useAIMarkdownRenderState();\n\n // Resolve extra-syntax remark plugins and check if definition list HAST handlers are needed.\n const { extraSyntaxRemarkPlugins, enableDefinitionList } = useMemo(\n () => ({\n extraSyntaxRemarkPlugins: config.extraSyntaxSupported.map((syntax) => ExtraSyntaxRemarkPluginMap[syntax]),\n enableDefinitionList: config.extraSyntaxSupported.includes(AIMarkdownRenderExtraSyntax.DEFINITION_LIST),\n }),\n [config.extraSyntaxSupported]\n );\n\n const displayOptimizeRemarkPlugins = useMemo(() => {\n return config.displayOptimizeAbilities.map((ability) => DisplayOptimizeRemarkPluginMap[ability]);\n }, [config.displayOptimizeAbilities]);\n\n const usedComponents = useMemo(() => {\n return customComponents ? { ...DefaultCustomComponents, ...customComponents } : DefaultCustomComponents;\n }, [customComponents]);\n\n return (\n <ReactMarkdown\n remarkPlugins={[\n // --- Core plugins (always active) ---\n remarkGfm,\n [\n remarkMath,\n {\n // Disable single-dollar inline math to avoid conflicts with currency\n // signs and other dollar usages; the preprocessor converts $...$ to $$...$$.\n singleDollarTextMath: false,\n },\n ],\n // --- Configurable extra syntax plugins ---\n ...extraSyntaxRemarkPlugins,\n // --- Formatting & normalization ---\n remarkBreaks,\n remarkEmoji,\n remarkSqueezeParagraphs,\n remarkCjkFriendly,\n remarkCjkFriendlyGfmStrikethrough,\n // --- Configurable display optimizations ---\n ...displayOptimizeRemarkPlugins,\n ]}\n rehypePlugins={[\n // Allow raw HTML through so rehype-sanitize can handle it.\n [\n rehypeRaw,\n {\n passThrough: [],\n },\n ],\n // Sanitize HTML while allowing <mark> (highlight) and KaTeX class names.\n [\n rehypeSanitize,\n {\n ...defaultSchema,\n tagNames: [...(defaultSchema.tagNames || []), 'mark'],\n attributes: {\n ...defaultSchema.attributes,\n // The `language-*` regex is allowed by default.\n code: [['className', /^language-./, 'math-inline', 'math-display']],\n },\n },\n ],\n rehypeKatex,\n rehypeUnwrapImages,\n ]}\n remarkRehypeOptions={{\n allowDangerousHtml: true,\n handlers: {\n // Inject definition-list HAST handlers when the extension is active.\n ...(enableDefinitionList ? defListHastHandlers : {}),\n },\n }}\n components={usedComponents}\n // NOTE: The default `urlTransform` in Windows environments treats local\n // paths (e.g. `C:/...`) as unsafe. Uncomment the line below if needed:\n // urlTransform={(url: string) => url}\n >\n {content}\n </ReactMarkdown>\n );\n});\n\nAIMarkdownContent.displayName = 'AIMarkdownContent';\n\nexport default AIMarkdownContent;\n","/**\n * Hook for referential stability of deep-equal values.\n *\n * @module hooks/useStableValue\n */\n\nimport { useRef, useEffect } from 'react';\nimport isEqual from 'lodash/isEqual';\n\n/**\n * Returns a referentially stable version of `value`.\n *\n * On each render the new value is deep-compared (via `lodash/isEqual`) against\n * the previous one. If they are structurally equal the *previous* reference is\n * returned, preventing unnecessary re-renders in downstream `useMemo` / `useEffect`\n * consumers that depend on reference equality.\n *\n * @typeParam T - The value type.\n * @param value - The potentially new value to stabilize.\n * @returns The previous reference when deep-equal, otherwise the new value.\n *\n * @example\n * ```tsx\n * const stableConfig = useStableValue(config);\n * // stableConfig keeps the same reference as long as config is deep-equal.\n * ```\n */\nexport default function useStableValue<T>(value: T): T {\n const ref = useRef(value);\n\n // eslint-disable-next-line react-hooks/refs\n const prev = ref.current;\n const stableValue = isEqual(prev, value) ? prev : value;\n\n useEffect(() => {\n ref.current = stableValue;\n }, [stableValue]);\n\n return stableValue;\n}\n","/**\n * Default typography wrapper component.\n *\n * Renders a `<div>` container that applies CSS class names for the active\n * variant and color scheme, and sets the root font-size as an inline style.\n * The corresponding CSS custom properties are defined in the SCSS variant\n * files under `typography/variants/`.\n *\n * Consumers can replace this with a custom {@link AIMarkdownTypographyComponent}\n * via the `typography` prop on `<AIMarkdown>`.\n *\n * @module components/typography/Default\n */\n\nimport { memo } from 'react';\nimport type { AIMarkdownTypographyProps } from '../../defs';\n\nconst DefaultTypography = memo(({ children, fontSize, variant, colorScheme }: AIMarkdownTypographyProps) => (\n <div\n className={`aim-typography-root ${variant ?? ''} ${colorScheme ?? ''}`.trim()}\n style={{ width: '100%', fontSize }}\n >\n {children}\n </div>\n));\n\nDefaultTypography.displayName = 'DefaultTypography';\n\nexport default DefaultTypography;\n"],"mappings":"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","names":["useMemo","memo","AIMarkdownRenderExtraSyntax","AIMarkdownRenderDisplayOptimizeAbility","useMemo","jsx","useMemo","memo","jsx","jsx","useMemo","memo"]}
package/package.json CHANGED
@@ -1,21 +1,49 @@
1
1
  {
2
2
  "name": "@ai-react-markdown/core",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
+ "description": "Core rendering engine for ai-react-markdown — LaTeX preprocessing, plugin pipeline, and React components.",
4
5
  "publishConfig": {
5
6
  "access": "public"
6
7
  },
7
8
  "type": "module",
8
- "main": "./dist/index.js",
9
- "module": "./dist/index.mjs",
9
+ "license": "MIT",
10
+ "author": "Brian Lee <aiephoenixbl@gmail.com> (https://github.com/AIEPhoenix)",
11
+ "homepage": "https://github.com/AIEPhoenix/ai-react-markdown/tree/main/packages/core#readme",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/AIEPhoenix/ai-react-markdown.git",
15
+ "directory": "packages/core"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/AIEPhoenix/ai-react-markdown/issues"
19
+ },
20
+ "keywords": [
21
+ "react",
22
+ "markdown",
23
+ "ai",
24
+ "llm",
25
+ "latex",
26
+ "katex",
27
+ "gfm",
28
+ "streaming"
29
+ ],
30
+ "sideEffects": [
31
+ "**/*.css"
32
+ ],
33
+ "main": "./dist/index.cjs",
34
+ "module": "./dist/index.js",
10
35
  "types": "./dist/index.d.ts",
11
36
  "files": [
12
37
  "dist"
13
38
  ],
14
39
  "exports": {
15
40
  ".": {
16
- "types": "./dist/index.d.ts",
17
- "import": "./dist/index.mjs",
18
- "require": "./dist/index.js"
41
+ "types": {
42
+ "import": "./dist/index.d.ts",
43
+ "require": "./dist/index.d.cts"
44
+ },
45
+ "import": "./dist/index.js",
46
+ "require": "./dist/index.cjs"
19
47
  },
20
48
  "./typography/*.css": "./dist/typography/*.css"
21
49
  },