@lobehub/lobehub 2.0.0-next.343 → 2.0.0-next.345

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 (169) hide show
  1. package/.cursor/rules/i18n.mdc +1 -1
  2. package/.cursor/rules/modal-imperative.mdc +162 -0
  3. package/.cursor/rules/rules-index.mdc +1 -0
  4. package/.env.example +0 -14
  5. package/.eslintrc.js +8 -1
  6. package/CHANGELOG.md +66 -0
  7. package/Dockerfile +3 -13
  8. package/README.md +3 -5
  9. package/README.zh-CN.md +3 -5
  10. package/changelog/v1.json +24 -0
  11. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +11 -42
  12. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +10 -41
  13. package/e2e/src/support/webServer.ts +2 -0
  14. package/locales/ar/error.json +0 -4
  15. package/locales/bg-BG/error.json +0 -4
  16. package/locales/de-DE/error.json +0 -4
  17. package/locales/en-US/error.json +0 -4
  18. package/locales/es-ES/error.json +0 -4
  19. package/locales/fa-IR/error.json +0 -4
  20. package/locales/fr-FR/error.json +0 -4
  21. package/locales/it-IT/error.json +0 -4
  22. package/locales/ja-JP/error.json +0 -4
  23. package/locales/ko-KR/error.json +0 -4
  24. package/locales/nl-NL/error.json +0 -4
  25. package/locales/pl-PL/error.json +0 -4
  26. package/locales/pt-BR/error.json +0 -4
  27. package/locales/ru-RU/error.json +0 -4
  28. package/locales/tr-TR/error.json +0 -4
  29. package/locales/vi-VN/error.json +0 -4
  30. package/locales/zh-CN/error.json +0 -4
  31. package/locales/zh-TW/error.json +0 -4
  32. package/package.json +7 -9
  33. package/packages/builtin-agents/package.json +2 -0
  34. package/packages/builtin-agents/src/agents/agent-builder/index.ts +4 -2
  35. package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +4 -2
  36. package/packages/builtin-agents/src/agents/page-agent/index.ts +5 -2
  37. package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +161 -12
  38. package/packages/context-engine/src/engine/messages/MessagesEngine.ts +9 -9
  39. package/packages/context-engine/src/providers/GroupContextInjector.ts +19 -33
  40. package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +79 -43
  41. package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +5 -15
  42. package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts +24 -3
  43. package/packages/model-bank/src/modelProviders/comfyui.ts +0 -1
  44. package/packages/model-bank/src/modelProviders/fal.ts +0 -1
  45. package/packages/types/src/fetch.ts +1 -2
  46. package/packages/utils/src/server/__tests__/auth.test.ts +0 -47
  47. package/packages/utils/src/server/auth.ts +1 -9
  48. package/scripts/_shared/checkDeprecatedClerkEnv.js +42 -0
  49. package/scripts/changelogWorkflow/buildStaticChangelog.ts +2 -1
  50. package/scripts/clerk-to-betterauth/_internal/types.ts +53 -20
  51. package/scripts/clerk-to-betterauth/export-clerk-users-with-api.ts +43 -36
  52. package/scripts/countEnWord.ts +1 -1
  53. package/scripts/electronWorkflow/modifiers/appCode.mts +2 -131
  54. package/scripts/i18nWorkflow/protectedPatterns.ts +1 -2
  55. package/scripts/prebuild.mts +10 -8
  56. package/scripts/serverLauncher/startServer.js +23 -5
  57. package/src/app/(backend)/middleware/auth/index.test.ts +8 -4
  58. package/src/app/(backend)/middleware/auth/index.ts +0 -15
  59. package/src/app/(backend)/middleware/auth/utils.test.ts +0 -28
  60. package/src/app/(backend)/middleware/auth/utils.ts +2 -17
  61. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +3 -51
  62. package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -4
  63. package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +7 -6
  64. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -16
  65. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/index.tsx +1 -1
  66. package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +13 -13
  67. package/src/app/[variants]/(main)/home/features/RecentPage/Item.tsx +2 -2
  68. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +3 -21
  69. package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -2
  70. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -22
  71. package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +12 -14
  72. package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +8 -14
  73. package/src/app/[variants]/(main)/settings/skill/index.tsx +7 -5
  74. package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +2 -35
  75. package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -20
  76. package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -2
  77. package/src/app/[variants]/(mobile)/me/profile/features/Category.tsx +3 -13
  78. package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +2 -3
  79. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
  80. package/src/app/[variants]/share/t/[id]/index.tsx +1 -1
  81. package/src/app/robots.tsx +1 -1
  82. package/src/envs/auth.ts +2 -27
  83. package/src/envs/llm.ts +2 -2
  84. package/src/features/AgentSetting/AgentPlugin/index.tsx +9 -12
  85. package/src/features/ChatInput/ActionBar/Tools/index.tsx +7 -5
  86. package/src/features/ChatMiniMap/utils.ts +1 -1
  87. package/src/features/CommandMenu/SearchResults.tsx +1 -1
  88. package/src/features/Conversation/ChatList/components/AutoScroll/DebugInspector.tsx +166 -0
  89. package/src/features/Conversation/ChatList/components/AutoScroll/index.tsx +86 -0
  90. package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +11 -17
  91. package/src/features/Conversation/Messages/AgentCouncil/components/AutoScrollShadow.tsx +25 -14
  92. package/src/features/Conversation/Messages/AgentCouncil/components/CouncilMember.tsx +1 -1
  93. package/src/features/IntegrationDetailModal/IntegrationDetailContent.tsx +305 -0
  94. package/src/features/IntegrationDetailModal/index.tsx +21 -283
  95. package/src/features/MCPPluginDetail/Deployment/index.tsx +1 -1
  96. package/src/features/MCPPluginDetail/Schema/Prompts.tsx +1 -1
  97. package/src/features/MCPPluginDetail/Schema/Tools.tsx +1 -1
  98. package/src/features/ProfileEditor/AgentTool.tsx +14 -20
  99. package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/NoteFileItem.tsx +1 -1
  100. package/src/features/SkillStore/LobeHubList/index.tsx +50 -87
  101. package/src/features/SkillStore/Search/index.tsx +1 -1
  102. package/src/features/SkillStore/{Content.tsx → SkillStoreContent.tsx} +3 -8
  103. package/src/features/SkillStore/index.tsx +15 -33
  104. package/src/features/User/UserPanel/PanelContent.tsx +0 -8
  105. package/src/features/User/__tests__/PanelContent.test.tsx +1 -35
  106. package/src/features/User/__tests__/UserAvatar.test.tsx +30 -57
  107. package/src/features/User/__tests__/useMenu.test.tsx +2 -43
  108. package/src/layout/AuthProvider/index.tsx +0 -5
  109. package/src/libs/next/config/define-config.ts +6 -0
  110. package/src/libs/next/proxy/createRouteMatcher.test.ts +121 -0
  111. package/src/libs/next/proxy/createRouteMatcher.ts +18 -0
  112. package/src/libs/next/proxy/define-config.ts +4 -53
  113. package/src/libs/next-auth/adapter/index.ts +1 -2
  114. package/src/libs/oidc-provider/provider.test.ts +5 -316
  115. package/src/libs/trpc/lambda/context.test.ts +0 -13
  116. package/src/libs/trpc/lambda/context.ts +3 -22
  117. package/src/libs/trpc/middleware/userAuth.ts +2 -4
  118. package/src/libs/trusted-client/getSessionUser.ts +2 -17
  119. package/src/locales/default/error.ts +0 -6
  120. package/src/locales/default/index.ts +0 -2
  121. package/src/proxy.ts +0 -1
  122. package/src/server/routers/lambda/__tests__/user.test.ts +0 -71
  123. package/src/server/routers/lambda/user.ts +6 -63
  124. package/src/server/services/changelog/index.test.ts +3 -2
  125. package/src/server/services/changelog/index.ts +1 -1
  126. package/src/server/services/user/index.ts +0 -83
  127. package/src/services/chat/index.ts +1 -2
  128. package/src/services/chat/mecha/agentConfigResolver.test.ts +43 -0
  129. package/src/services/chat/mecha/agentConfigResolver.ts +3 -1
  130. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +58 -14
  131. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +10 -2
  132. package/src/store/user/slices/auth/action.test.ts +1 -81
  133. package/src/store/user/slices/auth/action.ts +3 -28
  134. package/src/store/user/slices/auth/initialState.ts +1 -18
  135. package/src/store/user/slices/auth/selectors.test.ts +2 -127
  136. package/src/store/user/slices/auth/selectors.ts +1 -21
  137. package/src/utils/errorResponse.ts +1 -4
  138. package/src/utils/markdownToTxt.ts +20 -0
  139. package/locales/ar/clerk.json +0 -545
  140. package/locales/bg-BG/clerk.json +0 -545
  141. package/locales/de-DE/clerk.json +0 -545
  142. package/locales/en-US/clerk.json +0 -545
  143. package/locales/es-ES/clerk.json +0 -545
  144. package/locales/fa-IR/clerk.json +0 -545
  145. package/locales/fr-FR/clerk.json +0 -545
  146. package/locales/it-IT/clerk.json +0 -545
  147. package/locales/ja-JP/clerk.json +0 -545
  148. package/locales/ko-KR/clerk.json +0 -545
  149. package/locales/nl-NL/clerk.json +0 -545
  150. package/locales/pl-PL/clerk.json +0 -545
  151. package/locales/pt-BR/clerk.json +0 -545
  152. package/locales/ru-RU/clerk.json +0 -545
  153. package/locales/tr-TR/clerk.json +0 -545
  154. package/locales/vi-VN/clerk.json +0 -545
  155. package/locales/zh-CN/clerk.json +0 -545
  156. package/locales/zh-TW/clerk.json +0 -545
  157. package/src/app/(backend)/api/webhooks/clerk/__tests__/fixtures/createUser.json +0 -73
  158. package/src/app/(backend)/api/webhooks/clerk/route.ts +0 -95
  159. package/src/app/(backend)/api/webhooks/clerk/validateRequest.ts +0 -22
  160. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +0 -27
  161. package/src/app/[variants]/(main)/settings/security/features/ClerkProfile.tsx +0 -67
  162. package/src/features/Conversation/ChatList/components/AutoScroll.tsx +0 -25
  163. package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +0 -40
  164. package/src/layout/AuthProvider/Clerk/index.tsx +0 -54
  165. package/src/layout/AuthProvider/Clerk/useAppearance.ts +0 -133
  166. package/src/libs/clerk-auth/index.test.ts +0 -216
  167. package/src/libs/clerk-auth/index.ts +0 -80
  168. package/src/locales/default/clerk.ts +0 -677
  169. package/src/server/services/user/index.test.ts +0 -220
