@lobehub/lobehub 2.0.0-next.104 → 2.0.0-next.106

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 (86) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/package.json +2 -2
  3. package/changelog/v1.json +14 -0
  4. package/locales/ar/image.json +8 -0
  5. package/locales/ar/models.json +110 -64
  6. package/locales/ar/providers.json +3 -0
  7. package/locales/bg-BG/image.json +8 -0
  8. package/locales/bg-BG/models.json +98 -68
  9. package/locales/bg-BG/providers.json +3 -0
  10. package/locales/de-DE/image.json +8 -0
  11. package/locales/de-DE/models.json +176 -38
  12. package/locales/de-DE/providers.json +3 -0
  13. package/locales/en-US/image.json +8 -0
  14. package/locales/en-US/models.json +176 -38
  15. package/locales/en-US/providers.json +3 -0
  16. package/locales/es-ES/image.json +8 -0
  17. package/locales/es-ES/models.json +176 -38
  18. package/locales/es-ES/providers.json +3 -0
  19. package/locales/fa-IR/image.json +8 -0
  20. package/locales/fa-IR/models.json +110 -64
  21. package/locales/fa-IR/providers.json +3 -0
  22. package/locales/fr-FR/image.json +8 -0
  23. package/locales/fr-FR/models.json +110 -64
  24. package/locales/fr-FR/providers.json +3 -0
  25. package/locales/it-IT/image.json +8 -0
  26. package/locales/it-IT/models.json +176 -38
  27. package/locales/it-IT/providers.json +3 -0
  28. package/locales/ja-JP/image.json +8 -0
  29. package/locales/ja-JP/models.json +110 -64
  30. package/locales/ja-JP/providers.json +3 -0
  31. package/locales/ko-KR/image.json +8 -0
  32. package/locales/ko-KR/models.json +110 -64
  33. package/locales/ko-KR/providers.json +3 -0
  34. package/locales/nl-NL/image.json +8 -0
  35. package/locales/nl-NL/models.json +176 -38
  36. package/locales/nl-NL/providers.json +3 -0
  37. package/locales/pl-PL/image.json +8 -0
  38. package/locales/pl-PL/models.json +110 -64
  39. package/locales/pl-PL/providers.json +3 -0
  40. package/locales/pt-BR/image.json +8 -0
  41. package/locales/pt-BR/models.json +176 -38
  42. package/locales/pt-BR/providers.json +3 -0
  43. package/locales/ru-RU/image.json +8 -0
  44. package/locales/ru-RU/models.json +98 -68
  45. package/locales/ru-RU/providers.json +3 -0
  46. package/locales/tr-TR/image.json +8 -0
  47. package/locales/tr-TR/models.json +110 -64
  48. package/locales/tr-TR/providers.json +3 -0
  49. package/locales/vi-VN/image.json +8 -0
  50. package/locales/vi-VN/models.json +176 -38
  51. package/locales/vi-VN/providers.json +3 -0
  52. package/locales/zh-CN/image.json +8 -0
  53. package/locales/zh-CN/models.json +179 -38
  54. package/locales/zh-CN/providers.json +3 -0
  55. package/locales/zh-TW/image.json +8 -0
  56. package/locales/zh-TW/models.json +176 -38
  57. package/locales/zh-TW/providers.json +3 -0
  58. package/package.json +9 -3
  59. package/packages/database/src/repositories/knowledge/index.ts +5 -8
  60. package/packages/model-bank/src/aiModels/moonshot.ts +46 -0
  61. package/packages/model-runtime/src/core/contextBuilders/openai.ts +1 -1
  62. package/packages/model-runtime/src/providers/moonshot/index.ts +17 -4
  63. package/packages/model-runtime/src/utils/postProcessModelList.ts +15 -13
  64. package/packages/types/src/user/settings/keyVaults.ts +0 -68
  65. package/packages/utils/src/client/parserPlaceholder.ts +1 -1
  66. package/src/services/__tests__/_auth.test.ts +1 -4
  67. package/src/services/_auth.ts +2 -3
  68. package/src/services/_header.ts +1 -8
  69. package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +18 -0
  70. package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +40 -11
  71. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +3 -0
  72. package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +15 -0
  73. package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +37 -11
  74. package/src/store/chat/agents/createAgentExecutors.ts +22 -13
  75. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +4 -8
  76. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +16 -2
  77. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +5 -1
  78. package/src/store/chat/slices/builtinTool/actions/search.ts +5 -1
  79. package/src/store/chat/slices/message/actions/publicApi.ts +10 -2
  80. package/src/store/chat/slices/message/actions/query.ts +17 -4
  81. package/src/store/chat/slices/operation/__tests__/selectors.test.ts +93 -5
  82. package/src/store/chat/slices/operation/selectors.ts +16 -3
  83. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +24 -18
  84. package/src/store/user/slices/settings/selectors/keyVaults.ts +0 -5
  85. package/src/features/ChatList/Error/AccessCodeForm.tsx +0 -63
  86. package/src/services/__tests__/share.test.ts +0 -61
