@lobehub/chat 1.85.2 → 1.85.4

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,64 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.85.4](https://github.com/lobehub/lobe-chat/compare/v1.85.3...v1.85.4)
6
+
7
+ <sup>Released on **2025-05-10**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix nothing return when reset the client db.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix nothing return when reset the client db, closes [#7738](https://github.com/lobehub/lobe-chat/issues/7738) ([90efb13](https://github.com/lobehub/lobe-chat/commit/90efb13))
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.85.3](https://github.com/lobehub/lobe-chat/compare/v1.85.2...v1.85.3)
31
+
32
+ <sup>Released on **2025-05-10**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Remove mcp client cache.
37
+
38
+ #### 💄 Styles
39
+
40
+ - **misc**: Improve pdf and xlsx file content parser.
41
+
42
+ <br/>
43
+
44
+ <details>
45
+ <summary><kbd>Improvements and Fixes</kbd></summary>
46
+
47
+ #### What's fixed
48
+
49
+ - **misc**: Remove mcp client cache, closes [#7776](https://github.com/lobehub/lobe-chat/issues/7776) ([0582134](https://github.com/lobehub/lobe-chat/commit/0582134))
50
+
51
+ #### Styles
52
+
53
+ - **misc**: Improve pdf and xlsx file content parser, closes [#7783](https://github.com/lobehub/lobe-chat/issues/7783) ([0376870](https://github.com/lobehub/lobe-chat/commit/0376870))
54
+
55
+ </details>
56
+
57
+ <div align="right">
58
+
59
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
60
+
61
+ </div>
62
+
5
63
  ### [Version 1.85.2](https://github.com/lobehub/lobe-chat/compare/v1.85.1...v1.85.2)
6
64
 
7
65
  <sup>Released on **2025-05-10**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,25 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix nothing return when reset the client db."
6
+ ]
7
+ },
8
+ "date": "2025-05-10",
9
+ "version": "1.85.4"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Remove mcp client cache."
15
+ ],
16
+ "improvements": [
17
+ "Improve pdf and xlsx file content parser."
18
+ ]
19
+ },
20
+ "date": "2025-05-10",
21
+ "version": "1.85.3"
22
+ },
2
23
  {
3
24
  "children": {
4
25
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.85.2",
3
+ "version": "1.85.4",
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",
@@ -1,8 +1,7 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
3
  exports[`ExcelLoader > should aggregate content correctly (joining sheets) > aggregated_content 1`] = `
4
- "## Sheet: 表1
5
-
4
+ "<sheet name="表1" index="0">
6
5
  | __EMPTY | 类别 A | 类别 B | __EMPTY_1 | __EMPTY_2 |
7
6
  | --- | --- | --- | --- | --- |
8
7
  | 项目 1 | 5 | 7 | | |
@@ -10,18 +9,17 @@ exports[`ExcelLoader > should aggregate content correctly (joining sheets) > agg
10
9
  | 项目 3 | 9 | 15 | | |
11
10
  | 项目 4 | 7 | 12 | | |
12
11
  | 项目 5 | 16 | 21 | | |
12
+ </sheet>
13
13
 
14
- ---
15
-
16
- ## Sheet: 表2 - 表格 2
17
-
14
+ <sheet name="表2 - 表格 2" index="1">
18
15
  | __EMPTY | 类别 A | 类别 B | __EMPTY_1 | __EMPTY_2 |
19
16
  | --- | --- | --- | --- | --- |
20
17
  | 项目 1 | 5 | 7 | | |
21
18
  | 项目 2 | 10 | 8 | | |
22
19
  | 项目 3 | 9 | 15 | | |
23
20
  | 项目 4 | 7 | 12 | | |
24
- | 项目 5 | 16 | 21 | | |"
21
+ | 项目 5 | 16 | 21 | | |
22
+ </sheet>"
25
23
  `;
26
24
 
27
25
  exports[`ExcelLoader > should load pages correctly from an Excel file (one page per sheet) 1`] = `
@@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises';
3
3
  import * as xlsx from 'xlsx';
4
4
 
5
5
  import type { DocumentPage, FileLoaderInterface } from '../../types';
6
+ import { promptTemplate } from './prompt';
6
7
 
7
8
  const log = debug('file-loaders:excel');
8
9
 
@@ -135,13 +136,7 @@ export class ExcelLoader implements FileLoaderInterface {
135
136
  */
136
137
  async aggregateContent(pages: DocumentPage[]): Promise<string> {
137
138
  log('Aggregating content from', pages.length, 'Excel pages');
138
- const result = pages
139
- .map((page) => {
140
- const sheetName = page.metadata.sheetName;
141
- const header = sheetName ? `## Sheet: ${sheetName}\n\n` : '';
142
- return header + page.pageContent;
143
- })
144
- .join('\n\n---\n\n'); // Separator between sheets
139
+ const result = promptTemplate(pages);
145
140
 
146
141
  log('Excel content aggregated successfully, length:', result.length);
147
142
  return result;
@@ -0,0 +1,18 @@
1
+ import type { DocumentPage } from '../../types';
2
+
3
+ export const promptTemplate = (pages: DocumentPage[]) => {
4
+ return (
5
+ pages
6
+ .map((page, index) => {
7
+ const sheetName = page.metadata.sheetName;
8
+
9
+ const sheetIndex = page.metadata?.pageNumber || index;
10
+
11
+ return `<sheet name="${sheetName}" index="${sheetIndex}">
12
+ ${page.pageContent}
13
+ </sheet>`;
14
+ })
15
+ // Separator between sheets
16
+ .join('\n\n')
17
+ );
18
+ };
@@ -1,7 +1,8 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
3
  exports[`PdfLoader > should aggregate content correctly 1`] = `
4
- "简单报告
4
+ "<page pageNumber="1">
5
+ 简单报告
5
6
  副标题
6
7
  轻点或点按此占位符⽂本并开始键⼊即可开始。你可以在 Mac、iPad、iPhone 或
7
8
  iCloud.com 上查看和编辑此⽂稿。
@@ -24,10 +25,13 @@ Pages ⽂稿可⽤于⽂字处理和⻚⾯布局。此“简单报告”模板
24
25
  ⽂本添加你⾃⼰的内容。”
25
26
  ⻚脚
26
27
  1
28
+ </page>
27
29
 
30
+ <page pageNumber="2">
28
31
  这是第⼆⻚的内容
29
32
  ⻚脚
30
- 2"
33
+ 2
34
+ </page>"
31
35
  `;
32
36
 
33
37
  exports[`PdfLoader > should attach document metadata correctly 1`] = `
@@ -7,6 +7,7 @@ import * as _pdfjsWorker from 'pdfjs-dist/legacy/build/pdf.worker.mjs';
7
7
  import type { TextContent } from 'pdfjs-dist/types/src/display/api';
8
8
 
9
9
  import type { DocumentPage, FileLoaderInterface } from '../../types';
10
+ import { promptTemplate } from './prompt';
10
11
 
11
12
  const log = debug('file-loaders:pdf');
12
13
 
@@ -132,7 +133,7 @@ export class PdfLoader implements FileLoaderInterface {
132
133
  `Found ${validPages.length} valid pages for aggregation (${pages.length - validPages.length} pages with errors filtered out)`,
133
134
  );
134
135
 
135
- const result = validPages.map((page) => page.pageContent).join('\n\n');
136
+ const result = promptTemplate(validPages);
136
137
  log('PDF content aggregated successfully, length:', result.length);
137
138
  return result;
138
139
  }
@@ -0,0 +1,13 @@
1
+ import type { DocumentPage } from '../../types';
2
+
3
+ export const promptTemplate = (pages: DocumentPage[]) => {
4
+ return pages
5
+ .map((page, index) => {
6
+ const pageNumber = page.metadata?.pageNumber || index;
7
+
8
+ return `<page pageNumber="${pageNumber}">
9
+ ${page.pageContent}
10
+ </page>`;
11
+ })
12
+ .join('\n\n');
13
+ };
@@ -2,7 +2,9 @@
2
2
 
3
3
  exports[`loadFile Integration Tests > PDF Handling > should load content from a pdf file using filePath 1`] = `
4
4
  {
5
- "content": "123",
5
+ "content": "<page pageNumber="1">
6
+ 123
7
+ </page>",
6
8
  "fileType": "pdf",
7
9
  "filename": "test.pdf",
8
10
  "metadata": {
@@ -44,7 +44,7 @@ describe('loadFile Integration Tests', () => {
44
44
  // Pass filePath directly to loadFile
45
45
  const docs = await loadFile(filePath);
46
46
 
47
- expect(docs.content).toEqual('123');
47
+ expect(docs.content).toContain('123');
48
48
  expect(docs.source).toEqual(filePath);
49
49
 
50
50
  // @ts-expect-error
@@ -313,16 +313,33 @@ export class DatabaseManager {
313
313
  }
314
314
 
315
315
  async resetDatabase(): Promise<void> {
316
- // 删除 IndexedDB 数据库
316
+ // 1. 关闭现有的 PGlite 连接(如果存在)
317
+ if (this.dbInstance) {
318
+ try {
319
+ // @ts-ignore
320
+ await (this.dbInstance.session as any).client.close();
321
+ console.log('PGlite instance closed successfully.');
322
+ } catch (e) {
323
+ console.error('Error closing PGlite instance:', e);
324
+ // 即使关闭失败,也尝试继续删除,IndexedDB 的 onblocked 或 onerror 会处理后续问题
325
+ }
326
+ }
327
+
328
+ // 2. 重置数据库实例和初始化状态
329
+ this.dbInstance = null;
330
+ this.initPromise = null;
331
+ this.isLocalDBSchemaSynced = false; // 重置同步状态
332
+
333
+ // 3. 删除 IndexedDB 数据库
317
334
  return new Promise<void>((resolve, reject) => {
318
335
  // 检查 IndexedDB 是否可用
319
336
  if (typeof indexedDB === 'undefined') {
320
337
  console.warn('IndexedDB is not available, cannot delete database');
321
- resolve();
338
+ resolve(); // 在此环境下无法删除,直接解决
322
339
  return;
323
340
  }
324
341
 
325
- const dbName = `/pglite/${DB_NAME}`;
342
+ const dbName = `/pglite/${DB_NAME}`; // PGlite IdbFs 使用的路径
326
343
  const request = indexedDB.deleteDatabase(dbName);
327
344
 
328
345
  request.onsuccess = () => {
@@ -338,8 +355,26 @@ export class DatabaseManager {
338
355
 
339
356
  // eslint-disable-next-line unicorn/prefer-add-event-listener
340
357
  request.onerror = (event) => {
341
- console.error('❌ Error resetting database:', event);
342
- reject(new Error(`Failed to reset database '${dbName}'`));
358
+ const error = (event.target as IDBOpenDBRequest)?.error;
359
+ console.error(`❌ Error resetting database '${dbName}':`, error);
360
+ reject(
361
+ new Error(
362
+ `Failed to reset database '${dbName}'. Error: ${error?.message || 'Unknown error'}`,
363
+ ),
364
+ );
365
+ };
366
+
367
+ request.onblocked = (event) => {
368
+ // 当其他打开的连接阻止数据库删除时,会触发此事件
369
+ console.warn(
370
+ `Deletion of database '${dbName}' is blocked. This usually means other connections (e.g., in other tabs) are still open. Event:`,
371
+ event,
372
+ );
373
+ reject(
374
+ new Error(
375
+ `Failed to reset database '${dbName}' because it is blocked by other open connections. Please close other tabs or applications using this database and try again.`,
376
+ ),
377
+ );
343
378
  };
344
379
  });
345
380
  }
@@ -0,0 +1,160 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { DataImporterRepos } from '@/database/repositories/dataImporter';
5
+ import { FileService } from '@/server/services/file';
6
+ import { ImportResultData } from '@/types/importer';
7
+
8
+ import { importerRouter } from '../importer';
9
+
10
+ const mockGetFileContent = vi.fn();
11
+ const mockImportData = vi.fn();
12
+ const mockImportPgData = vi.fn();
13
+
14
+ vi.mock('@/database/repositories/dataImporter', () => ({
15
+ DataImporterRepos: vi.fn().mockImplementation(() => ({
16
+ importData: mockImportData,
17
+ importPgData: mockImportPgData,
18
+ })),
19
+ }));
20
+
21
+ vi.mock('@/server/services/file', () => ({
22
+ FileService: vi.fn().mockImplementation(() => ({
23
+ getFileContent: mockGetFileContent,
24
+ })),
25
+ }));
26
+
27
+ describe('importerRouter', () => {
28
+ const mockFileContent = JSON.stringify({
29
+ version: 1,
30
+ messages: [],
31
+ });
32
+
33
+ const mockPgData = {
34
+ data: {},
35
+ mode: 'pglite' as const,
36
+ schemaHash: 'hash',
37
+ };
38
+
39
+ const mockImportResult: ImportResultData = {
40
+ success: true,
41
+ results: { messages: { added: 1, errors: 0, skips: 0 } },
42
+ };
43
+
44
+ const mockImportErrorResult: ImportResultData = {
45
+ success: false,
46
+ error: {
47
+ message: 'Import failed',
48
+ details: 'Error details',
49
+ },
50
+ results: {},
51
+ };
52
+
53
+ beforeEach(() => {
54
+ mockGetFileContent.mockResolvedValue(mockFileContent);
55
+ mockImportData.mockResolvedValue(mockImportResult);
56
+ mockImportPgData.mockResolvedValue(mockImportResult);
57
+ });
58
+
59
+ afterEach(() => {
60
+ vi.clearAllMocks();
61
+ });
62
+
63
+ const ctx = {
64
+ userId: 'user-1',
65
+ serverDB: {} as any,
66
+ };
67
+
68
+ describe('importByFile', () => {
69
+ it('should successfully import file data', async () => {
70
+ const caller = importerRouter.createCaller(ctx);
71
+
72
+ const result = await caller.importByFile({ pathname: 'test.json' });
73
+
74
+ expect(result).toEqual(mockImportResult);
75
+ expect(mockGetFileContent).toHaveBeenCalledWith('test.json');
76
+ expect(mockImportData).toHaveBeenCalledWith(JSON.parse(mockFileContent));
77
+ });
78
+
79
+ it('should handle PG data import', async () => {
80
+ mockGetFileContent.mockResolvedValue(JSON.stringify(mockPgData));
81
+
82
+ const caller = importerRouter.createCaller(ctx);
83
+
84
+ const result = await caller.importByFile({ pathname: 'test.json' });
85
+
86
+ expect(result).toEqual(mockImportResult);
87
+ expect(mockImportPgData).toHaveBeenCalledWith(mockPgData);
88
+ });
89
+
90
+ it('should throw error when file read fails', async () => {
91
+ mockGetFileContent.mockRejectedValue(new Error('File read error'));
92
+
93
+ const caller = importerRouter.createCaller(ctx);
94
+
95
+ await expect(caller.importByFile({ pathname: 'test.json' })).rejects.toThrow(TRPCError);
96
+ });
97
+
98
+ it('should throw error for invalid JSON', async () => {
99
+ mockGetFileContent.mockResolvedValue('invalid json');
100
+
101
+ const caller = importerRouter.createCaller(ctx);
102
+
103
+ await expect(caller.importByFile({ pathname: 'test.json' })).rejects.toThrow(TRPCError);
104
+ });
105
+ });
106
+
107
+ describe('importByPost', () => {
108
+ it('should successfully import posted data', async () => {
109
+ const caller = importerRouter.createCaller(ctx);
110
+
111
+ const postData = {
112
+ data: {
113
+ version: 1,
114
+ messages: [],
115
+ },
116
+ };
117
+
118
+ const result = await caller.importByPost(postData);
119
+
120
+ expect(result).toEqual(mockImportResult);
121
+ expect(mockImportData).toHaveBeenCalledWith(postData.data);
122
+ });
123
+
124
+ it('should handle import failure', async () => {
125
+ mockImportData.mockResolvedValue(mockImportErrorResult);
126
+
127
+ const caller = importerRouter.createCaller(ctx);
128
+
129
+ const result = await caller.importByPost({
130
+ data: {
131
+ version: 1,
132
+ messages: [],
133
+ },
134
+ });
135
+
136
+ expect(result).toEqual(mockImportErrorResult);
137
+ });
138
+ });
139
+
140
+ describe('importPgByPost', () => {
141
+ it('should successfully import PG data', async () => {
142
+ const caller = importerRouter.createCaller(ctx);
143
+
144
+ const result = await caller.importPgByPost(mockPgData);
145
+
146
+ expect(result).toEqual(mockImportResult);
147
+ expect(mockImportPgData).toHaveBeenCalledWith(mockPgData);
148
+ });
149
+
150
+ it('should handle import failure', async () => {
151
+ mockImportPgData.mockResolvedValue(mockImportErrorResult);
152
+
153
+ const caller = importerRouter.createCaller(ctx);
154
+
155
+ const result = await caller.importPgByPost(mockPgData);
156
+
157
+ expect(result).toEqual(mockImportErrorResult);
158
+ });
159
+ });
160
+ });
@@ -9,12 +9,7 @@ import { safeParseJSON } from '@/utils/safeParseJSON';
9
9
 
10
10
  const log = debug('lobe-mcp:service');
11
11
 
12
- // Removed MCPConnection interface as it's no longer needed
13
-
14
12
  class MCPService {
15
- // Store instances of the custom MCPClient, keyed by serialized MCPClientParams
16
- private clients: Map<string, MCPClient> = new Map();
17
-
18
13
  // --- MCP Interaction ---
19
14
 
20
15
  // listTools now accepts MCPClientParams
@@ -97,15 +92,6 @@ class MCPService {
97
92
 
98
93
  // Private method to get or initialize a client based on parameters
99
94
  private async getClient(params: MCPClientParams): Promise<MCPClient> {
100
- const key = this.serializeParams(params); // Use custom serialization
101
- log(`Attempting to get client for key: ${key} (params: %O)`, params);
102
-
103
- if (this.clients.has(key)) {
104
- log(`Returning cached client for key: ${key}`);
105
- return this.clients.get(key)!;
106
- }
107
-
108
- log(`No cached client found for key: ${key}. Initializing new client.`);
109
95
  try {
110
96
  // Ensure stdio is only attempted in desktop/server environments within the client itself
111
97
  // or add a check here if MCPClient doesn't handle it.
@@ -120,11 +106,10 @@ class MCPService {
120
106
  log(`New client initializing... ${progress.progress}/${progress.total}`);
121
107
  },
122
108
  }); // Initialization logic should be within MCPClient
123
- this.clients.set(key, client);
124
- log(`New client initialized and cached for key: ${key}`);
109
+ log(`New client initialized`);
125
110
  return client;
126
111
  } catch (error) {
127
- console.error(`Failed to initialize MCP client for key ${key}:`, error);
112
+ console.error(`Failed to initialize MCP client`, error);
128
113
  // Do not cache failed initializations
129
114
  throw new TRPCError({
130
115
  cause: error,
@@ -134,24 +119,6 @@ class MCPService {
134
119
  }
135
120
  }
136
121
 
137
- // Custom serialization function to ensure consistent keys
138
- private serializeParams(params: MCPClientParams): string {
139
- const sortedKeys = Object.keys(params).sort();
140
- const sortedParams: Record<string, any> = {};
141
-
142
- for (const key of sortedKeys) {
143
- const value = (params as any)[key];
144
- // Sort the 'args' array if it exists
145
- if (key === 'args' && Array.isArray(value)) {
146
- sortedParams[key] = JSON.stringify(key);
147
- } else {
148
- sortedParams[key] = value;
149
- }
150
- }
151
-
152
- return JSON.stringify(sortedParams);
153
- }
154
-
155
122
  async getStreamableMcpServerManifest(
156
123
  identifier: string,
157
124
  url: string,