@@ -18,7 +18,7 @@ import KlavisServerItem from '@/features/ChatInput/ActionBar/Tools/KlavisServerI
18
18
  import LobehubSkillServerItem from '@/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem';
19
19
  import ToolItem from '@/features/ChatInput/ActionBar/Tools/ToolItem';
20
20
  import ActionDropdown from '@/features/ChatInput/ActionBar/components/ActionDropdown';
21
- import SkillStore from '@/features/SkillStore';
21
+ import { createSkillStoreModal } from '@/features/SkillStore';
22
22
  import { useCheckPluginsIsInstalled } from '@/hooks/useCheckPluginsIsInstalled';
23
23
  import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
24
24
  import { useAgentStore } from '@/store/agent';
@@ -157,8 +157,6 @@ const AgentTool = memo<AgentToolProps>(
157
157
  const allLobehubSkillServers = useToolStore(lobehubSkillStoreSelectors.getServers, isEqual);
158
158
  const isLobehubSkillEnabled = useServerConfigStore(serverConfigSelectors.enableLobehubSkill);
159
159
 
160
- // Plugin store modal state
161
- const [modalOpen, setModalOpen] = useState(false);
162
160
  const [updating, setUpdating] = useState(false);
163
161
 
164
162
  // Tab state for dual-column layout
@@ -423,7 +421,7 @@ const AgentTool = memo<AgentToolProps>(
423
421
  key: 'plugin-store',
424
422
  label: t('tools.plugins.store'),
425
423
  onClick: () => {
426
- setModalOpen(true);
424
+ createSkillStoreModal();
427
425
  },
428
426
  },
429
427
  ],
