@will1123/lx-ui-utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -0
- package/dist/dist/index.esm.js +618 -0
- package/dist/dist/index.esm.min.js +431 -0
- package/dist/dist/index.esm.min.js.map +1 -0
- package/dist/dist/index.umd.js +622 -0
- package/dist/dist/index.umd.min.js +14 -0
- package/dist/dist/index.umd.min.js.map +1 -0
- package/dist/es/index.js +15 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/markdown.js +286 -0
- package/dist/es/markdown.js.map +1 -0
- package/dist/es/sse.js +186 -0
- package/dist/es/sse.js.map +1 -0
- package/dist/es/thinking.js +153 -0
- package/dist/es/thinking.js.map +1 -0
- package/dist/lib/index.js +15 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/markdown.js +286 -0
- package/dist/lib/markdown.js.map +1 -0
- package/dist/lib/sse.js +186 -0
- package/dist/lib/sse.js.map +1 -0
- package/dist/lib/thinking.js +153 -0
- package/dist/lib/thinking.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/markdown.d.ts +115 -0
- package/dist/types/markdown.d.ts.map +1 -0
- package/dist/types/sse.d.ts +70 -0
- package/dist/types/sse.d.ts.map +1 -0
- package/dist/types/thinking.d.ts +64 -0
- package/dist/types/thinking.d.ts.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
function extractThinking(text, config = {}) {
|
|
2
|
+
const { tagName = "think", includeTags = false, removeRest = false } = config;
|
|
3
|
+
const openTag = `<${tagName}>`;
|
|
4
|
+
const closeTag = `</${tagName}>`;
|
|
5
|
+
const openIndex = text.indexOf(openTag);
|
|
6
|
+
const closeIndex = text.indexOf(closeTag);
|
|
7
|
+
if (openIndex === -1 || closeIndex === -1) {
|
|
8
|
+
return {
|
|
9
|
+
thinking: "",
|
|
10
|
+
rest: text,
|
|
11
|
+
hasThinking: false
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const thinkingStart = openIndex + openTag.length;
|
|
15
|
+
const thinkingEnd = closeIndex;
|
|
16
|
+
const thinkingContent = text.slice(thinkingStart, thinkingEnd);
|
|
17
|
+
const beforeContent = text.slice(0, openIndex);
|
|
18
|
+
const afterContent = text.slice(closeIndex + closeTag.length);
|
|
19
|
+
const restContent = beforeContent + afterContent;
|
|
20
|
+
return {
|
|
21
|
+
thinking: includeTags ? `${openTag}${thinkingContent}${closeTag}` : thinkingContent,
|
|
22
|
+
rest: removeRest ? "" : restContent,
|
|
23
|
+
hasThinking: true
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
class ThinkingStreamExtractor {
|
|
27
|
+
constructor(config = {}) {
|
|
28
|
+
this.isComplete = false;
|
|
29
|
+
this.config = {
|
|
30
|
+
tagName: config.tagName || "think",
|
|
31
|
+
includeTags: config.includeTags || false,
|
|
32
|
+
removeRest: config.removeRest || false
|
|
33
|
+
};
|
|
34
|
+
this.state = {
|
|
35
|
+
inThinking: false,
|
|
36
|
+
tagBuffer: "",
|
|
37
|
+
contentBuffer: "",
|
|
38
|
+
beforeContent: "",
|
|
39
|
+
afterContent: ""
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 追加文本并提取思考内容
|
|
44
|
+
*/
|
|
45
|
+
append(text) {
|
|
46
|
+
if (this.isComplete) {
|
|
47
|
+
return {
|
|
48
|
+
thinking: this.getThinking(),
|
|
49
|
+
rest: this.getRest(),
|
|
50
|
+
isComplete: true
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const { tagName } = this.config;
|
|
54
|
+
const openTag = `<${tagName}>`;
|
|
55
|
+
const closeTag = `</${tagName}>`;
|
|
56
|
+
for (let i = 0; i < text.length; i++) {
|
|
57
|
+
const char = text[i];
|
|
58
|
+
if (!this.state.inThinking) {
|
|
59
|
+
this.state.tagBuffer += char;
|
|
60
|
+
if (this.state.tagBuffer.endsWith(openTag)) {
|
|
61
|
+
const tagStart = this.state.tagBuffer.length - openTag.length;
|
|
62
|
+
this.state.beforeContent += this.state.tagBuffer.slice(0, tagStart);
|
|
63
|
+
this.state.tagBuffer = "";
|
|
64
|
+
this.state.inThinking = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (this.state.tagBuffer.length > openTag.length) {
|
|
68
|
+
this.state.beforeContent += this.state.tagBuffer.slice(0, 1);
|
|
69
|
+
this.state.tagBuffer = this.state.tagBuffer.slice(1);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
this.state.tagBuffer += char;
|
|
73
|
+
if (this.state.tagBuffer.endsWith(closeTag)) {
|
|
74
|
+
const tagEnd = this.state.tagBuffer.length - closeTag.length;
|
|
75
|
+
this.state.contentBuffer += this.state.tagBuffer.slice(0, tagEnd);
|
|
76
|
+
this.state.tagBuffer = "";
|
|
77
|
+
this.state.inThinking = false;
|
|
78
|
+
this.isComplete = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (this.state.tagBuffer.length > closeTag.length) {
|
|
82
|
+
const overflow = this.state.tagBuffer.slice(0, 1);
|
|
83
|
+
this.state.contentBuffer += overflow;
|
|
84
|
+
this.state.tagBuffer = this.state.tagBuffer.slice(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!this.state.inThinking && this.state.tagBuffer) {
|
|
89
|
+
if (this.isComplete) {
|
|
90
|
+
this.state.afterContent += this.state.tagBuffer;
|
|
91
|
+
} else {
|
|
92
|
+
this.state.beforeContent += this.state.tagBuffer;
|
|
93
|
+
}
|
|
94
|
+
this.state.tagBuffer = "";
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
thinking: this.getThinking(),
|
|
98
|
+
rest: this.getRest(),
|
|
99
|
+
isComplete: this.isComplete
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 获取当前提取的思考内容
|
|
104
|
+
*/
|
|
105
|
+
getThinking() {
|
|
106
|
+
const { tagName, includeTags } = this.config;
|
|
107
|
+
const openTag = `<${tagName}>`;
|
|
108
|
+
const closeTag = `</${tagName}>`;
|
|
109
|
+
const content = this.state.contentBuffer;
|
|
110
|
+
if (includeTags) {
|
|
111
|
+
return this.state.inThinking ? `${openTag}${content}` : `${openTag}${content}${closeTag}`;
|
|
112
|
+
}
|
|
113
|
+
return content;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 获取剩余内容
|
|
117
|
+
*/
|
|
118
|
+
getRest() {
|
|
119
|
+
const { removeRest } = this.config;
|
|
120
|
+
if (removeRest) {
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
return this.state.beforeContent + this.state.afterContent;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 是否已完成提取
|
|
127
|
+
*/
|
|
128
|
+
complete() {
|
|
129
|
+
return this.isComplete;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 重置提取器
|
|
133
|
+
*/
|
|
134
|
+
reset() {
|
|
135
|
+
this.state = {
|
|
136
|
+
inThinking: false,
|
|
137
|
+
tagBuffer: "",
|
|
138
|
+
contentBuffer: "",
|
|
139
|
+
beforeContent: "",
|
|
140
|
+
afterContent: ""
|
|
141
|
+
};
|
|
142
|
+
this.isComplete = false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function createThinkingExtractor(config) {
|
|
146
|
+
return new ThinkingStreamExtractor(config);
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
ThinkingStreamExtractor,
|
|
150
|
+
createThinkingExtractor,
|
|
151
|
+
extractThinking
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=thinking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking.js","sources":["../../src/thinking.ts"],"sourcesContent":["/**\r\n * 思考过程提取配置\r\n */\r\nexport interface ThinkingExtractConfig {\r\n /** 思考标签名称(默认为 'think') */\r\n tagName?: string\r\n /** 是否提取完整标签内容(包含标签本身) */\r\n includeTags?: boolean\r\n /** 是否移除思考内容后的其余文本 */\r\n removeRest?: boolean\r\n}\r\n\r\n/**\r\n * 思考过程结果\r\n */\r\nexport interface ThinkingResult {\r\n /** 提取的思考内容 */\r\n thinking: string\r\n /** 移除思考后的剩余内容 */\r\n rest: string\r\n /** 是否找到思考内容 */\r\n hasThinking: boolean\r\n}\r\n\r\n/**\r\n * 思考标签匹配状态\r\n */\r\ninterface TagMatchState {\r\n /** 是否在思考标签内 */\r\n inThinking: boolean\r\n /** 已找到的标签部分 */\r\n tagBuffer: string\r\n /** 思考内容缓冲区 */\r\n contentBuffer: string\r\n /** 前置内容 */\r\n beforeContent: string\r\n /** 后置内容 */\r\n afterContent: string\r\n}\r\n\r\n/**\r\n * 从 AI 响应中提取思考过程(完整模式)\r\n */\r\nexport function extractThinking(\r\n text: string,\r\n config: ThinkingExtractConfig = {}\r\n): ThinkingResult {\r\n const { tagName = 'think', includeTags = false, removeRest = false } = config\r\n\r\n const openTag = `<${tagName}>`\r\n const closeTag = `</${tagName}>`\r\n\r\n const openIndex = text.indexOf(openTag)\r\n const closeIndex = text.indexOf(closeTag)\r\n\r\n // 没有找到思考标签\r\n if (openIndex === -1 || closeIndex === -1) {\r\n return {\r\n thinking: '',\r\n rest: text,\r\n hasThinking: false\r\n }\r\n }\r\n\r\n // 提取思考内容\r\n const thinkingStart = openIndex + openTag.length\r\n const thinkingEnd = closeIndex\r\n const thinkingContent = text.slice(thinkingStart, thinkingEnd)\r\n\r\n // 提取剩余内容\r\n const beforeContent = text.slice(0, openIndex)\r\n const afterContent = text.slice(closeIndex + closeTag.length)\r\n const restContent = beforeContent + afterContent\r\n\r\n // 返回结果\r\n return {\r\n thinking: includeTags\r\n ? `${openTag}${thinkingContent}${closeTag}`\r\n : thinkingContent,\r\n rest: removeRest ? '' : restContent,\r\n hasThinking: true\r\n }\r\n}\r\n\r\n/**\r\n * 流式提取思考过程(用于 SSE 流式响应)\r\n */\r\nexport class ThinkingStreamExtractor {\r\n private config: ThinkingExtractConfig\r\n private state: TagMatchState\r\n private isComplete: boolean = false\r\n\r\n constructor(config: ThinkingExtractConfig = {}) {\r\n this.config = {\r\n tagName: config.tagName || 'think',\r\n includeTags: config.includeTags || false,\r\n removeRest: config.removeRest || false\r\n }\r\n this.state = {\r\n inThinking: false,\r\n tagBuffer: '',\r\n contentBuffer: '',\r\n beforeContent: '',\r\n afterContent: ''\r\n }\r\n }\r\n\r\n /**\r\n * 追加文本并提取思考内容\r\n */\r\n append(text: string): { thinking: string; rest: string; isComplete: boolean } {\r\n if (this.isComplete) {\r\n return {\r\n thinking: this.getThinking(),\r\n rest: this.getRest(),\r\n isComplete: true\r\n }\r\n }\r\n\r\n const { tagName } = this.config\r\n const openTag = `<${tagName}>`\r\n const closeTag = `</${tagName}>`\r\n\r\n // 逐字符处理\r\n for (let i = 0; i < text.length; i++) {\r\n const char = text[i]\r\n\r\n if (!this.state.inThinking) {\r\n // 查找开始标签\r\n this.state.tagBuffer += char\r\n\r\n // 检查是否匹配到开始标签\r\n if (this.state.tagBuffer.endsWith(openTag)) {\r\n // 移除已匹配的标签部分\r\n const tagStart = this.state.tagBuffer.length - openTag.length\r\n this.state.beforeContent += this.state.tagBuffer.slice(0, tagStart)\r\n this.state.tagBuffer = ''\r\n this.state.inThinking = true\r\n continue\r\n }\r\n\r\n // 如果缓冲区过长,移除不匹配的部分\r\n if (this.state.tagBuffer.length > openTag.length) {\r\n this.state.beforeContent += this.state.tagBuffer.slice(0, 1)\r\n this.state.tagBuffer = this.state.tagBuffer.slice(1)\r\n }\r\n } else {\r\n // 查找结束标签\r\n this.state.tagBuffer += char\r\n\r\n // 检查是否匹配到结束标签\r\n if (this.state.tagBuffer.endsWith(closeTag)) {\r\n // 保存思考内容(不包含结束标签)\r\n const tagEnd = this.state.tagBuffer.length - closeTag.length\r\n this.state.contentBuffer += this.state.tagBuffer.slice(0, tagEnd)\r\n this.state.tagBuffer = ''\r\n this.state.inThinking = false\r\n this.isComplete = true\r\n break\r\n }\r\n\r\n // 如果缓冲区过长,将内容移到思考缓冲区\r\n if (this.state.tagBuffer.length > closeTag.length) {\r\n const overflow = this.state.tagBuffer.slice(0, 1)\r\n this.state.contentBuffer += overflow\r\n this.state.tagBuffer = this.state.tagBuffer.slice(1)\r\n }\r\n }\r\n }\r\n\r\n // 如果不在思考标签内,将缓冲区内容移到前置/后置内容\r\n if (!this.state.inThinking && this.state.tagBuffer) {\r\n if (this.isComplete) {\r\n this.state.afterContent += this.state.tagBuffer\r\n } else {\r\n this.state.beforeContent += this.state.tagBuffer\r\n }\r\n this.state.tagBuffer = ''\r\n }\r\n\r\n return {\r\n thinking: this.getThinking(),\r\n rest: this.getRest(),\r\n isComplete: this.isComplete\r\n }\r\n }\r\n\r\n /**\r\n * 获取当前提取的思考内容\r\n */\r\n getThinking(): string {\r\n const { tagName, includeTags } = this.config\r\n const openTag = `<${tagName}>`\r\n const closeTag = `</${tagName}>`\r\n\r\n const content = this.state.contentBuffer\r\n\r\n if (includeTags) {\r\n // 如果还在标签内,不添加闭合标签\r\n return this.state.inThinking\r\n ? `${openTag}${content}`\r\n : `${openTag}${content}${closeTag}`\r\n }\r\n\r\n return content\r\n }\r\n\r\n /**\r\n * 获取剩余内容\r\n */\r\n getRest(): string {\r\n const { removeRest } = this.config\r\n if (removeRest) {\r\n return ''\r\n }\r\n return this.state.beforeContent + this.state.afterContent\r\n }\r\n\r\n /**\r\n * 是否已完成提取\r\n */\r\n complete(): boolean {\r\n return this.isComplete\r\n }\r\n\r\n /**\r\n * 重置提取器\r\n */\r\n reset(): void {\r\n this.state = {\r\n inThinking: false,\r\n tagBuffer: '',\r\n contentBuffer: '',\r\n beforeContent: '',\r\n afterContent: ''\r\n }\r\n this.isComplete = false\r\n }\r\n}\r\n\r\n/**\r\n * 创建流式提取器实例\r\n */\r\nexport function createThinkingExtractor(\r\n config?: ThinkingExtractConfig\r\n): ThinkingStreamExtractor {\r\n return new ThinkingStreamExtractor(config)\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const sse = require("./sse.js");
|
|
4
|
+
const thinking = require("./thinking.js");
|
|
5
|
+
const markdown = require("./markdown.js");
|
|
6
|
+
exports.SSEConnection = sse.SSEConnection;
|
|
7
|
+
exports.sse = sse.sse;
|
|
8
|
+
exports.ThinkingStreamExtractor = thinking.ThinkingStreamExtractor;
|
|
9
|
+
exports.createThinkingExtractor = thinking.createThinkingExtractor;
|
|
10
|
+
exports.extractThinking = thinking.extractThinking;
|
|
11
|
+
exports.MarkdownRenderer = markdown.MarkdownRenderer;
|
|
12
|
+
exports.MarkdownStreamRenderer = markdown.MarkdownStreamRenderer;
|
|
13
|
+
exports.createMarkdownRenderer = markdown.createMarkdownRenderer;
|
|
14
|
+
exports.renderMarkdown = markdown.renderMarkdown;
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
class MarkdownRenderer {
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
this.config = {
|
|
6
|
+
highlight: config.highlight || false,
|
|
7
|
+
highlightLanguages: config.highlightLanguages || {},
|
|
8
|
+
taskList: config.taskList !== false,
|
|
9
|
+
table: config.table !== false,
|
|
10
|
+
link: config.link !== false,
|
|
11
|
+
image: config.image !== false
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 渲染 Markdown 为 HTML
|
|
16
|
+
*/
|
|
17
|
+
render(markdown) {
|
|
18
|
+
let html = markdown;
|
|
19
|
+
html = this.escapeHtml(html);
|
|
20
|
+
html = this.renderCodeBlocks(html);
|
|
21
|
+
html = this.renderHeadings(html);
|
|
22
|
+
html = this.renderEmphasis(html);
|
|
23
|
+
if (this.config.taskList) {
|
|
24
|
+
html = this.renderTaskLists(html);
|
|
25
|
+
}
|
|
26
|
+
html = this.renderUnorderedLists(html);
|
|
27
|
+
html = this.renderOrderedLists(html);
|
|
28
|
+
if (this.config.table) {
|
|
29
|
+
html = this.renderTables(html);
|
|
30
|
+
}
|
|
31
|
+
if (this.config.link) {
|
|
32
|
+
html = this.renderLinks(html);
|
|
33
|
+
}
|
|
34
|
+
if (this.config.image) {
|
|
35
|
+
html = this.renderImages(html);
|
|
36
|
+
}
|
|
37
|
+
html = this.renderParagraphs(html);
|
|
38
|
+
html = this.renderLineBreaks(html);
|
|
39
|
+
return html;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 转义 HTML 特殊字符
|
|
43
|
+
*/
|
|
44
|
+
escapeHtml(text) {
|
|
45
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 渲染代码块
|
|
49
|
+
*/
|
|
50
|
+
renderCodeBlocks(text) {
|
|
51
|
+
return text.replace(
|
|
52
|
+
/`{3}(\w*)\n([\s\S]*?)`{3}/g,
|
|
53
|
+
(match, lang, code) => {
|
|
54
|
+
const language = lang || "text";
|
|
55
|
+
return `<pre><code class="language-${language}">${code.trim()}</code></pre>`;
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 渲染标题
|
|
61
|
+
*/
|
|
62
|
+
renderHeadings(text) {
|
|
63
|
+
return text.replace(/^#{6}\s+(.+)$/gm, "<h6>$1</h6>").replace(/^#{5}\s+(.+)$/gm, "<h5>$1</h5>").replace(/^#{4}\s+(.+)$/gm, "<h4>$1</h4>").replace(/^#{3}\s+(.+)$/gm, "<h3>$1</h3>").replace(/^#{2}\s+(.+)$/gm, "<h2>$1</h2>").replace(/^#{1}\s+(.+)$/gm, "<h1>$1</h1>");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 渲染粗体和斜体
|
|
67
|
+
*/
|
|
68
|
+
renderEmphasis(text) {
|
|
69
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>");
|
|
70
|
+
text = text.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
71
|
+
text = text.replace(/___(.+?)___/g, "<strong><em>$1</em></strong>");
|
|
72
|
+
text = text.replace(/__(.+?)__/g, "<strong>$1</strong>");
|
|
73
|
+
text = text.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
|
74
|
+
text = text.replace(/_(.+?)_/g, "<em>$1</em>");
|
|
75
|
+
return text;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 渲染任务列表
|
|
79
|
+
*/
|
|
80
|
+
renderTaskLists(text) {
|
|
81
|
+
return text.replace(/^\s*-\s*\[x\]\s+(.+)$/gm, '<li class="task-checked"><input type="checkbox" checked disabled> $1</li>').replace(/^\s*-\s*\[\s*\]\s+(.+)$/gm, '<li><input type="checkbox" disabled> $1</li>');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 渲染无序列表
|
|
85
|
+
*/
|
|
86
|
+
renderUnorderedLists(text) {
|
|
87
|
+
const lines = text.split("\n");
|
|
88
|
+
const result = [];
|
|
89
|
+
const listStack = [];
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
const match = line.match(/^(\s*)-\s+(.+)$/);
|
|
92
|
+
if (!match) {
|
|
93
|
+
while (listStack.length > 0) {
|
|
94
|
+
result.push(listStack.pop() || "");
|
|
95
|
+
}
|
|
96
|
+
result.push(line);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const [_, indent, content] = match;
|
|
100
|
+
const indentLevel = indent.length;
|
|
101
|
+
while (listStack.length > 0 && listStack.length < indentLevel / 2 + 1) {
|
|
102
|
+
result.push(listStack.pop() || "");
|
|
103
|
+
}
|
|
104
|
+
while (listStack.length < indentLevel / 2 + 1) {
|
|
105
|
+
listStack.push("<ul>");
|
|
106
|
+
result.push("<ul>");
|
|
107
|
+
}
|
|
108
|
+
result.push(`<li>${content}</li>`);
|
|
109
|
+
}
|
|
110
|
+
while (listStack.length > 0) {
|
|
111
|
+
result.push("</ul>");
|
|
112
|
+
listStack.pop();
|
|
113
|
+
}
|
|
114
|
+
return result.join("\n");
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 渲染有序列表
|
|
118
|
+
*/
|
|
119
|
+
renderOrderedLists(text) {
|
|
120
|
+
const lines = text.split("\n");
|
|
121
|
+
const result = [];
|
|
122
|
+
let inList = false;
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
const match = line.match(/^\s*\d+\.\s+(.+)$/);
|
|
125
|
+
if (!match) {
|
|
126
|
+
if (inList) {
|
|
127
|
+
result.push("</ol>");
|
|
128
|
+
inList = false;
|
|
129
|
+
}
|
|
130
|
+
result.push(line);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const [_, content] = match;
|
|
134
|
+
if (!inList) {
|
|
135
|
+
result.push("<ol>");
|
|
136
|
+
inList = true;
|
|
137
|
+
}
|
|
138
|
+
result.push(`<li>${content}</li>`);
|
|
139
|
+
}
|
|
140
|
+
if (inList) {
|
|
141
|
+
result.push("</ol>");
|
|
142
|
+
}
|
|
143
|
+
return result.join("\n");
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 渲染表格
|
|
147
|
+
*/
|
|
148
|
+
renderTables(text) {
|
|
149
|
+
const lines = text.split("\n");
|
|
150
|
+
const result = [];
|
|
151
|
+
let inTable = false;
|
|
152
|
+
let headerRendered = false;
|
|
153
|
+
for (let i = 0; i < lines.length; i++) {
|
|
154
|
+
const line = lines[i];
|
|
155
|
+
if (line.match(/^\|[\s\-:|]+\|$/)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const match = line.match(/^\|(.+)\|$/);
|
|
159
|
+
if (!match) {
|
|
160
|
+
if (inTable) {
|
|
161
|
+
result.push("</table>");
|
|
162
|
+
inTable = false;
|
|
163
|
+
headerRendered = false;
|
|
164
|
+
}
|
|
165
|
+
result.push(line);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const cells = match[1].split("|").map((cell) => cell.trim());
|
|
169
|
+
if (!inTable) {
|
|
170
|
+
result.push("<table>");
|
|
171
|
+
inTable = true;
|
|
172
|
+
}
|
|
173
|
+
if (!headerRendered) {
|
|
174
|
+
result.push("<thead><tr>");
|
|
175
|
+
cells.forEach((cell) => {
|
|
176
|
+
result.push(`<th>${cell}</th>`);
|
|
177
|
+
});
|
|
178
|
+
result.push("</tr></thead><tbody>");
|
|
179
|
+
headerRendered = true;
|
|
180
|
+
} else {
|
|
181
|
+
result.push("<tr>");
|
|
182
|
+
cells.forEach((cell) => {
|
|
183
|
+
result.push(`<td>${cell}</td>`);
|
|
184
|
+
});
|
|
185
|
+
result.push("</tr>");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (inTable) {
|
|
189
|
+
result.push("</tbody></table>");
|
|
190
|
+
}
|
|
191
|
+
return result.join("\n");
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 渲染链接
|
|
195
|
+
*/
|
|
196
|
+
renderLinks(text) {
|
|
197
|
+
return text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 渲染图片
|
|
201
|
+
*/
|
|
202
|
+
renderImages(text) {
|
|
203
|
+
return text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" />');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 渲染段落
|
|
207
|
+
*/
|
|
208
|
+
renderParagraphs(text) {
|
|
209
|
+
const lines = text.split("\n");
|
|
210
|
+
const result = [];
|
|
211
|
+
let inParagraph = false;
|
|
212
|
+
for (const line of lines) {
|
|
213
|
+
if (!line.trim() || line.match(/^<(h|ul|ol|li|table|pre|div)/)) {
|
|
214
|
+
if (inParagraph) {
|
|
215
|
+
result.push("</p>");
|
|
216
|
+
inParagraph = false;
|
|
217
|
+
}
|
|
218
|
+
result.push(line);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (!inParagraph) {
|
|
222
|
+
result.push("<p>");
|
|
223
|
+
inParagraph = true;
|
|
224
|
+
}
|
|
225
|
+
result.push(line);
|
|
226
|
+
}
|
|
227
|
+
if (inParagraph) {
|
|
228
|
+
result.push("</p>");
|
|
229
|
+
}
|
|
230
|
+
return result.join("\n");
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 渲染换行
|
|
234
|
+
*/
|
|
235
|
+
renderLineBreaks(text) {
|
|
236
|
+
return text.replace(/(<p>.*?<\/p>|<li>.*?<\/li>)/g, (match) => {
|
|
237
|
+
return match.replace(/\n/g, "<br>\n");
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function renderMarkdown(markdown, config) {
|
|
242
|
+
const renderer = new MarkdownRenderer(config);
|
|
243
|
+
return renderer.render(markdown);
|
|
244
|
+
}
|
|
245
|
+
class MarkdownStreamRenderer {
|
|
246
|
+
constructor(config = {}) {
|
|
247
|
+
this.buffer = "";
|
|
248
|
+
this.config = config;
|
|
249
|
+
this.renderer = new MarkdownRenderer(config);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* 追加文本并渲染
|
|
253
|
+
*/
|
|
254
|
+
append(text) {
|
|
255
|
+
this.buffer += text;
|
|
256
|
+
const html = this.renderer.render(this.buffer);
|
|
257
|
+
if (this.config.onUpdate) {
|
|
258
|
+
this.config.onUpdate(html);
|
|
259
|
+
}
|
|
260
|
+
return html;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* 完成渲染
|
|
264
|
+
*/
|
|
265
|
+
complete() {
|
|
266
|
+
const html = this.renderer.render(this.buffer);
|
|
267
|
+
if (this.config.onComplete) {
|
|
268
|
+
this.config.onComplete(html);
|
|
269
|
+
}
|
|
270
|
+
return html;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* 重置渲染器
|
|
274
|
+
*/
|
|
275
|
+
reset() {
|
|
276
|
+
this.buffer = "";
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function createMarkdownRenderer(config) {
|
|
280
|
+
return new MarkdownStreamRenderer(config);
|
|
281
|
+
}
|
|
282
|
+
exports.MarkdownRenderer = MarkdownRenderer;
|
|
283
|
+
exports.MarkdownStreamRenderer = MarkdownStreamRenderer;
|
|
284
|
+
exports.createMarkdownRenderer = createMarkdownRenderer;
|
|
285
|
+
exports.renderMarkdown = renderMarkdown;
|
|
286
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sources":["../../src/markdown.ts"],"sourcesContent":["/**\r\n * Markdown 渲染配置\r\n */\r\nexport interface MarkdownRenderConfig {\r\n /** 是否启用代码高亮(默认 false) */\r\n highlight?: boolean\r\n /** 自定义代码高亮语言映射 */\r\n highlightLanguages?: Record<string, string>\r\n /** 是否渲染任务列表(默认 true) */\r\n taskList?: boolean\r\n /** 是否渲染表格(默认 true) */\r\n table?: boolean\r\n /** 是否渲染链接(默认 true) */\r\n link?: boolean\r\n /** 是否渲染图片(默认 true) */\r\n image?: boolean\r\n}\r\n\r\n/**\r\n * Markdown 流式更新配置\r\n */\r\nexport interface MarkdownStreamConfig extends MarkdownRenderConfig {\r\n /** 更新回调函数 */\r\n onUpdate?: (html: string) => void\r\n /** 完成回调函数 */\r\n onComplete?: (html: string) => void\r\n}\r\n\r\n/**\r\n * 简单的 Markdown 渲染器\r\n */\r\nexport class MarkdownRenderer {\r\n private config: MarkdownRenderConfig\r\n\r\n constructor(config: MarkdownRenderConfig = {}) {\r\n this.config = {\r\n highlight: config.highlight || false,\r\n highlightLanguages: config.highlightLanguages || {},\r\n taskList: config.taskList !== false,\r\n table: config.table !== false,\r\n link: config.link !== false,\r\n image: config.image !== false\r\n }\r\n }\r\n\r\n /**\r\n * 渲染 Markdown 为 HTML\r\n */\r\n render(markdown: string): string {\r\n let html = markdown\r\n\r\n // 转义 HTML 特殊字符(除了 Markdown 语法)\r\n html = this.escapeHtml(html)\r\n\r\n // 代码块(需要在其他处理之前)\r\n html = this.renderCodeBlocks(html)\r\n\r\n // 标题\r\n html = this.renderHeadings(html)\r\n\r\n // 粗体和斜体\r\n html = this.renderEmphasis(html)\r\n\r\n // 任务列表\r\n if (this.config.taskList) {\r\n html = this.renderTaskLists(html)\r\n }\r\n\r\n // 无序列表\r\n html = this.renderUnorderedLists(html)\r\n\r\n // 有序列表\r\n html = this.renderOrderedLists(html)\r\n\r\n // 表格\r\n if (this.config.table) {\r\n html = this.renderTables(html)\r\n }\r\n\r\n // 链接\r\n if (this.config.link) {\r\n html = this.renderLinks(html)\r\n }\r\n\r\n // 图片\r\n if (this.config.image) {\r\n html = this.renderImages(html)\r\n }\r\n\r\n // 段落\r\n html = this.renderParagraphs(html)\r\n\r\n // 换行\r\n html = this.renderLineBreaks(html)\r\n\r\n return html\r\n }\r\n\r\n /**\r\n * 转义 HTML 特殊字符\r\n */\r\n private escapeHtml(text: string): string {\r\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')\r\n }\r\n\r\n /**\r\n * 渲染代码块\r\n */\r\n private renderCodeBlocks(text: string): string {\r\n // 代码块(支持语法高亮标记)\r\n return text.replace(\r\n /`{3}(\\w*)\\n([\\s\\S]*?)`{3}/g,\r\n (match, lang, code) => {\r\n const language = lang || 'text'\r\n return `<pre><code class=\"language-${language}\">${code.trim()}</code></pre>`\r\n }\r\n )\r\n }\r\n\r\n /**\r\n * 渲染标题\r\n */\r\n private renderHeadings(text: string): string {\r\n return text\r\n .replace(/^#{6}\\s+(.+)$/gm, '<h6>$1</h6>')\r\n .replace(/^#{5}\\s+(.+)$/gm, '<h5>$1</h5>')\r\n .replace(/^#{4}\\s+(.+)$/gm, '<h4>$1</h4>')\r\n .replace(/^#{3}\\s+(.+)$/gm, '<h3>$1</h3>')\r\n .replace(/^#{2}\\s+(.+)$/gm, '<h2>$1</h2>')\r\n .replace(/^#{1}\\s+(.+)$/gm, '<h1>$1</h1>')\r\n }\r\n\r\n /**\r\n * 渲染粗体和斜体\r\n */\r\n private renderEmphasis(text: string): string {\r\n // 粗体\r\n text = text.replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, '<strong><em>$1</em></strong>')\r\n text = text.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\r\n text = text.replace(/___(.+?)___/g, '<strong><em>$1</em></strong>')\r\n text = text.replace(/__(.+?)__/g, '<strong>$1</strong>')\r\n\r\n // 斜体\r\n text = text.replace(/\\*(.+?)\\*/g, '<em>$1</em>')\r\n text = text.replace(/_(.+?)_/g, '<em>$1</em>')\r\n\r\n return text\r\n }\r\n\r\n /**\r\n * 渲染任务列表\r\n */\r\n private renderTaskLists(text: string): string {\r\n return text.replace(/^\\s*-\\s*\\[x\\]\\s+(.+)$/gm, '<li class=\"task-checked\"><input type=\"checkbox\" checked disabled> $1</li>')\r\n .replace(/^\\s*-\\s*\\[\\s*\\]\\s+(.+)$/gm, '<li><input type=\"checkbox\" disabled> $1</li>')\r\n }\r\n\r\n /**\r\n * 渲染无序列表\r\n */\r\n private renderUnorderedLists(text: string): string {\r\n const lines = text.split('\\n')\r\n const result: string[] = []\r\n const listStack: string[] = []\r\n\r\n for (const line of lines) {\r\n const match = line.match(/^(\\s*)-\\s+(.+)$/)\r\n if (!match) {\r\n // 关闭所有打开的列表\r\n while (listStack.length > 0) {\r\n result.push(listStack.pop() || '')\r\n }\r\n result.push(line)\r\n continue\r\n }\r\n\r\n const [_, indent, content] = match\r\n const indentLevel = indent.length\r\n const listTag = '<ul>'\r\n\r\n // 处理缩进层级\r\n while (listStack.length > 0 && listStack.length < indentLevel / 2 + 1) {\r\n result.push(listStack.pop() || '')\r\n }\r\n\r\n // 打开新的列表\r\n while (listStack.length < indentLevel / 2 + 1) {\r\n listStack.push('<ul>')\r\n result.push('<ul>')\r\n }\r\n\r\n result.push(`<li>${content}</li>`)\r\n }\r\n\r\n // 关闭所有打开的列表\r\n while (listStack.length > 0) {\r\n result.push('</ul>')\r\n listStack.pop()\r\n }\r\n\r\n return result.join('\\n')\r\n }\r\n\r\n /**\r\n * 渲染有序列表\r\n */\r\n private renderOrderedLists(text: string): string {\r\n const lines = text.split('\\n')\r\n const result: string[] = []\r\n let inList = false\r\n\r\n for (const line of lines) {\r\n const match = line.match(/^\\s*\\d+\\.\\s+(.+)$/)\r\n if (!match) {\r\n if (inList) {\r\n result.push('</ol>')\r\n inList = false\r\n }\r\n result.push(line)\r\n continue\r\n }\r\n\r\n const [_, content] = match\r\n if (!inList) {\r\n result.push('<ol>')\r\n inList = true\r\n }\r\n result.push(`<li>${content}</li>`)\r\n }\r\n\r\n if (inList) {\r\n result.push('</ol>')\r\n }\r\n\r\n return result.join('\\n')\r\n }\r\n\r\n /**\r\n * 渲染表格\r\n */\r\n private renderTables(text: string): string {\r\n const lines = text.split('\\n')\r\n const result: string[] = []\r\n let inTable = false\r\n let headerRendered = false\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n const line = lines[i]\r\n\r\n // 表格分隔符\r\n if (line.match(/^\\|[\\s\\-:|]+\\|$/)) {\r\n continue\r\n }\r\n\r\n // 表格行\r\n const match = line.match(/^\\|(.+)\\|$/)\r\n if (!match) {\r\n if (inTable) {\r\n result.push('</table>')\r\n inTable = false\r\n headerRendered = false\r\n }\r\n result.push(line)\r\n continue\r\n }\r\n\r\n const cells = match[1].split('|').map((cell: string) => cell.trim())\r\n\r\n if (!inTable) {\r\n result.push('<table>')\r\n inTable = true\r\n }\r\n\r\n if (!headerRendered) {\r\n result.push('<thead><tr>')\r\n cells.forEach((cell: string) => {\r\n result.push(`<th>${cell}</th>`)\r\n })\r\n result.push('</tr></thead><tbody>')\r\n headerRendered = true\r\n } else {\r\n result.push('<tr>')\r\n cells.forEach((cell: string) => {\r\n result.push(`<td>${cell}</td>`)\r\n })\r\n result.push('</tr>')\r\n }\r\n }\r\n\r\n if (inTable) {\r\n result.push('</tbody></table>')\r\n }\r\n\r\n return result.join('\\n')\r\n }\r\n\r\n /**\r\n * 渲染链接\r\n */\r\n private renderLinks(text: string): string {\r\n return text.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\" target=\"_blank\">$1</a>')\r\n }\r\n\r\n /**\r\n * 渲染图片\r\n */\r\n private renderImages(text: string): string {\r\n return text.replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, '<img src=\"$2\" alt=\"$1\" />')\r\n }\r\n\r\n /**\r\n * 渲染段落\r\n */\r\n private renderParagraphs(text: string): string {\r\n const lines = text.split('\\n')\r\n const result: string[] = []\r\n let inParagraph = false\r\n\r\n for (const line of lines) {\r\n // 跳过空行和 HTML 标签\r\n if (!line.trim() || line.match(/^<(h|ul|ol|li|table|pre|div)/)) {\r\n if (inParagraph) {\r\n result.push('</p>')\r\n inParagraph = false\r\n }\r\n result.push(line)\r\n continue\r\n }\r\n\r\n if (!inParagraph) {\r\n result.push('<p>')\r\n inParagraph = true\r\n }\r\n result.push(line)\r\n }\r\n\r\n if (inParagraph) {\r\n result.push('</p>')\r\n }\r\n\r\n return result.join('\\n')\r\n }\r\n\r\n /**\r\n * 渲染换行\r\n */\r\n private renderLineBreaks(text: string): string {\r\n // 在段落内的换行转换为 <br>\r\n return text.replace(/(<p>.*?<\\/p>|<li>.*?<\\/li>)/g, (match) => {\r\n return match.replace(/\\n/g, '<br>\\n')\r\n })\r\n }\r\n}\r\n\r\n/**\r\n * 渲染 Markdown 为 HTML(便捷方法)\r\n */\r\nexport function renderMarkdown(\r\n markdown: string,\r\n config?: MarkdownRenderConfig\r\n): string {\r\n const renderer = new MarkdownRenderer(config)\r\n return renderer.render(markdown)\r\n}\r\n\r\n/**\r\n * 流式渲染 Markdown(用于 SSE 流式响应)\r\n */\r\nexport class MarkdownStreamRenderer {\r\n private renderer: MarkdownRenderer\r\n private buffer: string = ''\r\n private config: MarkdownStreamConfig\r\n\r\n constructor(config: MarkdownStreamConfig = {}) {\r\n this.config = config\r\n this.renderer = new MarkdownRenderer(config)\r\n }\r\n\r\n /**\r\n * 追加文本并渲染\r\n */\r\n append(text: string): string {\r\n this.buffer += text\r\n const html = this.renderer.render(this.buffer)\r\n\r\n if (this.config.onUpdate) {\r\n this.config.onUpdate(html)\r\n }\r\n\r\n return html\r\n }\r\n\r\n /**\r\n * 完成渲染\r\n */\r\n complete(): string {\r\n const html = this.renderer.render(this.buffer)\r\n\r\n if (this.config.onComplete) {\r\n this.config.onComplete(html)\r\n }\r\n\r\n return html\r\n }\r\n\r\n /**\r\n * 重置渲染器\r\n */\r\n reset(): void {\r\n this.buffer = ''\r\n }\r\n}\r\n\r\n/**\r\n * 创建流式渲染器实例\r\n */\r\nexport function createMarkdownRenderer(\r\n config?: MarkdownStreamConfig\r\n): MarkdownStreamRenderer {\r\n return new MarkdownStreamRenderer(config)\r\n}\r\n"],"names":[],"mappings":"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"}
|