@lobehub/chat 0.147.16 → 0.147.18

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/contributing/Basic/Feature-Development.md +7 -7
  3. package/contributing/Basic/Feature-Development.zh-CN.md +7 -7
  4. package/next.config.mjs +61 -13
  5. package/package.json +2 -1
  6. package/sentry.client.config.ts +30 -0
  7. package/sentry.edge.config.ts +17 -0
  8. package/sentry.server.config.ts +19 -0
  9. package/src/app/home/Redirect.tsx +2 -2
  10. package/src/config/modelProviders/anthropic.ts +1 -0
  11. package/src/config/modelProviders/bedrock.ts +10 -0
  12. package/src/config/modelProviders/google.ts +1 -0
  13. package/src/config/modelProviders/groq.ts +1 -0
  14. package/src/config/modelProviders/mistral.ts +0 -5
  15. package/src/config/modelProviders/moonshot.ts +0 -7
  16. package/src/config/modelProviders/ollama.ts +0 -43
  17. package/src/config/modelProviders/openai.ts +0 -8
  18. package/src/config/modelProviders/openrouter.ts +10 -1
  19. package/src/config/modelProviders/perplexity.ts +16 -15
  20. package/src/config/modelProviders/togetherai.ts +1 -0
  21. package/src/database/client/models/__tests__/session.test.ts +1 -3
  22. package/src/database/client/models/session.ts +4 -4
  23. package/src/services/config.ts +4 -4
  24. package/src/services/file/client.test.ts +2 -2
  25. package/src/services/file/client.ts +35 -33
  26. package/src/services/file/index.ts +8 -2
  27. package/src/services/file/type.ts +11 -0
  28. package/src/services/message/client.test.ts +6 -32
  29. package/src/services/message/client.ts +24 -37
  30. package/src/services/message/index.test.ts +48 -0
  31. package/src/services/message/index.ts +22 -2
  32. package/src/services/message/type.ts +33 -0
  33. package/src/services/plugin/client.test.ts +2 -2
  34. package/src/services/plugin/client.ts +1 -1
  35. package/src/services/plugin/index.ts +9 -3
  36. package/src/services/session/client.test.ts +37 -44
  37. package/src/services/session/client.ts +30 -22
  38. package/src/services/session/index.ts +9 -2
  39. package/src/services/session/type.ts +44 -0
  40. package/src/services/topic/client.test.ts +18 -22
  41. package/src/services/topic/client.ts +31 -23
  42. package/src/services/topic/index.ts +10 -2
  43. package/src/services/topic/type.ts +32 -0
  44. package/src/services/user/client.ts +1 -1
  45. package/src/services/user/index.ts +10 -2
  46. package/src/store/chat/slices/message/action.test.ts +12 -12
  47. package/src/store/chat/slices/message/action.ts +4 -4
  48. package/src/store/chat/slices/plugin/action.test.ts +5 -6
  49. package/src/store/chat/slices/plugin/action.ts +1 -1
  50. package/src/store/chat/slices/topic/action.test.ts +11 -6
  51. package/src/store/chat/slices/topic/action.ts +7 -5
  52. package/src/store/global/slices/settings/selectors/modelProvider.test.ts +2 -2
  53. package/src/store/session/slices/agent/action.test.ts +175 -0
  54. package/src/store/session/slices/agent/action.ts +1 -1
  55. package/src/store/session/slices/session/action.test.ts +14 -15
  56. package/src/store/session/slices/session/action.ts +4 -4
  57. package/src/store/session/slices/sessionGroup/action.test.ts +6 -4
  58. package/src/store/session/slices/sessionGroup/action.ts +3 -3
@@ -3,9 +3,9 @@ import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { FileModel } from '@/database/client/models/file';
4
4
  import { DB_File } from '@/database/client/schemas/files';
5
5
 
6
- import { FileService } from './client';
6
+ import { ClientService } from './client';
7
7
 
8
- const fileService = new FileService();
8
+ const fileService = new ClientService();
9
9
 
10
10
  // Mocks for the FileModel
