@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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/en-US/common.json +2 -2
- package/package.json +1 -1
- package/src/services/file/_deprecated.test.ts +119 -0
- package/src/services/file/{pglite.ts → _deprecated.ts} +28 -32
- package/src/services/file/client.test.ts +153 -74
- package/src/services/file/client.ts +32 -28
- package/src/services/file/index.ts +2 -2
- package/src/services/import/_deprecated.ts +74 -0
- package/src/services/import/{pglite.test.ts → client.test.ts} +1 -1
- package/src/services/import/client.ts +21 -61
- package/src/services/import/index.ts +2 -2
- package/src/services/message/_deprecated.test.ts +398 -0
- package/src/services/message/_deprecated.ts +121 -0
- package/src/services/message/client.test.ts +191 -159
- package/src/services/message/client.ts +47 -50
- package/src/services/message/index.ts +2 -2
- package/src/services/plugin/_deprecated.test.ts +162 -0
- package/src/services/plugin/_deprecated.ts +42 -0
- package/src/services/plugin/client.test.ts +68 -55
- package/src/services/plugin/client.ts +20 -11
- package/src/services/plugin/index.ts +2 -2
- package/src/services/session/_deprecated.test.ts +440 -0
- package/src/services/session/_deprecated.ts +183 -0
- package/src/services/session/client.test.ts +212 -241
- package/src/services/session/client.ts +61 -60
- package/src/services/session/index.ts +2 -2
- package/src/services/topic/{client.test.ts → _deprecated.test.ts} +1 -1
- package/src/services/topic/_deprecated.ts +70 -0
- package/src/services/topic/client.ts +40 -25
- package/src/services/topic/index.ts +2 -2
- package/src/services/topic/pglite.test.ts +1 -1
- package/src/services/user/{pglite.test.ts → _deprecated.test.ts} +32 -29
- package/src/services/user/_deprecated.ts +57 -0
- package/src/services/user/client.test.ts +28 -31
- package/src/services/user/client.ts +51 -16
- package/src/services/user/index.ts +2 -2
- package/src/store/chat/slices/builtinTool/action.test.ts +1 -1
- package/src/store/user/slices/common/action.test.ts +1 -1
- package/src/services/file/pglite.test.ts +0 -198
- package/src/services/import/pglite.ts +0 -34
- package/src/services/message/pglite.test.ts +0 -430
- package/src/services/message/pglite.ts +0 -118
- package/src/services/plugin/pglite.test.ts +0 -175
- package/src/services/plugin/pglite.ts +0 -51
- package/src/services/session/pglite.test.ts +0 -411
- package/src/services/session/pglite.ts +0 -184
- package/src/services/topic/pglite.ts +0 -85
- 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 {
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
import {
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
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
|
58
|
+
return this.sessionModel.queryWithGroups();
|
46
59
|
}
|
47
60
|
|
48
61
|
async getSessionConfig(id: string): Promise<LobeAgentConfig> {
|
49
|
-
|
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.
|
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
|
-
|
76
|
+
// @ts-ignore
|
77
|
+
return this.sessionModel.query();
|
65
78
|
}
|
66
79
|
case 'agent': {
|
67
|
-
|
80
|
+
// @ts-ignore
|
81
|
+
return this.sessionModel.query();
|
68
82
|
}
|
69
83
|
|
70
84
|
case 'all': {
|
71
|
-
|
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
|
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
|
96
|
+
return this.sessionModel.queryByKeyword(keyword);
|
91
97
|
}
|
92
98
|
|
93
|
-
async updateSession(
|
94
|
-
id
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
110
|
-
|
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
|
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
|
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
|
140
|
+
return this.sessionModel.delete(id);
|
141
141
|
}
|
142
142
|
|
143
143
|
async removeAllSessions() {
|
144
|
-
return
|
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
|
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
|
-
|
161
|
-
|
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
|
165
|
-
return await
|
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
|
170
|
+
return this.sessionGroupModel.update(id, data);
|
170
171
|
}
|
171
172
|
|
172
173
|
async updateSessionGroupOrder(sortMap: { id: string; sort: number }[]) {
|
173
|
-
return
|
174
|
+
return this.sessionGroupModel.updateOrder(sortMap);
|
174
175
|
}
|
175
176
|
|
176
177
|
async getSessionGroups(): Promise<SessionGroupItem[]> {
|
177
|
-
return
|
178
|
+
return this.sessionGroupModel.query();
|
178
179
|
}
|
179
180
|
|
180
181
|
async removeSessionGroups() {
|
181
|
-
return
|
182
|
+
return this.sessionGroupModel.deleteAll();
|
182
183
|
}
|
183
184
|
}
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import { ClientService as DeprecatedService } from './
|
2
|
-
import { ClientService } from './
|
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 './
|
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 {
|
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
|
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
|
-
|
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
|
-
|
34
|
+
const data = await this.topicModel.duplicate(id, newTitle);
|
35
|
+
return data.topic.id;
|
23
36
|
}
|
24
37
|
|
25
|
-
async getTopics(params: QueryTopicParams)
|
26
|
-
|
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
|
-
|
31
|
-
}
|
47
|
+
const data = await this.topicModel.queryByKeyword(keyword, this.toDbSessionId(sessionId));
|
32
48
|
|
33
|
-
|
34
|
-
return TopicModel.queryAll();
|
49
|
+
return data as unknown as Promise<ChatTopic[]>;
|
35
50
|
}
|
36
51
|
|
37
|
-
async
|
38
|
-
|
39
|
-
}
|
52
|
+
async getAllTopics() {
|
53
|
+
const data = await this.topicModel.queryAll();
|
40
54
|
|
41
|
-
|
42
|
-
return this.updateTopic(id, { favorite });
|
55
|
+
return data as unknown as Promise<ChatTopic[]>;
|
43
56
|
}
|
44
57
|
|
45
|
-
async
|
46
|
-
return this.
|
58
|
+
async countTopics() {
|
59
|
+
return this.topicModel.count();
|
47
60
|
}
|
48
61
|
|
49
62
|
async updateTopic(id: string, data: Partial<ChatTopic>) {
|
50
|
-
|
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
|
67
|
+
return this.topicModel.delete(id);
|
57
68
|
}
|
58
69
|
|
59
70
|
async removeTopics(sessionId: string) {
|
60
|
-
return
|
71
|
+
return this.topicModel.batchDeleteBySessionId(this.toDbSessionId(sessionId));
|
61
72
|
}
|
62
73
|
|
63
74
|
async batchRemoveTopics(topics: string[]) {
|
64
|
-
return
|
75
|
+
return this.topicModel.batchDelete(topics);
|
65
76
|
}
|
66
77
|
|
67
78
|
async removeAllTopic() {
|
68
|
-
return
|
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 './
|
2
|
-
import { ClientService } from './
|
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 './
|
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 {
|
2
|
+
import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
|
4
3
|
|
5
|
-
import {
|
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 './
|
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
|
-
|
27
|
-
|
29
|
+
describe('ClientService', () => {
|
30
|
+
let clientService: ClientService;
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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).
|
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:
|
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
|
-
|
62
|
-
|
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
|
-
|
70
|
+
(UserModel.resetSettings as Mock).mockResolvedValue(undefined);
|
70
71
|
|
71
|
-
|
72
|
-
where: eq(userSettings.id, mockUser.uuid),
|
73
|
-
});
|
72
|
+
await clientService.resetUserSettings();
|
74
73
|
|
75
|
-
expect(
|
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
|
+
}
|