@@ -556,20 +554,7 @@ const AgentTool = memo<AgentToolProps>(
556
554
  <>
557
555
  {/* Plugin Selector and Tags */}
558
556
  <Flexbox align="center" gap={8} horizontal wrap={'wrap'}>
559
- {/* Second Row: Selected Plugins as Tags */}
560
- {allEnabledTools.map((pluginId) => {
561
- return (
562
- <PluginTag
563
- key={pluginId}
564
- onRemove={handleRemovePlugin(pluginId)}
565
- pluginId={pluginId}
566
- showDesktopOnlyLabel={filterAvailableInWeb}
567
- useAllMetaList={useAllMetaList}
568
- />
569
- );
570
- })}
571
557
  {/* Plugin Selector Dropdown - Using Action component pattern */}
572
-
573
558
  <Suspense fallback={button}>
574
559
  <ActionDropdown
575
560
  maxHeight={500}
@@ -622,10 +607,19 @@ const AgentTool = memo<AgentToolProps>(
622
607
  {button}
623
608
  </ActionDropdown>
624
609
  </Suspense>
610
+ {/* Selected Plugins as Tags */}
611
+ {allEnabledTools.map((pluginId) => {
612
+ return (
613
+ <PluginTag
614
+ key={pluginId}
615
+ onRemove={handleRemovePlugin(pluginId)}
616
+ pluginId={pluginId}
617
+ showDesktopOnlyLabel={filterAvailableInWeb}
618
+ useAllMetaList={useAllMetaList}
619
+ />
620
+ );
621
+ })}
625
622
  </Flexbox>
626
-
627
- {/* PluginStore Modal - rendered outside Flexbox to avoid event interference */}
628
- {modalOpen && <SkillStore open={modalOpen} setOpen={setModalOpen} />}
629
623
  </>
630
624
  );
631
625
  },
@@ -2,7 +2,6 @@ import { Button, Tooltip } from '@lobehub/ui';
2
2
  import { createStaticStyles, cx } from 'antd-style';
