@lobehub/chat 1.37.0 → 1.37.2

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 (50) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/en-US/common.json +2 -2
  4. package/package.json +1 -1
  5. package/src/services/file/_deprecated.test.ts +119 -0
  6. package/src/services/file/{pglite.ts → _deprecated.ts} +28 -32
  7. package/src/services/file/client.test.ts +153 -74
  8. package/src/services/file/client.ts +32 -28
  9. package/src/services/file/index.ts +2 -2
  10. package/src/services/import/_deprecated.ts +74 -0
  11. package/src/services/import/{pglite.test.ts → client.test.ts} +1 -1
  12. package/src/services/import/client.ts +21 -61
  13. package/src/services/import/index.ts +2 -2
  14. package/src/services/message/_deprecated.test.ts +398 -0
  15. package/src/services/message/_deprecated.ts +121 -0
  16. package/src/services/message/client.test.ts +191 -159
  17. package/src/services/message/client.ts +47 -50
  18. package/src/services/message/index.ts +2 -2
  19. package/src/services/plugin/_deprecated.test.ts +162 -0
  20. package/src/services/plugin/_deprecated.ts +42 -0
  21. package/src/services/plugin/client.test.ts +68 -55
  22. package/src/services/plugin/client.ts +20 -11
  23. package/src/services/plugin/index.ts +2 -2
  24. package/src/services/session/_deprecated.test.ts +440 -0
  25. package/src/services/session/_deprecated.ts +183 -0
  26. package/src/services/session/client.test.ts +212 -241
  27. package/src/services/session/client.ts +61 -60
  28. package/src/services/session/index.ts +2 -2
  29. package/src/services/topic/{client.test.ts → _deprecated.test.ts} +1 -1
  30. package/src/services/topic/_deprecated.ts +70 -0
  31. package/src/services/topic/client.ts +40 -25
  32. package/src/services/topic/index.ts +2 -2
  33. package/src/services/topic/pglite.test.ts +1 -1
  34. package/src/services/user/{pglite.test.ts → _deprecated.test.ts} +32 -29
  35. package/src/services/user/_deprecated.ts +57 -0
  36. package/src/services/user/client.test.ts +28 -31
  37. package/src/services/user/client.ts +51 -16
  38. package/src/services/user/index.ts +2 -2
  39. package/src/store/chat/slices/builtinTool/action.test.ts +1 -1
  40. package/src/store/user/slices/common/action.test.ts +1 -1
  41. package/src/services/file/pglite.test.ts +0 -198
  42. package/src/services/import/pglite.ts +0 -34
  43. package/src/services/message/pglite.test.ts +0 -430
  44. package/src/services/message/pglite.ts +0 -118
  45. package/src/services/plugin/pglite.test.ts +0 -175
  46. package/src/services/plugin/pglite.ts +0 -51
  47. package/src/services/session/pglite.test.ts +0 -411
  48. package/src/services/session/pglite.ts +0 -184
  49. package/src/services/topic/pglite.ts +0 -85
  50. package/src/services/user/pglite.ts +0 -92
@@ -1,10 +1,11 @@
1
1
  import { DeepPartial } from 'utility-types';
2
2
 
3
3
  import { INBOX_SESSION_ID } from '@/const/session';
4
- import { SessionModel } from '@/database/_deprecated/models/session';
5
- import { SessionGroupModel } from '@/database/_deprecated/models/sessionGroup';
6
- import { UserModel } from '@/database/_deprecated/models/user';
7
- import { useUserStore } from '@/store/user';
4
+ import { clientDB } from '@/database/client/db';
5
+ import { AgentItem } from '@/database/schemas';
6
+ import { SessionModel } from '@/database/server/models/session';
7
+ import { SessionGroupModel } from '@/database/server/models/sessionGroup';
8
+ import { BaseClientService } from '@/services/baseClientService';
8
9
  import { LobeAgentChatConfig, LobeAgentConfig } from '@/types/agent';
9
10
  import { MetaData } from '@/types/meta';
