@lobehub/lobehub 2.0.0-next.290 → 2.0.0-next.292

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 (89) hide show
  1. package/.conductor/setup.sh +107 -0
  2. package/.cursor/rules/linear.mdc +53 -0
  3. package/.github/actions/desktop-build-setup/action.yml +29 -0
  4. package/.github/actions/desktop-upload-artifacts/action.yml +46 -0
  5. package/.github/workflows/release-desktop-beta.yml +76 -115
  6. package/.github/workflows/release-desktop-stable.yml +461 -0
  7. package/CHANGELOG.md +68 -0
  8. package/CLAUDE.md +2 -48
  9. package/apps/desktop/dev-app-update.yml +10 -0
  10. package/apps/desktop/electron-builder.mjs +40 -10
  11. package/apps/desktop/electron.vite.config.ts +3 -2
  12. package/apps/desktop/package.json +2 -1
  13. package/apps/desktop/scripts/update-test/README.md +222 -0
  14. package/apps/desktop/scripts/update-test/dev-app-update.local.yml +18 -0
  15. package/apps/desktop/scripts/update-test/generate-manifest.sh +277 -0
  16. package/apps/desktop/scripts/update-test/run-test.sh +105 -0
  17. package/apps/desktop/scripts/update-test/setup.sh +111 -0
  18. package/apps/desktop/scripts/update-test/start-server.sh +70 -0
  19. package/apps/desktop/scripts/update-test/stop-server.sh +33 -0
  20. package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +120 -9
  21. package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +17 -1
  22. package/apps/desktop/src/main/env.ts +19 -11
  23. package/apps/desktop/src/main/modules/updater/configs.ts +14 -1
  24. package/changelog/v1.json +21 -0
  25. package/conductor.json +5 -0
  26. package/locales/en-US/chat.json +2 -0
  27. package/locales/en-US/subscription.json +2 -2
  28. package/locales/zh-CN/chat.json +2 -0
  29. package/locales/zh-CN/subscription.json +2 -2
  30. package/package.json +1 -1
  31. package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +16 -14
  32. package/packages/electron-client-ipc/src/useWatchBroadcast.ts +10 -4
  33. package/packages/model-bank/src/aiModels/qiniu.ts +6 -6
  34. package/packages/observability-otel/src/node.ts +39 -37
  35. package/scripts/electronWorkflow/mergeMacReleaseFiles.js +22 -8
  36. package/src/app/(backend)/api/version/route.ts +13 -0
  37. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +2 -1
  38. package/src/app/[variants]/(main)/_layout/index.tsx +2 -1
  39. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobScheduleConfig.tsx +0 -1
  40. package/src/app/[variants]/(main)/agent/cron/[cronId]/index.tsx +5 -5
  41. package/src/app/[variants]/(main)/agent/features/Conversation/ThreadHydration.tsx +3 -1
  42. package/src/app/[variants]/(main)/group/features/Conversation/ThreadHydration.tsx +3 -1
  43. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/Checker.tsx +15 -6
  44. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +68 -23
  45. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +1 -4
  46. package/src/app/[variants]/router/desktopRouter.config.tsx +1 -4
  47. package/src/components/HtmlPreview/PreviewDrawer.tsx +1 -1
  48. package/src/features/ChatInput/ChatInputProvider.tsx +1 -1
  49. package/src/features/Conversation/Messages/Assistant/components/MessageContent.tsx +9 -16
  50. package/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +12 -2
  51. package/src/features/Conversation/Messages/Task/components/MessageContent.tsx +1 -0
  52. package/src/features/Conversation/Messages/Tool/Tool/index.tsx +10 -1
  53. package/src/features/Conversation/Messages/components/ContentLoading.tsx +64 -0
  54. package/src/features/Conversation/Messages/components/DisplayContent.tsx +4 -2
  55. package/src/features/{ElectronTitlebar/hooks → Electron/navigation}/useNavigationHistory.ts +1 -1
  56. package/src/features/{ElectronTitlebar/NavigationBar/index.tsx → Electron/titlebar/NavigationBar.tsx} +1 -1
  57. package/src/features/{ElectronTitlebar/NavigationBar → Electron/titlebar}/RecentlyViewed.tsx +1 -1
  58. package/src/features/{ElectronTitlebar/index.tsx → Electron/titlebar/TitleBar.tsx} +19 -9
  59. package/src/features/Electron/titlebar/WinControl.tsx +5 -0
  60. package/src/features/Electron/updater/UpdateModal.tsx +299 -0
  61. package/src/features/LibraryModal/AddFilesToKnowledgeBase/index.test.tsx +24 -0
  62. package/src/features/LibraryModal/AddFilesToKnowledgeBase/index.tsx +21 -24
  63. package/src/features/LibraryModal/CreateNew/index.tsx +18 -22
  64. package/src/features/OllamaModelDownloader/index.tsx +3 -3
  65. package/src/features/PluginDevModal/index.tsx +1 -1
  66. package/src/layout/GlobalProvider/AppTheme.tsx +1 -1
  67. package/src/libs/swr/index.ts +17 -23
  68. package/src/locales/default/chat.ts +2 -0
  69. package/src/store/aiInfra/slices/aiProvider/action.ts +68 -1
  70. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +2 -1
  71. package/src/store/chat/slices/portal/action.test.ts +0 -2
  72. package/src/store/chat/slices/portal/action.ts +17 -44
  73. package/src/store/chat/slices/thread/action.test.ts +4 -1
  74. package/src/store/chat/slices/thread/action.ts +6 -1
  75. package/src/components/FunctionModal/createModalHooks.ts +0 -48
  76. package/src/components/FunctionModal/index.ts +0 -1
  77. package/src/components/FunctionModal/style.tsx +0 -44
  78. package/src/features/ElectronTitlebar/UpdateModal.tsx +0 -274
  79. package/src/features/ElectronTitlebar/WinControl/index.tsx +0 -90
  80. /package/src/features/{ElectronTitlebar/Connection/index.tsx → Electron/connection/Connection.tsx} +0 -0
  81. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/ConnectionMode.tsx +0 -0
  82. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/Option.tsx +0 -0
  83. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/RemoteStatus.tsx +0 -0
  84. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/Waiting.tsx +0 -0
  85. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/WaitingAnim.tsx +0 -0
  86. /package/src/features/{ElectronTitlebar/helpers → Electron/navigation}/routeMetadata.ts +0 -0
  87. /package/src/features/{ElectronTitlebar/hooks → Electron/system}/useWatchThemeUpdate.ts +0 -0
  88. /package/src/features/{ElectronTitlebar → Electron/titlebar}/SimpleTitleBar.tsx +0 -0
  89. /package/src/features/{ElectronTitlebar → Electron/updater}/UpdateNotification.tsx +0 -0
