@lobehub/lobehub 2.0.0-next.22 → 2.0.0-next.24

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 (54) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/package.json +1 -1
  3. package/changelog/v1.json +18 -0
  4. package/locales/ar/labs.json +4 -0
  5. package/locales/bg-BG/labs.json +4 -0
  6. package/locales/de-DE/labs.json +4 -0
  7. package/locales/en-US/labs.json +4 -0
  8. package/locales/es-ES/labs.json +4 -0
  9. package/locales/fa-IR/labs.json +4 -0
  10. package/locales/fr-FR/labs.json +4 -0
  11. package/locales/it-IT/labs.json +4 -0
  12. package/locales/ja-JP/labs.json +4 -0
  13. package/locales/ko-KR/labs.json +4 -0
  14. package/locales/nl-NL/labs.json +4 -0
  15. package/locales/pl-PL/labs.json +4 -0
  16. package/locales/pt-BR/labs.json +4 -0
  17. package/locales/ru-RU/labs.json +4 -0
  18. package/locales/tr-TR/labs.json +4 -0
  19. package/locales/vi-VN/labs.json +4 -0
  20. package/locales/zh-CN/labs.json +4 -0
  21. package/locales/zh-TW/labs.json +4 -0
  22. package/package.json +1 -1
  23. package/packages/const/src/user.ts +5 -2
  24. package/packages/memory-extract/package.json +1 -1
  25. package/packages/types/src/index.ts +0 -1
  26. package/packages/types/src/message/ui/params.ts +3 -3
  27. package/packages/types/src/user/index.ts +2 -88
  28. package/packages/types/src/user/preference.ts +105 -0
  29. package/renovate.json +44 -13
  30. package/src/app/[variants]/(main)/labs/components/LabCard.tsx +5 -5
  31. package/src/app/[variants]/(main)/labs/page.tsx +18 -22
  32. package/src/app/[variants]/(main)/settings/provider/detail/azure/index.tsx +1 -1
  33. package/src/app/[variants]/(main)/settings/provider/detail/azureai/index.tsx +1 -1
  34. package/src/app/[variants]/(main)/settings/provider/detail/bedrock/index.tsx +1 -1
  35. package/src/app/[variants]/(main)/settings/provider/detail/cloudflare/index.tsx +1 -1
  36. package/src/app/[variants]/(main)/settings/provider/detail/comfyui/index.tsx +1 -1
  37. package/src/app/[variants]/(main)/settings/provider/detail/github/index.tsx +1 -1
  38. package/src/app/[variants]/(main)/settings/provider/detail/vertexai/index.tsx +1 -1
  39. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +2 -4
  40. package/src/components/Skeleton/SkeletonSwitch.tsx +13 -0
  41. package/src/components/Skeleton/index.ts +2 -0
  42. package/src/features/ChatInput/ActionBar/index.tsx +2 -2
  43. package/src/features/ChatInput/InputEditor/index.tsx +2 -2
  44. package/src/locales/default/labs.ts +4 -0
  45. package/src/server/routers/lambda/message.ts +5 -20
  46. package/src/server/services/mcp/deps/MCPSystemDepsCheckService.test.ts +541 -0
  47. package/src/services/message/server.ts +0 -10
  48. package/src/services/message/type.ts +0 -2
  49. package/src/store/user/selectors.ts +1 -1
  50. package/src/store/user/slices/preference/action.ts +8 -1
  51. package/src/store/user/slices/preference/selectors/index.ts +2 -0
  52. package/src/store/user/slices/preference/selectors/labPrefer.ts +13 -0
  53. package/src/store/user/slices/preference/{selectors.ts → selectors/preference.ts} +0 -2
  54. /package/src/{app/[variants]/(main)/settings/provider/features/ProviderConfig → components/Skeleton}/SkeletonInput.tsx +0 -0
