@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.cjs CHANGED
@@ -57,18 +57,18 @@ var AIMarkdownRenderDisplayOptimizeAbility = /* @__PURE__ */ ((AIMarkdownRenderD
57
57
  AIMarkdownRenderDisplayOptimizeAbility2["PANGU"] = "PANGU";
58
58
  return AIMarkdownRenderDisplayOptimizeAbility2;
59
59
  })(AIMarkdownRenderDisplayOptimizeAbility || {});
60
- var defaultMRMarkdownRenderConfig = {
61
- extraSyntaxSupported: [
60
+ var defaultAIMarkdownRenderConfig = Object.freeze({
61
+ extraSyntaxSupported: Object.freeze([
62
62
  "HIGHLIGHT" /* HIGHLIGHT */,
63
63
  "DEFINITION_LIST" /* DEFINITION_LIST */,
64
64
  "SUBSCRIPT" /* SUBSCRIPT */
65
- ],
66
- displayOptimizeAbilities: [
65
+ ]),
66
+ displayOptimizeAbilities: Object.freeze([
67
67
  "REMOVE_COMMENTS" /* REMOVE_COMMENTS */,
68
68
  "SMARTYPANTS" /* SMARTYPANTS */,
69
69
  "PANGU" /* PANGU */
70
- ]
71
- };
70
+ ])
71
+ });
72
72
 
73
73
  // src/context.tsx
