@lobehub/lobehub 2.0.0-next.294 → 2.0.0-next.295

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 (170) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/apps/desktop/src/main/__mocks__/node-mac-permissions.ts +0 -1
  3. package/apps/desktop/src/main/__mocks__/setup.ts +0 -1
  4. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +1 -4
  5. package/apps/desktop/tsconfig.json +4 -10
  6. package/changelog/v1.json +9 -0
  7. package/e2e/scripts/setup.ts +45 -32
  8. package/package.json +1 -1
  9. package/packages/database/src/models/__tests__/knowledgeBase.test.ts +1 -1
  10. package/packages/database/src/repositories/knowledge/index.ts +1 -4
  11. package/packages/types/src/discover/assistants.ts +2 -2
  12. package/scripts/migrate-spa-navigation.ts +129 -0
  13. package/src/app/(backend)/api/workflows/memory-user-memory/pipelines/chat-topic/process-topics/route.ts +112 -109
  14. package/src/app/(backend)/api/workflows/memory-user-memory/pipelines/chat-topic/process-user-topics/route.ts +125 -113
  15. package/src/app/(backend)/api/workflows/memory-user-memory/pipelines/chat-topic/process-users/route.ts +74 -65
  16. package/src/app/[variants]/(auth)/auth-error/page.tsx +1 -1
  17. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +1 -1
  18. package/src/app/[variants]/(auth)/next-auth/error/AuthErrorPage.tsx +1 -1
  19. package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +1 -1
  20. package/src/app/[variants]/(auth)/oauth/callback/error/page.tsx +1 -1
  21. package/src/app/[variants]/(auth)/oauth/callback/success/page.tsx +1 -1
  22. package/src/app/[variants]/(auth)/oauth/consent/[uid]/page.tsx +1 -1
  23. package/src/app/[variants]/(auth)/reset-password/layout.tsx +1 -1
  24. package/src/app/[variants]/(auth)/reset-password/page.tsx +2 -2
  25. package/src/app/[variants]/(auth)/signin/layout.tsx +1 -1
  26. package/src/app/[variants]/(auth)/signin/useSignIn.ts +1 -1
  27. package/src/app/[variants]/(auth)/signup/[[...signup]]/BetterAuthSignUpForm.tsx +2 -2
  28. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -1
  29. package/src/app/[variants]/(auth)/signup/[[...signup]]/useSignUp.tsx +1 -1
  30. package/src/app/[variants]/(auth)/verify-email/layout.tsx +1 -1
  31. package/src/app/[variants]/(auth)/verify-email/page.tsx +2 -2
  32. package/src/app/[variants]/(main)/_layout/index.tsx +1 -1
  33. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +1 -1
  34. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Header/AddTopicButon.tsx +1 -1
  35. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Header/Nav.tsx +1 -1
  36. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/AllTopicsDrawer/index.tsx +1 -1
  37. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/hooks/useThreadNavigation.ts +1 -1
  38. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts +1 -1
  39. package/src/app/[variants]/(main)/agent/features/TelemetryNotification.tsx +2 -3
  40. package/src/app/[variants]/(main)/community/(detail)/assistant/features/Details/Nav.tsx +9 -9
  41. package/src/app/[variants]/(main)/community/(detail)/assistant/features/Details/Versions/index.tsx +2 -3
  42. package/src/app/[variants]/(main)/community/(detail)/features/MakedownRender.tsx +1 -2
  43. package/src/app/[variants]/(main)/community/(detail)/features/ShareButton.tsx +2 -3
  44. package/src/app/[variants]/(main)/community/(detail)/features/Toc/Heading.tsx +2 -3
  45. package/src/app/[variants]/(main)/community/(detail)/mcp/features/Details/Versions/index.tsx +2 -2
  46. package/src/app/[variants]/(main)/community/(detail)/model/features/Details/Nav.tsx +12 -11
  47. package/src/app/[variants]/(main)/community/(detail)/model/features/Details/Parameter/ParameterItem.tsx +2 -3
  48. package/src/app/[variants]/(main)/community/(detail)/provider/features/Details/Nav.tsx +11 -10
  49. package/src/app/[variants]/(main)/community/(detail)/provider/features/Header.tsx +10 -9
  50. package/src/app/[variants]/(main)/community/(list)/(home)/index.tsx +1 -1
  51. package/src/app/[variants]/(main)/community/(list)/assistant/features/Category/useCategory.tsx +1 -1
  52. package/src/app/[variants]/(main)/community/(list)/features/SortButton/index.tsx +2 -3
  53. package/src/app/[variants]/(main)/community/_layout/Sidebar/Header/Nav.tsx +1 -1
  54. package/src/app/[variants]/(main)/community/features/CreateButton/Inner.tsx +1 -1
  55. package/src/app/[variants]/(main)/community/features/CreateButton/index.tsx +1 -1
  56. package/src/app/[variants]/(main)/community/features/Search.tsx +1 -2
  57. package/src/app/[variants]/(main)/community/features/Title.tsx +5 -5
  58. package/src/app/[variants]/(main)/group/_layout/Sidebar/Header/Nav.tsx +1 -1
  59. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/AllTopicsDrawer/index.tsx +1 -1
  60. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/hooks/useThreadNavigation.ts +1 -1
  61. package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +1 -1
  62. package/src/app/[variants]/(main)/group/features/TelemetryNotification.tsx +2 -3
  63. package/src/app/[variants]/(main)/home/_layout/Body/Agent/AllAgentsDrawer/index.tsx +1 -1
  64. package/src/app/[variants]/(main)/hooks/useActiveTabKey.ts +6 -11
  65. package/src/app/[variants]/(main)/image/NotSupportClient.tsx +4 -3
  66. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ImageUpload.tsx +1 -1
  67. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/MultiImagesUpload/ImageManageModal.tsx +1 -1
  68. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/MultiImagesUpload/index.tsx +1 -1
  69. package/src/app/[variants]/(main)/memory/(home)/features/RoleTagCloud/index.tsx +1 -1
  70. package/src/app/[variants]/(main)/memory/_layout/Sidebar/Header/Nav.tsx +1 -1
  71. package/src/app/[variants]/(main)/memory/features/SourceLink.tsx +1 -1
  72. package/src/app/[variants]/(main)/page/_layout/Body/AllPagesDrawer/index.tsx +1 -1
  73. package/src/app/[variants]/(main)/settings/about/features/ItemCard.tsx +2 -3
  74. package/src/app/[variants]/(main)/settings/about/features/ItemLink.tsx +2 -3
  75. package/src/app/[variants]/(main)/settings/about/features/Version.tsx +6 -7
  76. package/src/app/[variants]/(main)/settings/features/SettingsContent.tsx +1 -1
  77. package/src/app/[variants]/(main)/settings/features/UpgradeAlert.tsx +4 -4
  78. package/src/app/[variants]/(main)/settings/provider/(list)/Footer.tsx +2 -2
  79. package/src/app/[variants]/(main)/settings/provider/detail/index.tsx +1 -1
  80. package/src/app/[variants]/(main)/settings/provider/detail/ollama/CheckError.tsx +1 -1
  81. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +12 -6
  82. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -1
  83. package/src/app/[variants]/(main)/settings/stats/features/overview/ShareButton/ShareModal.tsx +1 -1
  84. package/src/app/[variants]/(main)/settings/stats/features/rankings/AssistantsRank.tsx +1 -1
  85. package/src/app/[variants]/(main)/settings/stats/features/rankings/TopicsRank.tsx +1 -1
  86. package/src/app/[variants]/(mobile)/_layout/index.tsx +1 -1
  87. package/src/app/[variants]/(mobile)/chat/settings/features/AgentInfoDescription/index.tsx +1 -1
  88. package/src/app/[variants]/(mobile)/chat/settings/features/SettingButton.tsx +1 -1
  89. package/src/app/[variants]/(mobile)/router/index.tsx +1 -1
  90. package/src/app/[variants]/page.tsx +1 -1
  91. package/src/app/[variants]/router/index.tsx +1 -1
  92. package/src/components/404/index.tsx +4 -4
  93. package/src/components/Analytics/index.tsx +1 -1
  94. package/src/components/BrandWatermark/index.tsx +4 -4
  95. package/src/components/Branding/ProductLogo/Custom.tsx +1 -1
  96. package/src/components/Error/index.tsx +3 -4
  97. package/src/components/GoBack/index.tsx +2 -2
  98. package/src/components/LabsModal/LabCard.tsx +1 -1
  99. package/src/components/Link.tsx +25 -5
  100. package/src/components/OllamaSetupGuide/index.tsx +5 -4
  101. package/src/components/WebFavicon/index.tsx +1 -1
  102. package/src/components/client/ClientResponsiveContent/index.tsx +1 -1
  103. package/src/components/client/ClientResponsiveLayout.tsx +1 -1
  104. package/src/components/mdx/Image.tsx +1 -1
  105. package/src/components/mdx/Link.tsx +26 -9
  106. package/src/features/AlertBanner/CloudBanner.tsx +2 -3
  107. package/src/features/ChatInput/ActionBar/Model/ControlsForm.tsx +8 -7
  108. package/src/features/ChatInput/ActionBar/Params/Controls.tsx +1 -3
  109. package/src/features/ChatInput/ActionBar/Token/index.tsx +1 -1
  110. package/src/features/ChatInput/Mobile/index.tsx +1 -1
  111. package/src/features/Conversation/ChatItem/components/MessageContent/index.tsx +1 -1
  112. package/src/features/Conversation/Error/OllamaBizError/index.tsx +1 -1
  113. package/src/features/Conversation/Error/OllamaSetupGuide/Desktop.tsx +1 -2
  114. package/src/features/Conversation/Error/index.tsx +1 -1
  115. package/src/features/Conversation/Messages/AssistantGroup/Tool/Actions/Settings.tsx +1 -1
  116. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +1 -1
  117. package/src/features/Conversation/Messages/AssistantGroup/index.tsx +1 -1
  118. package/src/features/Conversation/Messages/Tool/Tool/index.tsx +1 -1
  119. package/src/features/Conversation/Messages/components/SearchGrounding.tsx +1 -1
  120. package/src/features/DataImporter/Error.tsx +3 -3
  121. package/src/features/DevPanel/CacheViewer/cacheProvider.tsx +2 -1
  122. package/src/features/DevPanel/MetadataViewer/Og.tsx +1 -1
  123. package/src/features/DevPanel/features/FloatPanel.tsx +1 -1
  124. package/src/features/DevPanel/features/Table/TooltipContent.tsx +3 -4
  125. package/src/features/DevPanel/index.tsx +1 -1
  126. package/src/features/EditorCanvas/InlineToolbar.tsx +1 -6
  127. package/src/features/FileViewer/NotSupport/index.tsx +2 -3
  128. package/src/features/Follow/index.tsx +8 -9
  129. package/src/features/LibraryModal/AddFilesToKnowledgeBase/SelectForm.tsx +2 -2
  130. package/src/features/MCP/Scores.tsx +1 -1
  131. package/src/features/MCPPluginDetail/Nav.tsx +8 -8
  132. package/src/features/MCPPluginDetail/Overview/TagList.tsx +1 -1
  133. package/src/features/MobileTabBar/index.tsx +2 -1
  134. package/src/features/OllamaSetupGuide/Desktop.tsx +1 -2
  135. package/src/features/PWAInstall/Install.tsx +1 -1
  136. package/src/features/PWAInstall/index.tsx +1 -1
  137. package/src/features/PluginStore/McpList/index.tsx +1 -1
  138. package/src/features/PluginStore/PluginList/Detail/Header.tsx +6 -6
  139. package/src/features/PluginsUI/Render/DefaultType/index.tsx +1 -1
  140. package/src/features/Portal/Artifacts/Body/Renderer/index.tsx +1 -1
  141. package/src/features/ResourceManager/components/ChunkDrawer/index.tsx +1 -1
  142. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +26 -26
  143. package/src/features/ResourceManager/components/Explorer/ToolBar/BatchActionsDropdown.tsx +147 -149
  144. package/src/features/ResourceManager/index.tsx +1 -1
  145. package/src/features/Setting/Footer.tsx +4 -5
  146. package/src/features/User/UserPanel/PanelContent.tsx +1 -1
  147. package/src/hooks/useActiveTabKey.ts +6 -3
  148. package/src/hooks/useIsSingleMode.test.ts +10 -24
  149. package/src/hooks/useIsSingleMode.ts +4 -2
  150. package/src/hooks/useIsSubSlug.ts +2 -1
  151. package/src/hooks/useQuery.ts +5 -5
  152. package/src/layout/GlobalProvider/AppTheme.tsx +2 -2
  153. package/src/layout/GlobalProvider/StyleRegistry.tsx +1 -1
  154. package/src/layout/GlobalProvider/useUserStateRedirect.ts +13 -25
  155. package/src/libs/next/Image.tsx +13 -0
  156. package/src/libs/next/Link.tsx +13 -0
  157. package/src/libs/next/dynamic.tsx +13 -0
  158. package/src/libs/next/index.ts +22 -0
  159. package/src/libs/next/navigation.ts +22 -0
  160. package/src/libs/router/Link.tsx +30 -0
  161. package/src/libs/router/index.ts +18 -0
  162. package/src/libs/router/navigation.ts +72 -0
  163. package/src/server/modules/AgentRuntime/AgentStateManager.ts +5 -1
  164. package/src/store/chat/slices/portal/selectors.test.ts +5 -15
  165. package/src/store/page/index.ts +1 -1
  166. package/src/store/page/slices/crud/index.ts +1 -1
  167. package/src/app/[variants]/(main)/hooks/usePathname.ts +0 -10
  168. package/src/app/[variants]/(main)/hooks/useQuery.ts +0 -12
  169. package/src/app/[variants]/(main)/hooks/useRouter.ts +0 -22
  170. package/src/app/[variants]/(main)/hooks/useSearchParams.ts +0 -11
