@lobehub/chat 1.36.45 → 1.37.0

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 (88) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.ja-JP.md +8 -8
  3. package/README.md +8 -8
  4. package/README.zh-CN.md +8 -8
  5. package/changelog/v1.json +18 -0
  6. package/next.config.mjs +4 -1
  7. package/package.json +5 -3
  8. package/scripts/migrateClientDB/compile-migrations.ts +14 -0
  9. package/src/app/(main)/(mobile)/me/(home)/layout.tsx +2 -0
  10. package/src/app/(main)/chat/_layout/Desktop/index.tsx +3 -2
  11. package/src/app/(main)/chat/_layout/Mobile.tsx +5 -3
  12. package/src/app/(main)/chat/features/Migration/DBReader.ts +290 -0
  13. package/src/app/(main)/chat/features/Migration/UpgradeButton.tsx +4 -8
  14. package/src/app/(main)/chat/features/Migration/index.tsx +26 -15
  15. package/src/app/(main)/settings/_layout/Desktop/index.tsx +2 -0
  16. package/src/app/loading/Client/Content.tsx +11 -1
  17. package/src/app/loading/Client/Error.tsx +27 -0
  18. package/src/app/loading/stage.ts +8 -0
  19. package/src/components/FullscreenLoading/index.tsx +4 -3
  20. package/src/const/version.ts +1 -0
  21. package/src/database/_deprecated/models/file.ts +17 -3
  22. package/src/database/client/db.test.ts +172 -0
  23. package/src/database/client/db.ts +246 -0
  24. package/src/database/client/migrations.json +289 -0
  25. package/src/features/InitClientDB/EnableModal.tsx +111 -0
  26. package/src/features/InitClientDB/ErrorResult.tsx +125 -0
  27. package/src/features/InitClientDB/InitIndicator.tsx +124 -0
  28. package/src/features/InitClientDB/PGliteSVG.tsx +22 -0
  29. package/src/features/InitClientDB/index.tsx +37 -0
  30. package/src/hooks/useCheckPluginsIsInstalled.ts +2 -2
  31. package/src/hooks/useFetchInstalledPlugins.ts +2 -2
  32. package/src/hooks/useFetchMessages.ts +2 -2
  33. package/src/hooks/useFetchSessions.ts +2 -2
  34. package/src/hooks/useFetchThreads.ts +2 -2
  35. package/src/hooks/useFetchTopics.ts +2 -2
  36. package/src/layout/GlobalProvider/StoreInitialization.tsx +2 -2
  37. package/src/server/routers/lambda/file.ts +1 -3
  38. package/src/services/__tests__/upload.test.ts +175 -0
  39. package/src/services/baseClientService/index.ts +9 -0
  40. package/src/services/debug.ts +32 -34
  41. package/src/services/file/ClientS3/index.test.ts +115 -0
  42. package/src/services/file/ClientS3/index.ts +58 -0
  43. package/src/services/file/client.test.ts +9 -4
  44. package/src/services/file/client.ts +36 -8
  45. package/src/services/file/index.ts +6 -2
  46. package/src/services/file/pglite.test.ts +198 -0
  47. package/src/services/file/pglite.ts +84 -0
  48. package/src/services/file/type.ts +4 -3
  49. package/src/services/github.ts +17 -0
  50. package/src/services/import/index.ts +6 -2
  51. package/src/services/import/pglite.test.ts +997 -0
  52. package/src/services/import/pglite.ts +34 -0
  53. package/src/services/message/client.ts +2 -0
  54. package/src/services/message/index.ts +6 -2
  55. package/src/services/message/pglite.test.ts +430 -0
  56. package/src/services/message/pglite.ts +118 -0
  57. package/src/services/message/server.ts +9 -9
  58. package/src/services/message/type.ts +3 -4
  59. package/src/services/plugin/index.ts +6 -2
  60. package/src/services/plugin/pglite.test.ts +175 -0
  61. package/src/services/plugin/pglite.ts +51 -0
  62. package/src/services/session/client.ts +1 -1
  63. package/src/services/session/index.ts +6 -2
  64. package/src/services/session/pglite.test.ts +411 -0
  65. package/src/services/session/pglite.ts +184 -0
  66. package/src/services/session/type.ts +14 -1
  67. package/src/services/topic/index.ts +6 -3
  68. package/src/services/topic/pglite.test.ts +212 -0
  69. package/src/services/topic/pglite.ts +85 -0
  70. package/src/services/upload.ts +8 -16
  71. package/src/services/user/client.test.ts +0 -1
  72. package/src/services/user/index.ts +8 -2
  73. package/src/services/user/pglite.test.ts +98 -0
  74. package/src/services/user/pglite.ts +92 -0
  75. package/src/store/chat/slices/builtinTool/action.test.ts +12 -4
  76. package/src/store/file/slices/upload/action.ts +33 -67
  77. package/src/store/global/actions/clientDb.ts +51 -0
  78. package/src/store/global/initialState.ts +13 -0
  79. package/src/store/global/selectors.ts +24 -3
  80. package/src/store/global/store.ts +3 -1
  81. package/src/store/session/slices/sessionGroup/reducer.test.ts +6 -6
  82. package/src/store/user/slices/common/action.ts +2 -4
  83. package/src/types/clientDB.ts +29 -0
  84. package/src/types/files/upload.ts +8 -2
  85. package/src/types/importer.ts +17 -5
  86. package/src/types/meta.ts +0 -9
  87. package/src/types/session/sessionGroup.ts +3 -3
  88. package/src/services/message/index.test.ts +0 -48