3
3
  import { isNull } from 'es-toolkit/compat';
4
4
  import { FileBoxIcon } from 'lucide-react';
5
- import markdownToTxt from 'markdown-to-txt';
6
5
  import { memo } from 'react';
7
6
  import { useTranslation } from 'react-i18next';
8
7
 
@@ -10,6 +9,7 @@ import FileIcon from '@/components/FileIcon';
10
9
  import { fileManagerSelectors, useFileStore } from '@/store/file';
11
10
  import { type AsyncTaskStatus, type IAsyncTaskError } from '@/types/asyncTask';
12
11
  import { isChunkingUnsupported } from '@/utils/isChunkingUnsupported';
12
+ import markdownToTxt from '@/utils/markdownToTxt';
13
13
 
14
14
  import ChunksBadge from '../../ListView/ListItem/ChunkTag';
15
15
 
@@ -3,10 +3,9 @@
3
3
  import { KLAVIS_SERVER_TYPES, LOBEHUB_SKILL_PROVIDERS } from '@lobechat/const';
4
4
  import { createStaticStyles } from 'antd-style';
5
5
  import isEqual from 'fast-deep-equal';
6
- import type { Klavis } from 'klavis';
7
- import { memo, useMemo, useState } from 'react';
6
+ import { memo, useCallback, useMemo } from 'react';
8
7
 
9
- import IntegrationDetailModal from '@/features/IntegrationDetailModal';
8
+ import { createIntegrationDetailModal } from '@/features/IntegrationDetailModal';
10
9
  import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
11
10
  import { useToolStore } from '@/store/tool';
12
11
  import { klavisStoreSelectors, lobehubSkillStoreSelectors } from '@/store/tool/selectors';
@@ -15,7 +14,6 @@ import { LobehubSkillStatus } from '@/store/tool/slices/lobehubSkillStore/types'
15
14
 
16
15
  import Empty from '../Empty';
17
16
  import Item from './Item';
18
- import { useSkillConnect } from './useSkillConnect';
19
17
 