@@ -0,0 +1,541 @@
1
+ import { DeploymentOption, SystemDependency } from '@lobehub/market-sdk';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ // Import after mock setup
5
+ import { mcpSystemDepsCheckService } from './MCPSystemDepsCheckService';
6
+ import { InstallationChecker } from './types';
7
+
8
+ // Hoist the mock to ensure it's available in the factory
9
+ const { mockExecPromise } = vi.hoisted(() => {
10
+ return {
11
+ mockExecPromise: vi.fn(),
12
+ };
13
+ });
14
+
15
+ // Mock node:child_process
16
+ vi.mock('node:child_process');
17
+
18
+ // Mock node:util to return our hoisted mock when promisify is called
19
+ vi.mock('node:util', () => ({
20
+ default: {
21
+ promisify: () => mockExecPromise,
22
+ },
23
+ promisify: () => mockExecPromise,
24
+ }));
25
+
26
+ describe('MCPSystemDepsCheckService', () => {
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ });
30
+
31
+ describe('registerChecker', () => {
32
+ it('should register an installation checker', () => {
33
+ const mockChecker: InstallationChecker = {
34
+ checkPackageInstalled: vi.fn(),
35
+ };
36
+
37
+ mcpSystemDepsCheckService.registerChecker('npm', mockChecker);
38
+
39
+ // Verify checker is registered by using it in checkDeployOption
40
+ expect(() => mcpSystemDepsCheckService.registerChecker('npm', mockChecker)).not.toThrow();
41
+ });
42
+ });
43
+
44
+ describe('checkSystemDependency', () => {
45
+ it('should successfully check installed dependency without version requirement', async () => {
46
+ const mockDependency: SystemDependency = {
47
+ name: 'node',
48
+ checkCommand: 'node --version',
49
+ };
50
+
51
+ mockExecPromise.mockResolvedValue({ stdout: 'v18.16.0\n', stderr: '' });
52
+
53
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
54
+
55
+ expect(result).toEqual({
56
+ installed: true,
57
+ meetRequirement: true,
58
+ name: 'node',
59
+ requiredVersion: undefined,
60
+ version: 'v18.16.0',
61
+ installInstructions: undefined,
62
+ });
63
+ });
64
+
65
+ it('should parse version when versionParsingRequired is true', async () => {
66
+ const mockDependency: SystemDependency = {
67
+ name: 'python',
68
+ checkCommand: 'python --version',
69
+ versionParsingRequired: true,
70
+ };
71
+
72
+ mockExecPromise.mockResolvedValue({ stdout: 'Python 3.10.5\n', stderr: '' });
73
+
74
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
75
+
76
+ expect(result.version).toBe('3.10.5');
77
+ expect(result.installed).toBe(true);
78
+ });
79
+
80
+ it('should handle version with v prefix in parsing', async () => {
81
+ const mockDependency: SystemDependency = {
82
+ name: 'node',
83
+ checkCommand: 'node --version',
84
+ versionParsingRequired: true,
85
+ };
86
+
87
+ mockExecPromise.mockResolvedValue({ stdout: 'v20.1.0\n', stderr: '' });
88
+
89
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
90
+
91
+ expect(result.version).toBe('v20.1.0');
92
+ expect(result.installed).toBe(true);
93
+ });
94
+
95
+ it('should check version with >= operator', async () => {
96
+ const mockDependency: SystemDependency = {
97
+ name: 'node',
98
+ checkCommand: 'node --version',
99
+ requiredVersion: '>=18.0.0',
100
+ versionParsingRequired: true,
101
+ };
102
+
103
+ mockExecPromise.mockResolvedValue({ stdout: 'v20.1.0\n', stderr: '' });
104
+
105
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
106
+
107
+ expect(result.meetRequirement).toBe(true);
108
+ expect(result.installed).toBe(true);
109
+ expect(result.version).toBe('v20.1.0');
110
+ });
111
+
112
+ it('should fail version check with >= operator when version is lower', async () => {
113
+ const mockDependency: SystemDependency = {
114
+ name: 'node',
115
+ checkCommand: 'node --version',
116
+ requiredVersion: '>=20.0.0',
117
+ versionParsingRequired: true,
118
+ };
119
+
120
+ mockExecPromise.mockResolvedValue({ stdout: 'v18.16.0\n', stderr: '' });
121
+
122
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
123
+
124
+ expect(result.meetRequirement).toBe(false);
125
+ expect(result.installed).toBe(true);
126
+ });
127
+
128
+ it('should check version with > operator', async () => {
129
+ const mockDependency: SystemDependency = {
130
+ name: 'python',
131
+ checkCommand: 'python --version',
132
+ requiredVersion: '>3.0.0',
133
+ versionParsingRequired: true,
134
+ };
135
+
136
+ mockExecPromise.mockResolvedValue({ stdout: 'Python 3.10.5\n', stderr: '' });
137
+
138
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
139
+
140
+ expect(result.meetRequirement).toBe(true);
141
+ });
142
+
143
+ it('should check version with <= operator', async () => {
144
+ const mockDependency: SystemDependency = {
145
+ name: 'tool',
146
+ checkCommand: 'tool --version',
147
+ requiredVersion: '<=2.0.0',
148
+ versionParsingRequired: true,
149
+ };
150
+
151
+ mockExecPromise.mockResolvedValue({ stdout: '1.5.0\n', stderr: '' });
152
+
153
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
154
+
155
+ expect(result.meetRequirement).toBe(true);
156
+ });
157
+
158
+ it('should check version with < operator', async () => {
159
+ const mockDependency: SystemDependency = {
160
+ name: 'tool',
161
+ checkCommand: 'tool --version',
162
+ requiredVersion: '<2.0.0',
163
+ versionParsingRequired: true,
164
+ };
165
+
166
+ mockExecPromise.mockResolvedValue({ stdout: '2.5.0\n', stderr: '' });
167
+
168
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
169
+
170
+ expect(result.meetRequirement).toBe(false);
171
+ });
172
+
173
+ it('should check version with = operator (default)', async () => {
174
+ const mockDependency: SystemDependency = {
175
+ name: 'tool',
176
+ checkCommand: 'tool --version',
177
+ requiredVersion: '3.0.0',
178
+ versionParsingRequired: true,
179
+ };
180
+
181
+ mockExecPromise.mockResolvedValue({ stdout: '3.0.0\n', stderr: '' });
182
+
183
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
184
+
185
+ expect(result.meetRequirement).toBe(true);
186
+ });
187
+
188
+ it('should use default check command when not provided', async () => {
189
+ const mockDependency: SystemDependency = {
190
+ name: 'git',
191
+ };
192
+
193
+ mockExecPromise.mockResolvedValue({ stdout: 'git version 2.39.0\n', stderr: '' });
194
+
195
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
196
+
197
+ expect(result.installed).toBe(true);
198
+ });
199
+
200
+ it('should handle stderr without stdout as error', async () => {
201
+ const mockDependency: SystemDependency = {
202
+ name: 'invalid-tool',
203
+ checkCommand: 'invalid-tool --version',
204
+ };
205
+
206
+ mockExecPromise.mockResolvedValue({ stdout: '', stderr: 'command not found' });
207
+
208
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
209
+
210
+ expect(result.installed).toBe(false);
211
+ expect(result.meetRequirement).toBe(false);
212
+ expect(result.error).toBe('command not found');
213
+ });
214
+
215
+ it('should handle command execution error', async () => {
216
+ const mockDependency: SystemDependency = {
217
+ name: 'missing-tool',
218
+ checkCommand: 'missing-tool --version',
219
+ };
220
+
221
+ mockExecPromise.mockRejectedValue(new Error('command not found'));
222
+
223
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
224
+
225
+ expect(result.installed).toBe(false);
226
+ expect(result.meetRequirement).toBe(false);
227
+ expect(result.error).toBe('command not found');
228
+ });
229
+
230
+ it('should handle unknown error types', async () => {
231
+ const mockDependency: SystemDependency = {
232
+ name: 'tool',
233
+ checkCommand: 'tool --version',
234
+ };
235
+
236
+ mockExecPromise.mockRejectedValue('string error');
237
+
238
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
239
+
240
+ expect(result.installed).toBe(false);
241
+ expect(result.error).toBe('Unknown error');
242
+ });
243
+
244
+ it('should include install instructions on macOS', async () => {
245
+ const originalPlatform = process.platform;
246
+ Object.defineProperty(process, 'platform', {
247
+ value: 'darwin',
248
+ });
249
+
250
+ const mockDependency: SystemDependency = {
251
+ name: 'brew',
252
+ checkCommand: 'brew --version',
253
+ installInstructions: {
254
+ macos: 'Install via Homebrew',
255
+ linux: 'Use apt-get',
256
+ manual: 'Download from website',
257
+ },
258
+ } as any;
259
+
260
+ mockExecPromise.mockRejectedValue(new Error('not found'));
261
+
262
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
263
+
264
+ expect(result.installInstructions).toEqual({
265
+ current: 'Install via Homebrew',
266
+ manual: 'Download from website',
267
+ });
268
+
269
+ Object.defineProperty(process, 'platform', {
270
+ value: originalPlatform,
271
+ });
272
+ });
273
+
274
+ it('should include install instructions on Linux', async () => {
275
+ const originalPlatform = process.platform;
276
+ Object.defineProperty(process, 'platform', {
277
+ value: 'linux',
278
+ });
279
+
280
+ const mockDependency: SystemDependency = {
281
+ name: 'tool',
282
+ checkCommand: 'tool --version',
283
+ installInstructions: {
284
+ linux_debian: 'apt-get install tool',
285
+ manual: 'Manual install',
286
+ },
287
+ } as any;
288
+
289
+ mockExecPromise.mockRejectedValue(new Error('not found'));
290
+
291
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
292
+
293
+ expect(result.installInstructions).toEqual({
294
+ current: 'apt-get install tool',
295
+ manual: 'Manual install',
296
+ });
297
+
298
+ Object.defineProperty(process, 'platform', {
299
+ value: originalPlatform,
300
+ });
301
+ });
302
+
303
+ it('should fallback to linux instruction when linux_debian is not available', async () => {
304
+ const originalPlatform = process.platform;
305
+ Object.defineProperty(process, 'platform', {
306
+ value: 'linux',
307
+ });
308
+
309
+ const mockDependency: SystemDependency = {
310
+ name: 'tool',
311
+ checkCommand: 'tool --version',
312
+ installInstructions: {
313
+ linux: 'Generic linux install',
314
+ manual: 'Manual install',
315
+ },
316
+ } as any;
317
+
318
+ mockExecPromise.mockRejectedValue(new Error('not found'));
319
+
320
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
321
+
322
+ expect(result.installInstructions?.current).toBe('Generic linux install');
323
+
324
+ Object.defineProperty(process, 'platform', {
325
+ value: originalPlatform,
326
+ });
327
+ });
328
+
329
+ it('should include install instructions on Windows', async () => {
330
+ const originalPlatform = process.platform;
331
+ Object.defineProperty(process, 'platform', {
332
+ value: 'win32',
333
+ });
334
+
335
+ const mockDependency: SystemDependency = {
336
+ name: 'tool',
337
+ checkCommand: 'tool --version',
338
+ installInstructions: {
339
+ windows: 'Install via Chocolatey',
340
+ manual: 'Download from website',
341
+ },
342
+ } as any;
343
+
344
+ mockExecPromise.mockRejectedValue(new Error('not found'));
345
+
346
+ const result = await mcpSystemDepsCheckService.checkSystemDependency(mockDependency);
347
+
348
+ expect(result.installInstructions).toEqual({
349
+ current: 'Install via Chocolatey',
350
+ manual: 'Download from website',
351
+ });
352
+
353
+ Object.defineProperty(process, 'platform', {
354
+ value: originalPlatform,
355
+ });
356
+ });
357
+ });
358
+
359
+ describe('checkDeployOption', () => {
360
+ it('should use installation checker when available', async () => {
361
+ const mockChecker: InstallationChecker = {
362
+ checkPackageInstalled: vi.fn().mockResolvedValue({
363
+ installed: true,
364
+ packageName: 'test-package',
365
+ }),
366
+ };
367
+
368
+ mcpSystemDepsCheckService.registerChecker('npm', mockChecker);
369
+
370
+ const mockOption: DeploymentOption = {
371
+ installationMethod: 'npm',
372
+ installationDetails: { packageName: 'test-package' },
373
+ connection: {
374
+ command: 'node',
375
+ args: ['index.js'],
376
+ },
377
+ } as any;
378
+
379
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
380
+
381
+ expect(mockChecker.checkPackageInstalled).toHaveBeenCalledWith({
382
+ packageName: 'test-package',
383
+ });
384
+ expect(result.packageInstalled).toBe(true);
385
+ });
386
+
387
+ it('should set connection type to http when url is provided', async () => {
388
+ const mockOption: DeploymentOption = {
389
+ installationMethod: 'manual',
390
+ installationDetails: {},
391
+ connection: {
392
+ url: 'http://localhost:3000',
393
+ },
394
+ } as any;
395
+
396
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
397
+
398
+ expect(result.connection.type).toBe('http');
399
+ expect(result.connection.url).toBe('http://localhost:3000');
400
+ });
401
+
402
+ it('should detect configuration requirements from required array', async () => {
403
+ const mockOption: DeploymentOption = {
404
+ installationMethod: 'npm',
405
+ installationDetails: { packageName: 'test-package' },
406
+ connection: {
407
+ command: 'node',
408
+ args: ['index.js'],
409
+ configSchema: {
410
+ type: 'object',
411
+ required: ['apiKey'],
412
+ properties: {
413
+ apiKey: { type: 'string' },
414
+ },
415
+ },
416
+ },
417
+ } as any;
418
+
419
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
420
+
421
+ expect(result.needsConfig).toBe(true);
422
+ expect(result.configSchema).toBeDefined();
423
+ });
424
+
425
+ it('should detect configuration requirements from property-level required flag', async () => {
426
+ const mockOption: DeploymentOption = {
427
+ installationMethod: 'npm',
428
+ installationDetails: { packageName: 'test-package' },
429
+ connection: {
430
+ command: 'node',
431
+ args: ['index.js'],
432
+ configSchema: {
433
+ type: 'object',
434
+ properties: {
435
+ apiKey: { type: 'string', required: true },
436
+ },
437
+ },
438
+ },
439
+ } as any;
440
+
441
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
442
+
443
+ expect(result.needsConfig).toBe(true);
444
+ });
445
+
446
+ it('should not require config when schema has no required fields', async () => {
447
+ const mockOption: DeploymentOption = {
448
+ installationMethod: 'npm',
449
+ installationDetails: { packageName: 'test-package' },
450
+ connection: {
451
+ command: 'node',
452
+ args: ['index.js'],
453
+ configSchema: {
454
+ type: 'object',
455
+ properties: {
456
+ optional: { type: 'string' },
457
+ },
458
+ },
459
+ },
460
+ } as any;
461
+
462
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
463
+
464
+ expect(result.needsConfig).toBe(false);
465
+ });
466
+
467
+ it('should not require config when schema has empty required array', async () => {
468
+ const mockOption: DeploymentOption = {
469
+ installationMethod: 'npm',
470
+ installationDetails: { packageName: 'test-package' },
471
+ connection: {
472
+ command: 'node',
473
+ args: ['index.js'],
474
+ configSchema: {
475
+ type: 'object',
476
+ required: [],
477
+ properties: {
478
+ optional: { type: 'string' },
479
+ },
480
+ },
481
+ },
482
+ } as any;
483
+
484
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
485
+
486
+ expect(result.needsConfig).toBe(false);
487
+ });
488
+
489
+ it('should include isRecommended flag from deployment option', async () => {
490
+ const mockOption: DeploymentOption = {
491
+ installationMethod: 'npm',
492
+ installationDetails: { packageName: 'test-package' },
493
+ isRecommended: true,
494
+ connection: {
495
+ command: 'node',
496
+ args: ['index.js'],
497
+ },
498
+ } as any;
499
+
500
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
501
+
502
+ expect(result.isRecommended).toBe(true);
503
+ });
504
+
505
+ it('should handle multiple system dependencies', async () => {
506
+ const mockOption: DeploymentOption = {
507
+ installationMethod: 'npm',
508
+ installationDetails: { packageName: 'test-package' },
509
+ systemDependencies: [
510
+ {
511
+ name: 'node',
512
+ checkCommand: 'node --version',
513
+ requiredVersion: '>=18.0.0',
514
+ versionParsingRequired: true,
515
+ },
516
+ {
517
+ name: 'python',
518
+ checkCommand: 'python --version',
519
+ requiredVersion: '>=3.0.0',
520
+ versionParsingRequired: true,
521
+ },
522
+ ],
523
+ connection: {
524
+ command: 'node',
525
+ args: ['index.js'],
526
+ },
527
+ } as any;
528
+
529
+ mockExecPromise
530
+ .mockResolvedValueOnce({ stdout: 'v20.1.0\n', stderr: '' })
531
+ .mockResolvedValueOnce({ stdout: 'Python 3.10.5\n', stderr: '' });
532
+
533
+ const result = await mcpSystemDepsCheckService.checkDeployOption(mockOption);
534
+
535
+ expect(result.systemDependencies).toHaveLength(2);
536
+ expect(result.allDependenciesMet).toBe(true);
537
+ expect(result.systemDependencies[0]!.name).toBe('node');
538
+ expect(result.systemDependencies[1]!.name).toBe('python');
539
+ });
540
+ });
541
+ });
@@ -39,16 +39,6 @@ export class ServerService implements IMessageService {
39
39
  return data as unknown as UIChatMessage[];
40
40
  };
