@lobehub/chat 1.85.1 → 1.85.3

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.3](https://github.com/lobehub/lobe-chat/compare/v1.85.2...v1.85.3)
6
+
7
+ <sup>Released on **2025-05-10**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Remove mcp client cache.
12
+
13
+ #### 💄 Styles
14
+
15
+ - **misc**: Improve pdf and xlsx file content parser.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### What's fixed
23
+
24
+ - **misc**: Remove mcp client cache, closes [#7776](https://github.com/lobehub/lobe-chat/issues/7776) ([0582134](https://github.com/lobehub/lobe-chat/commit/0582134))
25
+
26
+ #### Styles
27
+
28
+ - **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))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
38
+ ### [Version 1.85.2](https://github.com/lobehub/lobe-chat/compare/v1.85.1...v1.85.2)
39
+
40
+ <sup>Released on **2025-05-10**</sup>
41
+
42
+ #### ♻ Code Refactoring
43
+
44
+ - **misc**: Upgrade anthropic sdk.
45
+
46
+ <br/>
47
+
48
+ <details>
49
+ <summary><kbd>Improvements and Fixes</kbd></summary>
50
+
51
+ #### Code refactoring
52
+
53
+ - **misc**: Upgrade anthropic sdk, closes [#7773](https://github.com/lobehub/lobe-chat/issues/7773) ([39e871f](https://github.com/lobehub/lobe-chat/commit/39e871f))
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.1](https://github.com/lobehub/lobe-chat/compare/v1.85.0...v1.85.1)
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
+ "Remove mcp client cache."
6
+ ],
7
+ "improvements": [
8
+ "Improve pdf and xlsx file content parser."
9
+ ]
10
+ },
11
+ "date": "2025-05-10",
12
+ "version": "1.85.3"
13
+ },
14
+ {
15
+ "children": {
16
+ "improvements": [
17
+ "Upgrade anthropic sdk."
18
+ ]
19
+ },
20
+ "date": "2025-05-10",
21
+ "version": "1.85.2"
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.1",
3
+ "version": "1.85.3",
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",
@@ -121,7 +121,7 @@
121
121
  "dependencies": {
122
122
  "@ant-design/icons": "^5.6.1",
123
123
  "@ant-design/pro-components": "^2.8.7",
124
- "@anthropic-ai/sdk": "^0.41.0",
124
+ "@anthropic-ai/sdk": "^0.50.3",
125
125
  "@auth/core": "^0.38.0",
126
126
  "@aws-sdk/client-bedrock-runtime": "^3.779.0",
127
127
  "@aws-sdk/client-s3": "^3.779.0",
@@ -359,6 +359,9 @@
359
359
  "registry": "https://registry.npmjs.org"
360
360
  },
361
361
  "pnpm": {
362
+ "onlyBuiltDependencies": [
363
+ "@vercel/speed-insights"
364
+ ],
362
365
  "overrides": {
363
366
  "mdast-util-gfm-autolink-literal": "2.0.0"
364
367
  },
@@ -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
@@ -1,7 +1,5 @@
1
- // sort-imports-ignore
2
- import '@anthropic-ai/sdk/shims/web';
3
- import Anthropic from '@anthropic-ai/sdk';
4
- import { ClientOptions } from 'openai';
1
+ import Anthropic, { ClientOptions } from '@anthropic-ai/sdk';
2
+
5
3
  import type { ChatModelCard } from '@/types/llm';
6
4
 
7
5
  import { LobeRuntimeAI } from '../BaseAI';
@@ -12,10 +10,10 @@ import {
12
10
  ChatStreamPayload,
13
11
  ModelProvider,
14
12
  } from '../types';
13
+ import { buildAnthropicMessages, buildAnthropicTools } from '../utils/anthropicHelpers';
15
14
  import { AgentRuntimeError } from '../utils/createError';
16
15
  import { debugStream } from '../utils/debugStream';
17
16
  import { desensitizeUrl } from '../utils/desensitizeUrl';
18
- import { buildAnthropicMessages, buildAnthropicTools } from '../utils/anthropicHelpers';
19
17
  import { StreamingResponse } from '../utils/response';
20
18
  import { AnthropicStream } from '../utils/streams';
21
19
  import { handleAnthropicError } from './handleAnthropicError';
@@ -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,