@lobehub/chat 1.53.4 → 1.53.6

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.
@@ -0,0 +1,396 @@
1
+ import { toMarkdown } from 'mdast-util-to-markdown';
2
+ import { Parent } from 'unist';
3
+ import { expect } from 'vitest';
4
+
5
+ import { treeNodeToString } from '@/features/Conversation/components/MarkdownElements/remarkPlugins/getNodeContent';
6
+
7
+ describe('treeNodeToString', () => {
8
+ it('with latex', () => {
9
+ const nodes = [
10
+ {
11
+ type: 'paragraph',
12
+ children: [
13
+ {
14
+ type: 'text',
15
+ value: '设正向数列 ',
16
+ position: {
17
+ start: {
18
+ line: 3,
19
+ column: 1,
20
+ offset: 9,
21
+ },
22
+ end: {
23
+ line: 3,
24
+ column: 7,
25
+ offset: 15,
26
+ },
27
+ },
28
+ },
29
+ {
30
+ type: 'inlineMath',
31
+ value: '{ a_n }',
32
+ data: {
33
+ hName: 'code',
34
+ hProperties: {
35
+ className: ['language-math', 'math-inline'],
36
+ },
37
+ hChildren: [
38
+ {
39
+ type: 'text',
40
+ value: '{ a_n }',
41
+ },
42
+ ],
43
+ },
44
+ position: {
45
+ start: {
46
+ line: 3,
47
+ column: 7,
48
+ offset: 15,
49
+ },
50
+ end: {
51
+ line: 3,
52
+ column: 18,
53
+ offset: 26,
54
+ },
55
+ },
56
+ },
57
+ {
58
+ type: 'text',
59
+ value: ' 的首项为 ',
60
+ position: {
61
+ start: {
62
+ line: 3,
63
+ column: 18,
64
+ offset: 26,
65
+ },
66
+ end: {
67
+ line: 3,
68
+ column: 24,
69
+ offset: 32,
70
+ },
71
+ },
72
+ },
73
+ {
74
+ type: 'inlineMath',
75
+ value: '4',
76
+ data: {
77
+ hName: 'code',
78
+ hProperties: {
79
+ className: ['language-math', 'math-inline'],
80
+ },
81
+ hChildren: [
82
+ {
83
+ type: 'text',
84
+ value: '4',
85
+ },
86
+ ],
87
+ },
88
+ position: {
89
+ start: {
90
+ line: 3,
91
+ column: 24,
92
+ offset: 32,
93
+ },
94
+ end: {
95
+ line: 3,
96
+ column: 29,
97
+ offset: 37,
98
+ },
99
+ },
100
+ },
101
+ {
102
+ type: 'text',
103
+ value: ' ,满足 ',
104
+ position: {
105
+ start: {
106
+ line: 3,
107
+ column: 29,
108
+ offset: 37,
109
+ },
110
+ end: {
111
+ line: 3,
112
+ column: 34,
113
+ offset: 42,
114
+ },
115
+ },
116
+ },
117
+ {
118
+ type: 'inlineMath',
119
+ value: 'a^2_n = a_{n+1} + 3na_n - 3',
120
+ data: {
121
+ hName: 'code',
122
+ hProperties: {
123
+ className: ['language-math', 'math-inline'],
124
+ },
125
+ hChildren: [
126
+ {
127
+ type: 'text',
128
+ value: 'a^2_n = a_{n+1} + 3na_n - 3',
129
+ },
130
+ ],
131
+ },
132
+ position: {
133
+ start: {
134
+ line: 3,
135
+ column: 34,
136
+ offset: 42,
137
+ },
138
+ end: {
139
+ line: 3,
140
+ column: 65,
141
+ offset: 73,
142
+ },
143
+ },
144
+ },
145
+ {
146
+ type: 'text',
147
+ value: '.',
148
+ position: {
149
+ start: {
150
+ line: 3,
151
+ column: 65,
152
+ offset: 73,
153
+ },
154
+ end: {
155
+ line: 3,
156
+ column: 66,
157
+ offset: 74,
158
+ },
159
+ },
160
+ },
161
+ ],
162
+ position: {
163
+ start: {
164
+ line: 3,
165
+ column: 1,
166
+ offset: 9,
167
+ },
168
+ end: {
169
+ line: 3,
170
+ column: 66,
171
+ offset: 74,
172
+ },
173
+ },
174
+ },
175
+ {
176
+ type: 'list',
177
+ ordered: true,
178
+ start: 1,
179
+ spread: false,
180
+ children: [
181
+ {
182
+ type: 'listItem',
183
+ spread: false,
184
+ checked: null,
185
+ children: [
186
+ {
187
+ type: 'paragraph',
188
+ children: [
189
+ {
190
+ type: 'text',
191
+ value: '求 ',
192
+ position: {
193
+ start: {
194
+ line: 5,
195
+ column: 5,
196
+ offset: 80,
197
+ },
198
+ end: {
199
+ line: 5,
200
+ column: 7,
201
+ offset: 82,
202
+ },
203
+ },
204
+ },
205
+ {
206
+ type: 'inlineMath',
207
+ value: 'a_2',
208
+ data: {
209
+ hName: 'code',
210
+ hProperties: {
211
+ className: ['language-math', 'math-inline'],
212
+ },
213
+ hChildren: [
214
+ {
215
+ type: 'text',
216
+ value: 'a_2',
217
+ },
218
+ ],
219
+ },
220
+ position: {
221
+ start: {
222
+ line: 5,
223
+ column: 7,
224
+ offset: 82,
225
+ },
226
+ end: {
227
+ line: 5,
228
+ column: 14,
229
+ offset: 89,
230
+ },
231
+ },
232
+ },
233
+ {
234
+ type: 'text',
235
+ value: ' 和 ',
236
+ position: {
237
+ start: {
238
+ line: 5,
239
+ column: 14,
240
+ offset: 89,
241
+ },
242
+ end: {
243
+ line: 5,
244
+ column: 17,
245
+ offset: 92,
246
+ },
247
+ },
248
+ },
249
+ {
250
+ type: 'inlineMath',
251
+ value: 'a_3',
252
+ data: {
253
+ hName: 'code',
254
+ hProperties: {
255
+ className: ['language-math', 'math-inline'],
256
+ },
257
+ hChildren: [
258
+ {
259
+ type: 'text',
260
+ value: 'a_3',
261
+ },
262
+ ],
263
+ },
264
+ position: {
265
+ start: {
266
+ line: 5,
267
+ column: 17,
268
+ offset: 92,
269
+ },
270
+ end: {
271
+ line: 5,
272
+ column: 24,
273
+ offset: 99,
274
+ },
275
+ },
276
+ },
277
+ {
278
+ type: 'text',
279
+ value: ',根据前三项的规律猜想该数列的通项公式',
280
+ position: {
281
+ start: {
282
+ line: 5,
283
+ column: 24,
284
+ offset: 99,
285
+ },
286
+ end: {
287
+ line: 5,
288
+ column: 43,
289
+ offset: 118,
290
+ },
291
+ },
292
+ },
293
+ ],
294
+ position: {
295
+ start: {
296
+ line: 5,
297
+ column: 5,
298
+ offset: 80,
299
+ },
300
+ end: {
301
+ line: 5,
302
+ column: 43,
303
+ offset: 118,
304
+ },
305
+ },
306
+ },
307
+ ],
308
+ position: {
309
+ start: {
310
+ line: 5,
311
+ column: 2,
312
+ offset: 77,
313
+ },
314
+ end: {
315
+ line: 5,
316
+ column: 43,
317
+ offset: 118,
318
+ },
319
+ },
320
+ },
321
+ {
322
+ type: 'listItem',
323
+ spread: false,
324
+ checked: null,
325
+ children: [
326
+ {
327
+ type: 'paragraph',
328
+ children: [
329
+ {
330
+ type: 'text',
331
+ value: '用数学归纳法证明你的猜想。',
332
+ position: {
333
+ start: {
334
+ line: 6,
335
+ column: 5,
336
+ offset: 123,
337
+ },
338
+ end: {
339
+ line: 6,
340
+ column: 18,
341
+ offset: 136,
342
+ },
343
+ },
344
+ },
345
+ ],
346
+ position: {
347
+ start: {
348
+ line: 6,
349
+ column: 5,
350
+ offset: 123,
351
+ },
352
+ end: {
353
+ line: 6,
354
+ column: 18,
355
+ offset: 136,
356
+ },
357
+ },
358
+ },
359
+ ],
360
+ position: {
361
+ start: {
362
+ line: 6,
363
+ column: 2,
364
+ offset: 120,
365
+ },
366
+ end: {
367
+ line: 6,
368
+ column: 18,
369
+ offset: 136,
370
+ },
371
+ },
372
+ },
373
+ ],
374
+ position: {
375
+ start: {
376
+ line: 5,
377
+ column: 2,
378
+ offset: 77,
379
+ },
380
+ end: {
381
+ line: 6,
382
+ column: 18,
383
+ offset: 136,
384
+ },
385
+ },
386
+ },
387
+ ];
388
+
389
+ const result = treeNodeToString(nodes as Parent[]);
390
+
391
+ expect(result).toEqual(`设正向数列 \${ a_n }$ 的首项为 $4$ ,满足 $a^2_n = a_{n+1} + 3na_n - 3$.
392
+
393
+ 1. 求 $a_2$ 和 $a_3$,根据前三项的规律猜想该数列的通项公式
394
+ 2. 用数学归纳法证明你的猜想。`);
395
+ });
396
+ });
@@ -0,0 +1,55 @@
1
+ import { toMarkdown } from 'mdast-util-to-markdown';
2
+ import { Parent } from 'unist';
3
+
4
+ const processNode = (node: any): string => {
5
+ // 处理数学公式节点
6
+ if (node.type === 'inlineMath') {
7
+ return `$${node.value}$`;
8
+ }
9
+
10
+ // 处理带有子节点的容器
11
+ if (node.children) {
12
+ const content = node.children.map((element: Parent) => processNode(element)).join('');
13
+
14
+ // 处理列表的特殊换行逻辑
15
+ if (node.type === 'list') {
16
+ return `\n${content}\n`;
17
+ }
18
+
19
+ // 处理列表项的前缀
20
+ if (node.type === 'listItem') {
21
+ const prefix = node.checked !== null ? `[${node.checked ? 'x' : ' '}] ` : '';
22
+ return `${prefix}${content}`;
23
+ }
24
+
25
+ return content;
26
+ }
27
+
28
+ // 处理文本节点
29
+ if (node.value) {
30
+ // 保留原始空白字符处理逻辑
31
+ return node.value.replaceAll(/^\s+|\s+$/g, ' ');
32
+ }
33
+
34
+ // 兜底使用标准转换
35
+ return toMarkdown(node);
36
+ };
37
+
38
+ export const treeNodeToString = (nodes: Parent[]) => {
39
+ return nodes
40
+ .map((node) => {
41
+ // 处理列表的缩进问题
42
+ if (node.type === 'list') {
43
+ return node.children
44
+ .map((item, index) => {
45
+ const prefix = (node as any).ordered ? `${(node as any).start + index}. ` : '- ';
46
+ return `${prefix}${processNode(item)}`;
47
+ })
48
+ .join('\n');
49
+ }
50
+
51
+ return processNode(node);
52
+ })
53
+ .join('\n\n')
54
+ .trim();
55
+ };
@@ -0,0 +1,123 @@
1
+ import OpenAI from 'openai';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+
4
+ import { testProvider } from './providerTestUtils';
5
+
6
+ describe('testProvider', () => {
7
+ it('should run provider tests correctly', () => {
8
+ class MockRuntime {
9
+ baseURL: string;
10
+ client: any;
11
+
12
+ constructor({
13
+ apiKey,
14
+ baseURL = 'https://default.test',
15
+ }: {
16
+ apiKey?: string;
17
+ baseURL?: string;
18
+ }) {
19
+ if (!apiKey) throw { errorType: 'InvalidAPIKey' };
20
+ this.baseURL = baseURL;
21
+ this.client = {
22
+ chat: {
23
+ completions: {
24
+ create: vi.fn().mockResolvedValue(new ReadableStream()),
25
+ },
26
+ },
27
+ };
28
+ }
29
+
30
+ async chat(params: any) {
31
+ return this.client.chat.completions.create(params);
32
+ }
33
+ }
34
+
35
+ testProvider({
36
+ Runtime: MockRuntime,
37
+ bizErrorType: 'TestBizError',
38
+ chatDebugEnv: 'TEST_DEBUG',
39
+ chatModel: 'test-model',
40
+ defaultBaseURL: 'https://default.test',
41
+ invalidErrorType: 'InvalidAPIKey',
42
+ provider: 'TestProvider',
43
+ });
44
+ });
45
+
46
+ it('should handle OpenAI API errors correctly', async () => {
47
+ class MockRuntime {
48
+ baseURL: string;
49
+ client: any;
50
+
51
+ constructor({ apiKey }: { apiKey?: string }) {
52
+ if (!apiKey) throw { errorType: 'InvalidAPIKey' };
53
+ this.baseURL = 'test';
54
+ this.client = {
55
+ chat: {
56
+ completions: {
57
+ create: vi.fn().mockRejectedValue(
58
+ new OpenAI.APIError(
59
+ 400,
60
+ {
61
+ error: { message: 'Test Error' },
62
+ status: 400,
63
+ },
64
+ 'Test Error',
65
+ {},
66
+ ),
67
+ ),
68
+ },
69
+ },
70
+ };
71
+ }
72
+
73
+ async chat(params: any) {
74
+ return this.client.chat.completions.create(params);
75
+ }
76
+ }
77
+
78
+ testProvider({
79
+ Runtime: MockRuntime,
80
+ bizErrorType: 'TestBizError',
81
+ chatDebugEnv: 'TEST_DEBUG',
82
+ chatModel: 'test-model',
83
+ defaultBaseURL: 'test',
84
+ invalidErrorType: 'InvalidAPIKey',
85
+ provider: 'TestProvider',
86
+ });
87
+ });
88
+
89
+ it('should handle debug stream correctly', () => {
90
+ class MockRuntime {
91
+ baseURL: string;
92
+ client: any;
93
+
94
+ constructor({ apiKey }: { apiKey?: string }) {
95
+ if (!apiKey) throw { errorType: 'InvalidAPIKey' };
96
+ this.baseURL = 'test';
97
+ this.client = {
98
+ chat: {
99
+ completions: {
100
+ create: vi.fn().mockResolvedValue({
101
+ tee: () => [new ReadableStream(), { toReadableStream: () => new ReadableStream() }],
102
+ }),
103
+ },
104
+ },
105
+ };
106
+ }
107
+
108
+ async chat(params: any) {
109
+ return this.client.chat.completions.create(params);
110
+ }
111
+ }
112
+
113
+ testProvider({
114
+ Runtime: MockRuntime,
115
+ bizErrorType: 'TestBizError',
116
+ chatDebugEnv: 'TEST_DEBUG',
117
+ chatModel: 'test-model',
118
+ defaultBaseURL: 'test',
119
+ invalidErrorType: 'InvalidAPIKey',
120
+ provider: 'TestProvider',
121
+ });
122
+ });
123
+ });