@huyooo/ai-chat-shared 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -561
- package/package.json +2 -3
- package/dist/index.js.map +0 -1
- package/src/highlighter.ts +0 -80
- package/src/index.ts +0 -59
- package/src/markdown.ts +0 -335
- package/src/parser.ts +0 -235
- package/src/styles.css +0 -757
- package/src/types.ts +0 -77
- package/src/visual.ts +0 -68
package/src/parser.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 内容解析器
|
|
3
|
-
* 将 AI 输出的原始文本解析为结构化的内容块
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ContentBlock, TextBlock, CodeBlock } from './types'
|
|
7
|
-
|
|
8
|
-
/** 生成唯一 ID */
|
|
9
|
-
function generateId(): string {
|
|
10
|
-
return Math.random().toString(36).slice(2, 11)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** 代码块正则:匹配 ```language\ncode\n``` 或 ```\ncode\n``` */
|
|
14
|
-
const CODE_BLOCK_REGEX = /```(\w*)\n([\s\S]*?)```/g
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 解析原始内容为内容块列表
|
|
18
|
-
* @param raw 原始文本内容
|
|
19
|
-
* @returns 内容块列表
|
|
20
|
-
*/
|
|
21
|
-
export function parseContent(raw: string): ContentBlock[] {
|
|
22
|
-
if (!raw) return []
|
|
23
|
-
|
|
24
|
-
const blocks: ContentBlock[] = []
|
|
25
|
-
let lastIndex = 0
|
|
26
|
-
|
|
27
|
-
// 使用正则匹配所有代码块
|
|
28
|
-
let match: RegExpExecArray | null
|
|
29
|
-
const regex = new RegExp(CODE_BLOCK_REGEX.source, 'g')
|
|
30
|
-
|
|
31
|
-
while ((match = regex.exec(raw)) !== null) {
|
|
32
|
-
// 添加代码块前的文本
|
|
33
|
-
if (match.index > lastIndex) {
|
|
34
|
-
const textContent = raw.slice(lastIndex, match.index)
|
|
35
|
-
if (textContent.trim()) {
|
|
36
|
-
blocks.push(createTextBlock(textContent, undefined, true))
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 添加代码块
|
|
41
|
-
const language = match[1] || undefined
|
|
42
|
-
const code = match[2]
|
|
43
|
-
blocks.push(createCodeBlock(code, language))
|
|
44
|
-
|
|
45
|
-
lastIndex = match.index + match[0].length
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// 添加最后一段文本
|
|
49
|
-
if (lastIndex < raw.length) {
|
|
50
|
-
const textContent = raw.slice(lastIndex)
|
|
51
|
-
if (textContent.trim()) {
|
|
52
|
-
blocks.push(createTextBlock(textContent, undefined, true))
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return blocks
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** 创建文本块 */
|
|
60
|
-
function createTextBlock(content: string, id?: string, trimContent = true): TextBlock {
|
|
61
|
-
return {
|
|
62
|
-
id: id || generateId(),
|
|
63
|
-
type: 'text',
|
|
64
|
-
content: trimContent ? content.trim() : content,
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** 创建代码块 */
|
|
69
|
-
function createCodeBlock(content: string, language?: string, filename?: string, id?: string): CodeBlock {
|
|
70
|
-
return {
|
|
71
|
-
id: id || generateId(),
|
|
72
|
-
type: 'code',
|
|
73
|
-
content,
|
|
74
|
-
language,
|
|
75
|
-
filename,
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ============ 流式解析支持 ============
|
|
80
|
-
|
|
81
|
-
/** 流式解析状态 */
|
|
82
|
-
export interface StreamParseState {
|
|
83
|
-
/** 未完成的缓冲区 */
|
|
84
|
-
buffer: string
|
|
85
|
-
/** 已解析的块 */
|
|
86
|
-
blocks: ContentBlock[]
|
|
87
|
-
/** 是否在代码块中 */
|
|
88
|
-
inCodeBlock: boolean
|
|
89
|
-
/** 当前代码块语言 */
|
|
90
|
-
codeLanguage?: string
|
|
91
|
-
/** 当前代码块内容 */
|
|
92
|
-
codeContent: string
|
|
93
|
-
/** 当前代码块 ID(用于稳定 key,避免流式时每次 render 都生成新 id) */
|
|
94
|
-
codeBlockId: string | null
|
|
95
|
-
/** 当前文本块内容(流式追加,避免把一句话拆成很多 text block) */
|
|
96
|
-
textContent: string
|
|
97
|
-
/** 当前文本块 ID(稳定 key) */
|
|
98
|
-
textBlockId: string | null
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** 创建初始流式解析状态 */
|
|
102
|
-
export function createStreamParseState(): StreamParseState {
|
|
103
|
-
return {
|
|
104
|
-
buffer: '',
|
|
105
|
-
blocks: [],
|
|
106
|
-
inCodeBlock: false,
|
|
107
|
-
codeLanguage: undefined,
|
|
108
|
-
codeContent: '',
|
|
109
|
-
codeBlockId: null,
|
|
110
|
-
textContent: '',
|
|
111
|
-
textBlockId: null,
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function appendText(state: StreamParseState, text: string): void {
|
|
116
|
-
if (!text) return
|
|
117
|
-
if (state.textBlockId === null) state.textBlockId = generateId()
|
|
118
|
-
state.textContent += text
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function flushTextBlock(state: StreamParseState): void {
|
|
122
|
-
if (!state.textContent.trim()) return
|
|
123
|
-
state.blocks.push(createTextBlock(state.textContent, state.textBlockId || undefined, false))
|
|
124
|
-
state.textContent = ''
|
|
125
|
-
state.textBlockId = null
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 流式解析(增量更新)
|
|
130
|
-
* @param chunk 新增的文本块
|
|
131
|
-
* @param state 当前解析状态
|
|
132
|
-
* @returns 更新后的状态
|
|
133
|
-
*/
|
|
134
|
-
export function parseContentStream(chunk: string, state: StreamParseState): StreamParseState {
|
|
135
|
-
const newState: StreamParseState = { ...state, blocks: [...state.blocks] }
|
|
136
|
-
newState.buffer += chunk
|
|
137
|
-
|
|
138
|
-
// 处理缓冲区
|
|
139
|
-
while (true) {
|
|
140
|
-
if (newState.inCodeBlock) {
|
|
141
|
-
// 在代码块中,查找结束标记
|
|
142
|
-
const endIndex = newState.buffer.indexOf('```')
|
|
143
|
-
if (endIndex !== -1) {
|
|
144
|
-
// 找到结束标记
|
|
145
|
-
newState.codeContent += newState.buffer.slice(0, endIndex)
|
|
146
|
-
newState.blocks.push(
|
|
147
|
-
createCodeBlock(
|
|
148
|
-
newState.codeContent.trim(),
|
|
149
|
-
newState.codeLanguage,
|
|
150
|
-
undefined,
|
|
151
|
-
newState.codeBlockId || undefined
|
|
152
|
-
)
|
|
153
|
-
)
|
|
154
|
-
newState.buffer = newState.buffer.slice(endIndex + 3)
|
|
155
|
-
newState.inCodeBlock = false
|
|
156
|
-
newState.codeLanguage = undefined
|
|
157
|
-
newState.codeContent = ''
|
|
158
|
-
newState.codeBlockId = null
|
|
159
|
-
} else {
|
|
160
|
-
// 没找到结束标记,继续累积
|
|
161
|
-
newState.codeContent += newState.buffer
|
|
162
|
-
newState.buffer = ''
|
|
163
|
-
break
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
// 不在代码块中,查找开始标记
|
|
167
|
-
const startIndex = newState.buffer.indexOf('```')
|
|
168
|
-
if (startIndex !== -1) {
|
|
169
|
-
// 找到开始标记
|
|
170
|
-
const beforeCode = newState.buffer.slice(0, startIndex)
|
|
171
|
-
appendText(newState, beforeCode)
|
|
172
|
-
flushTextBlock(newState)
|
|
173
|
-
|
|
174
|
-
// 解析语言标识
|
|
175
|
-
const afterStart = newState.buffer.slice(startIndex + 3)
|
|
176
|
-
const newlineIndex = afterStart.indexOf('\n')
|
|
177
|
-
if (newlineIndex !== -1) {
|
|
178
|
-
newState.codeLanguage = afterStart.slice(0, newlineIndex).trim() || undefined
|
|
179
|
-
newState.buffer = afterStart.slice(newlineIndex + 1)
|
|
180
|
-
newState.inCodeBlock = true
|
|
181
|
-
newState.codeContent = ''
|
|
182
|
-
newState.codeBlockId = generateId()
|
|
183
|
-
} else {
|
|
184
|
-
// 语言行还没完整,等待更多数据
|
|
185
|
-
break
|
|
186
|
-
}
|
|
187
|
-
} else {
|
|
188
|
-
// 没有代码块标记:追加到“当前文本块”里(不 flush),避免碎片化
|
|
189
|
-
// 同时检查末尾是否可能是不完整的 ```,如果是就保留在 buffer 等待后续补全
|
|
190
|
-
const lastBackticks = newState.buffer.lastIndexOf('`')
|
|
191
|
-
if (lastBackticks !== -1 && newState.buffer.length - lastBackticks < 3) {
|
|
192
|
-
// 可能是不完整的 ```,保留
|
|
193
|
-
const safeText = newState.buffer.slice(0, lastBackticks)
|
|
194
|
-
appendText(newState, safeText)
|
|
195
|
-
newState.buffer = newState.buffer.slice(lastBackticks)
|
|
196
|
-
} else {
|
|
197
|
-
appendText(newState, newState.buffer)
|
|
198
|
-
newState.buffer = ''
|
|
199
|
-
}
|
|
200
|
-
break
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return newState
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* 完成流式解析(处理剩余缓冲区)
|
|
210
|
-
* @param state 当前解析状态
|
|
211
|
-
* @returns 最终的内容块列表
|
|
212
|
-
*/
|
|
213
|
-
export function finishStreamParse(state: StreamParseState): ContentBlock[] {
|
|
214
|
-
const blocks = [...state.blocks]
|
|
215
|
-
|
|
216
|
-
if (state.inCodeBlock) {
|
|
217
|
-
// 未闭合的代码块,作为代码块处理
|
|
218
|
-
blocks.push(
|
|
219
|
-
createCodeBlock(
|
|
220
|
-
(state.codeContent + state.buffer).trim(),
|
|
221
|
-
state.codeLanguage,
|
|
222
|
-
undefined,
|
|
223
|
-
state.codeBlockId || undefined
|
|
224
|
-
)
|
|
225
|
-
)
|
|
226
|
-
} else {
|
|
227
|
-
// 剩余文本 + 累积文本:合并成一个 text block(稳定 id),避免碎片化
|
|
228
|
-
const merged = state.textContent + state.buffer
|
|
229
|
-
if (merged.trim()) {
|
|
230
|
-
blocks.push(createTextBlock(merged, state.textBlockId || undefined, false))
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return blocks
|
|
235
|
-
}
|