11
11
  vi.mock('@/database/client/models/file', () => ({
@@ -4,12 +4,9 @@ import { FilePreview } from '@/types/files';
4
4
  import compressImage from '@/utils/compressImage';
5
5
 
6
6
  import { API_ENDPOINTS } from '../_url';
7
+ import { IFileService } from './type';
7
8
 
8
- export class FileService {
9
- private isImage(fileType: string) {
10
- const imageRegex = /^image\//;
11
- return imageRegex.test(fileType);
12
- }
9
+ export class ClientService implements IFileService {
13
10
  async uploadFile(file: DB_File) {
14
11
  // 跳过图片上传测试
15
12
  const isTestData = file.size === 1;
@@ -22,26 +19,6 @@ export class FileService {
22
19
  return FileModel.create(file);
23
20
  }
24
21
 
25
- async uploadImageFile(file: DB_File) {
26
- // 加载图片
27
- const url = file.url || URL.createObjectURL(new Blob([file.data]));
28
-
29
- const img = new Image();
30
- img.src = url;
31
- await (() =>
32
- new Promise((resolve) => {
33
- img.addEventListener('load', resolve);
34
- }))();
35
-
36
- // 压缩图片
37
- const base64String = compressImage({ img, type: file.fileType });
38
- const binaryString = atob(base64String.split('base64,')[1]);
39
- const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
40
- file.data = uint8Array.buffer;
41
-
42
- return FileModel.create(file);
43
- }
44
-
45
22
  async uploadImageByUrl(url: string, file: Pick<DB_File, 'name' | 'metadata'>) {
46
23
  const res = await fetch(API_ENDPOINTS.proxy, { body: url, method: 'POST' });
47
24
  const data = await res.arrayBuffer();
@@ -57,14 +34,6 @@ export class FileService {
57
34
  });
58
35
  }
59
36
 
60
- async removeFile(id: string) {
61
- return FileModel.delete(id);
62
- }
63
-
64
- async removeAllFiles() {
65
- return FileModel.clear();
66
- }
67
-
68
37
  async getFile(id: string): Promise<FilePreview> {
69
38
  const item = await FileModel.findById(id);
70
39
  if (!item) {
@@ -83,4 +52,37 @@ export class FileService {
83
52
  url,
84
53
  };
85
54
  }
55
+
56
+ async removeFile(id: string) {
57
+ return FileModel.delete(id);
58
+ }
59
+
60
+ async removeAllFiles() {
61
+ return FileModel.clear();
62
+ }
63
+
64
+ private isImage(fileType: string) {
65
+ const imageRegex = /^image\//;
66
+ return imageRegex.test(fileType);
67
+ }
68
+
69
+ private async uploadImageFile(file: DB_File) {
70
+ // 加载图片
71
+ const url = file.url || URL.createObjectURL(new Blob([file.data]));
72
+
73
+ const img = new Image();
74
+ img.src = url;
75
+ await (() =>
76
+ new Promise((resolve) => {
77
+ img.addEventListener('load', resolve);
78
+ }))();
79
+
80
+ // 压缩图片
81
+ const base64String = compressImage({ img, type: file.fileType });
82
+ const binaryString = atob(base64String.split('base64,')[1]);
83
+ const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
84
+ file.data = uint8Array.buffer;
85
+
86
+ return FileModel.create(file);
87
+ }
86
88
  }
@@ -1,3 +1,9 @@
1
- import { FileService } from './client';
1
+ // import { getClientConfig } from '@/config/client';
2
+ import { ClientService } from './client';
2
3
 
3
- export const fileService = new FileService();
4
+ // import { ServerService } from './server';
5
+ //
6
+ // const { ENABLED_SERVER_SERVICE } = getClientConfig();
7
+ //
8
+ // export const fileService = ENABLED_SERVER_SERVICE ? new ServerService() : new ClientService();
9
+ export const fileService = new ClientService();
@@ -0,0 +1,11 @@
1
+ /* eslint-disable typescript-sort-keys/interface */
2
+ import { DB_File } from '@/database/client/schemas/files';
3
+ import { FilePreview } from '@/types/files';
4
+
5
+ export interface IFileService {
6
+ uploadFile(file: DB_File): Promise<any>;
7
+ uploadImageByUrl(url: string, file: Pick<DB_File, 'name' | 'metadata'>): Promise<any>;
8
+ removeFile(id: string): Promise<any>;
9
+ removeAllFiles(): Promise<any>;
10
+ getFile(id: string): Promise<FilePreview>;
11
+ }
@@ -3,9 +3,9 @@ import { Mock, describe, expect, it, vi } from 'vitest';
3
3
  import { CreateMessageParams, MessageModel } from '@/database/client/models/message';
4
4
  import { ChatMessage, ChatMessageError, ChatPluginPayload } from '@/types/message';
5
5
 
6
- import { MessageService } from './client';
6
+ import { ClientService } from './client';
7
7
 
8
- const messageService = new MessageService();
8
+ const messageService = new ClientService();
9
9
 
10
10
  // Mock the MessageModel
11
11
  vi.mock('@/database/client/models/message', () => {
@@ -27,7 +27,7 @@ vi.mock('@/database/client/models/message', () => {
27
27
  };
28
28
  });
29
29
 
30
- describe('MessageService', () => {
30
+ describe('MessageClientService', () => {
31
31
  // Mock data
32
32
  const mockMessageId = 'mock-message-id';
33
33
  const mockMessage = {
@@ -57,7 +57,7 @@ describe('MessageService', () => {
57
57
  (MessageModel.create as Mock).mockResolvedValue({ id: mockMessageId });
58
58
 
59
59
  // Execute
60
- const messageId = await messageService.create(createParams);
60
+ const messageId = await messageService.createMessage(createParams);
61
61
 
62
62
  // Assert
63
63
  expect(MessageModel.create).toHaveBeenCalledWith(createParams);
@@ -71,7 +71,7 @@ describe('MessageService', () => {
71
71
  (MessageModel.batchCreate as Mock).mockResolvedValue(mockMessages);
72
72
 
73
73
  // Execute
74
- const result = await messageService.batchCreate(mockMessages);
74
+ const result = await messageService.batchCreateMessages(mockMessages);
75
75
 
76
76
  // Assert
77
77
  expect(MessageModel.batchCreate).toHaveBeenCalledWith(mockMessages);
@@ -79,32 +79,6 @@ describe('MessageService', () => {
79
79
  });
80
80
  });
81
81
 
82
- describe('hasMessages', () => {
83
- it('should return true if there are messages', async () => {
84
- // Setup
85
- (MessageModel.count as Mock).mockResolvedValue(1);
86
-
87
- // Execute
88
- const hasMessages = await messageService.hasMessages();
89
-
90
- // Assert
91
- expect(MessageModel.count).toHaveBeenCalled();
92
- expect(hasMessages).toBe(true);
93
- });
94
-
95
- it('should return false if there are no messages', async () => {
96
- // Setup
97
- (MessageModel.count as Mock).mockResolvedValue(0);
98
-
99
- // Execute
100
- const hasMessages = await messageService.hasMessages();
101
-
102
- // Assert
103
- expect(MessageModel.count).toHaveBeenCalled();
104
- expect(hasMessages).toBe(false);
105
- });
106
- });
107
-
108
82
  describe('removeMessage', () => {
109
83
  it('should remove a message by id', async () => {
110
84
  // Setup
@@ -172,7 +146,7 @@ describe('MessageService', () => {
172
146
  (MessageModel.clearTable as Mock).mockResolvedValue(true);
173
147
 
174
148
  // Execute
175
- const result = await messageService.clearAllMessage();
149
+ const result = await messageService.removeAllMessages();
176
150
 
177
151
  // Assert
178
152
  expect(MessageModel.clearTable).toHaveBeenCalled();
@@ -2,42 +2,29 @@ import { MessageModel } from '@/database/client/models/message';
2
2
  import { DB_Message } from '@/database/client/schemas/message';
3
3
  import { ChatMessage, ChatMessageError, ChatPluginPayload } from '@/types/message';
4
4
 
5
- export interface CreateMessageParams
6
- extends Partial<Omit<ChatMessage, 'content' | 'role'>>,
7
- Pick<ChatMessage, 'content' | 'role'> {
8
- fromModel?: string;
9
- fromProvider?: string;
10
- sessionId: string;
11
- traceId?: string;
12
- }
5
+ import { CreateMessageParams, IMessageService } from './type';
13
6
 
14
- export class MessageService {
15
- async create(data: CreateMessageParams) {
7
+ export class ClientService implements IMessageService {
8
+ async createMessage(data: CreateMessageParams) {
16
9
  const { id } = await MessageModel.create(data);
17
10
 
18
11
  return id;
19
12
  }
20
13
 
21
- async batchCreate(messages: ChatMessage[]) {
14
+ async batchCreateMessages(messages: ChatMessage[]) {
22
15
  return MessageModel.batchCreate(messages);
23
16
  }
24
17
 
25
- async hasMessages() {
26
- const number = await MessageModel.count();
27
- return number > 0;
28
- }
29
-
30
- async messageCountToCheckTrace() {
31
- const number = await MessageModel.count();
32
- return number >= 4;
33
- }
34
-
35
18
  async getMessages(sessionId: string, topicId?: string): Promise<ChatMessage[]> {
36
19
  return MessageModel.query({ sessionId, topicId });
37
20
  }
38
21
 
39
- async removeMessage(id: string) {
40
- return MessageModel.delete(id);
22
+ async getAllMessages() {
23
+ return MessageModel.queryAll();
24
+ }
25
+
26
+ async countMessages() {
27
+ return MessageModel.count();
41
28
  }
42
29
 
43
30
  async getAllMessagesInSession(sessionId: string) {
@@ -48,18 +35,6 @@ export class MessageService {
48
35
  return MessageModel.update(id, { error });
49
36
  }
50
37
 
51
- async removeMessages(assistantId: string, topicId?: string) {
52
- return MessageModel.batchDelete(assistantId, topicId);
53
- }
54
-
55
- async clearAllMessage() {
56
- return MessageModel.clearTable();
57
- }
58
-
59
- async bindMessagesToTopic(topicId: string, messageIds: string[]) {
60
- return MessageModel.batchUpdate(messageIds, { topicId });
61
- }
62
-
63
38
  async updateMessage(id: string, message: Partial<DB_Message>) {
64
39
  return MessageModel.update(id, message);
65
40
  }
@@ -72,7 +47,19 @@ export class MessageService {
72
47
  return MessageModel.updatePluginState(id, key, value);
73
48
  }
74
49
 
75
- async getAllMessages() {
76
- return MessageModel.queryAll();
50
+ async bindMessagesToTopic(topicId: string, messageIds: string[]) {
51
+ return MessageModel.batchUpdate(messageIds, { topicId });
52
+ }
53
+
54
+ async removeMessage(id: string) {
55
+ return MessageModel.delete(id);
56
+ }
57
+
58
+ async removeMessages(assistantId: string, topicId?: string) {
59
+ return MessageModel.batchDelete(assistantId, topicId);
60
+ }
61
+
62
+ async removeAllMessages() {
63
+ return MessageModel.clearTable();
77
64
  }
78
65
  }
@@ -0,0 +1,48 @@
1
+ import { Mock, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { CreateMessageParams, MessageModel } from '@/database/client/models/message';
4
+ import { ChatMessage, ChatMessageError, ChatPluginPayload } from '@/types/message';
5
+
6
+ import { messageService } from './index';
7
+
8
+ // Mock the MessageModel
9
+ vi.mock('@/database/client/models/message', () => {
10
+ return {
11
+ MessageModel: {
12
+ count: vi.fn(),
13
+ },
14
+ };
15
+ });
16
+
17
+ describe('MessageService', () => {
18
+ beforeEach(() => {
19
+ // Reset all mocks before running each test case
20
+ vi.resetAllMocks();
21
+ });
22
+
23
+ describe('hasMessages', () => {
24
+ it('should return true if there are messages', async () => {
25
+ // Setup
26
+ (MessageModel.count as Mock).mockResolvedValue(1);
27
+
28
+ // Execute
29
+ const hasMessages = await messageService.hasMessages();
30
+
31
+ // Assert
32
+ expect(MessageModel.count).toHaveBeenCalled();
33
+ expect(hasMessages).toBe(true);
34
+ });
35
+
36
+ it('should return false if there are no messages', async () => {
37
+ // Setup
38
+ (MessageModel.count as Mock).mockResolvedValue(0);
39
+
40
+ // Execute
41
+ const hasMessages = await messageService.hasMessages();
42
+
43
+ // Assert
44
+ expect(MessageModel.count).toHaveBeenCalled();
45
+ expect(hasMessages).toBe(false);
46
+ });
47
+ });
48
+ });
@@ -1,4 +1,24 @@
1
- import { MessageService } from './client';
1
+ // import { getClientConfig } from '@/config/client';
2
+ // import { ServerService } from './server';
3
+ // import { ClientService } from './client';
4
+ //
5
+ // const { ENABLED_SERVER_SERVICE } = getClientConfig();
6
+ //
7
+ // export const messageService = ENABLED_SERVER_SERVICE ? new ServerService() : new ClientService();
8
+ import { ClientService } from './client';
9
+
10
+ export type { CreateMessageParams } from './type';
11
+
12
+ class MessageService extends ClientService {
13
+ async hasMessages() {
14
+ const number = await this.countMessages();
15
+ return number > 0;
16
+ }
17
+
18
+ async messageCountToCheckTrace() {
19
+ const number = await this.countMessages();
20
+ return number >= 4;
21
+ }
22
+ }
2
23
 
3
24
  export const messageService = new MessageService();
4
- export type { CreateMessageParams } from './client';
@@ -0,0 +1,33 @@
1
+ import { DB_Message } from '@/database/client/schemas/message';
2
+ import { ChatMessage, ChatMessageError, ChatPluginPayload } from '@/types/message';
3
+
4
+ /* eslint-disable typescript-sort-keys/interface */
5
+
6
+ export interface CreateMessageParams
7
+ extends Partial<Omit<ChatMessage, 'content' | 'role'>>,
8
+ Pick<ChatMessage, 'content' | 'role'> {
9
+ fromModel?: string;
10
+ fromProvider?: string;
11
+ sessionId: string;
12
+ traceId?: string;
13
+ }
14
+
15
+ export interface IMessageService {
16
+ createMessage(data: CreateMessageParams): Promise<string>;
17
+ batchCreateMessages(messages: ChatMessage[]): Promise<any>;
18
+
19
+ getMessages(sessionId: string, topicId?: string): Promise<ChatMessage[]>;
20
+ getAllMessages(): Promise<ChatMessage[]>;
21
+ getAllMessagesInSession(sessionId: string): Promise<ChatMessage[]>;
22
+ countMessages(): Promise<number>;
23
+
24
+ updateMessageError(id: string, error: ChatMessageError): Promise<any>;
25
+ updateMessage(id: string, message: Partial<DB_Message>): Promise<any>;
26
+ updateMessagePlugin(id: string, plugin: ChatPluginPayload): Promise<any>;
27
+ updateMessagePluginState(id: string, key: string, value: any): Promise<any>;
28
+ bindMessagesToTopic(topicId: string, messageIds: string[]): Promise<any>;
29
+
30
+ removeMessage(id: string): Promise<any>;
31
+ removeMessages(assistantId: string, topicId?: string): Promise<any>;
32
+ removeAllMessages(): Promise<any>;
33
+ }
@@ -6,9 +6,9 @@ import { DB_Plugin } from '@/database/client/schemas/plugin';
6
6
  import { LobeTool } from '@/types/tool';
7
7
  import { LobeToolCustomPlugin } from '@/types/tool/plugin';
8
8
 
9
- import { InstallPluginParams, PluginService } from './client';
9
+ import { ClientService, InstallPluginParams } from './client';
10
10
 
11
- const pluginService = new PluginService();
11
+ const pluginService = new ClientService();
12
12
 
13
13
  // Mocking modules and functions
14
14
 
@@ -10,7 +10,7 @@ export interface InstallPluginParams {
10
10
  type: 'plugin' | 'customPlugin';
11
11
  }
12
12
 
13
- export class PluginService {
13
+ export class ClientService {
14
14
  installPlugin = async (plugin: InstallPluginParams) => {
15
15
  return PluginModel.create(plugin);
16
16
  };
@@ -1,5 +1,11 @@
1
- import { PluginService } from './client';
1
+ // import { getClientConfig } from '@/config/client';
2
+ import { ClientService } from './client';
2
3
 
3
- export type { InstallPluginParams } from './client';
4
+ // import { ServerService } from './server';
5
+ //
6
+ // export type { InstallPluginParams } from './client';
7
+ //
8
+ // const { ENABLED_SERVER_SERVICE } = getClientConfig();
4
9
 
5
- export const pluginService = new PluginService();
10
+ // export const pluginService = ENABLED_SERVER_SERVICE ? new ServerService() : new ClientService();
11
+ export const pluginService = new ClientService();