@@ -1,5 +1,9 @@
1
- import { ClientService } from './client';
1
+ import { ClientService as DeprecatedService } from './client';
2
+ import { ClientService } from './pglite';
2
3
  import { ServerService } from './server';
3
4
 
5
+ const clientService =
6
+ process.env.NEXT_PUBLIC_CLIENT_DB === 'pglite' ? new ClientService() : new DeprecatedService();
7
+
4
8
  export const pluginService =
5
- process.env.NEXT_PUBLIC_SERVICE_MODE === 'server' ? new ServerService() : new ClientService();
9
+ process.env.NEXT_PUBLIC_SERVICE_MODE === 'server' ? new ServerService() : clientService;
@@ -0,0 +1,175 @@
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
+ import { eq } from 'drizzle-orm';
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { clientDB, initializeDB } from '@/database/client/db';
6
+ import { installedPlugins, users } from '@/database/schemas';
7
+ import { LobeTool } from '@/types/tool';
8
+ import { LobeToolCustomPlugin } from '@/types/tool/plugin';
9
+
10
+ import { ClientService } from './pglite';
11
+ import { InstallPluginParams } from './type';
12
+
13
+ // Mocking modules and functions
14
+
15
+ const userId = 'message-db';
16
+ const pluginService = new ClientService(userId);
17
+
18
+ // Mock data
19
+ beforeEach(async () => {
20
+ await initializeDB();
21
+
22
+ // 在每个测试用例之前,重置表数据
23
+ await clientDB.transaction(async (trx) => {
24
+ await trx.delete(users);
25
+ await trx.insert(users).values([{ id: userId }, { id: '456' }]);
26
+ });
27
+ });
28
+
29
+ describe('PluginService', () => {
30
+ describe('installPlugin', () => {
31
+ it('should install a plugin', async () => {
32
+ // Arrange
33
+ const fakePlugin = {
34
+ identifier: 'test-plugin-d',
35
+ manifest: { name: 'TestPlugin', version: '1.0.0' } as unknown as LobeChatPluginManifest,
36
+ type: 'plugin',
37
+ } as InstallPluginParams;
38
+
39
+ // Act
40
+ await pluginService.installPlugin(fakePlugin);
41
+
42
+ // Assert
43
+ const result = await clientDB.query.installedPlugins.findFirst({
44
+ where: eq(installedPlugins.identifier, fakePlugin.identifier),
45
+ });
46
+ expect(result).toMatchObject(fakePlugin);
47
+ });
48
+ });
49
+
50
+ describe('getInstalledPlugins', () => {
51
+ it('should return a list of installed plugins', async () => {
52
+ // Arrange
53
+ const fakePlugins = [{ identifier: 'test-plugin', type: 'plugin' }] as LobeTool[];
54
+ await clientDB
55
+ .insert(installedPlugins)
56
+ .values([{ identifier: 'test-plugin', type: 'plugin', userId }]);
57
+ // Act
58
+ const data = await pluginService.getInstalledPlugins();
59
+
60
+ // Assert
61
+ expect(data).toMatchObject(fakePlugins);
62
+ });
63
+ });
64
+
65
+ describe('uninstallPlugin', () => {
66
+ it('should uninstall a plugin', async () => {
67
+ // Arrange
68
+ const identifier = 'test-plugin';
69
+ await clientDB.insert(installedPlugins).values([{ identifier, type: 'plugin', userId }]);
70
+
71
+ // Act
72
+ await pluginService.uninstallPlugin(identifier);
73
+
74
+ // Assert
75
+ const result = await clientDB.query.installedPlugins.findFirst({
76
+ where: eq(installedPlugins.identifier, identifier),
77
+ });
78
+ expect(result).toBe(undefined);
79
+ });
80
+ });
81
+
82
+ describe('createCustomPlugin', () => {
83
+ it('should create a custom plugin', async () => {
84
+ // Arrange
85
+ const customPlugin = {
86
+ identifier: 'custom-plugin-x',
87
+ manifest: {},
88
+ type: 'customPlugin',
89
+ } as LobeToolCustomPlugin;
90
+
91
+ // Act
92
+ await pluginService.createCustomPlugin(customPlugin);
93
+
94
+ // Assert
95
+ const result = await clientDB.query.installedPlugins.findFirst({
96
+ where: eq(installedPlugins.identifier, customPlugin.identifier),
97
+ });
98
+ expect(result).toMatchObject(customPlugin);
99
+ });
100
+ });
101
+
102
+ describe('updatePlugin', () => {
103
+ it('should update a plugin', async () => {
104
+ // Arrange
105
+ const identifier = 'plugin-id';
106
+ const value = { customParams: { ab: '1' } } as unknown as LobeToolCustomPlugin;
107
+ await clientDB.insert(installedPlugins).values([{ identifier, type: 'plugin', userId }]);
108
+
109
+ // Act
110
+ await pluginService.updatePlugin(identifier, value);
111
+
112
+ // Assert
113
+ const result = await clientDB.query.installedPlugins.findFirst({
114
+ where: eq(installedPlugins.identifier, identifier),
115
+ });
116
+ expect(result).toMatchObject(value);
117
+ });
118
+ });
119
+
120
+ describe('updatePluginManifest', () => {
121
+ it('should update a plugin manifest', async () => {
122
+ // Arrange
123
+ const identifier = 'plugin-id';
124
+ const manifest = { name: 'NewPluginManifest' } as unknown as LobeChatPluginManifest;
125
+ await clientDB.insert(installedPlugins).values([{ identifier, type: 'plugin', userId }]);
126
+
127
+ // Act
128
+ await pluginService.updatePluginManifest(identifier, manifest);
129
+
130
+ // Assert
131
+ const result = await clientDB.query.installedPlugins.findFirst({
132
+ where: eq(installedPlugins.identifier, identifier),
133
+ });
134
+ expect(result).toMatchObject({ manifest });
135
+ });
136
+ });
137
+
138
+ describe('removeAllPlugins', () => {
139
+ it('should remove all plugins', async () => {
140
+ // Arrange
141
+ await clientDB.insert(installedPlugins).values([
142
+ { identifier: '123', type: 'plugin', userId },
143
+ { identifier: '234', type: 'plugin', userId },
144
+ ]);
145
+
146
+ // Act
147
+ await pluginService.removeAllPlugins();
148
+
149
+ // Assert
150
+ const result = await clientDB.query.installedPlugins.findMany({
151
+ where: eq(installedPlugins.userId, userId),
152
+ });
153
+ expect(result.length).toEqual(0);
154
+ });
155
+ });
156
+
157
+ describe('updatePluginSettings', () => {
158
+ it('should update plugin settings', async () => {
159
+ // Arrange
160
+ const id = 'plugin-id';
161
+ const settings = { color: 'blue' };
162
+ await clientDB.insert(installedPlugins).values([{ identifier: id, type: 'plugin', userId }]);
163
+
164
+ // Act
165
+ await pluginService.updatePluginSettings(id, settings);
166
+
167
+ // Assert
168
+ const result = await clientDB.query.installedPlugins.findFirst({
169
+ where: eq(installedPlugins.identifier, id),
170
+ });
171
+
172
+ expect(result).toMatchObject({ settings });
173
+ });
174
+ });
175
+ });
@@ -0,0 +1,51 @@
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
+
3
+ import { clientDB } from '@/database/client/db';
4
+ import { PluginModel } from '@/database/server/models/plugin';
5
+ import { BaseClientService } from '@/services/baseClientService';
6
+ import { LobeTool } from '@/types/tool';
7
+ import { LobeToolCustomPlugin } from '@/types/tool/plugin';
8
+
9
+ import { IPluginService, InstallPluginParams } from './type';
10
+
11
+ export class ClientService extends BaseClientService implements IPluginService {
12
+ private get pluginModel(): PluginModel {
13
+ return new PluginModel(clientDB as any, this.userId);
14
+ }
15
+
16
+ installPlugin = async (plugin: InstallPluginParams) => {
17
+ await this.pluginModel.create(plugin);
18
+ return;
19
+ };
20
+
21
+ getInstalledPlugins = () => {
22
+ return this.pluginModel.query() as Promise<LobeTool[]>;
23
+ };
24
+
25
+ async uninstallPlugin(identifier: string) {
26
+ await this.pluginModel.delete(identifier);
27
+ return;
28
+ }
29
+
30
+ async createCustomPlugin(customPlugin: LobeToolCustomPlugin) {
31
+ await this.pluginModel.create({ ...customPlugin, type: 'customPlugin' });
32
+ return;
33
+ }
34
+
35
+ async updatePlugin(id: string, value: LobeToolCustomPlugin) {
36
+ await this.pluginModel.update(id, value);
37
+ return;
38
+ }
39
+ async updatePluginManifest(id: string, manifest: LobeChatPluginManifest) {
40
+ await this.pluginModel.update(id, { manifest });
41
+ }
42
+
43
+ async removeAllPlugins() {
44
+ await this.pluginModel.deleteAll();
45
+ }
46
+
47
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
48
+ async updatePluginSettings(id: string, settings: any, _?: AbortSignal) {
49
+ await this.pluginModel.update(id, { settings });
50
+ }
51
+ }
@@ -166,7 +166,7 @@ export class ClientService implements ISessionService {
166
166
  }