41
41
 
42
- getAllMessages: IMessageService['getAllMessages'] = async () => {
43
- return lambdaClient.message.getAllMessages.query();
44
- };
45
-
46
- getAllMessagesInSession: IMessageService['getAllMessagesInSession'] = async (sessionId) => {
47
- return lambdaClient.message.getAllMessagesInSession.query({
48
- sessionId: this.toDbSessionId(sessionId),
49
- });
50
- };
51
-
52
42
  countMessages: IMessageService['countMessages'] = async (params) => {
53
43
  return lambdaClient.message.count.query(params);
54
44
  };
@@ -21,8 +21,6 @@ export interface IMessageService {
21
21
 
22
22
  getMessages(sessionId: string, topicId?: string, groupId?: string): Promise<UIChatMessage[]>;
23
23
  getGroupMessages(groupId: string, topicId?: string): Promise<UIChatMessage[]>;
24
- getAllMessages(): Promise<UIChatMessage[]>;
25
- getAllMessagesInSession(sessionId: string): Promise<UIChatMessage[]>;
26
24
  countMessages(params?: {
27
25
  endDate?: string;
28
26
  range?: [string, string];
@@ -1,5 +1,5 @@
1
1
  export { authSelectors, userProfileSelectors } from './slices/auth/selectors';
2
- export { preferenceSelectors } from './slices/preference/selectors';
2
+ export { labPreferSelectors, preferenceSelectors } from './slices/preference/selectors';
3
3
  export {
4
4
  keyVaultsConfigSelectors,
5
5
  settingsSelectors,
@@ -2,7 +2,7 @@ import type { StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { userService } from '@/services/user';
4
4
  import type { UserStore } from '@/store/user';
5
- import { UserGuide, UserPreference } from '@/types/user';
5
+ import { UserGuide, UserLab, UserPreference } from '@/types/user';
6
6
  import { merge } from '@/utils/merge';
7
7
  import { setNamespace } from '@/utils/storeDebug';
8
8
 
@@ -10,6 +10,7 @@ const n = setNamespace('preference');
10
10
 
11
11
  export interface PreferenceAction {
12
12
  updateGuideState: (guide: Partial<UserGuide>) => Promise<void>;
13
+ updateLab: (lab: Partial<UserLab>) => Promise<void>;
13
14
  updatePreference: (preference: Partial<UserPreference>, action?: any) => Promise<void>;
14
15
  }
15
16
 
@@ -25,6 +26,12 @@ export const createPreferenceSlice: StateCreator<
25
26
  await updatePreference({ guide: nextGuide });
26
27
  },
27
28
 
29
+ updateLab: async (lab) => {
30
+ const { updatePreference } = get();
31
+ const nextLab = merge(get().preference.lab, lab);
32
+ await updatePreference({ lab: nextLab }, n('updateLab'));
33
+ },
34
+
28
35
  updatePreference: async (preference, action) => {
29
36
  const nextPreference = merge(get().preference, preference);
30
37
 
@@ -0,0 +1,2 @@
1
+ export * from './labPrefer';
2
+ export * from './preference';
@@ -0,0 +1,13 @@
1
+ import { DEFAULT_PREFERENCE } from '@lobechat/const';
2
+
3
+ import type { UserState } from '@/store/user/initialState';
4
+
5
+ export const labPreferSelectors = {
6
+ enableAssistantMessageGroup: (s: UserState): boolean =>
7
+ s.preference.lab?.enableAssistantMessageGroup ??
8
+ DEFAULT_PREFERENCE.lab!.enableAssistantMessageGroup!,
9
+ enableGroupChat: (s: UserState): boolean =>
10
+ s.preference.lab?.enableGroupChat ?? DEFAULT_PREFERENCE.lab!.enableGroupChat!,
11
+ enableInputMarkdown: (s: UserState): boolean =>
12
+ s.preference.lab?.enableInputMarkdown ?? DEFAULT_PREFERENCE.lab!.enableInputMarkdown!,
13
+ };
@@ -21,10 +21,8 @@ const shouldTriggerFileInKnowledgeBaseTip = (s: UserStore) =>
21
21
  const isPreferenceInit = (s: UserStore) => s.isUserStateInit;
22
22
 
23
23
  export const preferenceSelectors = {
24
- enableGroupChat: (s: UserStore) => s.preference.enableGroupChat || false,
25
24
  hideSettingsMoveGuide,
26
25
  hideSyncAlert,
27
- inputMarkdownRender: (s: UserStore) => !s.preference.disableInputMarkdownRender,
28
26
  isPreferenceInit,
29
27
  shouldTriggerFileInKnowledgeBaseTip,
30
28
  showUploadFileInKnowledgeBaseTip,