20
18
  const styles = createStaticStyles(({ css }) => ({
21
19
  grid: css`
@@ -36,40 +34,7 @@ interface LobeHubListProps {
36
34
  keywords: string;
37
35
  }
38
36
 
39
- interface DetailState {
40
- identifier: string;
41
- serverName?: Klavis.McpServerName;
42
- type: 'klavis' | 'lobehub';
43
- }
44
-
45
- interface DetailModalWithConnectProps {
46
- detailState: DetailState;
47
- onClose: () => void;
48
- }
49
-
50
- const DetailModalWithConnect = memo<DetailModalWithConnectProps>(({ detailState, onClose }) => {
51
- const { handleConnect, isConnecting } = useSkillConnect({
52
- identifier: detailState.identifier,
53
- serverName: detailState.serverName,
54
- type: detailState.type,
55
- });
56
-
57
- return (
58
- <IntegrationDetailModal
59
- identifier={detailState.identifier}
60
- isConnecting={isConnecting}
61
- onClose={onClose}
62
- onConnect={handleConnect}
63
- open
64
- type={detailState.type}
65
- />
66
- );
67
- });
68
-
69
- DetailModalWithConnect.displayName = 'DetailModalWithConnect';
70
-
71
37
  export const LobeHubList = memo<LobeHubListProps>(({ keywords }) => {
72
- const [detailState, setDetailState] = useState<DetailState | null>(null);
73
38
 
74
39
  const isLobehubSkillEnabled = useServerConfigStore(serverConfigSelectors.enableLobehubSkill);
75
40
  const isKlavisEnabled = useServerConfigStore(serverConfigSelectors.enableKlavis);
@@ -84,13 +49,19 @@ export const LobeHubList = memo<LobeHubListProps>(({ keywords }) => {
84
49
  useFetchLobehubSkillConnections(isLobehubSkillEnabled);
85
50
  useFetchUserKlavisServers(isKlavisEnabled);
86
51
 
87
- const getLobehubSkillServerByProvider = (providerId: string) => {
88
- return allLobehubSkillServers.find((server) => server.identifier === providerId);
89
- };
52
+ const getLobehubSkillServerByProvider = useCallback(
53
+ (providerId: string) => {
54
+ return allLobehubSkillServers.find((server) => server.identifier === providerId);
55
+ },
56
+ [allLobehubSkillServers],
57
+ );
90
58
 
91
- const getKlavisServerByIdentifier = (identifier: string) => {
92
- return allKlavisServers.find((server) => server.identifier === identifier);
93
- };
59
+ const getKlavisServerByIdentifier = useCallback(
60
+ (identifier: string) => {
61
+ return allKlavisServers.find((server) => server.identifier === identifier);
62
+ },
63
+ [allKlavisServers],
64
+ );
94
65
 
95
66
  const filteredItems = useMemo(() => {
96
67
  const items: Array<
@@ -127,57 +98,49 @@ export const LobeHubList = memo<LobeHubListProps>(({ keywords }) => {
127
98
  if (filteredItems.length === 0) return <Empty search={hasSearchKeywords} />;
128
99
 
129
100
  return (
130
- <>
131
- <div className={styles.grid}>
132
- {filteredItems.map((item) => {
133
- if (item.type === 'lobehub') {
134
- const server = getLobehubSkillServerByProvider(item.provider.id);
135
- const isConnected = server?.status === LobehubSkillStatus.CONNECTED;
136
- return (
137
- <Item
138
- description={item.provider.description}
139
- icon={item.provider.icon}
140
- identifier={item.provider.id}
141
- isConnected={isConnected}
142
- key={item.provider.id}
143
- label={item.provider.label}
144
- onOpenDetail={() =>
145
- setDetailState({ identifier: item.provider.id, type: 'lobehub' })
146
- }
147
- type="lobehub"
148
- />
149
- );
150
- }
151
- const server = getKlavisServerByIdentifier(item.serverType.identifier);
152
- const isConnected = server?.status === KlavisServerStatus.CONNECTED;
101
+ <div className={styles.grid}>
102
+ {filteredItems.map((item) => {
103
+ if (item.type === 'lobehub') {
104
+ const server = getLobehubSkillServerByProvider(item.provider.id);
105
+ const isConnected = server?.status === LobehubSkillStatus.CONNECTED;
153
106
  return (
154
107
  <Item
155
- description={item.serverType.description}
156
- icon={item.serverType.icon}
157
- identifier={item.serverType.identifier}
108
+ description={item.provider.description}
109
+ icon={item.provider.icon}
110
+ identifier={item.provider.id}
158
111
  isConnected={isConnected}
159
- key={item.serverType.identifier}
160
- label={item.serverType.label}
112
+ key={item.provider.id}
113
+ label={item.provider.label}
161
114
  onOpenDetail={() =>
162
- setDetailState({
163
- identifier: item.serverType.identifier,
164
- serverName: item.serverType.serverName,
165
- type: 'klavis',
166
- })
115
+ createIntegrationDetailModal({ identifier: item.provider.id, type: 'lobehub' })
167
116
  }
168
- serverName={item.serverType.serverName}
169
- type="klavis"
117
+ type="lobehub"
170
118
  />
171
119
  );
172
- })}
173
- </div>
174
- {detailState && (
175
- <DetailModalWithConnect
176
- detailState={detailState}
177
- onClose={() => setDetailState(null)}
178
- />
179
- )}
180
- </>
120
+ }
121
+ const server = getKlavisServerByIdentifier(item.serverType.identifier);
122
+ const isConnected = server?.status === KlavisServerStatus.CONNECTED;
123
+ return (
124
+ <Item
125
+ description={item.serverType.description}
126
+ icon={item.serverType.icon}
127
+ identifier={item.serverType.identifier}
128
+ isConnected={isConnected}
129
+ key={item.serverType.identifier}
130
+ label={item.serverType.label}
131
+ onOpenDetail={() =>
132
+ createIntegrationDetailModal({
133
+ identifier: item.serverType.identifier,
134
+ serverName: item.serverType.serverName,
135
+ type: 'klavis',
136
+ })
137
+ }
138
+ serverName={item.serverType.serverName}
139
+ type="klavis"
140
+ />
141
+ );
142
+ })}
143
+ </div>
181
144
  );
182
145
  });
183
146
 
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import { useToolStore } from '@/store/tool';
8
8
 
9
- import { SkillStoreTab } from '../Content';
9
+ import { SkillStoreTab } from '../SkillStoreContent';
10
10
 
11
11
  interface SearchProps {
12
12
  activeTab: SkillStoreTab;
@@ -2,11 +2,10 @@
2
2
 
3
3
  import { Flexbox, Segmented } from '@lobehub/ui';
4
4
  import { type SegmentedOptions } from 'antd/es/segmented';
5
- import { memo, useState } from 'react';
5
+ import { useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import AddSkillButton from './AddSkillButton';
9
-
10
9
  import CommunityList from './CommunityList';
11
10
  import LobeHubList from './LobeHubList';
12
11
  import Search from './Search';
@@ -16,7 +15,7 @@ export enum SkillStoreTab {
16
15
  LobeHub = 'lobehub',
17
16
  }
18
17
 
19
- export const Content = memo(() => {
18
+ export const SkillStoreContent = () => {
20
19
  const { t } = useTranslation('setting');
21
20
  const [activeTab, setActiveTab] = useState<SkillStoreTab>(SkillStoreTab.LobeHub);
22
21
  const [lobehubKeywords, setLobehubKeywords] = useState('');
@@ -52,8 +51,4 @@ export const Content = memo(() => {
52
51
  </Flexbox>
53
52
  </Flexbox>
54
53
  );
55
- });
56
-
57
- Content.displayName = 'SkillStoreContent';
58
-
59
- export default Content;
54
+ };
@@ -1,37 +1,19 @@
1
1
  'use client';
2
2
 
3
- import { Modal } from '@lobehub/ui';
4
- import { memo } from 'react';
5
- import { useTranslation } from 'react-i18next';
3
+ import { createModal } from '@lobehub/ui';
4
+ import { t } from 'i18next';
6
5
 
7
- import Content from './Content';
6
+ import { SkillStoreContent } from './SkillStoreContent';
8
7
 
9
- interface SkillStoreProps {
10
- open: boolean;
11
- setOpen: (open: boolean) => void;
12
- }
13
-
14
- export const SkillStore = memo<SkillStoreProps>(({ open, setOpen }) => {
15
- const { t } = useTranslation('setting');
16
-
17
- return (
18
- <Modal
19
- allowFullscreen
20
- destroyOnClose={false}
21
- footer={null}
22
- onCancel={() => setOpen(false)}
23
- open={open}
24
- styles={{
25
- body: { overflow: 'hidden', padding: 0 },
26
- }}
27
- title={t('skillStore.title')}
28
- width={'min(80%, 800px)'}
29
- >
30
- <Content />
31
- </Modal>
32
- );
33
- });
34
-
35
- SkillStore.displayName = 'SkillStore';
36
-
37
- export default SkillStore;
8
+ export const createSkillStoreModal = () =>
9
+ createModal({
10
+ allowFullscreen: true,
11
+ children: <SkillStoreContent />,
12
+ destroyOnHidden: false,
13
+ footer: null,
14
+ styles: {
15
+ body: { overflow: 'hidden', padding: 0 },
16
+ },
17
+ title: t('skillStore.title', { ns: 'setting' }),
18
+ width: 'min(80%, 800px)',
19
+ });
@@ -10,8 +10,6 @@ import BusinessPanelContent from '@/business/client/features/User/BusinessPanelC
10
10
  import BrandWatermark from '@/components/BrandWatermark';
11
11
  import Menu from '@/components/Menu';
12
12
  import { isDesktop } from '@/const/version';
13
- import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
14
- import { useRouter } from '@/libs/next/navigation';
15
13
  import { useUserStore } from '@/store/user';
16
14
  import { authSelectors } from '@/store/user/selectors';
17
15
 
@@ -22,8 +20,6 @@ import LangButton from './LangButton';
22
20
  import { useMenu } from './useMenu';
23
21
 
24
22
  const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
25
- const router = useRouter();
26
-
27
23
  const isLoginWithAuth = useUserStore(authSelectors.isLoginWithAuth);
28
24
  const [openSignIn, signOut] = useUserStore((s) => [s.openLogin, s.logout]);
29
25
  const { mainItems, logoutItems } = useMenu();
@@ -52,10 +48,6 @@ const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
52
48
 
53
49
  signOut();
54
50
  closePopover();
55
- // NextAuth and Better Auth handle redirect in their own signOut methods
56
- if (enableNextAuth || enableBetterAuth) return;
57
- // Clerk uses /login page
58
- router.push('/login');
59
51
  };
60
52
 
61
53
  return (
@@ -68,19 +68,11 @@ vi.mock('@/const/version', () => ({
68
68
  }));
69
69
 
70
70
  // Use vi.hoisted to ensure variables exist before vi.mock factory executes
71
- const { enableAuth, enableClerk, enableNextAuth } = vi.hoisted(() => ({
72
- enableAuth: { value: true },
73
- enableClerk: { value: false },
71
+ const { enableNextAuth } = vi.hoisted(() => ({
74
72
  enableNextAuth: { value: false },
75
73
  }));
76
74
 
77
75
  vi.mock('@/envs/auth', () => ({
78
- get enableAuth() {
79
- return enableAuth.value;
80
- },
81
- get enableClerk() {
82
- return enableClerk.value;
83
- },
84
76
  get enableNextAuth() {
85
77
  return enableNextAuth.value;
86
78
  },
@@ -140,32 +132,6 @@ describe('PanelContent', () => {
140
132
  });
141
133
  });
142
134
 
143
- describe('disable auth', () => {
144
- it('should render UserInfo', () => {
145
- act(() => {
146
- useUserStore.setState({ isSignedIn: true });
147
- });
148
-
149
- renderWithRouter(<PanelContent closePopover={closePopover} />);
150
-
151
- expect(screen.getByText('Mocked UserInfo')).toBeInTheDocument();
152
- expect(screen.getByText('Mocked DataStatistics')).toBeInTheDocument();
153
- expect(screen.queryByText('Mocked SignInBlock')).not.toBeInTheDocument();
154
- });
155
-
156
- it('should render BrandWatermark when disable auth', () => {
157
- enableAuth.value = false;
158
-
159
- act(() => {
160
- useUserStore.setState({ isSignedIn: false });
161
- });
162
-
163
- renderWithRouter(<PanelContent closePopover={closePopover} />);
164
-
165
- expect(screen.getByText('Mocked BrandWatermark')).toBeInTheDocument();
166
- });
167
- });
168
-
169
135
  it('should render Menu with main items', () => {
170
136
  renderWithRouter(<PanelContent closePopover={closePopover} />);
171
137
 
@@ -10,87 +10,60 @@ import UserAvatar from '../UserAvatar';
10
10
  vi.mock('zustand/traditional');
11
11
 
12
12
  // Use vi.hoisted to ensure variables exist before vi.mock factory executes
13
- const { enableAuth, enableClerk, enableNextAuth } = vi.hoisted(() => ({
14
- enableAuth: { value: true },
15
- enableClerk: { value: false },
13
+ const { enableNextAuth } = vi.hoisted(() => ({
16
14
  enableNextAuth: { value: false },
17
15
  }));
18
16
 
19
17
  vi.mock('@/envs/auth', () => ({
20
- get enableAuth() {
21
- return enableAuth.value;
22
- },
23
- get enableClerk() {
24
- return enableClerk.value;
25
- },
26
18
  get enableNextAuth() {
27
19
  return enableNextAuth.value;
28
20
  },
29
21
  }));
30
22
 
31
23
  afterEach(() => {
32
- enableAuth.value = true;
33
- enableClerk.value = false;
34
24
  enableNextAuth.value = false;
35
25
  });
36
26
 
37
27
  describe('UserAvatar', () => {
38
- describe('enable Auth', () => {
39
- it('should show the username and avatar are displayed when the user is logged in', async () => {
40
- const mockAvatar = 'https://example.com/avatar.png';
41
- const mockUsername = 'teeeeeestuser';
42
-
43
- act(() => {
44
- useUserStore.setState({
45
- enableAuth: () => true,
46
- isSignedIn: true,
47
- user: { avatar: mockAvatar, id: 'abc', username: mockUsername },
48
- });
28
+ it('should show the username and avatar are displayed when the user is logged in', async () => {
29
+ const mockAvatar = 'https://example.com/avatar.png';
30
+ const mockUsername = 'teeeeeestuser';
31
+
32
+ act(() => {
33
+ useUserStore.setState({
34
+ isSignedIn: true,
35
+ user: { avatar: mockAvatar, id: 'abc', username: mockUsername },
49
36
  });
50
-
51
- render(<UserAvatar />);
52
-
53
- expect(screen.getByAltText(mockUsername)).toBeInTheDocument();
54
- expect(screen.getByAltText(mockUsername)).toHaveAttribute('src', mockAvatar);
55
37
  });
56
38
 
57
- it('should show default avatar when the user is logged in but have no custom avatar', () => {
58
- const mockUsername = 'testuser';
39
+ render(<UserAvatar />);
59
40
 
60
- act(() => {
61
- useUserStore.setState({
62
- enableAuth: () => true,
63
- isSignedIn: true,
64
- user: { id: 'bbb', username: mockUsername },
65
- });
66
- });
41
+ expect(screen.getByAltText(mockUsername)).toBeInTheDocument();
42
+ expect(screen.getByAltText(mockUsername)).toHaveAttribute('src', mockAvatar);
43
+ });
67
44
 
68
- render(<UserAvatar />);
69
- // When user has no avatar url, <Avatar /> falls back to initials rendering (not an <img />)
70
- expect(screen.getByText('TE')).toBeInTheDocument();
71
- });
45
+ it('should show default avatar when the user is logged in but have no custom avatar', () => {
46
+ const mockUsername = 'testuser';
72
47
 
73
- it('should show LobeChat and default avatar when the user is not logged in and enable auth', () => {
74
- act(() => {
75
- useUserStore.setState({ enableAuth: () => true, isSignedIn: false, user: undefined });
48
+ act(() => {
49
+ useUserStore.setState({
50
+ isSignedIn: true,
51
+ user: { id: 'bbb', username: mockUsername },
76
52
  });
77
-
78
- render(<UserAvatar />);
79
- expect(screen.getByAltText(BRANDING_NAME)).toBeInTheDocument();
80
- expect(screen.getByAltText(BRANDING_NAME)).toHaveAttribute('src', DEFAULT_USER_AVATAR_URL);
81
53
  });
82
- });
83
54
 
84
- describe('disable Auth', () => {
85
- it('should show LobeChat and default avatar when the user is not logged in and disabled auth', () => {
86
- enableAuth.value = false;
87
- act(() => {
88
- useUserStore.setState({ enableAuth: () => false, isSignedIn: false, user: undefined });
89
- });
55
+ render(<UserAvatar />);
56
+ // When user has no avatar url, <Avatar /> falls back to initials rendering (not an <img />)
57
+ expect(screen.getByText('TE')).toBeInTheDocument();
58
+ });
90
59
 
91
- render(<UserAvatar />);
92
- expect(screen.getByAltText(BRANDING_NAME)).toBeInTheDocument();
93
- expect(screen.getByAltText(BRANDING_NAME)).toHaveAttribute('src', DEFAULT_USER_AVATAR_URL);
60
+ it('should show LobeChat and default avatar when the user is not logged in', () => {
61
+ act(() => {
62
+ useUserStore.setState({ isSignedIn: false, user: undefined });
94
63
  });
64
+
65
+ render(<UserAvatar />);
66
+ expect(screen.getByAltText(BRANDING_NAME)).toBeInTheDocument();
67
+ expect(screen.getByAltText(BRANDING_NAME)).toHaveAttribute('src', DEFAULT_USER_AVATAR_URL);
95
68
  });
96
69
  });
@@ -48,33 +48,11 @@ vi.mock('./useNewVersion', () => ({
48
48
  useNewVersion: vi.fn(() => false),
49
49
  }));
50
50
 
51
- // Use vi.hoisted to ensure variables exist before vi.mock factory executes
52
- const { enableAuth, enableClerk } = vi.hoisted(() => ({
53
- enableAuth: { value: true },
54
- enableClerk: { value: true },
55
- }));
56
-
57
- vi.mock('@/envs/auth', () => ({
58
- get enableAuth() {
59
- return enableAuth.value;
60
- },
61
- get enableClerk() {
62
- return enableClerk.value;
63
- },
64
- }));
65
-
66
- afterEach(() => {
67
- enableAuth.value = true;
68
- enableClerk.value = true;
69
- });
70
-
71
51
  describe('useMenu', () => {
72
52
  it('should provide correct menu items when user is logged in with auth', () => {
73
53
  act(() => {
74
- useUserStore.setState({ isSignedIn: true, enableAuth: () => true });
54
+ useUserStore.setState({ isSignedIn: true });
75
55
  });
76
- enableAuth.value = true;
77
- enableClerk.value = false;
78
56
 
79
57
  const { result } = renderHook(() => useMenu(), { wrapper });
80
58
 
@@ -88,29 +66,10 @@ describe('useMenu', () => {
88
66
  });
89
67
  });
90
68
 
91
- it('should provide correct menu items when user is logged in without auth', () => {
92
- act(() => {
93
- useUserStore.setState({ isSignedIn: false, enableAuth: () => false });
94
- });
95
- enableAuth.value = false;
96
-
97
- const { result } = renderHook(() => useMenu(), { wrapper });
98
-
99
- act(() => {
100
- const { mainItems, logoutItems } = result.current;
101
- // When not logged in (isLogin = false), these items should not be shown
102
- // isLogin checks isSignedIn, so with isSignedIn: false, isLogin should be false
103
- expect(mainItems?.some((item) => item?.key === 'setting')).toBe(false);
104
- expect(mainItems?.some((item) => item?.key === 'import')).toBe(false);
105
- expect(logoutItems.some((item) => item?.key === 'logout')).toBe(false);
106
- });
107
- });
108
-
109
69
  it('should provide correct menu items when user is not logged in', () => {
110
70
  act(() => {
111
- useUserStore.setState({ isSignedIn: false, enableAuth: () => true });
71
+ useUserStore.setState({ isSignedIn: false });
112
72
  });
113
- enableAuth.value = true;
114
73
 
115
74
  const { result } = renderHook(() => useMenu(), { wrapper });
116
75
 
@@ -4,7 +4,6 @@ import { type PropsWithChildren } from 'react';
4
4
  import { authEnv } from '@/envs/auth';
5
5
 
6
6
  import BetterAuth from './BetterAuth';
7
- import Clerk from './Clerk';
8
7
  import Desktop from './Desktop';
9
8
  import NextAuth from './NextAuth';
10
9
  import NoAuth from './NoAuth';
@@ -14,10 +13,6 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
14
13
  return <Desktop>{children}</Desktop>;
15
14
  }
16
15
 
17
- if (authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH) {
18
- return <Clerk>{children}</Clerk>;
19
- }
20
-
21
16
  if (authEnv.NEXT_PUBLIC_ENABLE_BETTER_AUTH) {
22
17
  return <BetterAuth>{children}</BetterAuth>;
23
18
  }
@@ -313,6 +313,12 @@ export function defineConfig(config: CustomNextConfig) {
313
313
  permanent: true,
314
314
  source: '/chat',
315
315
  },
316
+ // Redirect old Clerk login route to Better Auth signin
317
+ {
318
+ destination: '/signin',
319
+ permanent: true,
320
+ source: '/login',
321
+ },
316
322
  ...(config.redirects ?? []),
317
323
  ],
318
324