@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.37.2](https://github.com/lobehub/lobe-chat/compare/v1.37.1...v1.37.2)
6
+
7
+ <sup>Released on **2024-12-22**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Move pglite to client service.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Move pglite to client service, closes [#5133](https://github.com/lobehub/lobe-chat/issues/5133) ([c2ded24](https://github.com/lobehub/lobe-chat/commit/c2ded24))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.37.1](https://github.com/lobehub/lobe-chat/compare/v1.37.0...v1.37.1)
31
+
32
+ <sup>Released on **2024-12-22**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Refactor the client service to deprecated.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **misc**: Refactor the client service to deprecated, closes [#5132](https://github.com/lobehub/lobe-chat/issues/5132) ([e603234](https://github.com/lobehub/lobe-chat/commit/e603234))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 1.37.0](https://github.com/lobehub/lobe-chat/compare/v1.36.46...v1.37.0)
6
56
 
7
57
  <sup>Released on **2024-12-22**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Move pglite to client service."
6
+ ]
7
+ },
8
+ "date": "2024-12-22",
9
+ "version": "1.37.2"
10
+ },
11
+ {
12
+ "children": {
13
+ "improvements": [
14
+ "Refactor the client service to deprecated."
15
+ ]
16
+ },
17
+ "date": "2024-12-22",
18
+ "version": "1.37.1"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "features": [
@@ -59,14 +59,14 @@
59
59
  "features": {
60
60
  "knowledgeBase": {
61
61
  "desc": "Build your personal knowledge base and easily start conversations with your assistant (coming soon)",
62
- "title": "Support for knowledge base conversations, unlock your second brain"
62
+ "title": "Support for knowledge base conversations"
63
63
  },
64
64
  "localFirst": {
65
65
  "desc": "Chat data is stored entirely in the browser, keeping your data always under your control.",
66
66
  "title": "Local first, privacy first"
67
67
  },
68
68
  "pglite": {
69
- "desc": "Built on PGlite, natively supports AI Native advanced features (vector retrieval)",
69
+ "desc": "Built on PGlite, natively supports AI Native advanced features (vector search)",
70
70
  "title": "Next-generation client storage architecture"
71
71
  }
72
72
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.37.0",
3
+ "version": "1.37.2",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -0,0 +1,119 @@
1
+ import { Mock, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { fileEnv } from '@/config/file';
4
+ import { FileModel } from '@/database/_deprecated/models/file';
5
+ import { DB_File } from '@/database/_deprecated/schemas/files';
6
+ import { clientS3Storage } from '@/services/file/ClientS3';
7
+ import { serverConfigSelectors } from '@/store/serverConfig/selectors';
8
+ import { createServerConfigStore } from '@/store/serverConfig/store';
9
+
10
+ import { ClientService } from './_deprecated';
11
+
12
+ const fileService = new ClientService();
13
+
14
+ beforeAll(() => {
15
+ createServerConfigStore();
16
+ });
17
+ // Mocks for the FileModel
18
+ vi.mock('@/database/_deprecated/models/file', () => ({
19
+ FileModel: {
20
+ create: vi.fn(),
21
+ delete: vi.fn(),
22
+ findById: vi.fn(),
23
+ clear: vi.fn(),
24
+ },
25
+ }));
26
+
27
+ let s3Domain: string;
28
+
29
+ vi.mock('@/config/file', () => ({
30
+ fileEnv: {
31
+ get NEXT_PUBLIC_S3_DOMAIN() {
32
+ return s3Domain;
33
+ },
34
+ },
35
+ }));
36
+
37
+ // Mocks for the URL and Blob objects
38
+ global.URL.createObjectURL = vi.fn();
39
+ global.Blob = vi.fn();
40
+
41
+ beforeEach(() => {
42
+ // Reset all mocks before each test
43
+ vi.resetAllMocks();
44
+ s3Domain = '';
45
+ });
46
+
47
+ describe('FileService', () => {
48
+ it('createFile should save the file to the database', async () => {
49
+ const localFile = {
50
+ name: 'test',
51
+ fileType: 'image/png',
52
+ url: 'client-s3://123',
53
+ size: 1,
54
+ hash: '123',
55
+ };
56
+
57
+ await clientS3Storage.putObject(
58
+ '123',
59
+ new File([new ArrayBuffer(1)], 'test.png', { type: 'image/png' }),
60
+ );
61
+
62
+ (FileModel.create as Mock).mockResolvedValue(localFile);
63
+
64
+ const result = await fileService.createFile(localFile);
65
+
66
+ expect(result).toEqual({ url: 'data:image/png;base64,AA==' });
67
+ });
68
+
69
+ it('removeFile should delete the file from the database', async () => {
70
+ const fileId = '1';
71
+ (FileModel.delete as Mock).mockResolvedValue(true);
72
+
73
+ const result = await fileService.removeFile(fileId);
74
+
75
+ expect(FileModel.delete).toHaveBeenCalledWith(fileId);
76
+ expect(result).toBe(true);
77
+ });
78
+
79
+ describe('getFile', () => {
80
+ it('should retrieve and convert local file info to FilePreview', async () => {
81
+ const fileId = '1';
82
+ const fileData = {
83
+ name: 'test',
84
+ data: new ArrayBuffer(1),
85
+ fileType: 'image/png',
86
+ saveMode: 'local',
87
+ size: 1,
88
+ createdAt: 1,
89
+ updatedAt: 2,
90
+ } as DB_File;
91
+
92
+ (FileModel.findById as Mock).mockResolvedValue(fileData);
93
+ (global.URL.createObjectURL as Mock).mockReturnValue('blob:test');
94
+ (global.Blob as Mock).mockImplementation(() => ['test']);
95
+
96
+ const result = await fileService.getFile(fileId);
97
+
98
+ expect(FileModel.findById).toHaveBeenCalledWith(fileId);
99
+ expect(result).toEqual({
100
+ createdAt: new Date(1),
101
+ id: '1',
102
+ size: 1,
103
+ type: 'image/png',
104
+ name: 'test',
105
+ url: 'blob:test',
106
+ updatedAt: new Date(2),
107
+ });
108
+ });
109
+
110
+ it('should throw an error when the file is not found', async () => {
111
+ const fileId = 'non-existent';
112
+ (FileModel.findById as Mock).mockResolvedValue(null);
113
+
114
+ const getFilePromise = fileService.getFile(fileId);
115
+
116
+ await expect(getFilePromise).rejects.toThrow('file not found');
117
+ });
118
+ });
119
+ });
@@ -1,31 +1,24 @@
1
- import { clientDB } from '@/database/client/db';
2
- import { FileModel } from '@/database/server/models/file';
3
- import { BaseClientService } from '@/services/baseClientService';
1
+ import { FileModel } from '@/database/_deprecated/models/file';
4
2
  import { clientS3Storage } from '@/services/file/ClientS3';
5
3
  import { FileItem, UploadFileParams } from '@/types/files';
6
4
 
7
5
  import { IFileService } from './type';
8
6
 
9
- export class ClientService extends BaseClientService implements IFileService {
10
- private get fileModel(): FileModel {
11
- return new FileModel(clientDB as any, this.userId);
12
- }
13
-
7
+ export class ClientService implements IFileService {
14
8
  async createFile(file: UploadFileParams) {
15
9
  // save to local storage
16
10
  // we may want to save to a remote server later
17
- const res = await this.fileModel.create(
18
- {
19
- fileHash: file.hash,
20
- fileType: file.fileType,
21
- knowledgeBaseId: file.knowledgeBaseId,
22
- metadata: file.metadata,
23
- name: file.name,
24
- size: file.size,
25
- url: file.url!,
26
- },
27
- true,
28
- );
11
+ const res = await FileModel.create({
12
+ createdAt: Date.now(),
13
+ data: undefined,
14
+ fileHash: file.hash,
15
+ fileType: file.fileType,
16
+ metadata: file.metadata,
17
+ name: file.name,
18
+ saveMode: 'url',
19
+ size: file.size,
20
+ url: file.url,
21
+ } as any);
29
22
 
30
23
  // get file to base64 url
31
24
  const base64 = await this.getBase64ByFileHash(file.hash!);
@@ -36,17 +29,24 @@ export class ClientService extends BaseClientService implements IFileService {
36
29
  };
37
30
  }
38
31
 
32
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
+ async checkFileHash(_hash: string) {
34
+ return { isExist: false, metadata: {} };
35
+ }
36
+
39
37
  async getFile(id: string): Promise<FileItem> {
40
- const item = await this.fileModel.findById(id);
38
+ const item = await FileModel.findById(id);
41
39
  if (!item) {
42
40
  throw new Error('file not found');
43
41
  }
44
42
 
45
- // arrayBuffer to url
46
- const fileItem = await clientS3Storage.getObject(item.fileHash!);
47
- if (!fileItem) throw new Error('file not found');
43
+ // arrayBuffer to blob or base64 to blob
44
+ const blob = !!item.data
45
+ ? new Blob([item.data!], { type: item.fileType })
46
+ : // @ts-ignore
47
+ new Blob([Buffer.from(item.base64!, 'base64')], { type: item.fileType });
48
48
 
49
- const url = URL.createObjectURL(fileItem);
49
+ const url = URL.createObjectURL(blob);
50
50
 
51
51
  return {
52
52
  createdAt: new Date(item.createdAt),
@@ -60,19 +60,15 @@ export class ClientService extends BaseClientService implements IFileService {
60
60
  }
61
61
 
62
62
  async removeFile(id: string) {
63
- await this.fileModel.delete(id, false);
63
+ return FileModel.delete(id);
64
64
  }
65
65
 
66
66
  async removeFiles(ids: string[]) {
67
- await this.fileModel.deleteMany(ids, false);
67
+ await Promise.all(ids.map((id) => FileModel.delete(id)));
68
68
  }
69
69
 
70
70
  async removeAllFiles() {
71
- return this.fileModel.clear();
72
- }
73
-
74
- async checkFileHash(hash: string) {
75
- return this.fileModel.checkHash(hash);
71
+ return FileModel.clear();
76
72
  }
77
73
 
78
74
  private async getBase64ByFileHash(hash: string) {
@@ -1,119 +1,198 @@
1
- import { Mock, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
1
+ import { eq } from 'drizzle-orm';
2
+ import { beforeEach, describe, expect, it } from 'vitest';
2
3
 
3
- import { fileEnv } from '@/config/file';
4
- import { FileModel } from '@/database/_deprecated/models/file';
5
- import { DB_File } from '@/database/_deprecated/schemas/files';
4
+ import { clientDB, initializeDB } from '@/database/client/db';
5
+ import { files, globalFiles, users } from '@/database/schemas';
6
6
  import { clientS3Storage } from '@/services/file/ClientS3';
7
- import { serverConfigSelectors } from '@/store/serverConfig/selectors';
8
- import { createServerConfigStore } from '@/store/serverConfig/store';
7
+ import { UploadFileParams } from '@/types/files';
9
8
 
10
9
  import { ClientService } from './client';
11
10
 
12
- const fileService = new ClientService();
11
+ const userId = 'file-user';
13
12
 
14
- beforeAll(() => {
15
- createServerConfigStore();
16
- });
17
- // Mocks for the FileModel
18
- vi.mock('@/database/_deprecated/models/file', () => ({
19
- FileModel: {
20
- create: vi.fn(),
21
- delete: vi.fn(),
22
- findById: vi.fn(),
23
- clear: vi.fn(),
24
- },
25
- }));
26
-
27
- let s3Domain: string;
28
-
29
- vi.mock('@/config/file', () => ({
30
- fileEnv: {
31
- get NEXT_PUBLIC_S3_DOMAIN() {
32
- return s3Domain;
33
- },
34
- },
35
- }));
36
-
37
- // Mocks for the URL and Blob objects
38
- global.URL.createObjectURL = vi.fn();
39
- global.Blob = vi.fn();
40
-
41
- beforeEach(() => {
42
- // Reset all mocks before each test
43
- vi.resetAllMocks();
44
- s3Domain = '';
13
+ const fileService = new ClientService(userId);
14
+
15
+ const mockFile = {
16
+ name: 'mock.png',
17
+ fileType: 'image/png',
18
+ size: 1,
19
+ url: '',
20
+ };
21
+
22
+ beforeEach(async () => {
23
+ await initializeDB();
24
+
25
+ await clientDB.delete(users);
26
+ await clientDB.delete(globalFiles);
27
+ // 创建测试数据
28
+ await clientDB.transaction(async (tx) => {
29
+ await tx.insert(users).values({ id: userId });
30
+ });
45
31
  });
46
32
 
47
33
  describe('FileService', () => {
48
- it('createFile should save the file to the database', async () => {
49
- const localFile = {
50
- name: 'test',
51
- fileType: 'image/png',
52
- url: 'client-s3://123',
53
- size: 1,
54
- hash: '123',
55
- };
34
+ describe('createFile', () => {
35
+ it('createFile should save the file to the database', async () => {
36
+ const localFile: UploadFileParams = {
37
+ name: 'test',
38
+ fileType: 'image/png',
39
+ url: '',
40
+ size: 1,
41
+ hash: '123',
42
+ };
43
+
44
+ await clientS3Storage.putObject(
45
+ '123',
46
+ new File([new ArrayBuffer(1)], 'test.png', { type: 'image/png' }),
47
+ );
56
48
 
57
- await clientS3Storage.putObject(
58
- '123',
59
- new File([new ArrayBuffer(1)], 'test.png', { type: 'image/png' }),
60
- );
49
+ const result = await fileService.createFile(localFile);
50
+
51
+ expect(result).toMatchObject({ url: 'data:image/png;base64,AA==' });
52
+ });
61
53
 
62
- (FileModel.create as Mock).mockResolvedValue(localFile);
54
+ it('should throw error when file is not found in storage during base64 conversion', async () => {
55
+ const localFile: UploadFileParams = {
56
+ name: 'test',
57
+ fileType: 'image/png',
58
+ url: '',
59
+ size: 1,
60
+ hash: 'non-existing-hash',
61
+ };
63
62
 
64
- const result = await fileService.createFile(localFile);
63
+ // 不调用 clientS3Storage.putObject,模拟文件不存在的情况
65
64
 
66
- expect(result).toEqual({ url: 'data:image/png;base64,AA==' });
65
+ const promise = fileService.createFile(localFile);
66
+
67
+ await expect(promise).rejects.toThrow('file not found');
68
+ });
67
69
  });
68
70
 
69
71
  it('removeFile should delete the file from the database', async () => {
70
72
  const fileId = '1';
71
- (FileModel.delete as Mock).mockResolvedValue(true);
73
+ await clientDB.insert(files).values({ id: fileId, userId, ...mockFile });
72
74
 
73
- const result = await fileService.removeFile(fileId);
75
+ await fileService.removeFile(fileId);
76
+
77
+ const result = await clientDB.query.files.findFirst({
78
+ where: eq(files.id, fileId),
79
+ });
74
80
 
75
- expect(FileModel.delete).toHaveBeenCalledWith(fileId);
76
- expect(result).toBe(true);
81
+ expect(result).toBeUndefined();
77
82
  });
78
83
 
79
84
  describe('getFile', () => {
80
85
  it('should retrieve and convert local file info to FilePreview', async () => {
81
- const fileId = '1';
82
- const fileData = {
83
- name: 'test',
84
- data: new ArrayBuffer(1),
86
+ const fileId = 'rwlijweled';
87
+ const file = {
85
88
  fileType: 'image/png',
86
- saveMode: 'local',
87
89
  size: 1,
88
- createdAt: 1,
89
- updatedAt: 2,
90
- } as DB_File;
90
+ name: 'test.png',
91
+ url: 'idb://12312/abc.png',
92
+ hashId: '123tttt',
93
+ };
91
94
 
92
- (FileModel.findById as Mock).mockResolvedValue(fileData);
93
- (global.URL.createObjectURL as Mock).mockReturnValue('blob:test');
94
- (global.Blob as Mock).mockImplementation(() => ['test']);
95
+ await clientDB.insert(globalFiles).values(file);
96
+
97
+ await clientDB.insert(files).values({
98
+ id: fileId,
99
+ userId,
100
+ ...file,
101
+ createdAt: new Date(1),
102
+ updatedAt: new Date(2),
103
+ fileHash: file.hashId,
104
+ });
105
+
106
+ await clientS3Storage.putObject(
107
+ file.hashId,
108
+ new File([new ArrayBuffer(1)], file.name, { type: file.fileType }),
109
+ );
95
110
 
96
111
  const result = await fileService.getFile(fileId);
97
112
 
98
- expect(FileModel.findById).toHaveBeenCalledWith(fileId);
99
- expect(result).toEqual({
113
+ expect(result).toMatchObject({
100
114
  createdAt: new Date(1),
101
- id: '1',
115
+ id: 'rwlijweled',
102
116
  size: 1,
103
117
  type: 'image/png',
104
- name: 'test',
105
- url: 'blob:test',
118
+ name: 'test.png',
106
119
  updatedAt: new Date(2),
107
120
  });
108
121
  });
109
122
 
110
123
  it('should throw an error when the file is not found', async () => {
111
124
  const fileId = 'non-existent';
112
- (FileModel.findById as Mock).mockResolvedValue(null);
113
125
 
114
126
  const getFilePromise = fileService.getFile(fileId);
115
127
 
116
128
  await expect(getFilePromise).rejects.toThrow('file not found');
117
129
  });
118
130
  });
131
+
132
+ describe('removeFiles', () => {
133
+ it('should delete multiple files from the database', async () => {
134
+ const fileIds = ['1', '2', '3'];
135
+
136
+ // 插入测试文件数据
137
+ await Promise.all(
138
+ fileIds.map((id) => clientDB.insert(files).values({ id, userId, ...mockFile })),
139
+ );
140
+
141
+ await fileService.removeFiles(fileIds);
142
+
143
+ // 验证所有文件都被删除
144
+ const remainingFiles = await clientDB.query.files.findMany({
145
+ where: (fields, { inArray }) => inArray(fields.id, fileIds),
146
+ });
147
+
148
+ expect(remainingFiles).toHaveLength(0);
149
+ });
150
+ });
151
+
152
+ describe('removeAllFiles', () => {
153
+ it('should clear all files for the user', async () => {
154
+ // 插入测试文件数据
155
+ await Promise.all([
156
+ clientDB.insert(files).values({ id: '1', userId, ...mockFile }),
157
+ clientDB.insert(files).values({ id: '2', userId, ...mockFile }),
158
+ ]);
159
+
160
+ await fileService.removeAllFiles();
161
+
162
+ // 验证用户的所有文件都被删除
163
+ const remainingFiles = await clientDB.query.files.findMany({
164
+ where: eq(files.userId, userId),
165
+ });
166
+
167
+ expect(remainingFiles).toHaveLength(0);
168
+ });
169
+ });
170
+
171
+ describe('checkFileHash', () => {
172
+ it('should return true if file hash exists', async () => {
173
+ const hash = 'existing-hash';
174
+ await clientDB.insert(globalFiles).values({
175
+ ...mockFile,
176
+ hashId: hash,
177
+ });
178
+ await clientDB.insert(files).values({
179
+ id: '1',
180
+ userId,
181
+ ...mockFile,
182
+ fileHash: hash,
183
+ });
184
+
185
+ const exists = await fileService.checkFileHash(hash);
186
+
187
+ expect(exists).toMatchObject({ isExist: true });
188
+ });
189
+
190
+ it('should return false if file hash does not exist', async () => {
191
+ const hash = 'non-existing-hash';
192
+
193
+ const exists = await fileService.checkFileHash(hash);
194
+
195
+ expect(exists).toEqual({ isExist: false });
196
+ });
197
+ });
119
198
  });