167
167
 
168
168
  async updateSessionGroup(id: string, data: Partial<SessionGroupItem>) {
169
- return SessionGroupModel.update(id, data);
169
+ return SessionGroupModel.update(id, data as any);
170
170
  }
171
171
 
172
172
  async updateSessionGroupOrder(sortMap: { id: string; sort: number }[]) {
@@ -1,5 +1,9 @@
1
- import { ClientService } from './client';
1
+ import { ClientService as DeprecatedService } from './client';
2
+ import { ClientService } from './pglite';
2
3
  import { ServerService } from './server';
3
4
 
5
+ const clientService =
6
+ process.env.NEXT_PUBLIC_CLIENT_DB === 'pglite' ? new ClientService() : new DeprecatedService();
7
+
4
8
  export const sessionService =
5
- process.env.NEXT_PUBLIC_SERVICE_MODE === 'server' ? new ServerService() : new ClientService();
9
+ process.env.NEXT_PUBLIC_SERVICE_MODE === 'server' ? new ServerService() : clientService;
@@ -0,0 +1,411 @@
1
+ import { eq, not } from 'drizzle-orm/expressions';
2
+ import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { INBOX_SESSION_ID } from '@/const/session';
5
+ import { clientDB, initializeDB } from '@/database/client/db';
6
+ import {
7
+ NewSession,
8
+ SessionItem,
9
+ agents,
10
+ agentsToSessions,
11
+ sessionGroups,
12
+ sessions,
13
+ users,
14
+ } from '@/database/schemas';
15
+ import { LobeAgentChatConfig, LobeAgentConfig } from '@/types/agent';
16
+ import { LobeAgentSession, LobeSessionType, SessionGroups } from '@/types/session';
17
+
18
+ import { ClientService } from './pglite';
19
+
20
+ const userId = 'message-db';
21
+ const sessionService = new ClientService(userId);
22
+
23
+ const mockSessionId = 'mock-session-id';
24
+ const mockAgentId = 'agent-id';
25
+
26
+ // Mock data
27
+ beforeEach(async () => {
28
+ await initializeDB();
29
+
30
+ // 在每个测试用例之前,清空表
31
+ await clientDB.transaction(async (trx) => {
32
+ await trx.insert(users).values([{ id: userId }, { id: '456' }]);
33
+ await trx.insert(sessions).values([{ id: mockSessionId, userId }]);
34
+ await trx.insert(agents).values([{ id: mockAgentId, userId }]);
35
+ await trx.insert(agentsToSessions).values([{ agentId: mockAgentId, sessionId: mockSessionId }]);
36
+ await trx.insert(sessionGroups).values([
37
+ { id: 'group-1', name: 'group-A', sort: 2, userId },
38
+ { id: 'group-2', name: 'group-B', sort: 1, userId },
39
+ { id: 'group-4', name: 'group-C', sort: 1, userId: '456' },
40
+ ]);
41
+ });
42
+ });
43
+
44
+ afterEach(async () => {
45
+ // 在每个测试用例之后,清空表
46
+ await clientDB.delete(users);
47
+ });
48
+
49
+ describe('SessionService', () => {
50
+ const mockSession = {
51
+ id: mockSessionId,
52
+ type: 'agent',
53
+ meta: { title: 'Mock Session' },
54
+ } as LobeAgentSession;
55
+
56
+ describe('createSession', () => {
57
+ it('should create a new session and return its id', async () => {
58
+ // Setup
59
+ const sessionType = LobeSessionType.Agent;
60
+ const defaultValue = { meta: { title: 'New Session' } } as Partial<LobeAgentSession>;
61
+
62
+ // Execute
63
+ const sessionId = await sessionService.createSession(sessionType, defaultValue);
64
+
65
+ // Assert
66
+ expect(sessionId).toMatch(/^ssn_/);
67
+ });
68
+ });
69
+
70
+ describe('removeSession', () => {
71
+ it('should remove a session by its id', async () => {
72
+ // Execute
73
+ await sessionService.removeSession(mockSessionId);
74
+
75
+ // Assert
76
+
77
+ const result = await clientDB.query.sessions.findFirst({
78
+ where: eq(sessions.id, mockSessionId),
79
+ });
80
+ // Assert
81
+ expect(result).toBeUndefined();
82
+ });
83
+ });
84
+
85
+ describe('removeAllSessions', () => {
86
+ it('should clear all sessions from the table', async () => {
87
+ // Setup
88
+ await clientDB
89
+ .insert(sessions)
90
+ .values([{ userId: userId }, { userId: userId }, { userId: userId }]);
91
+
92
+ // Execute
93
+ await sessionService.removeAllSessions();
94
+
95
+ // Assert
96
+ const result = await clientDB.query.sessions.findMany({
97
+ where: eq(sessionGroups.userId, userId),
98
+ });
99
+
100
+ expect(result.length).toBe(0);
101
+ });
102
+ });
103
+
104
+ describe('updateSession', () => {
105
+ it('should update the group of a session', async () => {
106
+ // Setup
107
+ const groupId = 'group-1';
108
+
109
+ // Execute
110
+ await sessionService.updateSession(mockSessionId, { group: groupId });
111
+
112
+ // Assert
113
+ const result = await clientDB.query.sessions.findFirst({
114
+ where: eq(sessions.id, mockSessionId),
115
+ });
116
+ expect(result).toMatchObject({ groupId });
117
+ });
118
+
119
+ it('should update the pinned status of a session', async () => {
120
+ // Setup
121
+ const pinned = true;
122
+
123
+ // Execute
124
+ await sessionService.updateSession(mockSessionId, { pinned });
125
+
126
+ // Assert
127
+ const result = await clientDB.query.sessions.findFirst({
128
+ where: eq(sessions.id, mockSessionId),
129
+ });
130
+
131
+ expect(result!.pinned).toBeTruthy();
132
+ });
133
+ });
134
+
135
+ describe('updateSessionConfig', () => {
136
+ it('should update the config of a session', async () => {
137
+ // Setup
138
+ const newConfig = { model: 'abc' } as LobeAgentConfig;
139
+
140
+ // Execute
141
+ await sessionService.updateSessionConfig(mockSessionId, newConfig);
142
+
143
+ // Assert
144
+ const result = await sessionService.getSessionConfig(mockSessionId);
145
+ expect(result).toMatchObject(newConfig);
146
+ });
147
+ });
148
+
149
+ describe('countSessions', () => {
150
+ it('should return false if no sessions exist', async () => {
151
+ await clientDB.delete(sessions);
152
+
153
+ // Execute
154
+ const result = await sessionService.countSessions();
155
+
156
+ // Assert
157
+ expect(result).toBe(0);
158
+ });
159
+
160
+ it('should return true if sessions exist', async () => {
161
+ // Setup
162
+ await clientDB.delete(sessions);
163
+ await clientDB.insert(sessions).values([{ userId }]);
164
+
165
+ // Execute
166
+ const result = await sessionService.countSessions();
167
+
168
+ // Assert
169
+ expect(result).toBe(1);
170
+ });
171
+ });
172
+
173
+ describe('searchSessions', () => {
174
+ it('should return sessions that match the keyword', async () => {
175
+ // Setup
176
+ await clientDB.insert(agents).values({ userId, id: 'agent-1', title: 'Session Name' });
177
+ await clientDB
178
+ .insert(agentsToSessions)
179
+ .values({ agentId: 'agent-1', sessionId: mockSessionId });
180
+
181
+ // Execute
182
+ const keyword = 'Name';
183
+ const result = await sessionService.searchSessions(keyword);
184
+
185
+ // Assert
186
+ // TODO: 后续需要把这个搜索的标题和描述都加上,现在这个 client 搜索会有问题
187
+ expect(result).toMatchObject([{ id: mockSessionId }]);
188
+ });
189
+ });
190
+
191
+ describe('cloneSession', () => {
192
+ it('should duplicate a session and return its id', async () => {
193
+ // Setup
194
+ const newTitle = 'Duplicated Session';
195
+ const session: NewSession = {
196
+ id: 'duplicated-session-id',
197
+ title: '123',
198
+ userId,
199
+ };
200
+ await clientDB.insert(sessions).values([session]);
201
+ await clientDB.insert(agents).values({ userId, id: 'agent-1' });
202
+ await clientDB
203
+ .insert(agentsToSessions)
204
+ .values({ agentId: 'agent-1', sessionId: 'duplicated-session-id' });
205
+
206
+ // Execute
207
+ const duplicatedSessionId = await sessionService.cloneSession(mockSessionId, newTitle);
208
+
209
+ // Assert
210
+
211
+ const result = await clientDB.query.sessions.findFirst({
212
+ where: eq(sessions.id, duplicatedSessionId!),
213
+ });
214
+ expect(result).toMatchObject({ title: 'Duplicated Session' });
215
+ });
216
+ });
217
+
218
+ describe('getGroupedSessions', () => {
219
+ it('should retrieve sessions with their group', async () => {
220
+ // Execute
221
+ const sessionsWithGroup = await sessionService.getGroupedSessions();
222
+
223
+ expect(sessionsWithGroup).toMatchObject({
224
+ sessionGroups: [
225
+ { id: 'group-2', name: 'group-B', sort: 1 },
226
+ { id: 'group-1', name: 'group-A', sort: 2 },
227
+ ],
228
+ sessions: [{ id: 'mock-session-id', type: 'agent' }],
229
+ });
230
+ });
231
+ });
232
+
233
+ describe('getSessionsByType', () => {
234
+ it('should get sessions by type "all"', async () => {
235
+ const sessions = await sessionService.getSessionsByType('all');
236
+ expect(sessions).toBeDefined();
237
+ });
238
+
239
+ it('should get sessions by type "agent"', async () => {
240
+ const sessions = await sessionService.getSessionsByType('agent');
241
+ expect(sessions).toBeDefined();
242
+ });
243
+
244
+ it('should get sessions by type "group"', async () => {
245
+ const sessions = await sessionService.getSessionsByType('group');
246
+ expect(sessions).toBeDefined();
247
+ });
248
+ });
249
+
250
+ describe('getSessionConfig', () => {
251
+ it.skip('should get default config for INBOX_SESSION_ID', async () => {
252
+ const config = await sessionService.getSessionConfig(INBOX_SESSION_ID);
253
+ expect(config).toBeDefined();
254
+ });
255
+
256
+ it('should throw error for non-existent session', async () => {
257
+ await expect(sessionService.getSessionConfig('non-existent')).rejects.toThrow(
258
+ 'Session not found',
259
+ );
260
+ });
261
+ });
262
+
263
+ describe('updateSessionMeta', () => {
264
+ it('should not update meta for INBOX_SESSION_ID', async () => {
265
+ const result = await sessionService.updateSessionMeta(INBOX_SESSION_ID, {
266
+ title: 'New Title',
267
+ });
268
+ expect(result).toBeUndefined();
269
+ });
270
+
271
+ it('should update meta for normal session', async () => {
272
+ const meta = { title: 'Updated Title' };
273
+ await sessionService.updateSessionMeta(mockSessionId, meta);
274
+
275
+ const session = await clientDB.query.sessions.findFirst({
276
+ where: eq(sessions.id, mockSessionId),
277
+ });
278
+ expect(session).toBeDefined();
279
+ });
280
+ });
281
+
282
+ describe('updateSessionChatConfig', () => {
283
+ it('should update chat config', async () => {
284
+ const chatConfig = { temperature: 0.8 } as Partial<LobeAgentChatConfig>;
285
+ const result = await sessionService.updateSessionChatConfig(mockSessionId, chatConfig);
286
+ expect(result).toBeDefined();
287
+ });
288
+ });
289
+
290
+ describe('model getters', () => {
291
+ it('should return session model instance', () => {
292
+ // @ts-ignore - accessing private getter
293
+ const model = sessionService.sessionModel;
294
+ expect(model).toBeDefined();
295
+ });
296
+
297
+ it('should return session group model instance', () => {
298
+ // @ts-ignore - accessing private getter
299
+ const model = sessionService.sessionGroupModel;
300
+ expect(model).toBeDefined();
301
+ });
302
+ });
303
+
304
+ // SessionGroup related tests
305
+ describe('createSessionGroup', () => {
306
+ it('should create a new session group and return its id', async () => {
307
+ // Setup
308
+ const groupName = 'New Group';
309
+ const sort = 1;
310
+
311
+ // Execute
312
+ const groupId = await sessionService.createSessionGroup(groupName, sort);
313
+
314
+ // Assert
315
+ expect(groupId).toMatch(/^sg_/);
316
+
317
+ const result = await clientDB.query.sessionGroups.findFirst({
318
+ where: eq(sessionGroups.id, groupId),
319
+ });
320
+
321
+ expect(result).toMatchObject({ id: groupId, name: groupName, sort });
322
+ });
323
+ });
324
+
325
+ describe('removeSessionGroup', () => {
326
+ it('should remove a session group by its id', async () => {
327
+ const groupId = 'group-1';
328
+ // Execute
329
+ await sessionService.removeSessionGroup(groupId);
330
+
331
+ const result = await clientDB.query.sessionGroups.findFirst({
332
+ where: eq(sessionGroups.id, groupId),
333
+ });
334
+ // Assert
335
+ expect(result).toBeUndefined();
336
+ });
337
+ });
338
+
339
+ describe('clearSessionGroups', () => {
340
+ it('should clear all session groups', async () => {
341
+ // Execute
342
+ await sessionService.removeSessionGroups();
343
+
344
+ // Assert
345
+ const result = await clientDB.query.sessionGroups.findMany({
346
+ where: eq(sessionGroups.userId, userId),
347
+ });
348
+
349
+ expect(result.length).toBe(0);
350
+
351
+ const result2 = await clientDB.query.sessionGroups.findMany({
352
+ where: not(eq(sessionGroups.userId, userId)),
353
+ });
354
+
355
+ expect(result2.length).toBeGreaterThan(0);
356
+ });
357
+ });
358
+
359
+ describe('getSessionGroups', () => {
360
+ it('should retrieve all session groups', async () => {
361
+ // Execute
362
+ const result = await sessionService.getSessionGroups();
363
+
364
+ // Assert
365
+ const groups = [
366
+ { id: 'group-2', name: 'group-B', sort: 1 },
367
+ { id: 'group-1', name: 'group-A', sort: 2 },
368
+ ];
369
+ expect(result).toMatchObject(groups);
370
+ });
371
+ });
372
+
373
+ describe('updateSessionGroup', () => {
374
+ it('should update a session group', async () => {
375
+ // Setup
376
+ const groupId = 'group-1';
377
+ const data = { name: 'Updated Group', sort: 2 };
378
+
379
+ // Execute
380
+ await sessionService.updateSessionGroup(groupId, data);
381
+
382
+ // Assert
383
+ const result = await clientDB.query.sessionGroups.findFirst({
384
+ where: eq(sessionGroups.id, groupId),
385
+ });
386
+ expect(result).toMatchObject({ id: groupId, ...data });
387
+ });
388
+ });
389
+
390
+ describe('updateSessionGroupOrder', () => {
391
+ it('should update the order of session groups', async () => {
392
+ // Setup
393
+ const sortMap = [
394
+ { id: 'group-1', sort: 2 },
395
+ { id: 'group-2', sort: 1 },
396
+ ];
397
+
398
+ // Execute
399
+ await sessionService.updateSessionGroupOrder(sortMap);
400
+
401
+ // Assert
402
+ const data = await clientDB.query.sessionGroups.findMany({
403
+ where: eq(sessionGroups.userId, userId),
404
+ });
405
+ expect(data).toMatchObject([
406
+ { id: 'group-1', sort: 2 },
407
+ { id: 'group-2', sort: 1 },
408
+ ]);
409
+ });
410
+ });
411
+ });