10
11
  import {
@@ -14,17 +15,28 @@ import {
14
15
  LobeSessions,
15
16
  SessionGroupItem,
16
17
  SessionGroups,
18
+ UpdateSessionParams,
17
19
  } from '@/types/session';
18
- import { merge } from '@/utils/merge';
19
20
 
20
21
  import { ISessionService } from './type';
21
22
 
22
- export class ClientService implements ISessionService {
23
- async createSession(
24
- type: LobeSessionType,
25
- defaultValue: Partial<LobeAgentSession>,
26
- ): Promise<string> {
27
- const item = await SessionModel.create(type, defaultValue);
23
+ export class ClientService extends BaseClientService implements ISessionService {
24
+ private get sessionModel(): SessionModel {
25
+ return new SessionModel(clientDB as any, this.userId);
26
+ }
27
+
28
+ private get sessionGroupModel(): SessionGroupModel {
29
+ return new SessionGroupModel(clientDB as any, this.userId);
30
+ }
31
+
32
+ async createSession(type: LobeSessionType, data: Partial<LobeAgentSession>): Promise<string> {
33
+ const { config, group, meta, ...session } = data;
34
+
35
+ const item = await this.sessionModel.create({
36
+ config: { ...config, ...meta } as any,
37
+ session: { ...session, groupId: group },
38
+ type,
39
+ });
28
40
  if (!item) {
29
41
  throw new Error('session create Error');
30
42
  }
@@ -32,72 +44,63 @@ export class ClientService implements ISessionService {
32
44
  }
33
45
 
34
46
  async batchCreateSessions(importSessions: LobeSessions) {
35
- return SessionModel.batchCreate(importSessions);
47
+ // @ts-ignore
48
+ return this.sessionModel.batchCreate(importSessions);
36
49
  }
37
50
 
38
51
  async cloneSession(id: string, newTitle: string): Promise<string | undefined> {
39
- const res = await SessionModel.duplicate(id, newTitle);
52
+ const res = await this.sessionModel.duplicate(id, newTitle);
40
53
 
41
54
  if (res) return res?.id;
42
55
  }
43
56
 
44
57
  async getGroupedSessions(): Promise<ChatSessionList> {
45
- return SessionModel.queryWithGroups();
58
+ return this.sessionModel.queryWithGroups();
46
59
  }
47
60
 
48
61
  async getSessionConfig(id: string): Promise<LobeAgentConfig> {
49
- if (!id || id === INBOX_SESSION_ID) {
50
- return UserModel.getAgentConfig();
51
- }
52
-
53
- const res = await SessionModel.findById(id);
62
+ const res = await this.sessionModel.findByIdOrSlug(id);
54
63
 
55
64
  if (!res) throw new Error('Session not found');
56
65
 
57
- return res.config as LobeAgentConfig;
66
+ return res.agent as LobeAgentConfig;
58
67
  }
59
68
 
69
+ /**
70
+ * 这个方法要对应移除的
71
+ */
60
72
  async getSessionsByType(type: 'agent' | 'group' | 'all' = 'all'): Promise<LobeSessions> {
61
73
  switch (type) {
62
74
  // TODO: add a filter to get only agents or agents
63
75
  case 'group': {
64
- return SessionModel.query();
76
+ // @ts-ignore
77
+ return this.sessionModel.query();
65
78
  }
66
79
  case 'agent': {
67
- return SessionModel.query();
80
+ // @ts-ignore
81
+ return this.sessionModel.query();
68
82
  }
69
83
 
70
84
  case 'all': {
71
- return SessionModel.query();
85
+ // @ts-ignore
86
+ return this.sessionModel.query();
72
87
  }
73
88
  }
74
89
  }
75
90
 
76
- async getAllAgents(): Promise<LobeSessions> {
77
- // TODO: add a filter to get only agents
78
- return await SessionModel.query();
79
- }
80
-
81
91
  async countSessions() {
82
- return SessionModel.count();
83
- }
84
-
85
- async hasSessions() {
86
- return (await this.countSessions()) !== 0;
92
+ return this.sessionModel.count();
87
93
  }
88
94
 
89
95
  async searchSessions(keyword: string) {
90
- return SessionModel.queryByKeyword(keyword);
96
+ return this.sessionModel.queryByKeyword(keyword);
91
97
  }
92
98
 
93
- async updateSession(
94
- id: string,
95
- data: Partial<Pick<LobeAgentSession, 'group' | 'meta' | 'pinned'>>,
96
- ) {
97
- const pinned = typeof data.pinned === 'boolean' ? (data.pinned ? 1 : 0) : undefined;
98
- const prev = await SessionModel.findById(id);
99
-
100
- return SessionModel.update(id, merge(prev, { ...data, pinned }));
99
+ async updateSession(id: string, value: Partial<UpdateSessionParams>) {
100
+ return this.sessionModel.update(id, {
101
+ ...value,
102
+ groupId: value.group === 'default' ? null : value.group,
103
+ });
101
104
  }
102
105
 
103
106
  async updateSessionConfig(
@@ -106,13 +109,10 @@ export class ClientService implements ISessionService {
106
109
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
107
110
  _?: AbortSignal,
108
111
  ) {
109
- // TODO: 需要删除这部分处理逻辑
110
- // 后续直接给用户创建一个 inbox session
111
- if (activeId === INBOX_SESSION_ID) {
112
- return useUserStore.getState().updateDefaultAgent({ config });
113
- }
112
+ const session = await this.sessionModel.findByIdOrSlug(activeId);
113
+ if (!session || !config) return;
114
114
 
115
- return SessionModel.updateConfig(activeId, config);
115
+ return this.sessionModel.updateConfig(session.agent.id, config as AgentItem);
116
116
  }
117
117
 
118
118
  async updateSessionMeta(
@@ -124,7 +124,7 @@ export class ClientService implements ISessionService {
124
124
  // inbox 不允许修改 meta
125
125
  if (activeId === INBOX_SESSION_ID) return;
126
126
 
127
- return SessionModel.update(activeId, { meta });
127
+ return this.sessionModel.update(activeId, meta);
128
128
  }
129
129
 
130
130
  async updateSessionChatConfig(
@@ -137,11 +137,11 @@ export class ClientService implements ISessionService {
137
137
  }
138
138
 
139
139
  async removeSession(id: string) {
140
- return SessionModel.delete(id);
140
+ return this.sessionModel.delete(id);
141
141
  }
142
142
 
143
143
  async removeAllSessions() {
144
- return SessionModel.clearTable();
144
+ return this.sessionModel.deleteAll();
145
145
  }
146
146
 
147
147
  // ************************************** //
@@ -149,7 +149,7 @@ export class ClientService implements ISessionService {
149
149
  // ************************************** //
150
150
 
151
151
  async createSessionGroup(name: string, sort?: number) {
152
- const item = await SessionGroupModel.create(name, sort);
152
+ const item = await this.sessionGroupModel.create({ name, sort });
153
153
  if (!item) {
154
154
  throw new Error('session group create Error');
155
155
  }
@@ -157,27 +157,28 @@ export class ClientService implements ISessionService {
157
157
  return item.id;
158
158
  }
159
159
 
160
- async batchCreateSessionGroups(groups: SessionGroups) {
161
- return SessionGroupModel.batchCreate(groups);
160
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
161
+ async batchCreateSessionGroups(_groups: SessionGroups) {
162
+ return { added: 0, ids: [], skips: [], success: true };
162
163
  }
163
164
 
164
- async removeSessionGroup(id: string, removeChildren?: boolean) {
165
- return await SessionGroupModel.delete(id, removeChildren);
165
+ async removeSessionGroup(id: string) {
166
+ return await this.sessionGroupModel.delete(id);
166
167
  }
167
168
 
168
169
  async updateSessionGroup(id: string, data: Partial<SessionGroupItem>) {
169
- return SessionGroupModel.update(id, data as any);
170
+ return this.sessionGroupModel.update(id, data);
170
171
  }
171
172
 
172
173
  async updateSessionGroupOrder(sortMap: { id: string; sort: number }[]) {
173
- return SessionGroupModel.updateOrder(sortMap);
174
+ return this.sessionGroupModel.updateOrder(sortMap);
174
175
  }
175
176
 
176
177
  async getSessionGroups(): Promise<SessionGroupItem[]> {
177
- return SessionGroupModel.query();
178
+ return this.sessionGroupModel.query();
178
179
  }
179
180
 
180
181
  async removeSessionGroups() {
181
- return SessionGroupModel.clear();
182
+ return this.sessionGroupModel.deleteAll();
182
183
  }
183
184
  }
@@ -1,5 +1,5 @@
1
- import { ClientService as DeprecatedService } from './client';
2
- import { ClientService } from './pglite';
1
+ import { ClientService as DeprecatedService } from './_deprecated';
2
+ import { ClientService } from './client';
3
3
  import { ServerService } from './server';
4
4
 
5
5
  const clientService =
@@ -4,7 +4,7 @@ import { SessionModel } from '@/database/_deprecated/models/session';
4
4
  import { CreateTopicParams, TopicModel } from '@/database/_deprecated/models/topic';
5
5
  import { ChatTopic } from '@/types/topic';
6
6
 
7
- import { ClientService } from './client';
7
+ import { ClientService } from './_deprecated';
8
8
 
9
9
  const topicService = new ClientService();
10
10
  // Mock the TopicModel
@@ -0,0 +1,70 @@
1
+ import { TopicModel } from '@/database/_deprecated/models/topic';
2
+ import { ChatTopic } from '@/types/topic';
3
+
4
+ import { CreateTopicParams, ITopicService, QueryTopicParams } from './type';
5
+
6
+ export class ClientService implements ITopicService {
7
+ async createTopic(params: CreateTopicParams): Promise<string> {
8
+ const item = await TopicModel.create(params as any);
9
+
10
+ if (!item) {
11
+ throw new Error('topic create Error');
12
+ }
13
+
14
+ return item.id;
15
+ }
16
+
17
+ async batchCreateTopics(importTopics: ChatTopic[]) {
18
+ return TopicModel.batchCreate(importTopics as any);
19
+ }
20
+
21
+ async cloneTopic(id: string, newTitle?: string) {
22
+ return TopicModel.duplicateTopic(id, newTitle);
23
+ }
24
+
25
+ async getTopics(params: QueryTopicParams): Promise<ChatTopic[]> {
26
+ return TopicModel.query(params);
27
+ }
28
+
29
+ async searchTopics(keyword: string, sessionId?: string) {
30
+ return TopicModel.queryByKeyword(keyword, sessionId);
31
+ }
32
+
33
+ async getAllTopics() {
34
+ return TopicModel.queryAll();
35
+ }
36
+
37
+ async countTopics() {
38
+ return TopicModel.count();
39
+ }
40
+
41
+ async updateTopicFavorite(id: string, favorite?: boolean) {
42
+ return this.updateTopic(id, { favorite });
43
+ }
44
+
45
+ async updateTopicTitle(id: string, text: string) {
46
+ return this.updateTopic(id, { title: text });
47
+ }
48
+
49
+ async updateTopic(id: string, data: Partial<ChatTopic>) {
50
+ const favorite = typeof data.favorite !== 'undefined' ? (data.favorite ? 1 : 0) : undefined;
51
+
52
+ return TopicModel.update(id, { ...data, favorite });
53
+ }
54
+
55
+ async removeTopic(id: string) {
56
+ return TopicModel.delete(id);
57
+ }
58
+
59
+ async removeTopics(sessionId: string) {
60
+ return TopicModel.batchDeleteBySessionId(sessionId);
61
+ }
62
+
63
+ async batchRemoveTopics(topics: string[]) {
64
+ return TopicModel.batchDelete(topics);
65
+ }
66
+
67
+ async removeAllTopic() {
68
+ return TopicModel.clearTable();
69
+ }
70
+ }
@@ -1,11 +1,21 @@
1
- import { TopicModel } from '@/database/_deprecated/models/topic';
1
+ import { INBOX_SESSION_ID } from '@/const/session';
2
+ import { clientDB } from '@/database/client/db';
3
+ import { TopicModel } from '@/database/server/models/topic';
4
+ import { BaseClientService } from '@/services/baseClientService';
2
5
  import { ChatTopic } from '@/types/topic';
3
6
 
4
7
  import { CreateTopicParams, ITopicService, QueryTopicParams } from './type';
5
8
 
6
- export class ClientService implements ITopicService {
9
+ export class ClientService extends BaseClientService implements ITopicService {
10
+ private get topicModel(): TopicModel {
11
+ return new TopicModel(clientDB as any, this.userId);
12
+ }
13
+
7
14
  async createTopic(params: CreateTopicParams): Promise<string> {
8
- const item = await TopicModel.create(params as any);
15
+ const item = await this.topicModel.create({
16
+ ...params,
17
+ sessionId: this.toDbSessionId(params.sessionId),
18
+ } as any);
9
19
 
10
20
  if (!item) {
11
21
  throw new Error('topic create Error');
@@ -15,56 +25,61 @@ export class ClientService implements ITopicService {
15
25
  }
16
26
 
17
27
  async batchCreateTopics(importTopics: ChatTopic[]) {
18
- return TopicModel.batchCreate(importTopics as any);
28
+ const data = await this.topicModel.batchCreate(importTopics as any);
29
+
30
+ return { added: data.length, ids: [], skips: [], success: true };
19
31
  }
20
32
 
21
33
  async cloneTopic(id: string, newTitle?: string) {
22
- return TopicModel.duplicateTopic(id, newTitle);
34
+ const data = await this.topicModel.duplicate(id, newTitle);
35
+ return data.topic.id;
23
36
  }
24
37
 
25
- async getTopics(params: QueryTopicParams): Promise<ChatTopic[]> {
26
- return TopicModel.query(params);
38
+ async getTopics(params: QueryTopicParams) {
39
+ const data = await this.topicModel.query({
40
+ ...params,
41
+ sessionId: this.toDbSessionId(params.sessionId),
42
+ });
43
+ return data as unknown as Promise<ChatTopic[]>;
27
44
  }
28
45
 
29
46
  async searchTopics(keyword: string, sessionId?: string) {
30
- return TopicModel.queryByKeyword(keyword, sessionId);
31
- }
47
+ const data = await this.topicModel.queryByKeyword(keyword, this.toDbSessionId(sessionId));
32
48
 
33
- async getAllTopics() {
34
- return TopicModel.queryAll();
49
+ return data as unknown as Promise<ChatTopic[]>;
35
50
  }
36
51
 
37
- async countTopics() {
38
- return TopicModel.count();
39
- }
52
+ async getAllTopics() {
53
+ const data = await this.topicModel.queryAll();
40
54
 
41
- async updateTopicFavorite(id: string, favorite?: boolean) {
42
- return this.updateTopic(id, { favorite });
55
+ return data as unknown as Promise<ChatTopic[]>;
43
56
  }
44
57
 
45
- async updateTopicTitle(id: string, text: string) {
46
- return this.updateTopic(id, { title: text });
58
+ async countTopics() {
59
+ return this.topicModel.count();
47
60
  }
48
61
 
49
62
  async updateTopic(id: string, data: Partial<ChatTopic>) {
50
- const favorite = typeof data.favorite !== 'undefined' ? (data.favorite ? 1 : 0) : undefined;
51
-
52
- return TopicModel.update(id, { ...data, favorite });
63
+ return this.topicModel.update(id, data as any);
53
64
  }
54
65
 
55
66
  async removeTopic(id: string) {
56
- return TopicModel.delete(id);
67
+ return this.topicModel.delete(id);
57
68
  }
58
69
 
59
70
  async removeTopics(sessionId: string) {
60
- return TopicModel.batchDeleteBySessionId(sessionId);
71
+ return this.topicModel.batchDeleteBySessionId(this.toDbSessionId(sessionId));
61
72
  }
62
73
 
63
74
  async batchRemoveTopics(topics: string[]) {
64
- return TopicModel.batchDelete(topics);
75
+ return this.topicModel.batchDelete(topics);
65
76
  }
66
77
 
67
78
  async removeAllTopic() {
68
- return TopicModel.clearTable();
79
+ return this.topicModel.deleteAll();
80
+ }
81
+
82
+ private toDbSessionId(sessionId?: string | null) {
83
+ return sessionId === INBOX_SESSION_ID ? null : sessionId;
69
84
  }
70
85
  }
@@ -1,5 +1,5 @@
1
- import { ClientService as DeprecatedService } from './client';
2
- import { ClientService } from './pglite';
1
+ import { ClientService as DeprecatedService } from './_deprecated';
2
+ import { ClientService } from './client';
3
3
  import { ServerService } from './server';
4
4
 
5
5
  const clientService =
@@ -5,7 +5,7 @@ import { clientDB, initializeDB } from '@/database/client/db';
5
5
  import { sessions, topics, users } from '@/database/schemas';
6
6
  import { ChatTopic } from '@/types/topic';
7
7
 
8
- import { ClientService } from './pglite';
8
+ import { ClientService } from './client';
9
9
 
10
10
  // Mock data
11
11
  const userId = 'topic-user-test';
@@ -1,13 +1,20 @@
1
- import { eq } from 'drizzle-orm';
2
1
  import { DeepPartial } from 'utility-types';
3
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
4
3
 
5
- import { clientDB, initializeDB } from '@/database/client/db';
6
- import { userSettings, users } from '@/database/schemas';
4
+ import { UserModel } from '@/database/_deprecated/models/user';
7
5
  import { UserPreference } from '@/types/user';
8
6
  import { UserSettings } from '@/types/user/settings';
9
7
 
10
- import { ClientService } from './pglite';
8
+ import { ClientService } from './_deprecated';
9
+
10
+ vi.mock('@/database/_deprecated/models/user', () => ({
11
+ UserModel: {
12
+ getUser: vi.fn(),
13
+ updateSettings: vi.fn(),
14
+ resetSettings: vi.fn(),
15
+ updateAvatar: vi.fn(),
16
+ },
17
+ }));
11
18
 
12
19
  const mockUser = {
13
20
  avatar: 'avatar.png',
@@ -18,67 +25,63 @@ const mockUser = {
18
25
  const mockPreference = {
19
26
  useCmdEnterToSend: true,
20
27
  } as UserPreference;
21
- const clientService = new ClientService(mockUser.uuid);
22
-
23
- beforeEach(async () => {
24
- vi.clearAllMocks();
25
28
 
26
- await initializeDB();
27
- await clientDB.delete(users);
29
+ describe('ClientService', () => {
30
+ let clientService: ClientService;
28
31
 
29
- await clientDB.insert(users).values({ id: mockUser.uuid, avatar: 'avatar.png' });
30
- await clientDB
31
- .insert(userSettings)
32
- .values({ id: mockUser.uuid, general: { themeMode: 'light' } });
33
- });
32
+ beforeEach(() => {
33
+ vi.clearAllMocks();
34
+ clientService = new ClientService();
35
+ });
34
36
 
35
- describe('ClientService', () => {
36
37
  it('should get user state correctly', async () => {
38
+ (UserModel.getUser as Mock).mockResolvedValue(mockUser);
37
39
  const spyOn = vi
38
40
  .spyOn(clientService['preferenceStorage'], 'getFromLocalStorage')
39
41
  .mockResolvedValue(mockPreference);
40
42
 
41
43
  const userState = await clientService.getUserState();
42
44
 
43
- expect(userState).toMatchObject({
45
+ expect(userState).toEqual({
44
46
  avatar: mockUser.avatar,
45
47
  isOnboard: true,
46
48
  canEnablePWAGuide: false,
47
49
  hasConversation: false,
48
50
  canEnableTrace: false,
49
51
  preference: mockPreference,
50
- settings: { general: { themeMode: 'light' } },
52
+ settings: mockUser.settings,
51
53
  userId: mockUser.uuid,
52
54
  });
55
+ expect(UserModel.getUser).toHaveBeenCalledTimes(1);
53
56
  expect(spyOn).toHaveBeenCalledTimes(1);
54
57
  });
55
58
 
56
59
  it('should update user settings correctly', async () => {
57
60
  const settingsPatch: DeepPartial<UserSettings> = { general: { themeMode: 'dark' } };
61
+ (UserModel.updateSettings as Mock).mockResolvedValue(undefined);
58
62
 
59
63
  await clientService.updateUserSettings(settingsPatch);
60
64
 
61
- const result = await clientDB.query.userSettings.findFirst({
62
- where: eq(userSettings.id, mockUser.uuid),
63
- });
64
-
65
- expect(result).toMatchObject(settingsPatch);
65
+ expect(UserModel.updateSettings).toHaveBeenCalledWith(settingsPatch);
66
+ expect(UserModel.updateSettings).toHaveBeenCalledTimes(1);
66
67
  });
67
68
 
68
69
  it('should reset user settings correctly', async () => {
69
- await clientService.resetUserSettings();
70
+ (UserModel.resetSettings as Mock).mockResolvedValue(undefined);
70
71
 
71
- const result = await clientDB.query.userSettings.findFirst({
72
- where: eq(userSettings.id, mockUser.uuid),
73
- });
72
+ await clientService.resetUserSettings();
74
73
 
75
- expect(result).toBeUndefined();
74
+ expect(UserModel.resetSettings).toHaveBeenCalledTimes(1);
76
75
  });
77
76
 
78
77
  it('should update user avatar correctly', async () => {
79
78
  const newAvatar = 'new-avatar.png';
79
+ (UserModel.updateAvatar as Mock).mockResolvedValue(undefined);
80
80
 
81
81
  await clientService.updateAvatar(newAvatar);
82
+
83
+ expect(UserModel.updateAvatar).toHaveBeenCalledWith(newAvatar);
84
+ expect(UserModel.updateAvatar).toHaveBeenCalledTimes(1);
82
85
  });
83
86
 
84
87
  it('should update user preference correctly', async () => {
@@ -0,0 +1,57 @@
1
+ import { DeepPartial } from 'utility-types';
2
+
3
+ import { MessageModel } from '@/database/_deprecated/models/message';
4
+ import { SessionModel } from '@/database/_deprecated/models/session';
5
+ import { UserModel } from '@/database/_deprecated/models/user';
6
+ import { UserGuide, UserInitializationState, UserPreference } from '@/types/user';
7
+ import { UserSettings } from '@/types/user/settings';
8
+ import { AsyncLocalStorage } from '@/utils/localStorage';
9
+
10
+ import { IUserService } from './type';
11
+
12
+ export class ClientService implements IUserService {
13
+ private preferenceStorage: AsyncLocalStorage<UserPreference>;
14
+
15
+ constructor() {
16
+ this.preferenceStorage = new AsyncLocalStorage('LOBE_PREFERENCE');
17
+ }
18
+
19
+ async getUserState(): Promise<UserInitializationState> {
20
+ const user = await UserModel.getUser();
21
+ const messageCount = await MessageModel.count();
22
+ const sessionCount = await SessionModel.count();
23
+
24
+ return {
25
+ avatar: user.avatar,
26
+ canEnablePWAGuide: messageCount >= 4,
27
+ canEnableTrace: messageCount >= 4,
28
+ hasConversation: messageCount > 0 || sessionCount > 0,
29
+ isOnboard: true,
30
+ preference: await this.preferenceStorage.getFromLocalStorage(),
31
+ settings: user.settings as UserSettings,
32
+ userId: user.uuid,
33
+ };
34
+ }
35
+
36
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
37
+ updateUserSettings = async (patch: DeepPartial<UserSettings>, _?: any) => {
38
+ return UserModel.updateSettings(patch);
39
+ };
40
+
41
+ resetUserSettings = async () => {
42
+ return UserModel.resetSettings();
43
+ };
44
+
45
+ async updateAvatar(avatar: string) {
46
+ await UserModel.updateAvatar(avatar);
47
+ }
48
+
49
+ async updatePreference(preference: Partial<UserPreference>) {
50
+ await this.preferenceStorage.saveToLocalStorage(preference);
51
+ }
52
+
53
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars,unused-imports/no-unused-vars
54
+ async updateGuide(guide: Partial<UserGuide>) {
55
+ throw new Error('Method not implemented.');
56
+ }
57
+ }