@@ -29,168 +29,166 @@ interface BatchActionsDropdownProps {
29
29
  selectCount: number;
30
30
  }
31
31
 
32
- const BatchActionsDropdown = memo<BatchActionsDropdownProps>(
33
- ({ selectCount, onActionClick }) => {
34
- const { t } = useTranslation(['components', 'common', 'file', 'knowledgeBase']);
35
- const { modal, message } = App.useApp();
36
-
37
- const [libraryId, selectedFileIds] = useResourceManagerStore((s) => [
38
- s.libraryId,
39
- s.selectedFileIds,
40
- ]);
41
- const [useFetchKnowledgeBaseList, addFilesToKnowledgeBase] = useKnowledgeBaseStore((s) => [
42
- s.useFetchKnowledgeBaseList,
43
- s.addFilesToKnowledgeBase,
44
- ]);
45
- const { data: knowledgeBases } = useFetchKnowledgeBaseList();
46
-
47
- const menuItems = useMemo<DropdownItem[]>(() => {
48
- const items: DropdownItem[] = [];
49
-
50
- // Show delete library option only when in a knowledge base and no files selected
51
- if (libraryId && selectCount === 0) {
52
- items.push({
53
- danger: true,
54
- icon: <Icon icon={Trash2Icon} />,
55
- key: 'deleteLibrary',
56
- label: t('header.actions.deleteLibrary', { ns: 'file' }),
57
- onClick: async () => {
58
- modal.confirm({
59
- okButtonProps: {
60
- danger: true,
61
- },
62
- onOk: async () => {
63
- await onActionClick('deleteLibrary');
64
- },
65
- title: t('library.list.confirmRemoveLibrary', { ns: 'file' }),
66
- });
67
- },
68
- });
69
- return items;
70
- }
71
-
72
- // Filter out current knowledge base and create submenu items
73
- const availableKnowledgeBases = (knowledgeBases || []).filter((kb) => kb.id !== libraryId);
74
-
75
- const addToKnowledgeBaseSubmenu: DropdownItem[] = availableKnowledgeBases.map((kb) => ({
76
- disabled: selectCount === 0,
77
- icon: <RepoIcon />,
78
- key: `add-to-kb-${kb.id}`,
79
- label: <span style={{ marginLeft: 8 }}>{kb.name}</span>,
32
+ const BatchActionsDropdown = memo<BatchActionsDropdownProps>(({ selectCount, onActionClick }) => {
33
+ const { t } = useTranslation(['components', 'common', 'file', 'knowledgeBase']);
34
+ const { modal, message } = App.useApp();
35
+
36
+ const [libraryId, selectedFileIds] = useResourceManagerStore((s) => [
37
+ s.libraryId,
38
+ s.selectedFileIds,
39
+ ]);
40
+ const [useFetchKnowledgeBaseList, addFilesToKnowledgeBase] = useKnowledgeBaseStore((s) => [
41
+ s.useFetchKnowledgeBaseList,
42
+ s.addFilesToKnowledgeBase,
43
+ ]);
44
+ const { data: knowledgeBases } = useFetchKnowledgeBaseList();
45
+
46
+ const menuItems = useMemo<DropdownItem[]>(() => {
47
+ const items: DropdownItem[] = [];
48
+
49
+ // Show delete library option only when in a knowledge base and no files selected
50
+ if (libraryId && selectCount === 0) {
51
+ items.push({
52
+ danger: true,
53
+ icon: <Icon icon={Trash2Icon} />,
54
+ key: 'deleteLibrary',
55
+ label: t('header.actions.deleteLibrary', { ns: 'file' }),
80
56
  onClick: async () => {
81
- try {
82
- await addFilesToKnowledgeBase(kb.id, selectedFileIds);
83
- message.success(
84
- t('addToKnowledgeBase.addSuccess', {
85
- count: selectCount,
86
- ns: 'knowledgeBase',
87
- }),
88
- );
89
- } catch (e) {
90
- console.error(e);
91
- message.error(t('addToKnowledgeBase.error', { ns: 'knowledgeBase' }));
92
- }
57
+ modal.confirm({
58
+ okButtonProps: {
59
+ danger: true,
60
+ },
61
+ onOk: async () => {
62
+ await onActionClick('deleteLibrary');
63
+ },
64
+ title: t('library.list.confirmRemoveLibrary', { ns: 'file' }),
65
+ });
93
66
  },
94
- }));
95
-
96
- if (libraryId) {
97
- items.push({
98
- disabled: selectCount === 0,
99
- icon: <Icon icon={BookMinusIcon} />,
100
- key: 'removeFromKnowledgeBase',
101
- label: t('FileManager.actions.removeFromKnowledgeBase'),
102
- onClick: () => {
103
- modal.confirm({
104
- okButtonProps: {
105
- danger: true,
106
- },
107
- onOk: async () => {
108
- await onActionClick('removeFromKnowledgeBase');
109
- message.success(t('FileManager.actions.removeFromKnowledgeBaseSuccess'));
110
- },
111
- title: t('FileManager.actions.confirmRemoveFromKnowledgeBase', {
112
- count: selectCount,
113
- }),
114
- });
115
- },
116
- });
67
+ });
68
+ return items;
69
+ }
70
+
71
+ // Filter out current knowledge base and create submenu items
72
+ const availableKnowledgeBases = (knowledgeBases || []).filter((kb) => kb.id !== libraryId);
73
+
74
+ const addToKnowledgeBaseSubmenu: DropdownItem[] = availableKnowledgeBases.map((kb) => ({
75
+ disabled: selectCount === 0,
76
+ icon: <RepoIcon />,
77
+ key: `add-to-kb-${kb.id}`,
78
+ label: <span style={{ marginLeft: 8 }}>{kb.name}</span>,
79
+ onClick: async () => {
80
+ try {
81
+ await addFilesToKnowledgeBase(kb.id, selectedFileIds);
82
+ message.success(
83
+ t('addToKnowledgeBase.addSuccess', {
84
+ count: selectCount,
85
+ ns: 'knowledgeBase',
86
+ }),
87
+ );
88
+ } catch (e) {
89
+ console.error(e);
90
+ message.error(t('addToKnowledgeBase.error', { ns: 'knowledgeBase' }));
91
+ }
92
+ },
93
+ }));
117
94
 
118
- if (availableKnowledgeBases.length > 0) {
119
- items.push({
120
- children: addToKnowledgeBaseSubmenu as any,
121
- disabled: selectCount === 0,
122
- icon: <Icon icon={BookPlusIcon} />,
123
- key: 'addToOtherKnowledgeBase',
124
- label: t('FileManager.actions.addToOtherKnowledgeBase'),
95
+ if (libraryId) {
96
+ items.push({
97
+ disabled: selectCount === 0,
98
+ icon: <Icon icon={BookMinusIcon} />,
99
+ key: 'removeFromKnowledgeBase',
100
+ label: t('FileManager.actions.removeFromKnowledgeBase'),
101
+ onClick: () => {
102
+ modal.confirm({
103
+ okButtonProps: {
104
+ danger: true,
105
+ },
106
+ onOk: async () => {
107
+ await onActionClick('removeFromKnowledgeBase');
108
+ message.success(t('FileManager.actions.removeFromKnowledgeBaseSuccess'));
109
+ },
110
+ title: t('FileManager.actions.confirmRemoveFromKnowledgeBase', {
111
+ count: selectCount,
112
+ }),
125
113
  });
126
- }
127
- } else if (availableKnowledgeBases.length > 0) {
114
+ },
115
+ });
116
+
117
+ if (availableKnowledgeBases.length > 0) {
128
118
  items.push({
129
119
  children: addToKnowledgeBaseSubmenu as any,
130
120
  disabled: selectCount === 0,
131
121
  icon: <Icon icon={BookPlusIcon} />,
132
- key: 'addToKnowledgeBase',
133
- label: t('FileManager.actions.addToKnowledgeBase'),
122
+ key: 'addToOtherKnowledgeBase',
123
+ label: t('FileManager.actions.addToOtherKnowledgeBase'),
134
124
  });
135
125
  }
136
-
137
- items.push(
138
- {
139
- disabled: selectCount === 0,
140
- icon: <Icon icon={FileBoxIcon} />,
141
- key: 'batchChunking',
142
- label: t('FileManager.actions.batchChunking'),
143
- onClick: async () => {
144
- await onActionClick('batchChunking');
145
- },
146
- },
147
- {
148
- type: 'divider',
126
+ } else if (availableKnowledgeBases.length > 0) {
127
+ items.push({
128
+ children: addToKnowledgeBaseSubmenu as any,
129
+ disabled: selectCount === 0,
130
+ icon: <Icon icon={BookPlusIcon} />,
131
+ key: 'addToKnowledgeBase',
132
+ label: t('FileManager.actions.addToKnowledgeBase'),
133
+ });
134
+ }
135
+
136
+ items.push(
137
+ {
138
+ disabled: selectCount === 0,
139
+ icon: <Icon icon={FileBoxIcon} />,
140
+ key: 'batchChunking',
141
+ label: t('FileManager.actions.batchChunking'),
142
+ onClick: async () => {
143
+ await onActionClick('batchChunking');
149
144
  },
150
- {
151
- danger: true,
152
- disabled: selectCount === 0,
153
- icon: <Icon icon={Trash2Icon} />,
154
- key: 'delete',
155
- label: t('delete', { ns: 'common' }),
156
- onClick: async () => {
157
- modal.confirm({
158
- okButtonProps: {
159
- danger: true,
160
- },
161
- onOk: async () => {
162
- await onActionClick('delete');
163
- message.success(t('FileManager.actions.deleteSuccess'));
164
- },
165
- title: t('FileManager.actions.confirmDeleteMultiFiles', { count: selectCount }),
166
- });
167
- },
145
+ },
146
+ {
147
+ type: 'divider',
148
+ },
149
+ {
150
+ danger: true,
151
+ disabled: selectCount === 0,
152
+ icon: <Icon icon={Trash2Icon} />,
153
+ key: 'delete',
154
+ label: t('delete', { ns: 'common' }),
155
+ onClick: async () => {
156
+ modal.confirm({
157
+ okButtonProps: {
158
+ danger: true,
159
+ },
160
+ onOk: async () => {
161
+ await onActionClick('delete');
162
+ message.success(t('FileManager.actions.deleteSuccess'));
163
+ },
164
+ title: t('FileManager.actions.confirmDeleteMultiFiles', { count: selectCount }),
165
+ });
168
166
  },
169
- );
170
-
171
- return items;
172
- }, [
173
- libraryId,
174
- selectCount,
175
- selectedFileIds,
176
- onActionClick,
177
- addFilesToKnowledgeBase,
178
- t,
179
- modal,
180
- message,
181
- knowledgeBases,
182
- ]);
183
-
184
- return (
185
- <DropdownMenu items={menuItems} placement="bottomLeft">
186
- <ActionIconWithChevron
187
- icon={CircleEllipsisIcon}
188
- title={t('FileManager.actions.batchActions', 'Batch actions')}
189
- />
190
- </DropdownMenu>
167
+ },
191
168
  );
192
- },
193
- );
169
+
170
+ return items;
171
+ }, [
172
+ libraryId,
173
+ selectCount,
174
+ selectedFileIds,
175
+ onActionClick,
176
+ addFilesToKnowledgeBase,
177
+ t,
178
+ modal,
179
+ message,
180
+ knowledgeBases,
181
+ ]);
182
+
183
+ return (
184
+ <DropdownMenu items={menuItems} placement="bottomLeft">
185
+ <ActionIconWithChevron
186
+ icon={CircleEllipsisIcon}
187
+ title={t('FileManager.actions.batchActions', 'Batch actions')}
188
+ />
189
+ </DropdownMenu>
190
+ );
191
+ });
194
192
 
195
193
  BatchActionsDropdown.displayName = 'BatchActionsDropdown';
196
194
 
@@ -3,12 +3,12 @@
3
3
  import { BRANDING_NAME } from '@lobechat/business-const';
4
4
  import { Flexbox } from '@lobehub/ui';
5
5
  import { createStyles, cssVar } from 'antd-style';
6
- import dynamic from 'next/dynamic';
7
6
  import { memo, useEffect } from 'react';
8
7
  import { useSearchParams } from 'react-router-dom';
9
8
 
10
9
  import { useResourceManagerStore } from '@/app/[variants]/(main)/resource/features/store';
11
10
  import { PageEditor } from '@/features/PageEditor';
11
+ import dynamic from '@/libs/next/dynamic';
12
12
  import { documentService } from '@/services/document';
13
13
  import { useFileStore } from '@/store/file';
14
14
  import { documentSelectors } from '@/store/file/slices/document/selectors';
@@ -4,7 +4,6 @@ import { BRANDING_NAME } from '@lobechat/business-const';
4
4
  import { Center, Flexbox, Icon } from '@lobehub/ui';
5
5
  import { createStaticStyles } from 'antd-style';
6
6
  import { MessageSquareHeart } from 'lucide-react';
7
- import Link from 'next/link';
8
7
  import { type PropsWithChildren, memo, useState } from 'react';
9
8
  import { useTranslation } from 'react-i18next';
10
9
 
@@ -43,7 +42,7 @@ const Footer = memo<PropsWithChildren>(() => {
43
42
  >
44
43
  <div style={{ textAlign: 'center' }}>
45
44
  <Icon icon={MessageSquareHeart} /> {`${t('footer.title')} `}
46
- <Link
45
+ <a
47
46
  aria-label={'star'}
48
47
  href={GITHUB}
49
48
  onClick={(e) => {
@@ -52,9 +51,9 @@ const Footer = memo<PropsWithChildren>(() => {
52
51
  }}
53
52
  >
54
53
  {t('footer.action.star')}
55
- </Link>
54
+ </a>
56
55
  {` ${t('footer.and')} `}
57
- <Link
56
+ <a
58
57
  aria-label={'feedback'}
59
58
  href={GITHUB_ISSUES}
60
59
  onClick={(e) => {
@@ -63,7 +62,7 @@ const Footer = memo<PropsWithChildren>(() => {
63
62
  }}
64
63
  >
65
64
  {t('footer.action.feedback')}
66
- </Link>
65
+ </a>
67
66
  {' !'}
68
67
  </div>
69
68
  </Center>
@@ -1,6 +1,6 @@
1
1
  import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
2
2
  import { Flexbox } from '@lobehub/ui';
3
- import { useRouter } from 'next/navigation';
3
+ import { useRouter } from '@/libs/next/navigation';
4
4
  import { memo } from 'react';
5
5
  import { Link, useNavigate } from 'react-router-dom';
6
6
 
@@ -1,9 +1,10 @@
1
- import { usePathname, useSearchParams } from 'next/navigation';
1
+ import { usePathname , useSearchParams } from '@/libs/router/navigation';
2
2
 
3
3
  import { ProfileTabs, SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
4
4
 
5
5
  /**
6
6
  * Returns the active tab key (chat/market/settings/...)
7
+ * React Router version for SPA
7
8
  */
8
9
  export const useActiveTabKey = () => {
9
10
  const pathname = usePathname();
@@ -12,16 +13,18 @@ export const useActiveTabKey = () => {
12
13
 
13
14
  /**
14
15
  * Returns the active setting page key (?active=common/sync/agent/...)
16
+ * React Router version for SPA
15
17
  */
16
18
  export const useActiveSettingsKey = () => {
17
- const search = useSearchParams();
18
- const tabs = search.get('active');
19
+ const [searchParams] = useSearchParams();
20
+ const tabs = searchParams.get('active');
19
21
  if (!tabs) return SettingsTabs.Common;
20
22
  return tabs as SettingsTabs;
21
23
  };
22
24
 
23
25
  /**
24
26
  * Returns the active profile page key (profile/security/stats/...)
27
+ * React Router version for SPA
25
28
  */
26
29
  export const useActiveProfileKey = () => {
27
30
  const pathname = usePathname();
@@ -1,23 +1,18 @@
1
1
  import { renderHook } from '@testing-library/react';
2
- import { ReadonlyURLSearchParams } from 'next/navigation';
3
2
  import { describe, expect, it, vi } from 'vitest';
4
3
 
5
4
  import { useIsSingleMode } from './useIsSingleMode';
6
5
 
7
- // Mock next/navigation
6
+ // Mock react-router-dom useSearchParams (via the wrapper hook)
8
7
  const mockUseSearchParams = vi.hoisted(() => vi.fn());
9
- vi.mock('next/navigation', () => ({
8
+ vi.mock('@/libs/router/navigation', () => ({
10
9
  useSearchParams: mockUseSearchParams,
11
10
  }));
12
11
 
13
12
  describe('useIsSingleMode', () => {
14
-
15
13
  it('should return false initially (during SSR)', () => {
16
- const mockSearchParams = {
17
- get: vi.fn(() => 'single'),
18
- } as unknown as ReadonlyURLSearchParams;
19
-
20
- mockUseSearchParams.mockReturnValue(mockSearchParams);
14
+ const mockSearchParams = new URLSearchParams('mode=single');
15
+ mockUseSearchParams.mockReturnValue([mockSearchParams, vi.fn()]);
21
16
 
22
17
  const { result } = renderHook(() => useIsSingleMode());
23
18
 
@@ -26,11 +21,8 @@ describe('useIsSingleMode', () => {
26
21
  });
27
22
 
28
23
  it('should return true when mode=single', () => {
29
- const mockSearchParams = {
30
- get: vi.fn((key: string) => (key === 'mode' ? 'single' : null)),
31
- } as unknown as ReadonlyURLSearchParams;
32
-
33
- mockUseSearchParams.mockReturnValue(mockSearchParams);
24
+ const mockSearchParams = new URLSearchParams('mode=single');
25
+ mockUseSearchParams.mockReturnValue([mockSearchParams, vi.fn()]);
34
26
 
35
27
  const { result } = renderHook(() => useIsSingleMode());
36
28
 
@@ -39,11 +31,8 @@ describe('useIsSingleMode', () => {
39
31
  });
40
32
 
41
33
  it('should return false when mode is not single', () => {
42
- const mockSearchParams = {
43
- get: vi.fn((key: string) => (key === 'mode' ? 'normal' : null)),
44
- } as unknown as ReadonlyURLSearchParams;
45
-
46
- mockUseSearchParams.mockReturnValue(mockSearchParams);
34
+ const mockSearchParams = new URLSearchParams('mode=normal');
35
+ mockUseSearchParams.mockReturnValue([mockSearchParams, vi.fn()]);
47
36
 
48
37
  const { result } = renderHook(() => useIsSingleMode());
49
38
 
@@ -52,11 +41,8 @@ describe('useIsSingleMode', () => {
52
41
  });
53
42
 
54
43
  it('should return false when no mode parameter exists', () => {
55
- const mockSearchParams = {
56
- get: vi.fn(() => null),
57
- } as unknown as ReadonlyURLSearchParams;
58
-
59
- mockUseSearchParams.mockReturnValue(mockSearchParams);
44
+ const mockSearchParams = new URLSearchParams();
45
+ mockUseSearchParams.mockReturnValue([mockSearchParams, vi.fn()]);
60
46
 
61
47
  const { result } = renderHook(() => useIsSingleMode());
62
48
 
@@ -1,11 +1,13 @@
1
1
  'use client';
2
2
 
3
- import { useSearchParams } from 'next/navigation';
4
3
  import { useEffect, useState } from 'react';
5
4
 
5
+ import { useSearchParams } from '@/libs/router/navigation';
6
+
6
7
  /**
7
8
  * Hook to check if the current page is in single mode
8
9
  * Single mode is used for standalone windows in desktop app
10
+ * React Router version for SPA
9
11
  * @returns boolean indicating if the current page is in single mode
10
12
  */
11
13
  export const useIsSingleMode = (): boolean => {
@@ -16,7 +18,7 @@ export const useIsSingleMode = (): boolean => {
16
18
  setMounted(true);
17
19
  }, []);
18
20
 
19
- const searchParams = useSearchParams();
21
+ const [searchParams] = useSearchParams();
20
22
 
21
23
  useEffect(() => {
22
24
  if (mounted) {
@@ -1,7 +1,8 @@
1
- import { usePathname } from 'next/navigation';
1
+ import { usePathname } from '@/libs/router/navigation';
2
2
 
3
3
  /**
4
4
  * Returns true if the current path has a sub slug (`/chat/mobile` or `/chat/settings`)
5
+ * React Router version for SPA
5
6
  */
6
7
  export const useIsSubSlug = () => {
7
8
  const pathname = usePathname();
@@ -1,13 +1,13 @@
1
- import { useSearchParams } from 'next/navigation';
2
1
  import qs from 'query-string';
3
2
  import { useMemo } from 'react';
4
3
 
4
+ import { useSearchParams } from '@/libs/router/navigation';
5
+
5
6
  /**
6
7
  * Hook to get query parameters
7
- * This is the Next.js version
8
- * For React Router version, use useQuery from @/app/[variants]/(main)/hooks/useQuery
8
+ * React Router version for SPA
9
9
  */
10
10
  export const useQuery = () => {
11
- const rawQuery = useSearchParams();
12
- return useMemo(() => qs.parse(rawQuery.toString()), [rawQuery]);
11
+ const [searchParams] = useSearchParams();
12
+ return useMemo(() => qs.parse(searchParams.toString()), [searchParams]);
13
13
  };
@@ -13,15 +13,15 @@ import { createStaticStyles, cx, useTheme } from 'antd-style';
13
13
  import 'antd/dist/reset.css';
14
14
  import { AppConfigContext } from 'antd/es/app/context';
15
15
  import * as motion from 'motion/react-m';
16
- import Image from 'next/image';
17
- import Link from 'next/link';
18
16
  import { type ReactNode, memo, useEffect, useMemo, useState } from 'react';
19
17
 
20
18
  import AntdStaticMethods from '@/components/AntdStaticMethods';
19
+ import Link from '@/components/Link';
21
20
  import { LOBE_THEME_NEUTRAL_COLOR, LOBE_THEME_PRIMARY_COLOR } from '@/const/theme';
22
21
  import { isDesktop } from '@/const/version';
23
22
  import { useIsDark } from '@/hooks/useIsDark';
24
23
  import { getUILocaleAndResources } from '@/libs/getUILocaleAndResources';
24
+ import Image from '@/libs/next/Image';
25
25
  import { useGlobalStore } from '@/store/global';
26
26
  import { systemStatusSelectors } from '@/store/global/selectors';
27
27
  import { useUserStore } from '@/store/user';
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { StyleProvider } from 'antd-style';
4
- import { useServerInsertedHTML } from 'next/navigation';
4
+ import { useServerInsertedHTML } from '@/libs/next/navigation';
5
5
  import { type PropsWithChildren } from 'react';
6
6
 
7
7
  import { isDesktop } from '@/const/version';
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { OFFICIAL_URL } from '@lobechat/const';
4
- import { usePathname } from 'next/navigation';
5
4
  import { useCallback } from 'react';
6
5
 
7
6
  import { getDesktopOnboardingCompleted } from '@/app/[variants]/(desktop)/desktop-onboarding/storage';
@@ -11,20 +10,12 @@ import { useUserStore } from '@/store/user';
11
10
  import { onboardingSelectors } from '@/store/user/selectors';
12
11
  import type { UserInitializationState } from '@/types/user';
13
12
 
14
- const redirectIfNotOn = (currentPath: string | null | undefined, path: string) => {
15
- if (!currentPath?.startsWith(path)) {
13
+ const redirectIfNotOn = (currentPath: string, path: string) => {
14
+ if (!currentPath.startsWith(path)) {
16
15
  window.location.href = path;
17
16
  }
18
17
  };
19
18
 
20
- const useCurrentPathname = () => {
21
- const pathname = usePathname();
22
- return useCallback(() => {
23
- if (typeof window === 'undefined') return pathname;
24
- return window.location.pathname || pathname;
25
- }, [pathname]);
26
- };
27
-
28
19
  export const useDesktopUserStateRedirect = () => {
29
20
  const dataSyncConfig = useElectronStore((s) => s.dataSyncConfig);
30
21
  const logout = useUserStore((s) => s.logout);
@@ -69,25 +60,22 @@ export const useDesktopUserStateRedirect = () => {
69
60
  );
70
61
  };
71
62
 
72
- export const useWebUserStateRedirect = (getCurrentPathname: () => string | null | undefined) =>
73
- useCallback(
74
- (state: UserInitializationState) => {
75
- if (state.isInWaitList === true) {
76
- redirectIfNotOn(getCurrentPathname(), '/waitlist');
77
- return;
78
- }
63
+ export const useWebUserStateRedirect = () =>
64
+ useCallback((state: UserInitializationState) => {
65
+ const pathname = window.location.pathname;
66
+ if (state.isInWaitList === true) {
67
+ redirectIfNotOn(pathname, '/waitlist');
68
+ return;
69
+ }
79
70
 
80
- if (!onboardingSelectors.needsOnboarding(state)) return;
71
+ if (!onboardingSelectors.needsOnboarding(state)) return;
81
72
 
82
- redirectIfNotOn(getCurrentPathname(), '/onboarding');
83
- },
84
- [getCurrentPathname],
85
- );
73
+ redirectIfNotOn(pathname, '/onboarding');
74
+ }, []);
86
75
 
87
76
  export const useUserStateRedirect = () => {
88
- const getCurrentPathname = useCurrentPathname();
89
77
  const desktopRedirect = useDesktopUserStateRedirect();
90
- const webRedirect = useWebUserStateRedirect(getCurrentPathname);
78
+ const webRedirect = useWebUserStateRedirect();
91
79
 
92
80
  return useCallback(
93
81
  (state: UserInitializationState) => {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Image component wrapper for Next.js Image.
3
+ * This module provides a unified interface that can be easily replaced
4
+ * with a generic <img> or custom image component in the future.
5
+ *
6
+ * @see Phase 3.4: LOBE-2991
7
+ */
8
+
9
+ // Re-export the Image component
10
+
11
+ // Re-export types
12
+ export type { ImageProps, StaticImageData } from 'next/image';
13
+ export { default } from 'next/image';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Link component wrapper for Next.js Link.
3
+ * This module provides a unified interface that can be easily replaced
4
+ * with react-router-dom Link in the future.
5
+ *
6
+ * @see Phase 3.2: LOBE-2989
7
+ */
8
+
9
+ // Re-export the Link component
10
+
11
+ // Re-export the type for props
12
+ export type { LinkProps } from 'next/link';
13
+ export { default } from 'next/link';