@@ -108,7 +108,11 @@ export const localSystemSlice: StateCreator<
108
108
  },
109
109
 
110
110
  reSearchLocalFiles: async (id, params) => {
111
- await get().optimisticUpdatePluginArguments(id, params);
111
+ // Get operationId from messageOperationMap to ensure proper context isolation
112
+ const operationId = get().messageOperationMap[id];
113
+ const context = operationId ? { operationId } : undefined;
114
+
115
+ await get().optimisticUpdatePluginArguments(id, params, false, context);
112
116
 
113
117
  return get().searchLocalFiles(id, params);
114
118
  },
@@ -276,7 +276,11 @@ export const searchSlice: StateCreator<
276
276
  },
277
277
 
278
278
  triggerSearchAgain: async (id, data, options) => {
279
- await get().optimisticUpdatePluginArguments(id, data);
279
+ // Get operationId from messageOperationMap to ensure proper context isolation
280
+ const operationId = get().messageOperationMap[id];
281
+ const context = operationId ? { operationId } : undefined;
282
+
283
+ await get().optimisticUpdatePluginArguments(id, data, false, context);
280
284
 
281
285
  await get().search(id, data, options?.aiSummary);
282
286
  },
@@ -167,14 +167,22 @@ export const messagePublicApi: StateCreator<
167
167
  const message = dbMessageSelectors.getDbMessageById(id)(get());
168
168
  if (!message || message.role !== 'tool') return;
169
169
 
170
+ // Get operationId from messageOperationMap to ensure proper context isolation
171
+ const operationId = get().messageOperationMap[id];
172
+ const context = operationId ? { operationId } : undefined;
173
+
170
174
  const removeToolInAssistantMessage = async () => {
171
175
  if (!message.parentId) return;
172
- await get().optimisticRemoveToolFromAssistantMessage(message.parentId, message.tool_call_id);
176
+ await get().optimisticRemoveToolFromAssistantMessage(
177
+ message.parentId,
178
+ message.tool_call_id,
179
+ context,
180
+ );
173
181
  };
174
182
 
175
183
  await Promise.all([
176
184
  // 1. remove tool message
177
- get().optimisticDeleteMessage(id),
185
+ get().optimisticDeleteMessage(id, context),
178
186
  // 2. remove the tool item in the assistant tools
179
187
  removeToolInAssistantMessage(),
180
188
  ]);
@@ -32,6 +32,7 @@ export interface MessageQueryAction {
32
32
  messages: UIChatMessage[],
33
33
  params?: {
34
34
  action?: any;
35
+ operationId?: string;
35
36
  sessionId?: string;
36
37
  topicId?: string | null;
37
38
  },
@@ -66,10 +67,22 @@ export const messageQuery: StateCreator<
66
67
  },
67
68
 