74
74
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -93,11 +93,11 @@ var AIMarkdownRenderStateProvider = ({
93
93
  children
94
94
  }) => {
95
95
  const mergedConfig = (0, import_react.useMemo)(
96
- () => config ? (0, import_mergeWith.default)((0, import_cloneDeep.default)(defaultMRMarkdownRenderConfig), config, configMergeCustomizer) : defaultMRMarkdownRenderConfig,
96
+ () => config ? (0, import_mergeWith.default)((0, import_cloneDeep.default)(defaultAIMarkdownRenderConfig), config, configMergeCustomizer) : defaultAIMarkdownRenderConfig,
97
97
  [config]
98
98
  );
99
99
  const state = (0, import_react.useMemo)(
100
- () => ({
100
+ () => Object.freeze({
101
101
  streaming,
102
102
  fontSize,
103
103
  config: mergedConfig,
@@ -110,21 +110,27 @@ var AIMarkdownRenderStateProvider = ({
110
110
  var context_default = AIMarkdownRenderStateProvider;
111
111
 
112
112
  // src/preprocessors/latex.ts
113
- function escapeMhchemCommands(text) {
114
- return text.replaceAll("$\\ce{", "$\\\\ce{").replaceAll("$\\pu{", "$\\\\pu{");
115
- }
116
- function findCodeBlockRegions(content) {
117
- const regions = [];
113
+ function splitByProtectedRegions(content) {
114
+ const segments = [];
115
+ let lastIndex = 0;
118
116
  let inlineStart = -1;
119
117
  let multilineStart = -1;
118
+ function pushProtected(start, end) {
119
+ if (start > lastIndex) {
120
+ segments.push({ text: content.substring(lastIndex, start), isCode: false });
121
+ }
122
+ segments.push({ text: content.substring(start, end), isCode: true });
123
+ lastIndex = end;
124
+ }
120
125
  for (let i = 0; i < content.length; i++) {
121
126
  const char = content[i];
122
127
  if (char === "`" && i + 2 < content.length && content[i + 1] === "`" && content[i + 2] === "`") {
123
128
  if (multilineStart === -1) {
129
+ inlineStart = -1;
124
130
  multilineStart = i;
125
131
  i += 2;
126
132
  } else {
127
- regions.push([multilineStart, i + 2]);
133
+ pushProtected(multilineStart, i + 3);
128
134
  multilineStart = -1;
129
135
  i += 2;
130
136
  }
@@ -132,43 +138,71 @@ function findCodeBlockRegions(content) {
132
138
  if (inlineStart === -1) {
133
139
  inlineStart = i;
134
140
  } else {
135
- regions.push([inlineStart, i]);
141
+ pushProtected(inlineStart, i + 1);
136
142
  inlineStart = -1;
137
143
  }
144
+ } else if (char === "<" && multilineStart === -1 && inlineStart === -1) {
145
+ const rest = content.substring(i);
146
+ const tagMatch = rest.match(
147
+ /^<\/?(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
148
+ );
149
+ if (tagMatch) {
150
+ pushProtected(i, i + tagMatch[0].length);
151
+ i += tagMatch[0].length - 1;
152
+ }
138
153
  }
139
154
  }
140
- return regions;
141
- }
142
- function isInCodeBlock(position, codeRegions) {
143
- let left = 0;
144
- let right = codeRegions.length - 1;
145
- while (left <= right) {
146
- const mid = Math.floor((left + right) / 2);
147
- const [start, end] = codeRegions[mid];
148
- if (position >= start && position <= end) {
149
- return true;
150
- } else if (position < start) {
151
- right = mid - 1;
152
- } else {
153
- left = mid + 1;
154
- }
155
+ if (lastIndex < content.length) {
156
+ segments.push({ text: content.substring(lastIndex), isCode: false });
155
157
  }
156
- return false;
158
+ return segments;
159
+ }
160
+ function escapeMhchemCommands(text) {
161
+ return text.replaceAll("$\\ce{", "$\\\\ce{").replaceAll("$\\pu{", "$\\\\pu{");
157
162
  }
158
163
  var CURRENCY_REGEX = /(?<![\\$])\$(?!\$)(?=\d+(?:,\d{3})*(?:\.\d+)?(?:[KMBkmb])?(?:\s|$|[^a-zA-Z\d]))/g;
159
164
  var NO_ESCAPED_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)/g;
160
- var DELIMITERS_REGEX = /\\\[([\S\s]*?[^\\])\\]|\\\((.*?)\\\)/g;
161
- var UNESCAPED_PIPES_REGEX = /(?<!\\)\|/g;
162
- var LATEX_BLOCK_REGEX = /\$\$([\S\s]*?)\$\$|(?<![\\$])\$(?!\$)((?:.|\n)*?)(?<![\\`])\$(?!\$)/g;
165
+ var DELIMITERS_REGEX = /(?<!!)\\\[([\S\s]*?[^\\])\\](?!\()|\\\((.*?)\\\)/g;
166
+ var ARRAY_COL_SPEC_OR_PIPE_REGEX = /(\\begin\{(?:array|tabular[x*]?)\}\{[^}]*\})|(?<!\\)\|/g;
167
+ var LATEX_BLOCK_REGEX = /\$\$([\S\s]*?)\$\$|(?<![\\$])\$(?!\$)((?:[^$\n]|\\\$)*?)(?<![\\`])\$(?!\$)/g;
163
168
  var ESCAPE_TEXT_UNDERSCORES_REGEX = /\\text{([^}]*)}/g;
164
169
  var SINGLE_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)((?:[^$\n]|\\[$])+?)(?<!\\)(?<!`)\$(?!\$)/g;
165
- function convertLatexDelimiters(text, codeRegions) {
170
+ function escapeCurrencyDollarSigns(text) {
171
+ const parts = [];
172
+ let lastIndex = 0;
173
+ const currencyMatches = Array.from(text.matchAll(CURRENCY_REGEX));
174
+ for (let i = 0; i < currencyMatches.length; i++) {
175
+ const match = currencyMatches[i];
176
+ parts.push(text.substring(lastIndex, match.index));
177
+ let needEscape = true;
178
+ let restBeforeNextMatchOrEnd = "";
179
+ if (i < currencyMatches.length - 1) {
180
+ const nextMatch = currencyMatches[i + 1];
181
+ if (nextMatch.index - match.index > 1) {
182
+ restBeforeNextMatchOrEnd = text.substring(match.index + 1, nextMatch.index);
183
+ }
184
+ } else {
185
+ restBeforeNextMatchOrEnd = text.substring(match.index + 1);
186
+ }
187
+ const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\r\n|\r|\n/g)[0];
188
+ if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
189
+ const previousNewContent = parts.join("");
190
+ const previousLastLineContent = previousNewContent.split(/\r\n|\r|\n/g).pop();
191
+ const wholeLineBeforeNextMatchWithoutCurrentDollar = previousLastLineContent + firstLineBeforeNextMatch;
192
+ if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
193
+ needEscape = false;
194
+ }
195
+ }
196
+ parts.push(needEscape ? "\\$" : "$");
197
+ lastIndex = match.index + 1;
198
+ }
199
+ parts.push(text.substring(lastIndex));
200
+ return parts.join("");
201
+ }
202
+ function convertLatexDelimiters(text) {
166
203
  return text.replaceAll(
167
204
  DELIMITERS_REGEX,
168
- (match, squareBracket, roundBracket, index) => {
169
- if (isInCodeBlock(index, codeRegions)) {
170
- return match;
171
- }
205
+ (match, squareBracket, roundBracket) => {
172
206
  if (squareBracket !== void 0) {
173
207
  return `$$${squareBracket}$$`;
174
208
  } else if (roundBracket !== void 0) {
@@ -179,83 +213,43 @@ function convertLatexDelimiters(text, codeRegions) {
179
213
  );
180
214
  }
181
215
  var replaceUnescapedPipes = (formula) => (
182
- // Use \vert{} so the control sequence terminates before the next token
183
- formula.replaceAll(UNESCAPED_PIPES_REGEX, "\\vert{}")
216
+ // Use \vert{} so the control sequence terminates before the next token.
217
+ // Preserve `|` inside \begin{array}{...} / \begin{tabular}{...} column specifiers.
218
+ formula.replaceAll(
219
+ ARRAY_COL_SPEC_OR_PIPE_REGEX,
220
+ (match, colSpec) => colSpec !== void 0 ? match : "\\vert{}"
221
+ )
184
222
  );
185
- function escapeLatexPipes(text, codeRegions) {
186
- return text.replaceAll(LATEX_BLOCK_REGEX, (match, display, inline, index) => {
187
- if (isInCodeBlock(index, codeRegions)) {
188
- return match;
189
- }
223
+ function escapeLatexPipes(text) {
224
+ return text.replaceAll(LATEX_BLOCK_REGEX, (match, display, inline) => {
190
225
  if (display !== void 0) return `$$${replaceUnescapedPipes(display)}$$`;
191
226
  if (inline !== void 0) return `$${replaceUnescapedPipes(inline)}$`;
192
227
  return match;
193
228
  });
194
229
  }
195
- function escapeTextUnderscores(text, codeRegions) {
196
- return text.replaceAll(ESCAPE_TEXT_UNDERSCORES_REGEX, (match, textContent, index) => {
197
- if (isInCodeBlock(index, codeRegions)) {
198
- return match;
199
- }
230
+ function escapeTextUnderscores(text) {
231
+ return text.replaceAll(ESCAPE_TEXT_UNDERSCORES_REGEX, (_match, textContent) => {
200
232
  const escapedTextContent = textContent.replaceAll(/(?<!\\)_/g, "\\_");
201
233
  return `\\text{${escapedTextContent}}`;
202
234
  });
203
235
  }
236
+ function convertSingleToDoubleDollar(text) {
237
+ return text.replaceAll(SINGLE_DOLLAR_REGEX, (_match, content) => `$$${content}$$`);
238
+ }
204
239
  function preprocessLaTeX(str) {
205
240
  if (!str.includes("$") && !str.includes("\\[") && !str.includes("\\(")) return str;
206
- let processed = str;
207
- processed = escapeMhchemCommands(processed);
208
- const codeRegions = findCodeBlockRegions(processed);
209
- const parts = [];
210
- let lastIndex = 0;
211
- const currencyMatchesIterator = processed.matchAll(CURRENCY_REGEX);
212
- const currencyMatches = Array.from(currencyMatchesIterator);
213
- for (let i = 0; i < currencyMatches.length; i++) {
214
- const match = currencyMatches[i];
215
- parts.push(processed.substring(lastIndex, match.index));
216
- let needEscape = true;
217
- if (!isInCodeBlock(match.index, codeRegions)) {
218
- let restBeforeNextMatchOrEnd = "";
219
- if (i < currencyMatches.length - 1) {
220
- const nextMatch = currencyMatches[i + 1];
221
- if (nextMatch.index - match.index > 1) {
222
- restBeforeNextMatchOrEnd = processed.substring(match.index + 1, nextMatch.index);
223
- }
224
- } else {
225
- restBeforeNextMatchOrEnd = processed.substring(match.index + 1, processed.length);
226
- }
227
- const firstLineBeforeNextMatch = restBeforeNextMatchOrEnd.split(/\r\n|\r|\n/g)[0];
228
- if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
229
- const previousNewCotent = parts.join("");
230
- const previousLastLineContent = previousNewCotent.split(/\r\n|\r|\n/g).pop();
231
- const wholeLineBeforeNextMatchWithoutCurrentDollar = previousLastLineContent + firstLineBeforeNextMatch;
232
- if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
233
- needEscape = false;
234
- }
235
- }
236
- } else {
237
- needEscape = false;
238
- }
239
- parts.push(needEscape ? "\\$" : "$");
240
- lastIndex = match.index + 1;
241
- }
242
- parts.push(processed.substring(lastIndex));
243
- processed = parts.join("");
244
- console.log("processed", processed);
245
- processed = convertLatexDelimiters(processed, codeRegions);
246
- processed = escapeLatexPipes(processed, codeRegions);
247
- processed = escapeTextUnderscores(processed, codeRegions);
248
- const result = [];
249
- lastIndex = 0;
250
- const singleDollarMatchesIterator = processed.matchAll(SINGLE_DOLLAR_REGEX);
251
- for (const match of singleDollarMatchesIterator) {
252
- if (!isInCodeBlock(match.index, codeRegions)) {
253
- result.push(processed.substring(lastIndex, match.index));
254
- result.push(`$$${match[1]}$$`);
255
- lastIndex = match.index + match[0].length;
256
- }
257
- }
258
- result.push(processed.substring(lastIndex));
241
+ const segments = splitByProtectedRegions(str);
242
+ const result = segments.map((segment) => {
243
+ if (segment.isCode) return segment.text;
244
+ let text = segment.text;
245
+ text = escapeMhchemCommands(text);
246
+ text = escapeCurrencyDollarSigns(text);
247
+ text = convertLatexDelimiters(text);
248
+ text = escapeLatexPipes(text);
249
+ text = escapeTextUnderscores(text);
250
+ text = convertSingleToDoubleDollar(text);
251
+ return text;
252
+ });
259
253
  return result.join("");
260
254
  }
261
255
 
@@ -319,28 +313,36 @@ var AIMarkdownContent = (0, import_react2.memo)(({ content, customComponents })
319
313
  import_react_markdown.default,
320
314
  {
321
315
  remarkPlugins: [
316
+ // --- Core plugins (always active) ---
322
317
  import_remark_gfm.default,
323
318
  [
324
319
  import_remark_math.default,
325
320
  {
321
+ // Disable single-dollar inline math to avoid conflicts with currency
322
+ // signs and other dollar usages; the preprocessor converts $...$ to $$...$$.
326
323
  singleDollarTextMath: false
327
324
  }
328
325
  ],
326
+ // --- Configurable extra syntax plugins ---
329
327
  ...extraSyntaxRemarkPlugins,
328
+ // --- Formatting & normalization ---
330
329
  import_remark_breaks.default,
331
330
  import_remark_emoji.default,
332
331
  import_remark_squeeze_paragraphs.default,
333
332
  import_remark_cjk_friendly.default,
334
333
  import_remark_cjk_friendly_gfm_strikethrough.default,
334
+ // --- Configurable display optimizations ---
335
335
  ...displayOptimizeRemarkPlugins
336
336
  ],
337
337
  rehypePlugins: [
338
+ // Allow raw HTML through so rehype-sanitize can handle it.
338
339
  [
339
340
  import_rehype_raw.default,
340
341
  {
341
342
  passThrough: []
342
343
  }
343
344
  ],
345
+ // Sanitize HTML while allowing <mark> (highlight) and KaTeX class names.
344
346
  [
345
347
  import_rehype_sanitize.default,
346
348
  {
@@ -359,6 +361,7 @@ var AIMarkdownContent = (0, import_react2.memo)(({ content, customComponents })
359
361
  remarkRehypeOptions: {
360
362
  allowDangerousHtml: true,
361
363
  handlers: {
364
+ // Inject definition-list HAST handlers when the extension is active.
362
365
  ...enableDefinitionList ? import_remark_definition_list.defListHastHandlers : {}
363
366
  }
364
367
  },
@@ -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":["import_react","AIMarkdownRenderExtraSyntax","AIMarkdownRenderDisplayOptimizeAbility","mergeWith","cloneDeep","import_react","import_jsx_runtime","remarkRemoveComments","remarkSmartypants","remarkPangu","remarkMarkHighlight","remarkSupersub","ReactMarkdown","remarkGfm","remarkMath","remarkBreaks","remarkEmoji","remarkSqueezeParagraphs","remarkCjkFriendly","remarkCjkFriendlyGfmStrikethrough","rehypeRaw","rehypeSanitize","rehypeKatex","rehypeUnwrapImages","import_react","isEqual","import_react","import_jsx_runtime","import_jsx_runtime"]}
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":["import_react","AIMarkdownRenderExtraSyntax","AIMarkdownRenderDisplayOptimizeAbility","mergeWith","cloneDeep","import_react","import_jsx_runtime","remarkRemoveComments","remarkSmartypants","remarkPangu","remarkMarkHighlight","remarkSupersub","ReactMarkdown","remarkGfm","remarkMath","remarkBreaks","remarkEmoji","remarkSqueezeParagraphs","remarkCjkFriendly","remarkCjkFriendlyGfmStrikethrough","rehypeRaw","rehypeSanitize","rehypeKatex","rehypeUnwrapImages","import_react","isEqual","import_react","import_jsx_runtime","import_jsx_runtime"]}