@@ -1,10 +1,8 @@
1
- import { Flexbox, Icon } from '@lobehub/ui';
1
+ import { Flexbox, Icon, createModal, useModalContext } from '@lobehub/ui';
2
2
  import { BookUp2Icon } from 'lucide-react';
3
- import { Suspense, memo } from 'react';
3
+ import { Suspense, memo, useCallback } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
- import { createModal } from '@/components/FunctionModal';
7
-
8
6
  import SelectForm from './SelectForm';
9
7
 
10
8
  interface AddFilesToKnowledgeBaseModalProps {
@@ -16,12 +14,11 @@ interface AddFilesToKnowledgeBaseModalProps {
16
14
  interface ModalContentProps {
17
15
  fileIds: string[];
18
16
  knowledgeBaseId?: string;
19
- onClose?: () => void;
20
17
  }
21
18
 
22
- const ModalContent = memo<ModalContentProps>(({ fileIds, knowledgeBaseId, onClose }) => {
19
+ const ModalContent = memo<ModalContentProps>(({ fileIds, knowledgeBaseId }) => {
23
20
  const { t } = useTranslation('knowledgeBase');
24
-
21
+ const { close } = useModalContext();
25
22
  return (
26
23
  <>
27
24
  <Flexbox gap={8} horizontal paddingBlock={16} paddingInline={16} style={{ paddingBottom: 0 }}>
@@ -29,7 +26,7 @@ const ModalContent = memo<ModalContentProps>(({ fileIds, knowledgeBaseId, onClos
29
26
  {t('addToKnowledgeBase.title')}
30
27
  </Flexbox>
31
28
  <Flexbox padding={16} style={{ paddingTop: 0 }}>
32
- <SelectForm fileIds={fileIds} knowledgeBaseId={knowledgeBaseId} onClose={onClose} />
29
+ <SelectForm fileIds={fileIds} knowledgeBaseId={knowledgeBaseId} onClose={close} />
33
30
  </Flexbox>
34
31
  </>
35
32
  );
@@ -37,19 +34,19 @@ const ModalContent = memo<ModalContentProps>(({ fileIds, knowledgeBaseId, onClos
37
34
 
38
35
  ModalContent.displayName = 'AddFilesToKnowledgeBaseModalContent';
39
36
 
40
- export const useAddFilesToKnowledgeBaseModal = createModal<AddFilesToKnowledgeBaseModalProps>(
41
- (instance, params) => ({
42
- content: (
43
- <Suspense fallback={<div style={{ minHeight: 200 }} />}>
44
- <ModalContent
45
- fileIds={params?.fileIds || []}
46
- knowledgeBaseId={params?.knowledgeBaseId}
47
- onClose={() => {
48
- instance.current?.destroy();
49
- params?.onClose?.();
50
- }}
51
- />
52
- </Suspense>
53
- ),
54
- }),
55
- );
37
+ export const useAddFilesToKnowledgeBaseModal = () => {
38
+ const open = useCallback((params?: AddFilesToKnowledgeBaseModalProps) => {
39
+ createModal({
40
+ afterClose: params?.onClose,
41
+ children: (
42
+ <Suspense fallback={<div style={{ minHeight: 200 }} />}>
43
+ <ModalContent fileIds={params?.fileIds || []} knowledgeBaseId={params?.knowledgeBaseId} />
44
+ </Suspense>
45
+ ),
46
+ footer: null,
47
+ title: null,
48
+ });
49
+ }, []);
50
+
51
+ return { open };
52
+ };
@@ -1,41 +1,37 @@
1
- import { Flexbox } from '@lobehub/ui';
2
- import { Suspense, memo } from 'react';
3
-
4
- import { createModal } from '@/components/FunctionModal';
1
+ import { Flexbox, createModal, useModalContext } from '@lobehub/ui';
2
+ import { Suspense, memo, useCallback } from 'react';
5
3
 
6
4
  import CreateForm from './CreateForm';
7
5
 
8
6
  interface ModalContentProps {
9
- onClose?: () => void;
10
7
  onSuccess?: (id: string) => void;
11
8
  }
12
9
 
13
- const ModalContent = memo<ModalContentProps>(({ onClose, onSuccess }) => {
10
+ const ModalContent = memo<ModalContentProps>(({ onSuccess }) => {
11
+ const { close } = useModalContext();
12
+
14
13
  return (
15
14
  <Flexbox paddingInline={16} style={{ paddingBottom: 16 }}>
16
- <CreateForm onClose={onClose} onSuccess={onSuccess} />
15
+ <CreateForm onClose={close} onSuccess={onSuccess} />
17
16
  </Flexbox>
18
17
  );
19
18
  });
20
19
 
21
20
  ModalContent.displayName = 'KnowledgeBaseCreateModalContent';
22
21
 
23
- // eslint-disable-next-line unused-imports/no-unused-vars
24
- export const useCreateNewModal = createModal<{ onSuccess?: (id: string) => void }>(
25
- (instance, props) => {
26
- return {
27
- content: (
22
+ export const useCreateNewModal = () => {
23
+ const open = useCallback((props?: { onSuccess?: (id: string) => void }) => {
24
+ createModal({
25
+ children: (
28
26
  <Suspense fallback={<div style={{ minHeight: 200 }} />}>
29
- <ModalContent
30
- onClose={() => {
31
- instance.current?.destroy();
32
- }}
33
- onSuccess={props?.onSuccess}
34
- />
27
+ <ModalContent onSuccess={props?.onSuccess} />
35
28
  </Suspense>
36
29
  ),
37
30
  focusTriggerAfterClose: true,
38
- footer: false,
39
- };
40
- },
41
- );
31
+ footer: null,
32
+ title: null,
33
+ });
34
+ }, []);
35
+
36
+ return { open };
37
+ };
@@ -41,10 +41,10 @@ const OllamaModelDownloader = memo<OllamaModelDownloaderProps>(
41
41
  isValidating: isDownloading,
42
42
  error,
43
43
  } = useActionSWR(
44
- [modelToPull],
45
- async ([model]) => {
44
+ ['ollama.downloadModel', modelToPull],
45
+ async () => {
46
46
  await modelsService.downloadModel(
47
- { model, provider: 'ollama' },
47
+ { model: modelToPull, provider: 'ollama' },
48
48
  { onProgress: handleProgress },
49
49
  );
50
50
 
@@ -1,3 +1,4 @@
1
+ import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
1
2
  import { Alert, Button, Drawer, Flexbox, Icon, Segmented, Tag } from '@lobehub/ui';
2
3
  import { App, Form, Popconfirm } from 'antd';
3
4
  import { useResponsive } from 'antd-style';
@@ -7,7 +8,6 @@ import { Trans, useTranslation } from 'react-i18next';
7
8
 
8
9
  import { WIKI_PLUGIN_GUIDE } from '@/const/url';
9
10
  import { isDesktop } from '@/const/version';
10
- import { TITLE_BAR_HEIGHT } from '@/features/ElectronTitlebar';
11
11
  import { type LobeToolCustomPlugin } from '@/types/tool/plugin';
12
12
 
13
13
  import MCPManifestForm from './MCPManifestForm';
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
3
4
  import {
4
5
  ConfigProvider,
5
6
  FontLoader,
@@ -19,7 +20,6 @@ import { type ReactNode, memo, useEffect, useMemo, useState } from 'react';
19
20
  import AntdStaticMethods from '@/components/AntdStaticMethods';
20
21
  import { LOBE_THEME_NEUTRAL_COLOR, LOBE_THEME_PRIMARY_COLOR } from '@/const/theme';
21
22
  import { isDesktop } from '@/const/version';
22
- import { TITLE_BAR_HEIGHT } from '@/features/ElectronTitlebar';
23
23
  import { useIsDark } from '@/hooks/useIsDark';
24
24
  import { getUILocaleAndResources } from '@/libs/getUILocaleAndResources';
25
25
  import { useGlobalStore } from '@/store/global';
@@ -1,23 +1,13 @@
1
1
  import useSWR, { type SWRHook } from 'swr';
2
2
 
3
-
4
3
  /**
5
- * This type of request method is relatively flexible data, which will be triggered on the first time
4
+ * This type of request method is for relatively flexible data, which will be triggered on the first time.
6
5
  *
7
6
  * Refresh rules have two types:
8
- *
9
- * - when the user refocuses, it will be refreshed outside the 5mins interval.
10
- * - can be combined with refreshXXX methods to refresh data
7
+ * - When the user refocuses, it will be refreshed outside the 5mins interval.
8
+ * - Can be combined with refreshXXX methods to refresh data.
11
9
  *
12
10
  * Suitable for messages, topics, sessions, and other data that users will interact with on the client.
13
- *
14
- * 这一类请求方法是相对灵活的数据,会在请求时触发
15
- *
16
- * 刷新规则有两种:
17
- * - 当用户重新聚焦时,在 5mins 间隔外会重新刷新一次
18
- * - 可以搭配 refreshXXX 这样的方法刷新数据
19
- *
20
- * 适用于 messages、topics、sessions 等用户会在客户端交互的数据
21
11
  */
22
12
  // @ts-ignore
23
13
  export const useClientDataSWR: SWRHook = (key, fetch, config) =>
@@ -47,11 +37,8 @@ export const useClientDataSWR: SWRHook = (key, fetch, config) =>
47
37
  });
48
38
 
49
39
  /**
50
- * This type of request method is relatively "dead" request mode, which will only be triggered on the first request.
51
- * it suitable for first time request like `initUserState`
52
-
53
- * 这一类请求方法是相对"死"的请求模式,只会在第一次请求时触发。
54
- * 适用于第一次请求,例如 `initUserState`
40
+ * This type of request method is a relatively "static" request mode, which will only be triggered on the first request.
41
+ * Suitable for first time requests like `initUserState`.
55
42
  */
56
43
  // @ts-ignore
57
44
  export const useOnlyFetchOnceSWR: SWRHook = (key, fetch, config) =>
@@ -63,13 +50,20 @@ export const useOnlyFetchOnceSWR: SWRHook = (key, fetch, config) =>
63
50
  });
64
51
 
65
52
  /**
66
- * 这一类请求方法用于做操作触发,必须使用 mutute 来触发请求操作,好处是自带了 loading / error 状态。
67
- * 可以很简单地完成 loading / error 态的交互处理,同时,相同 swr key 的请求会自动共享 loading态(例如新建助理按钮和右上角的 + 号)
68
- * 非常适用于新建等操作。
53
+ * This type of request method is for action triggers. Must use mutate to trigger the request.
54
+ * Benefits: built-in loading/error states, easy to handle loading/error UI interactions.
55
+ * Components with the same SWR key will automatically share loading state (e.g., create agent button and the + button in header).
56
+ * Very suitable for create operations.
57
+ *
58
+ * Uses fallbackData as empty object so SWR thinks initial data exists.
59
+ * Combined with revalidateOnMount: false, this prevents auto-fetch on mount.
69
60
  */
70
61
  // @ts-ignore
71
62
  export const useActionSWR: SWRHook = (key, fetch, config) =>
72
63
  useSWR(key, fetch, {
64
+ // Use empty object as fallback to prevent auto-fetch when cache is empty
65
+ // Combined with revalidateOnMount: false, SWR won't call fetcher on mount
66
+ fallbackData: {},
73
67
  refreshWhenHidden: false,
74
68
  refreshWhenOffline: false,
75
69
  revalidateOnFocus: false,
@@ -87,8 +81,8 @@ export type SWRefreshMethod<T> = <A extends (...args: any[]) => Promise<any>>(
87
81
  params?: SWRRefreshParams<T, A>,
88
82
  ) => ReturnType<A>;
89
83
 
90
- // 导出带自动同步功能的 hook
84
+ // Export hook with auto-sync functionality
91
85
  export { useClientDataSWRWithSync } from './useClientDataSWRWithSync';
92
86
 
93
- // 导出 scoped mutate(用于自定义 cache provider 场景)
87
+ // Export scoped mutate (for custom cache provider scenarios)
94
88
  export { mutate, setScopedMutate } from './mutate';
@@ -229,6 +229,8 @@ export default {
229
229
  'noMembersYet': "This group doesn't have any members yet. Click the + button to invite agents.",
230
230
  'noSelectedAgents': 'No members selected yet',
231
231
  'openInNewWindow': 'Open in New Window',
232
+ 'operation.execAgentRuntime': 'Preparing response',
233
+ 'operation.sendMessage': 'Sending message',
232
234
  'owner': 'Group owner',
233
235
  'pageCopilot.title': 'Page Agent',
234
236
  'pageCopilot.welcome':
@@ -29,7 +29,6 @@ import {
29
29
  type UpdateAiProviderParams,
30
30
  } from '@/types/aiProvider';
31
31
 
32
-
33
32
  export type ProviderModelListItem = {
34
33
  abilities: ModelAbilities;
35
34
  approximatePricePerImage?: number;
@@ -260,6 +259,19 @@ export const createAiProviderSlice: StateCreator<
260
259
  toggleProviderEnabled: async (id: string, enabled: boolean) => {
261
260
  get().internal_toggleAiProviderLoading(id, true);
262
261
  await aiProviderService.toggleProviderEnabled(id, enabled);
262
+
263
+ // Immediately update local aiProviderList to reflect the change
264
+ // This ensures the switch displays correctly without waiting for SWR refresh
265
+ set(
266
+ (state) => ({
267
+ aiProviderList: state.aiProviderList.map((item) =>
268
+ item.id === id ? { ...item, enabled } : item,
269
+ ),
270
+ }),
271
+ false,
272
+ 'toggleProviderEnabled/syncEnabled',
273
+ );
274
+
263
275
  await get().refreshAiProviderList();
264
276
 
265
277
  get().internal_toggleAiProviderLoading(id, false);
@@ -277,6 +289,61 @@ export const createAiProviderSlice: StateCreator<
277
289
  updateAiProviderConfig: async (id, value) => {
278
290
  get().internal_toggleAiProviderConfigUpdating(id, true);
279
291
  await aiProviderService.updateAiProviderConfig(id, value);
292
+
293
+ // Immediately update local state for instant UI feedback
294
+ set(
295
+ (state) => {
296
+ const currentRuntimeConfig = state.aiProviderRuntimeConfig[id];
297
+ const currentDetailConfig = state.aiProviderDetailMap[id];
298
+
299
+ const updates: Partial<typeof currentRuntimeConfig> = {};
300
+ const detailUpdates: Partial<typeof currentDetailConfig> = {};
301
+
302
+ // Update fetchOnClient if changed
303
+ if (typeof value.fetchOnClient !== 'undefined') {
304
+ // Convert null to undefined to match the interface definition
305
+ const fetchOnClientValue = value.fetchOnClient === null ? undefined : value.fetchOnClient;
306
+ updates.fetchOnClient = fetchOnClientValue;
307
+ detailUpdates.fetchOnClient = fetchOnClientValue;
308
+ }
309
+
310
+ // Update config.enableResponseApi if changed
311
+ if (value.config?.enableResponseApi !== undefined && currentRuntimeConfig?.config) {
312
+ updates.config = {
313
+ ...currentRuntimeConfig.config,
314
+ enableResponseApi: value.config.enableResponseApi,
315
+ };
316
+ }
317
+
318
+ return {
319
+ // Update detail map for form display
320
+ aiProviderDetailMap:
321
+ currentDetailConfig && Object.keys(detailUpdates).length > 0
322
+ ? {
323
+ ...state.aiProviderDetailMap,
324
+ [id]: {
325
+ ...currentDetailConfig,
326
+ ...detailUpdates,
327
+ },
328
+ }
329
+ : state.aiProviderDetailMap,
330
+ // Update runtime config for selectors
331
+ aiProviderRuntimeConfig:
332
+ currentRuntimeConfig && Object.keys(updates).length > 0
333
+ ? {
334
+ ...state.aiProviderRuntimeConfig,
335
+ [id]: {
336
+ ...currentRuntimeConfig,
337
+ ...updates,
338
+ },
339
+ }
340
+ : state.aiProviderRuntimeConfig,
341
+ };
342
+ },
343
+ false,
344
+ 'updateAiProviderConfig/syncChanges',
345
+ );
346
+
280
347
  await get().refreshAiProviderDetail();
281
348
 
282
349
  get().internal_toggleAiProviderConfigUpdating(id, false);
@@ -204,8 +204,9 @@ export const conversationLifecycle: StateCreator<
204
204
  );
205
205
  get().internal_toggleMessageLoading(true, tempId);
206
206
 
207
- // Associate temp message with operation
207
+ // Associate temp messages with operation
208
208
  get().associateMessageWithOperation(tempId, operationId);
209
+ get().associateMessageWithOperation(tempAssistantId, operationId);
209
210
 
210
211
  // Store editor state in operation metadata for cancel restoration
211
212
  const jsonState = mainInputEditor?.getJSONState();
@@ -203,7 +203,6 @@ describe('chatDockSlice', () => {
203
203
  });
204
204
  });
205
205
 
206
-
207
206
  describe('closeToolUI', () => {
208
207
  it('should pop ToolUI view from stack', () => {
209
208
  const { result } = renderHook(() => useChatStore());
@@ -267,5 +266,4 @@ describe('chatDockSlice', () => {
267
266
  expect(result.current.showPortal).toBe(true);
268
267
  });
269
268
  });
270
-
271
269
  });
@@ -42,71 +42,57 @@ export const chatPortalSlice: StateCreator<
42
42
  [],
43
43
  ChatPortalAction
44
44
  > = (set, get) => ({
45
-
46
-
47
45
  clearPortalStack: () => {
48
46
  set({ portalStack: [], showPortal: false }, false, 'clearPortalStack');
49
47
  },
50
48
 
51
-
52
- closeArtifact: () => {
49
+ closeArtifact: () => {
53
50
  const { portalStack } = get();
54
51
  if (getCurrentViewType(portalStack) === PortalViewType.Artifact) {
55
52
  get().popPortalView();
56
53
  }
57
54
  },
58
55
 
59
-
60
- closeDocument: () => {
56
+ closeDocument: () => {
61
57
  const { portalStack } = get();
62
58
  if (getCurrentViewType(portalStack) === PortalViewType.Document) {
63
59
  get().popPortalView();
64
60
  }
65
61
  },
66
62
 
67
-
68
- closeFilePreview: () => {
63
+ closeFilePreview: () => {
69
64
  const { portalStack } = get();
70
65
  if (getCurrentViewType(portalStack) === PortalViewType.FilePreview) {
71
66
  get().popPortalView();
72
67
  }
73
68
  },
74
69
 
75
-
76
- closeMessageDetail: () => {
70
+ closeMessageDetail: () => {
77
71
  const { portalStack } = get();
78
72
  if (getCurrentViewType(portalStack) === PortalViewType.MessageDetail) {
79
73
  get().popPortalView();
80
74
  }
81
75
  },
82
76
 
83
-
84
- closeNotebook: () => {
77
+ closeNotebook: () => {
85
78
  const { portalStack } = get();
86
79
  if (getCurrentViewType(portalStack) === PortalViewType.Notebook) {
87
80
  get().popPortalView();
88
81
  }
89
82
  },
90
83
 
91
-
92
-
93
-
94
- closeToolUI: () => {
84
+ closeToolUI: () => {
95
85
  const { portalStack } = get();
96
86
  if (getCurrentViewType(portalStack) === PortalViewType.ToolUI) {
97
87
  get().popPortalView();
98
88
  }
99
89
  },
100
90
 
101
-
102
-
103
- goBack: () => {
91
+ goBack: () => {
104
92
  get().popPortalView();
105
93
  },
106
94
 
107
-
108
-
109
- goHome: () => {
95
+ goHome: () => {
110
96
  set(
111
97
  {
112
98
  portalStack: [{ type: PortalViewType.Home }],
@@ -117,45 +103,32 @@ goHome: () => {
117
103
  );
118
104
  },
119
105
 
120
-
121
-
122
- // ============== Convenience Methods (using stack operations) ==============
123
- openArtifact: (artifact) => {
106
+ // ============== Convenience Methods (using stack operations) ==============
107
+ openArtifact: (artifact) => {
124
108
  get().pushPortalView({ artifact, type: PortalViewType.Artifact });
125
109
  },
126
110
 
127
-
128
-
129
-
130
- openDocument: (documentId) => {
111
+ openDocument: (documentId) => {
131
112
  get().pushPortalView({ documentId, type: PortalViewType.Document });
132
113
  },
133
114
 
134
-
135
-
136
-
137
- openFilePreview: (file) => {
115
+ openFilePreview: (file) => {
138
116
  get().pushPortalView({ file, type: PortalViewType.FilePreview });
139
117
  },
140
118
 
141
-
142
-
143
- openMessageDetail: (messageId) => {
119
+ openMessageDetail: (messageId) => {
144
120
  get().pushPortalView({ messageId, type: PortalViewType.MessageDetail });
145
121
  },
146
122
 
147
-
148
- openNotebook: () => {
123
+ openNotebook: () => {
149
124
  get().pushPortalView({ type: PortalViewType.Notebook });
150
125
  },
151
126
 
152
-
153
- openToolUI: (messageId, identifier) => {
127
+ openToolUI: (messageId, identifier) => {
154
128
  get().pushPortalView({ identifier, messageId, type: PortalViewType.ToolUI });
155
129
  },
156
130
 
157
-
158
- popPortalView: () => {
131
+ popPortalView: () => {
159
132
  const { portalStack } = get();
160
133
 
161
134
  if (portalStack.length <= 1) {
@@ -167,7 +140,7 @@ popPortalView: () => {
167
140
  },
168
141
 
169
142
  // ============== Core Stack Operations ==============
170
- pushPortalView: (view) => {
143
+ pushPortalView: (view) => {
171
144
  const { portalStack } = get();
172
145
  const top = portalStack.at(-1);
173
146
 
@@ -150,7 +150,10 @@ describe('thread action', () => {
150
150
  expect(result.current.threadStartMessageId).toBe('message-id');
151
151
  expect(result.current.portalThreadId).toBeUndefined();
152
152
  expect(result.current.startToForkThread).toBe(true);
153
- expect(pushPortalViewSpy).toHaveBeenCalledWith({ type: 'thread', startMessageId: 'message-id' });
153
+ expect(pushPortalViewSpy).toHaveBeenCalledWith({
154
+ type: 'thread',
155
+ startMessageId: 'message-id',
156
+ });
154
157
  });
155
158
  });
156
159
 
@@ -2,7 +2,12 @@
2
2
  // Disable the auto sort key eslint rule to make the code more logic and readable
3
3
  import { LOADING_FLAT } from '@lobechat/const';
4
4
  import { chainSummaryTitle } from '@lobechat/prompts';
5
- import { type CreateMessageParams, type IThreadType, type ThreadItem, type UIChatMessage } from '@lobechat/types';
5
+ import {
6
+ type CreateMessageParams,
7
+ type IThreadType,
8
+ type ThreadItem,
9
+ type UIChatMessage,
10
+ } from '@lobechat/types';
6
11
  import isEqual from 'fast-deep-equal';
7
12
  import type { SWRResponse } from 'swr';
8
13
  import { type StateCreator } from 'zustand/vanilla';
@@ -1,48 +0,0 @@
1
- import { App } from 'antd';
2
- import { type ModalFuncProps } from 'antd/es/modal/interface';
3
- import { type MutableRefObject, type ReactNode, type RefObject, useRef } from 'react';
4
-
5
- import { closeIcon, styles } from './style';
6
-
7
- interface CreateModalProps extends ModalFuncProps {
8
- content: ReactNode;
9
- }
10
-
11
- interface ModalInstance {
12
- destroy: (...args: any[]) => void;
13
- }
14
-
15
- type PropsFunc<T = undefined> = (
16
- instance: MutableRefObject<ModalInstance | undefined>,
17
- props?: T,
18
- ) => CreateModalProps;
19
-
20
- const createModal = <T>(params: CreateModalProps | PropsFunc<T>) => {
21
- const useModal = () => {
22
- const { modal } = App.useApp();
23
- const instanceRef = useRef<ModalInstance>(null);
24
-
25
- const open = (outProps?: T) => {
26
- const props =
27
- typeof params === 'function'
28
- ? params(instanceRef as RefObject<ModalInstance>, outProps)
29
- : params;
30
-
31
- instanceRef.current = modal.confirm({
32
- className: styles.content,
33
- closable: true,
34
- closeIcon,
35
- footer: false,
36
- icon: null,
37
- wrapClassName: styles.wrap,
38
- ...props,
39
- });
40
- };
41
-
42
- return { open };
43
- };
44
-
45
- return useModal;
46
- };
47
-
48
- export { createModal };
@@ -1 +0,0 @@
1
- export * from './createModalHooks';
@@ -1,44 +0,0 @@
1
- import { Icon } from '@lobehub/ui';
2
- import { createStaticStyles , responsive } from 'antd-style';
3
- import { XIcon } from 'lucide-react';
4
-
5
- const prefixCls = 'ant';
6
-
7
- export const styles = createStaticStyles(({ css, cssVar }) => {
8
- return {
9
- content: css`
10
- .${prefixCls}-modal-container {
11
- overflow: hidden;
12
-
13
- width: min(90vw, 450px);
14
- padding: 0;
15
- border: 1px solid ${cssVar.colorSplit};
16
- border-radius: ${cssVar.borderRadiusLG};
17
-
18
- background: ${cssVar.colorBgLayout};
19
-
20
- ${responsive.sm} {
21
- width: unset;
22
- }
23
- }
24
- .${prefixCls}-modal-confirm-title {
25
- display: block;
26
- padding-block: 16px 0;
27
- padding-inline: 16px;
28
- }
29
- .${prefixCls}-modal-confirm-btns {
30
- margin-block-start: 0;
31
- padding: 16px;
32
- }
33
-
34
- .${prefixCls}-modal-confirm-paragraph {
35
- max-width: 100%;
36
- }
37
- `,
38
- wrap: css`
39
- overflow: hidden auto;
40
- `,
41
- };
42
- });
43
-
44
- export const closeIcon = <Icon icon={XIcon} size={20} />;