@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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.53.6](https://github.com/lobehub/lobe-chat/compare/v1.53.5...v1.53.6)
6
+
7
+ <sup>Released on **2025-02-13**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix not enable models correctly.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix not enable models correctly, closes [#6071](https://github.com/lobehub/lobe-chat/issues/6071) ([b78328e](https://github.com/lobehub/lobe-chat/commit/b78328e))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.53.5](https://github.com/lobehub/lobe-chat/compare/v1.53.4...v1.53.5)
31
+
32
+ <sup>Released on **2025-02-13**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Fix latex in thinking tag render.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Fix latex in thinking tag render, closes [#6063](https://github.com/lobehub/lobe-chat/issues/6063) ([7e89b2d](https://github.com/lobehub/lobe-chat/commit/7e89b2d))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 1.53.4](https://github.com/lobehub/lobe-chat/compare/v1.53.3...v1.53.4)
6
56
 
7
57
  <sup>Released on **2025-02-12**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix not enable models correctly."
6
+ ]
7
+ },
8
+ "date": "2025-02-13",
9
+ "version": "1.53.6"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Fix latex in thinking tag render."
15
+ ]
16
+ },
17
+ "date": "2025-02-13",
18
+ "version": "1.53.5"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "fixes": [
@@ -19,6 +19,16 @@ LobeChat provides a complete authentication service capability when deployed. Th
19
19
 
20
20
  ### General Settings
21
21
 
22
+ #### `NEXT_PUBLIC_ENABLE_NEXT_AUTH`
23
+
24
+ - Changes after v1.52.0.
25
+
26
+ - For users who deploy with Vercel using Next Auth, it is necessary to add the environment variable NEXT_PUBLIC_ENABLE_NEXT_AUTH=1 to ensure that Next Auth is enabled.
27
+ - For users who use Clerk in their self-built image, it is necessary to configure the environment variable NEXT_PUBLIC_ENABLE_NEXT_AUTH=0 to disable Next Auth.\n
28
+ - Other standard deployment scenarios (using Clerk on Vercel and next-auth in Docker) are not affected
29
+
30
+
31
+
22
32
  #### `NEXT_AUTH_SECRET`
23
33
 
24
34
  - Type: Required
@@ -17,6 +17,12 @@ LobeChat 在部署时提供了完善的身份验证服务能力,以下是相
17
17
 
18
18
  ### 通用设置
19
19
 
20
+ #### `NEXT_PUBLIC_ENABLE_NEXT_AUTH`
21
+ - v1.52.0 之后有变更
22
+ - 针对使用 Vercel 部署中使用 next-auth 的用户,需要额外添加 NEXT_PUBLIC_ENABLE_NEXT_AUTH=1 环境变量来确保开启 Next Auth
23
+ - 针对使用自构建镜像中使用 clerk 的用户,需要额外配置 NEXT_PUBLIC_ENABLE_NEXT_AUTH=0 环境变量来关闭 Next Auth
24
+ - 其他标准部署场景(Vercel 中使用 Clerk 与 Docker 中使用 next-auth )不受影响
25
+
20
26
  #### `NEXT_AUTH_SECRET`
21
27
 
22
28
  - 类型:必选
@@ -280,4 +286,4 @@ LobeChat 在部署时提供了完善的身份验证服务能力,以下是相
280
286
  - 类型:必选
281
287
  - 描述: Clerk 应用程序的 Secret key。您可以在[这里](https://dashboard.clerk.com)访问,并导航到 API Keys 以查看。
282
288
  - 默认值:`-`
283
- - 示例: `sk_test_513Ma0P7IAWM1XMv4waxZjRYRajWTaCfJLjpEO3SD2` (测试环境) / `sk_live_eMMlHjwJvZFUfczFljSKqZdwQtLvmczmsJSNmdrpeZ`(生产环境)
289
+ - 示例: `sk_test_513Ma0P7IAWM1XMv4waxZjRYRajWTaCfJLjpEO3SD2` (测试环境) / `sk_live_eMMlHjwJvZFUfczFljSKqZdwQtLvmczmsJSNmdrpeZ`(生产环境)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.53.4",
3
+ "version": "1.53.6",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -173,7 +173,7 @@
173
173
  "langfuse": "3.29.1",
174
174
  "langfuse-core": "3.29.1",
175
175
  "lodash-es": "^4.17.21",
176
- "lucide-react": "^0.471.0",
176
+ "lucide-react": "^0.475.0",
177
177
  "mammoth": "^1.9.0",
178
178
  "mdast-util-to-markdown": "^2.1.2",
179
179
  "modern-screenshot": "^4.5.5",
@@ -245,7 +245,7 @@
245
245
  "devDependencies": {
246
246
  "@commitlint/cli": "^19.6.1",
247
247
  "@edge-runtime/vm": "^5.0.0",
248
- "@huggingface/tasks": "^0.13.13",
248
+ "@huggingface/tasks": "^0.15.0",
249
249
  "@lobehub/i18n-cli": "^1.20.3",
250
250
  "@lobehub/lint": "^1.25.5",
251
251
  "@lobehub/seo-cli": "^1.4.3",
@@ -0,0 +1,415 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { DEFAULT_MODEL_PROVIDER_LIST } from '@/config/modelProviders';
4
+ import { clientDB, initializeDB } from '@/database/client/db';
5
+ import { AiProviderModel } from '@/database/server/models/aiProvider';
6
+ import { LobeChatDatabase } from '@/database/type';
7
+ import { AiProviderModelListItem } from '@/types/aiModel';
8
+ import {
9
+ AiProviderDetailItem,
10
+ AiProviderListItem,
11
+ AiProviderRuntimeConfig,
12
+ EnabledAiModel,
13
+ EnabledProvider,
14
+ } from '@/types/aiProvider';
15
+
16
+ import { AiInfraRepos } from './index';
17
+
18
+ const userId = 'test-user-id';
19
+ const mockProviderConfigs = {
20
+ openai: { enabled: true },
21
+ anthropic: { enabled: false },
22
+ };
23
+
24
+ let repo: AiInfraRepos;
25
+
26
+ beforeEach(async () => {
27
+ await initializeDB();
28
+ vi.clearAllMocks();
29
+
30
+ repo = new AiInfraRepos(clientDB as any, userId, mockProviderConfigs);
31
+ });
32
+
33
+ describe('AiInfraRepos', () => {
34
+ describe('getAiProviderList', () => {
35
+ it('should merge builtin and user providers correctly', async () => {
36
+ const mockUserProviders = [
37
+ { id: 'openai', enabled: true, name: 'Custom OpenAI' },
38
+ { id: 'custom', enabled: true, name: 'Custom Provider' },
39
+ ] as AiProviderListItem[];
40
+
41
+ vi.spyOn(repo.aiProviderModel, 'getAiProviderList').mockResolvedValueOnce(mockUserProviders);
42
+
43
+ const result = await repo.getAiProviderList();
44
+
45
+ expect(result).toBeDefined();
46
+ expect(result.length).toBeGreaterThan(0);
47
+ // Verify the merge logic
48
+ const openaiProvider = result.find((p) => p.id === 'openai');
49
+ expect(openaiProvider).toMatchObject({ enabled: true, name: 'Custom OpenAI' });
50
+ });
51
+
52
+ it('should sort providers according to DEFAULT_MODEL_PROVIDER_LIST order', async () => {
53
+ vi.spyOn(repo.aiProviderModel, 'getAiProviderList').mockResolvedValue([]);
54
+
55
+ const result = await repo.getAiProviderList();
56
+
57
+ expect(result).toEqual(
58
+ expect.arrayContaining(
59
+ DEFAULT_MODEL_PROVIDER_LIST.map((item) =>
60
+ expect.objectContaining({
61
+ id: item.id,
62
+ source: 'builtin',
63
+ }),
64
+ ),
65
+ ),
66
+ );
67
+ });
68
+ });
69
+
70
+ describe('getUserEnabledProviderList', () => {
71
+ it('should return only enabled providers', async () => {
72
+ const mockProviders = [
73
+ { id: 'openai', enabled: true, name: 'OpenAI', sort: 1 },
74
+ { id: 'anthropic', enabled: false, name: 'Anthropic', sort: 2 },
75
+ ] as AiProviderListItem[];
76
+
77
+ vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
78
+
79
+ const result = await repo.getUserEnabledProviderList();
80
+
81
+ expect(result).toHaveLength(1);
82
+ expect(result[0]).toMatchObject({
83
+ id: 'openai',
84
+ name: 'OpenAI',
85
+ });
86
+ });
87
+
88
+ it('should return only enabled provider', async () => {
89
+ const mockProviders = [
90
+ {
91
+ enabled: true,
92
+ id: 'openai',
93
+ logo: 'logo1',
94
+ name: 'OpenAI',
95
+ sort: 1,
96
+ source: 'builtin' as const,
97
+ },
98
+ {
99
+ enabled: false,
100
+ id: 'anthropic',
101
+ logo: 'logo2',
102
+ name: 'Anthropic',
103
+ sort: 2,
104
+ source: 'builtin' as const,
105
+ },
106
+ ];
107
+
108
+ vi.spyOn(repo.aiProviderModel, 'getAiProviderList').mockResolvedValue(mockProviders);
109
+
110
+ const result = await repo.getUserEnabledProviderList();
111
+
112
+ expect(result).toEqual([
113
+ {
114
+ id: 'openai',
115
+ logo: 'logo1',
116
+ name: 'OpenAI',
117
+ source: 'builtin',
118
+ },
119
+ ]);
120
+ });
121
+ });
122
+
123
+ describe('getEnabledModels', () => {
124
+ it('should merge and filter enabled models', async () => {
125
+ const mockProviders = [{ id: 'openai', enabled: true }] as AiProviderListItem[];
126
+ const mockAllModels = [
127
+ { id: 'gpt-4', providerId: 'openai', enabled: true },
128
+ ] as EnabledAiModel[];
129
+
130
+ vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
131
+ vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue(mockAllModels);
132
+ vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([
133
+ { id: 'gpt-4', enabled: true, type: 'chat' },
134
+ ]);
135
+
136
+ const result = await repo.getEnabledModels();
137
+
138
+ expect(result).toBeDefined();
139
+ expect(result.length).toBeGreaterThan(0);
140
+ expect(result[0]).toMatchObject({
141
+ id: 'gpt-4',
142
+ providerId: 'openai',
143
+ });
144
+ });
145
+
146
+ it('should merge builtin and user models correctly', async () => {
147
+ const mockProviders = [
148
+ { enabled: true, id: 'openai', name: 'OpenAI', sort: 1, source: 'builtin' as const },
149
+ ];
150
+
151
+ const mockAllModels = [
152
+ {
153
+ abilities: { vision: true },
154
+ displayName: 'Custom GPT-4',
155
+ enabled: true,
156
+ id: 'gpt-4',
157
+ providerId: 'openai',
158
+ sort: 1,
159
+ type: 'chat' as const,
160
+ contextWindowTokens: 10,
161
+ },
162
+ ];
163
+
164
+ vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
165
+ vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue(mockAllModels);
166
+ vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([
167
+ {
168
+ abilities: {},
169
+ displayName: 'GPT-4',
170
+ enabled: true,
171
+ id: 'gpt-4',
172
+ type: 'chat' as const,
173
+ },
174
+ ]);
175
+
176
+ const result = await repo.getEnabledModels();
177
+
178
+ expect(result).toContainEqual(
179
+ expect.objectContaining({
180
+ abilities: { vision: true },
181
+ displayName: 'Custom GPT-4',
182
+ enabled: true,
183
+ contextWindowTokens: 10,
184
+ id: 'gpt-4',
185
+ providerId: 'openai',
186
+ sort: 1,
187
+ type: 'chat',
188
+ }),
189
+ );
190
+ });
191
+
192
+ it('should handle case when user model not found', async () => {
193
+ const mockProviders = [
194
+ { enabled: true, id: 'openai', name: 'OpenAI', sort: 1, source: 'builtin' as const },
195
+ ];
196
+
197
+ const mockAllModels: any[] = [];
198
+
199
+ vi.spyOn(repo, 'getAiProviderList').mockResolvedValue(mockProviders);
200
+ vi.spyOn(repo.aiModelModel, 'getAllModels').mockResolvedValue(mockAllModels);
201
+ vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue([
202
+ {
203
+ abilities: { reasoning: true },
204
+ displayName: 'GPT-4',
205
+ enabled: true,
206
+ id: 'gpt-4',
207
+ type: 'chat' as const,
208
+ },
209
+ ]);
210
+
211
+ const result = await repo.getEnabledModels();
212
+
213
+ expect(result[0]).toEqual(
214
+ expect.objectContaining({
215
+ abilities: { reasoning: true },
216
+ enabled: true,
217
+ id: 'gpt-4',
218
+ providerId: 'openai',
219
+ }),
220
+ );
221
+ });
222
+ });
223
+
224
+ describe('getAiProviderModelList', () => {
225
+ it('should merge builtin and user models', async () => {
226
+ const providerId = 'openai';
227
+ const mockUserModels = [
228
+ { id: 'custom-gpt4', enabled: true, type: 'chat' },
229
+ ] as AiProviderModelListItem[];
230
+ const mockBuiltinModels = [{ id: 'gpt-4', enabled: true }];
231
+
232
+ vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(mockUserModels);
233
+ vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(mockBuiltinModels);
234
+
235
+ const result = await repo.getAiProviderModelList(providerId);
236
+
237
+ expect(result).toHaveLength(2);
238
+ expect(result).toEqual(
239
+ expect.arrayContaining([
240
+ expect.objectContaining({ id: 'custom-gpt4' }),
241
+ expect.objectContaining({ id: 'gpt-4' }),
242
+ ]),
243
+ );
244
+ });
245
+ it('should merge default and custom models', async () => {
246
+ const mockCustomModels = [
247
+ {
248
+ displayName: 'Custom GPT-4',
249
+ enabled: false,
250
+ id: 'gpt-4',
251
+ type: 'chat' as const,
252
+ },
253
+ ];
254
+
255
+ const mockDefaultModels = [
256
+ {
257
+ displayName: 'GPT-4',
258
+ enabled: true,
259
+ id: 'gpt-4',
260
+ type: 'chat' as const,
261
+ },
262
+ ];
263
+
264
+ vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue(mockCustomModels);
265
+ vi.spyOn(repo as any, 'fetchBuiltinModels').mockResolvedValue(mockDefaultModels);
266
+
267
+ const result = await repo.getAiProviderModelList('openai');
268
+
269
+ expect(result).toContainEqual(
270
+ expect.objectContaining({
271
+ displayName: 'Custom GPT-4',
272
+ enabled: false,
273
+ id: 'gpt-4',
274
+ }),
275
+ );
276
+ });
277
+
278
+ it('should use builtin models', async () => {
279
+ const providerId = 'taichu';
280
+
281
+ vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue([]);
282
+
283
+ const result = await repo.getAiProviderModelList(providerId);
284
+
285
+ expect(result).toHaveLength(2);
286
+ expect(result).toEqual(
287
+ expect.arrayContaining([
288
+ expect.objectContaining({ id: 'taichu_llm' }),
289
+ expect.objectContaining({ id: 'taichu2_mm' }),
290
+ ]),
291
+ );
292
+ });
293
+
294
+ it('should return empty if not exist provider', async () => {
295
+ const providerId = 'abc';
296
+
297
+ vi.spyOn(repo.aiModelModel, 'getModelListByProviderId').mockResolvedValue([]);
298
+
299
+ const result = await repo.getAiProviderModelList(providerId);
300
+
301
+ expect(result).toHaveLength(0);
302
+ });
303
+ });
304
+
305
+ describe('getAiProviderRuntimeState', () => {
306
+ it('should return complete runtime state', async () => {
307
+ const mockRuntimeConfig = {
308
+ openai: { apiKey: 'test-key' },
309
+ } as unknown as Record<string, AiProviderRuntimeConfig>;
310
+ const mockEnabledProviders = [{ id: 'openai', name: 'OpenAI' }] as EnabledProvider[];
311
+ const mockEnabledModels = [{ id: 'gpt-4', providerId: 'openai' }] as EnabledAiModel[];
312
+
313
+ vi.spyOn(repo.aiProviderModel, 'getAiProviderRuntimeConfig').mockResolvedValue(
314
+ mockRuntimeConfig,
315
+ );
316
+ vi.spyOn(repo, 'getUserEnabledProviderList').mockResolvedValue(mockEnabledProviders);
317
+ vi.spyOn(repo, 'getEnabledModels').mockResolvedValue(mockEnabledModels);
318
+
319
+ const result = await repo.getAiProviderRuntimeState();
320
+
321
+ expect(result).toMatchObject({
322
+ enabledAiProviders: mockEnabledProviders,
323
+ enabledAiModels: mockEnabledModels,
324
+ runtimeConfig: expect.any(Object),
325
+ });
326
+ });
327
+ it('should return provider runtime state', async () => {
328
+ const mockRuntimeConfig = {
329
+ openai: {
330
+ apiKey: 'test-key',
331
+ },
332
+ } as unknown as Record<string, AiProviderRuntimeConfig>;
333
+
334
+ vi.spyOn(repo.aiProviderModel, 'getAiProviderRuntimeConfig').mockResolvedValue(
335
+ mockRuntimeConfig,
336
+ );
337
+
338
+ vi.spyOn(repo, 'getUserEnabledProviderList').mockResolvedValue([
339
+ { id: 'openai', logo: 'logo1', name: 'OpenAI', source: 'builtin' },
340
+ ]);
341
+
342
+ vi.spyOn(repo, 'getEnabledModels').mockResolvedValue([
343
+ {
344
+ abilities: {},
345
+ enabled: true,
346
+ id: 'gpt-4',
347
+ providerId: 'openai',
348
+ type: 'chat',
349
+ },
350
+ ]);
351
+
352
+ const result = await repo.getAiProviderRuntimeState();
353
+
354
+ expect(result).toEqual({
355
+ enabledAiModels: [
356
+ expect.objectContaining({
357
+ enabled: true,
358
+ id: 'gpt-4',
359
+ providerId: 'openai',
360
+ }),
361
+ ],
362
+ enabledAiProviders: [{ id: 'openai', logo: 'logo1', name: 'OpenAI', source: 'builtin' }],
363
+ runtimeConfig: {
364
+ openai: {
365
+ apiKey: 'test-key',
366
+ enabled: true,
367
+ },
368
+ },
369
+ });
370
+ });
371
+ });
372
+
373
+ describe('getAiProviderDetail', () => {
374
+ it('should merge provider config with user settings', async () => {
375
+ const providerId = 'openai';
376
+ const mockProviderDetail = {
377
+ id: providerId,
378
+ customSetting: 'test',
379
+ } as unknown as AiProviderDetailItem;
380
+
381
+ vi.spyOn(repo.aiProviderModel, 'getAiProviderById').mockResolvedValue(mockProviderDetail);
382
+
383
+ const result = await repo.getAiProviderDetail(providerId);
384
+
385
+ expect(result).toMatchObject({
386
+ id: providerId,
387
+ customSetting: 'test',
388
+ enabled: true, // from mockProviderConfigs
389
+ });
390
+ });
391
+ it('should merge provider configs correctly', async () => {
392
+ const mockProviderDetail = {
393
+ enabled: true,
394
+ id: 'openai',
395
+ keyVaults: { apiKey: 'test-key' },
396
+ name: 'Custom OpenAI',
397
+ settings: {},
398
+ source: 'builtin' as const,
399
+ };
400
+
401
+ vi.spyOn(repo.aiProviderModel, 'getAiProviderById').mockResolvedValue(mockProviderDetail);
402
+
403
+ const result = await repo.getAiProviderDetail('openai');
404
+
405
+ expect(result).toEqual({
406
+ enabled: true,
407
+ id: 'openai',
408
+ keyVaults: { apiKey: 'test-key' },
409
+ name: 'Custom OpenAI',
410
+ settings: {},
411
+ source: 'builtin',
412
+ });
413
+ });
414
+ });
415
+ });
@@ -11,6 +11,7 @@ import {
11
11
  AiProviderListItem,
12
12
  AiProviderRuntimeState,
13
13
  EnabledAiModel,
14
+ EnabledProvider,
14
15
  } from '@/types/aiProvider';
15
16
  import { ProviderConfig } from '@/types/user/settings';
16
17
  import { merge, mergeArrayById } from '@/utils/merge';
@@ -22,7 +23,7 @@ export class AiInfraRepos {
22
23
  private db: LobeChatDatabase;
23
24
  aiProviderModel: AiProviderModel;
24
25
  private providerConfigs: Record<string, ProviderConfig>;
25
- private aiModelModel: AiModelModel;
26
+ aiModelModel: AiModelModel;
26
27
 
27
28
  constructor(
28
29
  db: LobeChatDatabase,
@@ -70,7 +71,14 @@ export class AiInfraRepos {
70
71
  return list
71
72
  .filter((item) => item.enabled)
72
73
  .sort((a, b) => a.sort! - b.sort!)
73
- .map((item) => ({ id: item.id, logo: item.logo, name: item.name, source: item.source }));
74
+ .map(
75
+ (item): EnabledProvider => ({
76
+ id: item.id,
77
+ logo: item.logo,
78
+ name: item.name,
79
+ source: item.source,
80
+ }),
81
+ );
74
82
  };
75
83
 
76
84
  getEnabledModels = async () => {
@@ -104,7 +112,7 @@ export class AiInfraRepos {
104
112
  ? user.contextWindowTokens
105
113
  : item.contextWindowTokens,
106
114
  displayName: user?.displayName || item.displayName,
107
- enabled: user.enabled || item.enabled,
115
+ enabled: typeof user.enabled === 'boolean' ? user.enabled : item.enabled,
108
116
  id: item.id,
109
117
  providerId: provider.id,
110
118
  sort: user.sort || undefined,
@@ -174,10 +174,10 @@ const Item = memo<ChatListItemProps>(
174
174
  () => ({
175
175
  components,
176
176
  customRender: markdownCustomRender,
177
- rehypePlugins,
178
- remarkPlugins,
177
+ rehypePlugins: item?.role === 'user' ? undefined : rehypePlugins,
178
+ remarkPlugins: item?.role === 'user' ? undefined : remarkPlugins,
179
179
  }),
180
- [components, markdownCustomRender],
180
+ [components, markdownCustomRender, item?.role],
181
181
  );
182
182
 
183
183
  const onChange = useCallback((value: string) => updateMessageContent(id, value), [id]);
@@ -1,6 +1,6 @@
1
+ import { createRemarkCustomTagPlugin } from '../remarkPlugins/createRemarkCustomTagPlugin';
1
2
  import { MarkdownElement } from '../type';
2
3
  import Component from './Render';
3
- import { createRemarkCustomTagPlugin } from './remarkPlugin';
4
4
 
5
5
  const ThinkingElement: MarkdownElement = {
6
6
  Component,
@@ -1,6 +1,7 @@
1
- import { toMarkdown } from 'mdast-util-to-markdown';
2
1
  import { SKIP, visit } from 'unist-util-visit';
3
2
 
3
+ import { treeNodeToString } from './getNodeContent';
4
+
4
5
  export const createRemarkCustomTagPlugin = (tag: string) => () => {
5
6
  return (tree: any) => {
6
7
  visit(tree, 'html', (node, index, parent) => {
@@ -31,17 +32,7 @@ export const createRemarkCustomTagPlugin = (tag: string) => () => {
31
32
  );
32
33
 
33
34
  // 转换为 Markdown 字符串
34
- const content = contentNodes
35
- .map((n: any) => {
36
- // fix https://github.com/lobehub/lobe-chat/issues/5668
37
- if (n.type === 'paragraph') {
38
- return n.children.map((child: any) => child.value).join('');
39
- }
40
-
41
- return toMarkdown(n);
42
- })
43
- .join('\n\n')
44
- .trim();
35
+ const content = treeNodeToString(contentNodes);
45
36
 
46
37
  // 创建自定义节点
47
38
  const customNode = {