68
69
  replaceMessages: (messages, params) => {
69
- const messagesKey = messageMapKey(
70
- params?.sessionId ?? get().activeId,
71
- params?.topicId ?? get().activeTopicId,
72
- );
70
+ let sessionId: string;
71
+ let topicId: string | null | undefined;
72
+
73
+ // Priority 1: Get context from operation if operationId is provided
74
+ if (params?.operationId) {
75
+ const { sessionId: opSessionId, topicId: opTopicId } =
76
+ get().internal_getSessionContext(params);
77
+ sessionId = opSessionId;
78
+ topicId = opTopicId;
79
+ } else {
80
+ // Priority 2: Use explicit sessionId/topicId or fallback to global state
81
+ sessionId = params?.sessionId ?? get().activeId;
82
+ topicId = params?.topicId ?? get().activeTopicId;
83
+ }
84
+
85
+ const messagesKey = messageMapKey(sessionId, topicId);
73
86
 
74
87
  // Get raw messages from dbMessagesMap and apply reducer
75
88
  const nextDbMap = { ...get().dbMessagesMap, [messagesKey]: messages };
@@ -393,6 +393,11 @@ describe('Operation Selectors', () => {
393
393
  it('isMainWindowAgentRuntimeRunning should only detect main window operations', () => {
394
394
  const { result } = renderHook(() => useChatStore());
395
395
 
396
+ // Set active context
397
+ act(() => {
398
+ useChatStore.setState({ activeId: 'session1', activeTopicId: undefined });
399
+ });
400
+
396
401
  expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
397
402
 
398
403
  // Start a main window operation (inThread: false)
@@ -400,7 +405,7 @@ describe('Operation Selectors', () => {
400
405
  act(() => {
401
406
  mainOpId = result.current.startOperation({
402
407
  type: 'execAgentRuntime',
403
- context: { sessionId: 'session1' },
408
+ context: { sessionId: 'session1', topicId: null },
404
409
  metadata: { inThread: false },
405
410
  }).operationId;
406
411
  });
@@ -420,12 +425,17 @@ describe('Operation Selectors', () => {
420
425
  it('isMainWindowAgentRuntimeRunning should exclude thread operations', () => {
421
426
  const { result } = renderHook(() => useChatStore());
422
427
 
428
+ // Set active context
429
+ act(() => {
430
+ useChatStore.setState({ activeId: 'session1', activeTopicId: undefined });
431
+ });
432
+
423
433
  // Start a thread operation (inThread: true)
424
434
  let threadOpId: string;
425
435
  act(() => {
426
436
  threadOpId = result.current.startOperation({
427
437
  type: 'execAgentRuntime',
428
- context: { sessionId: 'session1', threadId: 'thread1' },
438
+ context: { sessionId: 'session1', topicId: null, threadId: 'thread1' },
429
439
  metadata: { inThread: true },
430
440
  }).operationId;
431
441
  });
@@ -447,6 +457,11 @@ describe('Operation Selectors', () => {
447
457
  it('isMainWindowAgentRuntimeRunning should distinguish between main and thread operations', () => {
448
458
  const { result } = renderHook(() => useChatStore());
449
459
 
460
+ // Set active context
461
+ act(() => {
462
+ useChatStore.setState({ activeId: 'session1', activeTopicId: undefined });
463
+ });
464
+
450
465
  let mainOpId: string;
451
466
  let threadOpId: string;
452
467
 
@@ -454,13 +469,13 @@ describe('Operation Selectors', () => {
454
469
  act(() => {
455
470
  mainOpId = result.current.startOperation({
456
471
  type: 'execAgentRuntime',
457
- context: { sessionId: 'session1' },
472
+ context: { sessionId: 'session1', topicId: null },
458
473
  metadata: { inThread: false },
459
474
  }).operationId;
460
475
 
461
476
  threadOpId = result.current.startOperation({
462
477
  type: 'execAgentRuntime',
463
- context: { sessionId: 'session1', threadId: 'thread1' },
478
+ context: { sessionId: 'session1', topicId: null, threadId: 'thread1' },
464
479
  metadata: { inThread: true },
465
480
  }).operationId;
466
481
  });
@@ -489,11 +504,16 @@ describe('Operation Selectors', () => {
489
504
  it('isMainWindowAgentRuntimeRunning should exclude aborting operations', () => {
490
505
  const { result } = renderHook(() => useChatStore());
491
506
 
507
+ // Set active context
508
+ act(() => {
509
+ useChatStore.setState({ activeId: 'session1', activeTopicId: undefined });
510
+ });
511
+
492
512
  let opId: string;
493
513
  act(() => {
494
514
  opId = result.current.startOperation({
495
515
  type: 'execAgentRuntime',
496
- context: { sessionId: 'session1' },
516
+ context: { sessionId: 'session1', topicId: null },
497
517
  metadata: { inThread: false },
498
518
  }).operationId;
499
519
  });
@@ -509,5 +529,73 @@ describe('Operation Selectors', () => {
509
529
  expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
510
530
  expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(false);
511
531
  });
532
+
533
+ it('isMainWindowAgentRuntimeRunning should only detect operations in current active topic', () => {
534
+ const { result } = renderHook(() => useChatStore());
535
+
536
+ // Set active session and topic
537
+ act(() => {
538
+ useChatStore.setState({ activeId: 'session1', activeTopicId: 'topic1' });
539
+ });
540
+
541
+ let topic1OpId: string;
542
+ let topic2OpId: string;
543
+
544
+ // Start operation in topic1 (current active topic)
545
+ act(() => {
546
+ topic1OpId = result.current.startOperation({
547
+ type: 'execAgentRuntime',
548
+ context: { sessionId: 'session1', topicId: 'topic1' },
549
+ metadata: { inThread: false },
550
+ }).operationId;
551
+ });
552
+
553
+ // Should detect operation in current topic
554
+ expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);
555
+
556
+ // Start operation in topic2 (different topic)
557
+ act(() => {
558
+ topic2OpId = result.current.startOperation({
559
+ type: 'execAgentRuntime',
560
+ context: { sessionId: 'session1', topicId: 'topic2' },
561
+ metadata: { inThread: false },
562
+ }).operationId;
563
+ });
564
+
565
+ // Should still only detect topic1 operation (current active topic)
566
+ expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);
567
+
568
+ // Switch to topic2
569
+ act(() => {
570
+ useChatStore.setState({ activeTopicId: 'topic2' });
571
+ });
572
+
573
+ // Should now detect topic2 operation
574
+ expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);
575
+
576
+ // Complete topic2 operation
577
+ act(() => {
578
+ result.current.completeOperation(topic2OpId!);
579
+ });
580
+
581
+ // Should not detect any operation in topic2 now
582
+ expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
583
+
584
+ // Switch back to topic1
585
+ act(() => {
586
+ useChatStore.setState({ activeTopicId: 'topic1' });
587
+ });
588
+
589
+ // Should detect topic1 operation again
590
+ expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(true);
591
+
592
+ // Complete topic1 operation
593
+ act(() => {
594
+ result.current.completeOperation(topic1OpId!);
595
+ });
596
+
597
+ // Should not detect any operation now
598
+ expect(operationSelectors.isMainWindowAgentRuntimeRunning(result.current)).toBe(false);
599
+ });
512
600
  });
513
601
  });
@@ -180,14 +180,27 @@ const isAgentRuntimeRunning = (s: ChatStoreState): boolean => {
180
180
  /**
181
181
  * Check if agent runtime is running in main window only
182
182
  * Used for main window UI state (e.g., send button loading)
183
- * Excludes thread operations to prevent cross-contamination
183
+ * Excludes thread operations and operations from other topics to prevent cross-contamination
184
184
  */
185
185
  const isMainWindowAgentRuntimeRunning = (s: ChatStoreState): boolean => {
186
186
  const operationIds = s.operationsByType['execAgentRuntime'] || [];
187
+
187
188
  return operationIds.some((id) => {
188
189
  const op = s.operations[id];
189
- // Only include main window operations (not thread)
190
- return op && op.status === 'running' && !op.metadata.isAborting && !op.metadata.inThread;
190
+ if (!op || op.status !== 'running' || op.metadata.isAborting || op.metadata.inThread) {
191
+ return false;
192
+ }
193
+
194
+ // Session must match
195
+ if (s.activeId !== op.context.sessionId) return false;
196
+
197
+ // Topic comparison: normalize null/undefined (both mean "default topic")
198
+ // activeTopicId can be null (initial state) or undefined (after topic operations)
199
+ // Operation context topicId can also be null or undefined
200
+ const activeTopicId = s.activeTopicId ?? null;
201
+ const opTopicId = op.context.topicId ?? null;
202
+
203
+ return activeTopicId === opTopicId;
191
204
  });
192
205
  };
193
206
 
@@ -32,6 +32,7 @@ export interface PluginOptimisticUpdateAction {
32
32
  id: string,
33
33
  value: T,
34
34
  replace?: boolean,
35
+ context?: OptimisticUpdateContext,
35
36
  ) => Promise<void>;
36
37
 
37
38
  /**
@@ -106,7 +107,7 @@ export const pluginOptimisticUpdate: StateCreator<
106
107
  }
107
108
  },
108
109
 
109
- optimisticUpdatePluginArguments: async (id, value, replace = false) => {
110
+ optimisticUpdatePluginArguments: async (id, value, replace = false, context) => {
110
111
  const { refreshMessages } = get();
111
112
  const toolMessage = displayMessageSelectors.getDisplayMessageById(id)(get());
112
113
  if (!toolMessage || !toolMessage?.tool_call_id) return;
@@ -121,20 +122,22 @@ export const pluginOptimisticUpdate: StateCreator<
121
122
  if (isEqual(prevJson, nextValue)) return;
122
123
 
123
124
  // optimistic update
124
- get().internal_dispatchMessage({
125
- id,
126
- type: 'updateMessagePlugin',
127
- value: { arguments: JSON.stringify(nextValue) },
128
- });
125
+ get().internal_dispatchMessage(
126
+ { id, type: 'updateMessagePlugin', value: { arguments: JSON.stringify(nextValue) } },
127
+ context,
128
+ );
129
129
 
130
130
  // 同样需要更新 assistantMessage 的 pluginArguments
131
131
  if (assistantMessage) {
132
- get().internal_dispatchMessage({
133
- id: assistantMessage.id,
134
- type: 'updateMessageTools',
135
- tool_call_id: toolMessage?.tool_call_id,
136
- value: { arguments: JSON.stringify(nextValue) },
137
- });
132
+ get().internal_dispatchMessage(
133
+ {
134
+ id: assistantMessage.id,
135
+ type: 'updateMessageTools',
136
+ tool_call_id: toolMessage?.tool_call_id,
137
+ value: { arguments: JSON.stringify(nextValue) },
138
+ },
139
+ context,
140
+ );
138
141
  assistantMessage = displayMessageSelectors.getDisplayMessageById(assistantMessage?.id)(get());
139
142
  }
140
143
 
@@ -183,11 +186,14 @@ export const pluginOptimisticUpdate: StateCreator<
183
186
  if (!assistantMessage) return;
184
187
 
185
188
  const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
186
- internal_dispatchMessage({
187
- type: 'addMessageTool',
188
- value: tool,
189
- id: assistantMessage.id,
190
- });
189
+ internal_dispatchMessage(
190
+ {
191
+ type: 'addMessageTool',
192
+ value: tool,
193
+ id: assistantMessage.id,
194
+ },
195
+ context,
196
+ );
191
197
 
192
198
  await internal_refreshToUpdateMessageTools(id, context);
193
199
  },
@@ -199,7 +205,7 @@ export const pluginOptimisticUpdate: StateCreator<
199
205
  const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
200
206
 
201
207
  // optimistic update
202
- internal_dispatchMessage({ type: 'deleteMessageTool', tool_call_id, id: message.id });
208
+ internal_dispatchMessage({ type: 'deleteMessageTool', tool_call_id, id: message.id }, context);
203
209
 
204
210
  // update the message tools
205
211
  await internal_refreshToUpdateMessageTools(id, context);
@@ -10,12 +10,7 @@ const getVaultByProvider = (provider: string) => (s: UserStore) =>
10
10
  // @ts-ignore
11
11
  (keyVaultsSettings(s)[provider] || {}) as any;
12
12
 
13
- const password = (s: UserStore) => keyVaultsSettings(s).password || '';
14
-
15
13
  export const keyVaultsConfigSelectors = {
16
14
  getVaultByProvider,
17
-
18
15
  keyVaultsSettings,
19
-
20
- password,
21
16
  };
@@ -1,63 +0,0 @@
1
- import { Button, InputPassword } from '@lobehub/ui';
2
- import { memo } from 'react';
3
- import { useTranslation } from 'react-i18next';
4
- import { Flexbox } from 'react-layout-kit';
5
-
6
- import { useChatStore } from '@/store/chat';
7
- import { useUserStore } from '@/store/user';
8
- import { keyVaultsConfigSelectors } from '@/store/user/selectors';
9
-
10
- import { FormAction } from './style';
11
-
12
- interface AccessCodeFormProps {
13
- id: string;
14
- }
15
-
16
- const AccessCodeForm = memo<AccessCodeFormProps>(({ id }) => {
17
- const { t } = useTranslation('error');
18
- const [password, updateKeyVaults] = useUserStore((s) => [
19
- keyVaultsConfigSelectors.password(s),
20
- s.updateKeyVaults,
21
- ]);
22
- const [resend, deleteMessage] = useChatStore((s) => [s.delAndRegenerateMessage, s.deleteMessage]);
23
-
24
- return (
25
- <>
26
- <FormAction
27
- avatar={'🗳'}
28
- description={t('unlock.password.description')}
29
- title={t('unlock.password.title')}
30
- >
31
- <InputPassword
32
- autoComplete={'new-password'}
33
- onChange={(e) => {
34
- updateKeyVaults({ password: e.target.value });
35
- }}
36
- placeholder={t('unlock.password.placeholder')}
37
- value={password}
38
- variant={'filled'}
39
- />
40
- </FormAction>
41
- <Flexbox gap={12}>
42
- <Button
43
- onClick={() => {
44
- resend(id);
45
- deleteMessage(id);
46
- }}
47
- type={'primary'}
48
- >
49
- {t('unlock.confirm')}
50
- </Button>
51
- <Button
52
- onClick={() => {
53
- deleteMessage(id);
54
- }}
55
- >
56
- {t('unlock.closeMessage')}
57
- </Button>
58
- </Flexbox>
59
- </>
60
- );
61
- });
62
-
63
- export default AccessCodeForm;
@@ -1,61 +0,0 @@
1
- import type { PartialDeep } from 'type-fest';
2
- import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
-
4
- import { LOBE_URL_IMPORT_NAME } from '@/const/url';
5
- import { UserSettings } from '@/types/user/settings';
6
-
7
- import { shareService } from '../share';
8
-
9
- // Mock dependencies
10
- vi.mock('@/utils/parseMarkdown', () => ({
11
- parseMarkdown: vi.fn(),
12
- }));
13
-
14
- global.fetch = vi.fn();
15
-
16
- beforeEach(() => {
17
- vi.clearAllMocks();
18
- });
19
- describe('ShareGPTService', () => {
20
- describe('ShareViaUrl', () => {
21
- describe('createShareSettingsUrl', () => {
22
- it('should create a share settings URL with the provided settings', () => {
23
- const settings: PartialDeep<UserSettings> = {
24
- keyVaults: {
25
- openai: {
26
- apiKey: 'user-key',
27
- },
28
- },
29
- };
30
- const url = shareService.createShareSettingsUrl(settings);
31
- expect(url).toBe(
32
- `/?${LOBE_URL_IMPORT_NAME}=%7B%22keyVaults%22:%7B%22openai%22:%7B%22apiKey%22:%22user-key%22%7D%7D%7D`,
33
- );
34
- });
35
- });
36
-
37
- describe('decodeShareSettings', () => {
38
- it('should decode share settings from search params', () => {
39
- const settings = '{"languageModel":{"openai":{"apiKey":"user-key"}}}';
40
- const decodedSettings = shareService.decodeShareSettings(settings);
41
- expect(decodedSettings).toEqual({
42
- data: {
43
- languageModel: {
44
- openai: {
45
- apiKey: 'user-key',
46
- },
47
- },
48
- },
49
- });
50
- });
51
-
52
- it('should return an error message if decoding fails', () => {
53
- const settings = '%7B%22theme%22%3A%22dark%22%2C%22fontSize%22%3A16%';
54
- const decodedSettings = shareService.decodeShareSettings(settings);
55
- expect(decodedSettings).toEqual({
56
- message: expect.any(String),
57
- });
58
- });
59
- });
60
- });
61
- });