@huyooo/ai-chat-shared 0.2.13 → 0.2.14
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.d.ts +42 -12
- package/dist/index.js +335 -36
- package/dist/index.js.map +1 -1
- package/dist/styles.css +274 -22
- package/package.json +7 -3
- package/src/index.ts +15 -2
- package/src/markdown.ts +262 -6
- package/src/parser.ts +60 -30
- package/src/styles.css +274 -22
- package/src/types.ts +0 -13
- package/src/visual.ts +68 -0
package/dist/index.d.ts
CHANGED
|
@@ -22,17 +22,6 @@ interface CodeBlock extends ContentBlockBase {
|
|
|
22
22
|
}
|
|
23
23
|
/** 内容块联合类型 */
|
|
24
24
|
type ContentBlock = TextBlock | CodeBlock;
|
|
25
|
-
/** 工具渲染器 Props */
|
|
26
|
-
interface ToolRendererProps {
|
|
27
|
-
/** 工具名称 */
|
|
28
|
-
toolName: string;
|
|
29
|
-
/** 工具参数 */
|
|
30
|
-
toolArgs: Record<string, unknown>;
|
|
31
|
-
/** 工具返回结果 */
|
|
32
|
-
toolResult: unknown;
|
|
33
|
-
/** 执行状态 */
|
|
34
|
-
status: 'running' | 'completed' | 'error';
|
|
35
|
-
}
|
|
36
25
|
/** 天气数据 */
|
|
37
26
|
interface WeatherData {
|
|
38
27
|
city: string;
|
|
@@ -95,6 +84,12 @@ interface StreamParseState {
|
|
|
95
84
|
codeLanguage?: string;
|
|
96
85
|
/** 当前代码块内容 */
|
|
97
86
|
codeContent: string;
|
|
87
|
+
/** 当前代码块 ID(用于稳定 key,避免流式时每次 render 都生成新 id) */
|
|
88
|
+
codeBlockId: string | null;
|
|
89
|
+
/** 当前文本块内容(流式追加,避免把一句话拆成很多 text block) */
|
|
90
|
+
textContent: string;
|
|
91
|
+
/** 当前文本块 ID(稳定 key) */
|
|
92
|
+
textBlockId: string | null;
|
|
98
93
|
}
|
|
99
94
|
/** 创建初始流式解析状态 */
|
|
100
95
|
declare function createStreamParseState(): StreamParseState;
|
|
@@ -133,7 +128,28 @@ declare function getLanguageDisplayName(language?: string): string;
|
|
|
133
128
|
/**
|
|
134
129
|
* Markdown 渲染工具
|
|
135
130
|
* 提供统一的 Markdown 渲染函数,供 React 和 Vue 版本使用
|
|
131
|
+
* 支持 LaTeX 数学公式渲染和 Mermaid 图表
|
|
132
|
+
*/
|
|
133
|
+
/**
|
|
134
|
+
* Mermaid 源码编码成可安全放入 attribute 的 base64url(UTF-8)
|
|
135
|
+
* 给前端组件(非 markdown 渲染路径)复用,避免重复实现。
|
|
136
|
+
*/
|
|
137
|
+
declare function encodeMermaidCodeToBase64Url(code: string): string;
|
|
138
|
+
/**
|
|
139
|
+
* 初始化 Mermaid(仅需调用一次)
|
|
140
|
+
*/
|
|
141
|
+
declare function initMermaid(): void;
|
|
142
|
+
/**
|
|
143
|
+
* 渲染页面中的 Mermaid 图表
|
|
144
|
+
* 用户点击"显示图表"按钮后调用
|
|
145
|
+
* @param container 容器元素(可选,默认为 document)
|
|
146
|
+
*/
|
|
147
|
+
declare function renderMermaidDiagrams(container?: Element): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* 渲染 fenced 的 LaTeX 代码块(```latex / ```katex),用于“先代码、后渲染”的视图切换。
|
|
150
|
+
* 注意:这里只返回 HTML 字符串,容器样式由 `.latex-block` 控制。
|
|
136
151
|
*/
|
|
152
|
+
declare function renderLatexBlockToHtml(code: string): string;
|
|
137
153
|
/**
|
|
138
154
|
* 渲染 Markdown 为 HTML
|
|
139
155
|
* @param markdown Markdown 文本
|
|
@@ -141,4 +157,18 @@ declare function getLanguageDisplayName(language?: string): string;
|
|
|
141
157
|
*/
|
|
142
158
|
declare function renderMarkdown(markdown: string): string;
|
|
143
159
|
|
|
144
|
-
|
|
160
|
+
declare function isMermaidLanguage(lang?: string): boolean;
|
|
161
|
+
declare function isLatexLanguage(lang?: string): boolean;
|
|
162
|
+
declare function isLatexDocument(code: string): boolean;
|
|
163
|
+
declare function canVisualizeLatex(code: string): boolean;
|
|
164
|
+
declare function hasLatexDelimiters(code: string): boolean;
|
|
165
|
+
declare function shouldShowVisualToggle(lang?: string, code?: string): boolean;
|
|
166
|
+
/**
|
|
167
|
+
* 将 fenced 的 latex/katex/tex 代码块渲染成 HTML(复用 shared 的 renderMarkdown + latex 预处理)。
|
|
168
|
+
* - 如果是完整 LaTeX 文档:返回提示 + code block(避免 KaTeX 报错)
|
|
169
|
+
* - 如果已经带分隔符:直接渲染(避免二次包裹)
|
|
170
|
+
* - 否则:自动包一层 $$...$$ 作为 display 公式渲染
|
|
171
|
+
*/
|
|
172
|
+
declare function renderFencedLatexToHtml(code: string): string;
|
|
173
|
+
|
|
174
|
+
export { type CodeBlock, type CodeExecutionResult, type ContentBlock, type ContentBlockBase, type ContentBlockType, type FileOperationResult, type SearchResultItem, type StreamParseState, type TextBlock, type WeatherData, type WeatherForecastItem, canVisualizeLatex, createStreamParseState, encodeMermaidCodeToBase64Url, finishStreamParse, getLanguageDisplayName, hasLatexDelimiters, highlightCode, initMermaid, isLatexDocument, isLatexLanguage, isMermaidLanguage, parseContent, parseContentStream, renderFencedLatexToHtml, renderLatexBlockToHtml, renderMarkdown, renderMermaidDiagrams, shouldShowVisualToggle };
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ function parseContent(raw) {
|
|
|
13
13
|
if (match.index > lastIndex) {
|
|
14
14
|
const textContent = raw.slice(lastIndex, match.index);
|
|
15
15
|
if (textContent.trim()) {
|
|
16
|
-
blocks.push(createTextBlock(textContent));
|
|
16
|
+
blocks.push(createTextBlock(textContent, void 0, true));
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
const language = match[1] || void 0;
|
|
@@ -24,21 +24,21 @@ function parseContent(raw) {
|
|
|
24
24
|
if (lastIndex < raw.length) {
|
|
25
25
|
const textContent = raw.slice(lastIndex);
|
|
26
26
|
if (textContent.trim()) {
|
|
27
|
-
blocks.push(createTextBlock(textContent));
|
|
27
|
+
blocks.push(createTextBlock(textContent, void 0, true));
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
return blocks;
|
|
31
31
|
}
|
|
32
|
-
function createTextBlock(content) {
|
|
32
|
+
function createTextBlock(content, id, trimContent = true) {
|
|
33
33
|
return {
|
|
34
|
-
id: generateId(),
|
|
34
|
+
id: id || generateId(),
|
|
35
35
|
type: "text",
|
|
36
|
-
content: content.trim()
|
|
36
|
+
content: trimContent ? content.trim() : content
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
function createCodeBlock(content, language, filename) {
|
|
39
|
+
function createCodeBlock(content, language, filename, id) {
|
|
40
40
|
return {
|
|
41
|
-
id: generateId(),
|
|
41
|
+
id: id || generateId(),
|
|
42
42
|
type: "code",
|
|
43
43
|
content,
|
|
44
44
|
language,
|
|
@@ -51,25 +51,44 @@ function createStreamParseState() {
|
|
|
51
51
|
blocks: [],
|
|
52
52
|
inCodeBlock: false,
|
|
53
53
|
codeLanguage: void 0,
|
|
54
|
-
codeContent: ""
|
|
54
|
+
codeContent: "",
|
|
55
|
+
codeBlockId: null,
|
|
56
|
+
textContent: "",
|
|
57
|
+
textBlockId: null
|
|
55
58
|
};
|
|
56
59
|
}
|
|
60
|
+
function appendText(state, text) {
|
|
61
|
+
if (!text) return;
|
|
62
|
+
if (state.textBlockId === null) state.textBlockId = generateId();
|
|
63
|
+
state.textContent += text;
|
|
64
|
+
}
|
|
65
|
+
function flushTextBlock(state) {
|
|
66
|
+
if (!state.textContent.trim()) return;
|
|
67
|
+
state.blocks.push(createTextBlock(state.textContent, state.textBlockId || void 0, false));
|
|
68
|
+
state.textContent = "";
|
|
69
|
+
state.textBlockId = null;
|
|
70
|
+
}
|
|
57
71
|
function parseContentStream(chunk, state) {
|
|
58
|
-
const newState = { ...state };
|
|
72
|
+
const newState = { ...state, blocks: [...state.blocks] };
|
|
59
73
|
newState.buffer += chunk;
|
|
60
74
|
while (true) {
|
|
61
75
|
if (newState.inCodeBlock) {
|
|
62
76
|
const endIndex = newState.buffer.indexOf("```");
|
|
63
77
|
if (endIndex !== -1) {
|
|
64
78
|
newState.codeContent += newState.buffer.slice(0, endIndex);
|
|
65
|
-
newState.blocks.push(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
newState.blocks.push(
|
|
80
|
+
createCodeBlock(
|
|
81
|
+
newState.codeContent.trim(),
|
|
82
|
+
newState.codeLanguage,
|
|
83
|
+
void 0,
|
|
84
|
+
newState.codeBlockId || void 0
|
|
85
|
+
)
|
|
86
|
+
);
|
|
69
87
|
newState.buffer = newState.buffer.slice(endIndex + 3);
|
|
70
88
|
newState.inCodeBlock = false;
|
|
71
89
|
newState.codeLanguage = void 0;
|
|
72
90
|
newState.codeContent = "";
|
|
91
|
+
newState.codeBlockId = null;
|
|
73
92
|
} else {
|
|
74
93
|
newState.codeContent += newState.buffer;
|
|
75
94
|
newState.buffer = "";
|
|
@@ -79,9 +98,8 @@ function parseContentStream(chunk, state) {
|
|
|
79
98
|
const startIndex = newState.buffer.indexOf("```");
|
|
80
99
|
if (startIndex !== -1) {
|
|
81
100
|
const beforeCode = newState.buffer.slice(0, startIndex);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
101
|
+
appendText(newState, beforeCode);
|
|
102
|
+
flushTextBlock(newState);
|
|
85
103
|
const afterStart = newState.buffer.slice(startIndex + 3);
|
|
86
104
|
const newlineIndex = afterStart.indexOf("\n");
|
|
87
105
|
if (newlineIndex !== -1) {
|
|
@@ -89,6 +107,7 @@ function parseContentStream(chunk, state) {
|
|
|
89
107
|
newState.buffer = afterStart.slice(newlineIndex + 1);
|
|
90
108
|
newState.inCodeBlock = true;
|
|
91
109
|
newState.codeContent = "";
|
|
110
|
+
newState.codeBlockId = generateId();
|
|
92
111
|
} else {
|
|
93
112
|
break;
|
|
94
113
|
}
|
|
@@ -96,14 +115,10 @@ function parseContentStream(chunk, state) {
|
|
|
96
115
|
const lastBackticks = newState.buffer.lastIndexOf("`");
|
|
97
116
|
if (lastBackticks !== -1 && newState.buffer.length - lastBackticks < 3) {
|
|
98
117
|
const safeText = newState.buffer.slice(0, lastBackticks);
|
|
99
|
-
|
|
100
|
-
newState.blocks.push(createTextBlock(safeText));
|
|
101
|
-
}
|
|
118
|
+
appendText(newState, safeText);
|
|
102
119
|
newState.buffer = newState.buffer.slice(lastBackticks);
|
|
103
120
|
} else {
|
|
104
|
-
|
|
105
|
-
newState.blocks.push(createTextBlock(newState.buffer));
|
|
106
|
-
}
|
|
121
|
+
appendText(newState, newState.buffer);
|
|
107
122
|
newState.buffer = "";
|
|
108
123
|
}
|
|
109
124
|
break;
|
|
@@ -115,12 +130,19 @@ function parseContentStream(chunk, state) {
|
|
|
115
130
|
function finishStreamParse(state) {
|
|
116
131
|
const blocks = [...state.blocks];
|
|
117
132
|
if (state.inCodeBlock) {
|
|
118
|
-
blocks.push(
|
|
119
|
-
(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
blocks.push(
|
|
134
|
+
createCodeBlock(
|
|
135
|
+
(state.codeContent + state.buffer).trim(),
|
|
136
|
+
state.codeLanguage,
|
|
137
|
+
void 0,
|
|
138
|
+
state.codeBlockId || void 0
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
} else {
|
|
142
|
+
const merged = state.textContent + state.buffer;
|
|
143
|
+
if (merged.trim()) {
|
|
144
|
+
blocks.push(createTextBlock(merged, state.textBlockId || void 0, false));
|
|
145
|
+
}
|
|
124
146
|
}
|
|
125
147
|
return blocks;
|
|
126
148
|
}
|
|
@@ -174,16 +196,161 @@ function getLanguageDisplayName(language) {
|
|
|
174
196
|
// src/markdown.ts
|
|
175
197
|
import { marked, Renderer } from "marked";
|
|
176
198
|
import DOMPurify from "dompurify";
|
|
199
|
+
import katex from "katex";
|
|
200
|
+
import mermaid from "mermaid";
|
|
201
|
+
var mermaidInitialized = false;
|
|
202
|
+
function encodeBase64Utf8(text) {
|
|
203
|
+
const bytes = new TextEncoder().encode(text);
|
|
204
|
+
let binary = "";
|
|
205
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
206
|
+
const b64 = btoa(binary);
|
|
207
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
208
|
+
}
|
|
209
|
+
function encodeMermaidCodeToBase64Url(code) {
|
|
210
|
+
return encodeBase64Utf8(code);
|
|
211
|
+
}
|
|
212
|
+
function decodeBase64Utf8(b64) {
|
|
213
|
+
const normalized = b64.replace(/\s/g, "+");
|
|
214
|
+
let base64 = normalized.replace(/-/g, "+").replace(/_/g, "/");
|
|
215
|
+
const pad = base64.length % 4;
|
|
216
|
+
if (pad === 2) base64 += "==";
|
|
217
|
+
else if (pad === 3) base64 += "=";
|
|
218
|
+
else if (pad !== 0) {
|
|
219
|
+
}
|
|
220
|
+
const binary = atob(base64);
|
|
221
|
+
const bytes = new Uint8Array(binary.length);
|
|
222
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
223
|
+
return new TextDecoder().decode(bytes);
|
|
224
|
+
}
|
|
225
|
+
function initMermaid() {
|
|
226
|
+
if (mermaidInitialized) return;
|
|
227
|
+
mermaid.initialize({
|
|
228
|
+
startOnLoad: false,
|
|
229
|
+
theme: "dark",
|
|
230
|
+
securityLevel: "loose",
|
|
231
|
+
fontFamily: "inherit",
|
|
232
|
+
flowchart: {
|
|
233
|
+
useMaxWidth: true,
|
|
234
|
+
htmlLabels: true,
|
|
235
|
+
curve: "basis"
|
|
236
|
+
},
|
|
237
|
+
sequence: {
|
|
238
|
+
useMaxWidth: true
|
|
239
|
+
},
|
|
240
|
+
gantt: {
|
|
241
|
+
useMaxWidth: true
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
mermaidInitialized = true;
|
|
245
|
+
}
|
|
246
|
+
async function renderMermaidDiagrams(container) {
|
|
247
|
+
if (!mermaidInitialized) {
|
|
248
|
+
initMermaid();
|
|
249
|
+
}
|
|
250
|
+
const root = container || document;
|
|
251
|
+
const elements = root.querySelectorAll(".mermaid-placeholder:not(.mermaid-rendered):not(.mermaid-error-container)");
|
|
252
|
+
for (const el of elements) {
|
|
253
|
+
const b64 = el.getAttribute("data-mermaid-code-b64");
|
|
254
|
+
if (!b64) continue;
|
|
255
|
+
let code = "";
|
|
256
|
+
try {
|
|
257
|
+
code = decodeBase64Utf8(b64);
|
|
258
|
+
} catch (e) {
|
|
259
|
+
console.warn("Mermaid \u89E3\u7801\u5931\u8D25:", e);
|
|
260
|
+
el.innerHTML = `<div class="mermaid-error">\u56FE\u8868\u89E3\u7801\u5931\u8D25</div>`;
|
|
261
|
+
el.classList.remove("mermaid-placeholder");
|
|
262
|
+
el.classList.add("mermaid-error-container");
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
el.innerHTML = `<div class="mermaid-loading">\u6E32\u67D3\u4E2D...</div>`;
|
|
266
|
+
try {
|
|
267
|
+
const id = `mermaid-${Math.random().toString(36).slice(2, 11)}`;
|
|
268
|
+
const { svg } = await mermaid.render(id, code);
|
|
269
|
+
el.innerHTML = svg;
|
|
270
|
+
el.classList.remove("mermaid-placeholder");
|
|
271
|
+
el.classList.add("mermaid-rendered");
|
|
272
|
+
el.removeAttribute("data-mermaid-code-b64");
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.warn("Mermaid \u6E32\u67D3\u5931\u8D25:", error);
|
|
275
|
+
el.innerHTML = `<div class="mermaid-error">\u56FE\u8868\u6E32\u67D3\u5931\u8D25</div><pre class="mermaid-source">${code.replace(/</g, "<").replace(/>/g, ">")}</pre>`;
|
|
276
|
+
el.classList.remove("mermaid-placeholder");
|
|
277
|
+
el.classList.add("mermaid-error-container");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function renderLatex(latex, displayMode) {
|
|
282
|
+
try {
|
|
283
|
+
return katex.renderToString(latex, {
|
|
284
|
+
displayMode,
|
|
285
|
+
throwOnError: false,
|
|
286
|
+
strict: false,
|
|
287
|
+
trust: true,
|
|
288
|
+
output: "html"
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.warn("LaTeX \u6E32\u67D3\u5931\u8D25:", error);
|
|
292
|
+
return displayMode ? `<div class="latex-error">$$${latex}$$</div>` : `<span class="latex-error">$${latex}$</span>`;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function renderLatexBlockToHtml(code) {
|
|
296
|
+
return `<div class="latex-block chat-scrollbar">${renderLatex(code.trim(), true)}</div>`;
|
|
297
|
+
}
|
|
298
|
+
function preprocessLatex(text) {
|
|
299
|
+
const placeholders = /* @__PURE__ */ new Map();
|
|
300
|
+
let counter = 0;
|
|
301
|
+
const createPlaceholder = () => {
|
|
302
|
+
const placeholder = `<!--LATEX:${counter++}-->`;
|
|
303
|
+
return placeholder;
|
|
304
|
+
};
|
|
305
|
+
let processed = text;
|
|
306
|
+
processed = processed.replace(/\$\$([\s\S]*?)\$\$/g, (_, latex) => {
|
|
307
|
+
const placeholder = createPlaceholder();
|
|
308
|
+
const rendered = renderLatex(latex.trim(), true);
|
|
309
|
+
placeholders.set(placeholder, `<div class="latex-block chat-scrollbar">${rendered}</div>`);
|
|
310
|
+
return placeholder;
|
|
311
|
+
});
|
|
312
|
+
processed = processed.replace(/\\\[([\s\S]*?)\\\]/g, (_, latex) => {
|
|
313
|
+
const placeholder = createPlaceholder();
|
|
314
|
+
const rendered = renderLatex(latex.trim(), true);
|
|
315
|
+
placeholders.set(placeholder, `<div class="latex-block chat-scrollbar">${rendered}</div>`);
|
|
316
|
+
return placeholder;
|
|
317
|
+
});
|
|
318
|
+
processed = processed.replace(/(?<!\$)\$(?!\$)((?:\\.|[^$\n])+?)\$(?!\$)/g, (_, latex) => {
|
|
319
|
+
const placeholder = createPlaceholder();
|
|
320
|
+
const rendered = renderLatex(latex.trim(), false);
|
|
321
|
+
placeholders.set(placeholder, `<span class="latex-inline">${rendered}</span>`);
|
|
322
|
+
return placeholder;
|
|
323
|
+
});
|
|
324
|
+
processed = processed.replace(/\\\(([\s\S]*?)\\\)/g, (_, latex) => {
|
|
325
|
+
const placeholder = createPlaceholder();
|
|
326
|
+
const rendered = renderLatex(latex.trim(), false);
|
|
327
|
+
placeholders.set(placeholder, `<span class="latex-inline">${rendered}</span>`);
|
|
328
|
+
return placeholder;
|
|
329
|
+
});
|
|
330
|
+
return { processed, placeholders };
|
|
331
|
+
}
|
|
332
|
+
function postprocessLatex(html, placeholders) {
|
|
333
|
+
let result = html;
|
|
334
|
+
placeholders.forEach((rendered, placeholder) => {
|
|
335
|
+
result = result.replace(placeholder, rendered);
|
|
336
|
+
});
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
177
339
|
function renderMarkdown(markdown) {
|
|
178
340
|
if (!markdown) return "";
|
|
341
|
+
const { processed, placeholders } = preprocessLatex(markdown);
|
|
179
342
|
const renderer = new Renderer();
|
|
180
343
|
renderer.code = (code, language) => {
|
|
344
|
+
const lang = (language || "plaintext").toLowerCase();
|
|
345
|
+
if (lang === "mermaid") {
|
|
346
|
+
const b64 = encodeBase64Utf8(code);
|
|
347
|
+
return `<div class="mermaid-placeholder" data-mermaid-code-b64="${b64}"><div class="mermaid-loading">\u52A0\u8F7D\u56FE\u8868\u4E2D...</div></div>`;
|
|
348
|
+
}
|
|
181
349
|
const highlighted = highlightCode(code, language);
|
|
182
|
-
|
|
183
|
-
return `<pre class="markdown-code-block"><code class="language-${lang}">${highlighted}</code></pre>`;
|
|
350
|
+
return `<pre class="markdown-code-block chat-scrollbar"><code class="language-${lang}">${highlighted}</code></pre>`;
|
|
184
351
|
};
|
|
185
352
|
renderer.table = (header, body) => {
|
|
186
|
-
return `<div class="markdown-table-wrapper"><table class="markdown-table"><thead>${header}</thead><tbody>${body}</tbody></table></div>`;
|
|
353
|
+
return `<div class="markdown-table-wrapper chat-scrollbar"><table class="markdown-table"><thead>${header}</thead><tbody>${body}</tbody></table></div>`;
|
|
187
354
|
};
|
|
188
355
|
renderer.link = (href, title, text) => {
|
|
189
356
|
const titleAttr = title ? ` title="${title}"` : "";
|
|
@@ -203,7 +370,8 @@ function renderMarkdown(markdown) {
|
|
|
203
370
|
gfm: true
|
|
204
371
|
// GitHub Flavored Markdown
|
|
205
372
|
});
|
|
206
|
-
let html = marked(
|
|
373
|
+
let html = marked(processed, { renderer });
|
|
374
|
+
html = postprocessLatex(html, placeholders);
|
|
207
375
|
html = DOMPurify.sanitize(html, {
|
|
208
376
|
ALLOWED_TAGS: [
|
|
209
377
|
"p",
|
|
@@ -234,7 +402,39 @@ function renderMarkdown(markdown) {
|
|
|
234
402
|
"img",
|
|
235
403
|
"hr",
|
|
236
404
|
"div",
|
|
237
|
-
"span"
|
|
405
|
+
"span",
|
|
406
|
+
// KaTeX 相关标签
|
|
407
|
+
"math",
|
|
408
|
+
"semantics",
|
|
409
|
+
"mrow",
|
|
410
|
+
"mi",
|
|
411
|
+
"mn",
|
|
412
|
+
"mo",
|
|
413
|
+
"msup",
|
|
414
|
+
"msub",
|
|
415
|
+
"mfrac",
|
|
416
|
+
"mroot",
|
|
417
|
+
"msqrt",
|
|
418
|
+
"mtext",
|
|
419
|
+
"mspace",
|
|
420
|
+
"mtable",
|
|
421
|
+
"mtr",
|
|
422
|
+
"mtd",
|
|
423
|
+
"annotation",
|
|
424
|
+
"mover",
|
|
425
|
+
"munder",
|
|
426
|
+
"munderover",
|
|
427
|
+
"menclose",
|
|
428
|
+
"mpadded",
|
|
429
|
+
"svg",
|
|
430
|
+
"line",
|
|
431
|
+
"path",
|
|
432
|
+
"rect",
|
|
433
|
+
"circle",
|
|
434
|
+
"g",
|
|
435
|
+
"use",
|
|
436
|
+
"defs",
|
|
437
|
+
"symbol"
|
|
238
438
|
],
|
|
239
439
|
ALLOWED_ATTR: [
|
|
240
440
|
"href",
|
|
@@ -245,18 +445,117 @@ function renderMarkdown(markdown) {
|
|
|
245
445
|
"src",
|
|
246
446
|
"alt",
|
|
247
447
|
"width",
|
|
248
|
-
"height"
|
|
249
|
-
|
|
448
|
+
"height",
|
|
449
|
+
// Mermaid 相关属性
|
|
450
|
+
"data-mermaid-code-b64",
|
|
451
|
+
// KaTeX 相关属性
|
|
452
|
+
"style",
|
|
453
|
+
"xmlns",
|
|
454
|
+
"viewBox",
|
|
455
|
+
"preserveAspectRatio",
|
|
456
|
+
"d",
|
|
457
|
+
"x",
|
|
458
|
+
"y",
|
|
459
|
+
"x1",
|
|
460
|
+
"y1",
|
|
461
|
+
"x2",
|
|
462
|
+
"y2",
|
|
463
|
+
"r",
|
|
464
|
+
"cx",
|
|
465
|
+
"cy",
|
|
466
|
+
"fill",
|
|
467
|
+
"stroke",
|
|
468
|
+
"stroke-width",
|
|
469
|
+
"transform",
|
|
470
|
+
"xlink:href",
|
|
471
|
+
"aria-hidden",
|
|
472
|
+
"focusable",
|
|
473
|
+
"role",
|
|
474
|
+
"mathvariant",
|
|
475
|
+
"encoding",
|
|
476
|
+
"stretchy",
|
|
477
|
+
"fence",
|
|
478
|
+
"separator",
|
|
479
|
+
"lspace",
|
|
480
|
+
"rspace",
|
|
481
|
+
"minsize",
|
|
482
|
+
"maxsize",
|
|
483
|
+
"accent",
|
|
484
|
+
"accentunder"
|
|
485
|
+
],
|
|
486
|
+
ADD_ATTR: ["xmlns:xlink"]
|
|
250
487
|
});
|
|
251
488
|
return html;
|
|
252
489
|
}
|
|
490
|
+
|
|
491
|
+
// src/visual.ts
|
|
492
|
+
function normalizeLanguage(lang) {
|
|
493
|
+
return (lang || "").trim().toLowerCase();
|
|
494
|
+
}
|
|
495
|
+
function isMermaidLanguage(lang) {
|
|
496
|
+
return normalizeLanguage(lang) === "mermaid";
|
|
497
|
+
}
|
|
498
|
+
function isLatexLanguage(lang) {
|
|
499
|
+
const l = normalizeLanguage(lang);
|
|
500
|
+
return l === "latex" || l === "katex" || l === "tex";
|
|
501
|
+
}
|
|
502
|
+
function isLatexDocument(code) {
|
|
503
|
+
const t = (code || "").trim();
|
|
504
|
+
return /\\documentclass\b|\\usepackage\b|\\begin\{document\}|\\end\{document\}/.test(t);
|
|
505
|
+
}
|
|
506
|
+
function canVisualizeLatex(code) {
|
|
507
|
+
const t = (code || "").trim();
|
|
508
|
+
if (!t) return false;
|
|
509
|
+
if (isLatexDocument(t)) return false;
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
function hasLatexDelimiters(code) {
|
|
513
|
+
const t = (code || "").trim();
|
|
514
|
+
if (!t) return false;
|
|
515
|
+
return /\$\$[\s\S]*\$\$/.test(t) || /\\\[[\s\S]*\\\]/.test(t) || /\\\([\s\S]*\\\)/.test(t) || /(?<!\$)\$(?!\$)[\s\S]*?\$(?!\$)/.test(t);
|
|
516
|
+
}
|
|
517
|
+
function shouldShowVisualToggle(lang, code) {
|
|
518
|
+
if (isMermaidLanguage(lang)) return true;
|
|
519
|
+
if (isLatexLanguage(lang)) return canVisualizeLatex(code || "");
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
function renderFencedLatexToHtml(code) {
|
|
523
|
+
const t = (code || "").trim();
|
|
524
|
+
if (!t) return "";
|
|
525
|
+
if (!canVisualizeLatex(t)) {
|
|
526
|
+
return renderMarkdown(
|
|
527
|
+
`> \u4E0D\u652F\u6301\u5C06\u5B8C\u6574 LaTeX \u6587\u6863\u4F5C\u4E3A\u516C\u5F0F\u6E32\u67D3\uFF0C\u8BF7\u5207\u6362\u5230\u4EE3\u7801\u89C6\u56FE\u67E5\u770B\u3002
|
|
528
|
+
|
|
529
|
+
\`\`\`latex
|
|
530
|
+
${t}
|
|
531
|
+
\`\`\``
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
if (hasLatexDelimiters(t)) {
|
|
535
|
+
return renderMarkdown(t);
|
|
536
|
+
}
|
|
537
|
+
return renderMarkdown(`$$
|
|
538
|
+
${t}
|
|
539
|
+
$$`);
|
|
540
|
+
}
|
|
253
541
|
export {
|
|
542
|
+
canVisualizeLatex,
|
|
254
543
|
createStreamParseState,
|
|
544
|
+
encodeMermaidCodeToBase64Url,
|
|
255
545
|
finishStreamParse,
|
|
256
546
|
getLanguageDisplayName,
|
|
547
|
+
hasLatexDelimiters,
|
|
257
548
|
highlightCode,
|
|
549
|
+
initMermaid,
|
|
550
|
+
isLatexDocument,
|
|
551
|
+
isLatexLanguage,
|
|
552
|
+
isMermaidLanguage,
|
|
258
553
|
parseContent,
|
|
259
554
|
parseContentStream,
|
|
260
|
-
|
|
555
|
+
renderFencedLatexToHtml,
|
|
556
|
+
renderLatexBlockToHtml,
|
|
557
|
+
renderMarkdown,
|
|
558
|
+
renderMermaidDiagrams,
|
|
559
|
+
shouldShowVisualToggle
|
|
261
560
|
};
|
|
262
561
|
//# sourceMappingURL=index.js.map
|