@lobehub/lobehub 2.0.0-next.236 → 2.0.0-next.237

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 (26) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/changelog/v1.json +5 -0
  3. package/locales/zh-CN/subscription.json +1 -1
  4. package/package.json +1 -1
  5. package/packages/types/src/user/onboarding.ts +3 -1
  6. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Header/Nav.tsx +19 -1
  7. package/src/app/[variants]/(main)/chat/_layout/Sidebar/Header/index.tsx +1 -2
  8. package/src/app/[variants]/(main)/settings/profile/features/KlavisAuthorizationList/index.tsx +6 -24
  9. package/src/app/[variants]/(main)/settings/profile/index.tsx +17 -3
  10. package/src/app/[variants]/onboarding/features/FullNameStep.tsx +18 -5
  11. package/src/app/[variants]/onboarding/features/InterestsStep.tsx +19 -7
  12. package/src/app/[variants]/onboarding/features/ProSettingsStep.tsx +30 -8
  13. package/src/app/[variants]/onboarding/features/ResponseLanguageStep.tsx +18 -4
  14. package/src/app/[variants]/onboarding/features/TelemetryStep.tsx +14 -5
  15. package/src/app/[variants]/onboarding/index.tsx +2 -1
  16. package/src/server/services/search/impls/exa/index.ts +1 -1
  17. package/src/server/services/search/impls/search1api/index.ts +1 -1
  18. package/src/server/services/search/impls/tavily/index.ts +1 -1
  19. package/src/store/tool/slices/klavisStore/action.test.ts +167 -2
  20. package/src/store/tool/slices/klavisStore/action.ts +9 -8
  21. package/src/store/tool/slices/klavisStore/initialState.ts +3 -0
  22. package/src/store/user/slices/auth/action.ts +1 -0
  23. package/src/store/user/slices/onboarding/action.test.ts +342 -0
  24. package/src/store/user/slices/onboarding/action.ts +4 -9
  25. package/src/store/user/slices/onboarding/selectors.test.ts +222 -0
  26. package/src/store/user/slices/onboarding/selectors.ts +6 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.237](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.236...v2.0.0-next.237)
6
+
7
+ <sup>Released on **2026-01-08**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **ui**: Move new topic button to navigation panel.
12
+
13
+ #### 🐛 Bug Fixes
14
+
15
+ - **onboarding**: Prevent step overflow and misc improvements.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### What's improved
23
+
24
+ - **ui**: Move new topic button to navigation panel, closes [#11325](https://github.com/lobehub/lobe-chat/issues/11325) ([3d6b399](https://github.com/lobehub/lobe-chat/commit/3d6b399))
25
+
26
+ #### What's fixed
27
+
28
+ - **onboarding**: Prevent step overflow and misc improvements, closes [#11322](https://github.com/lobehub/lobe-chat/issues/11322) ([8586fd4](https://github.com/lobehub/lobe-chat/commit/8586fd4))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
5
38
  ## [Version 2.0.0-next.236](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.235...v2.0.0-next.236)
6
39
 
7
40
  <sup>Released on **2026-01-08**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-08",
5
+ "version": "2.0.0-next.237"
6
+ },
2
7
  {
3
8
  "children": {},
4
9
  "date": "2026-01-08",
@@ -330,7 +330,7 @@
330
330
  "switchToYearly.desc": "切换后,支付差价后立即生效年付计费,起始日期继承原计划。",
331
331
  "switchToYearly.title": "切换为年付",
332
332
  "tab.billing": "账单管理",
333
- "tab.funds": "点数管理",
333
+ "tab.funds": "积分管理",
334
334
  "tab.plans": "订阅计划",
335
335
  "tab.referral": "推荐奖励",
336
336
  "tab.spend": "点数明细",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.236",
3
+ "version": "2.0.0-next.237",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -9,8 +9,10 @@ export interface UserOnboarding {
9
9
  version: number;
10
10
  }
11
11
 
12
+ export const MAX_ONBOARDING_STEPS = 5;
13
+
12
14
  export const UserOnboardingSchema = z.object({
13
- currentStep: z.number().optional(),
15
+ currentStep: z.number().min(1).max(MAX_ONBOARDING_STEPS).optional(),
14
16
  finishedAt: z.string().optional(),
15
17
  version: z.number(),
16
18
  });
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { Flexbox } from '@lobehub/ui';
4
4
  import { BotPromptIcon } from '@lobehub/ui/icons';
5
- import { SearchIcon } from 'lucide-react';
5
+ import { MessageSquarePlusIcon, SearchIcon } from 'lucide-react';
6
6
  import { usePathname } from 'next/navigation';
7
7
  import { memo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
@@ -11,6 +11,7 @@ import urlJoin from 'url-join';
11
11
 
12
12
  import NavItem from '@/features/NavPanel/components/NavItem';
13
13
  import { useQueryRoute } from '@/hooks/useQueryRoute';
14
+ import { useActionSWR } from '@/libs/swr';
14
15
  import { useAgentStore } from '@/store/agent';
15
16
  import { builtinAgentSelectors } from '@/store/agent/selectors';
16
17
  import { useChatStore } from '@/store/chat';
@@ -19,6 +20,7 @@ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfi
19
20
 
20
21
  const Nav = memo(() => {
21
22
  const { t } = useTranslation('chat');
23
+ const { t: tTopic } = useTranslation('topic');
22
24
  const isInbox = useAgentStore(builtinAgentSelectors.isInboxAgent);
23
25
  const params = useParams();
24
26
  const agentId = params.aid;
@@ -29,9 +31,25 @@ const Nav = memo(() => {
29
31
  const toggleCommandMenu = useGlobalStore((s) => s.toggleCommandMenu);
30
32
  const hideProfile = isInbox || !isAgentEditable;
31
33
  const switchTopic = useChatStore((s) => s.switchTopic);
34
+ const [openNewTopicOrSaveTopic] = useChatStore((s) => [s.openNewTopicOrSaveTopic]);
35
+
36
+ const { mutate, isValidating } = useActionSWR('openNewTopicOrSaveTopic', openNewTopicOrSaveTopic);
37
+ const handleNewTopic = () => {
38
+ // If in agent sub-route, navigate back to agent chat first
39
+ if (isProfileActive && agentId) {
40
+ router.push(urlJoin('/agent', agentId));
41
+ }
42
+ mutate();
43
+ };
32
44
 
33
45
  return (
34
46
  <Flexbox gap={1} paddingInline={4}>
47
+ <NavItem
48
+ icon={MessageSquarePlusIcon}
49
+ loading={isValidating}
50
+ onClick={handleNewTopic}
51
+ title={tTopic('actions.addNewTopic')}
52
+ />
35
53
  {!hideProfile && (
36
54
  <NavItem
37
55
  active={isProfileActive}
@@ -4,14 +4,13 @@ import { type PropsWithChildren, memo } from 'react';
4
4
 
5
5
  import SideBarHeaderLayout from '@/features/NavPanel/SideBarHeaderLayout';
6
6
 
7
- import AddTopicButon from './AddTopicButon';
8
7
  import Agent from './Agent';
9
8
  import Nav from './Nav';
10
9
 
11
10
  const HeaderInfo = memo<PropsWithChildren>(() => {
12
11
  return (
13
12
  <>
14
- <SideBarHeaderLayout left={<Agent />} right={<AddTopicButon />} />
13
+ <SideBarHeaderLayout left={<Agent />} />
15
14
  <Nav />
16
15
  </>
17
16
  );
@@ -4,11 +4,8 @@ import { memo, useCallback, useState } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { modal } from '@/components/AntdStaticMethods';
7
- import { useServerConfigStore } from '@/store/serverConfig';
8
- import { serverConfigSelectors } from '@/store/serverConfig/selectors';
9
7
  import { useToolStore } from '@/store/tool';
10
- import { type KlavisServer, KlavisServerStatus } from '@/store/tool/slices/klavisStore';
11
- import { type ToolStore } from '@/store/tool/store';
8
+ import { type KlavisServer } from '@/store/tool/slices/klavisStore';
12
9
 
13
10
  interface KlavisAuthItemProps {
14
11
  server: KlavisServer;
@@ -54,11 +51,6 @@ const KlavisAuthItem = memo<KlavisAuthItemProps>(({ server }) => {
54
51
  return <IconComponent size={14} />;
55
52
  };
56
53
 
57
- // 只显示已连接的服务器
58
- if (server.status !== KlavisServerStatus.CONNECTED) {
59
- return null;
60
- }
61
-
62
54
  return (
63
55
  <Tag closable onClose={handleRevoke}>
64
56
  <Flexbox align="center" gap={4} horizontal style={{ opacity: isRevoking ? 0.5 : 1 }}>
@@ -69,24 +61,14 @@ const KlavisAuthItem = memo<KlavisAuthItemProps>(({ server }) => {
69
61
  );
70
62
  });
71
63
 
72
- export const KlavisAuthorizationList = memo(() => {
73
- const enableKlavis = useServerConfigStore(serverConfigSelectors.enableKlavis);
74
- const useFetchUserKlavisServers = useToolStore((s: ToolStore) => s.useFetchUserKlavisServers);
75
- const servers = useToolStore((s: ToolStore) => s.servers);
76
-
77
- // 获取已授权的服务器列表
78
- useFetchUserKlavisServers(enableKlavis);
79
-
80
- // 只显示已连接的服务器
81
- const connectedServers = servers.filter((s) => s.status === KlavisServerStatus.CONNECTED);
82
-
83
- if (!enableKlavis || connectedServers.length === 0) {
84
- return null;
85
- }
64
+ interface KlavisAuthorizationListProps {
65
+ servers: KlavisServer[];
66
+ }
86
67
 
68
+ export const KlavisAuthorizationList = memo<KlavisAuthorizationListProps>(({ servers }) => {
87
69
  return (
88
70
  <Flexbox gap={8} horizontal wrap="wrap">
89
- {connectedServers.map((server) => (
71
+ {servers.map((server) => (
90
72
  <KlavisAuthItem key={server.identifier} server={server} />
91
73
  ))}
92
74
  </Flexbox>
@@ -9,6 +9,8 @@ import { useTranslation } from 'react-i18next';
9
9
  import SettingHeader from '@/app/[variants]/(main)/settings/features/SettingHeader';
10
10
  import { useServerConfigStore } from '@/store/serverConfig';
11
11
  import { serverConfigSelectors } from '@/store/serverConfig/selectors';
12
+ import { useToolStore } from '@/store/tool';
13
+ import { KlavisServerStatus } from '@/store/tool/slices/klavisStore';
12
14
  import { useUserStore } from '@/store/user';
13
15
  import { authSelectors, userProfileSelectors } from '@/store/user/selectors';
14
16
 
@@ -37,9 +39,21 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
37
39
  const isLoadedAuthProviders = useUserStore(authSelectors.isLoadedAuthProviders);
38
40
  const fetchAuthProviders = useUserStore((s) => s.fetchAuthProviders);
39
41
  const enableKlavis = useServerConfigStore(serverConfigSelectors.enableKlavis);
42
+ const [servers, isServersInit, useFetchUserKlavisServers] = useToolStore((s) => [
43
+ s.servers,
44
+ s.isServersInit,
45
+ s.useFetchUserKlavisServers,
46
+ ]);
47
+ const connectedServers = servers.filter((s) => s.status === KlavisServerStatus.CONNECTED);
48
+
49
+ // Fetch Klavis servers
50
+ useFetchUserKlavisServers(enableKlavis);
40
51
 
41
52
  const isLoginWithAuth = isLoginWithNextAuth || isLoginWithBetterAuth;
42
- const isLoading = !isUserLoaded || (isLoginWithAuth && !isLoadedAuthProviders);
53
+ const isLoading =
54
+ !isUserLoaded ||
55
+ (isLoginWithAuth && !isLoadedAuthProviders) ||
56
+ (enableKlavis && !isServersInit);
43
57
 
44
58
  useEffect(() => {
45
59
  if (isLoginWithAuth) {
@@ -110,11 +124,11 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
110
124
  )}
111
125
 
112
126
  {/* Klavis Authorizations Row */}
113
- {enableKlavis && (
127
+ {enableKlavis && connectedServers.length > 0 && (
114
128
  <>
115
129
  <Divider style={{ margin: 0 }} />
116
130
  <ProfileRow label={t('profile.authorizations.title')} mobile={mobile}>
117
- <KlavisAuthorizationList />
131
+ <KlavisAuthorizationList servers={connectedServers} />
118
132
  </ProfileRow>
119
133
  </>
120
134
  )}
@@ -4,7 +4,7 @@ import { SendButton } from '@lobehub/editor/react';
4
4
  import { Button, Flexbox, Icon, Input } from '@lobehub/ui';
5
5
  import { cssVar } from 'antd-style';
6
6
  import { SignatureIcon, Undo2Icon } from 'lucide-react';
7
- import { memo, useState } from 'react';
7
+ import { memo, useCallback, useRef, useState } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
10
10
  import { useUserStore } from '@/store/user';
@@ -23,13 +23,25 @@ const FullNameStep = memo<FullNameStepProps>(({ onBack, onNext }) => {
23
23
  const updateFullName = useUserStore((s) => s.updateFullName);
24
24
 
25
25
  const [value, setValue] = useState(existingFullName || '');
26
+ const [isNavigating, setIsNavigating] = useState(false);
27
+ const isNavigatingRef = useRef(false);
26
28
 
27
- const handleNext = () => {
29
+ const handleNext = useCallback(() => {
30
+ if (isNavigatingRef.current) return;
31
+ isNavigatingRef.current = true;
32
+ setIsNavigating(true);
28
33
  if (value.trim()) {
29
34
  updateFullName(value.trim());
30
35
  }
31
36
  onNext();
32
- };
37
+ }, [value, updateFullName, onNext]);
38
+
39
+ const handleBack = useCallback(() => {
40
+ if (isNavigatingRef.current) return;
41
+ isNavigatingRef.current = true;
42
+ setIsNavigating(true);
43
+ onBack();
44
+ }, [onBack]);
33
45
 
34
46
  return (
35
47
  <Flexbox gap={16}>
@@ -59,7 +71,7 @@ const FullNameStep = memo<FullNameStepProps>(({ onBack, onNext }) => {
59
71
  }}
60
72
  suffix={
61
73
  <SendButton
62
- disabled={!value?.trim()}
74
+ disabled={!value?.trim() || isNavigating}
63
75
  onClick={handleNext}
64
76
  style={{
65
77
  zoom: 1.5,
@@ -73,8 +85,9 @@ const FullNameStep = memo<FullNameStepProps>(({ onBack, onNext }) => {
73
85
  </Flexbox>
74
86
  <Flexbox horizontal justify={'flex-start'} style={{ marginTop: 32 }}>
75
87
  <Button
88
+ disabled={isNavigating}
76
89
  icon={Undo2Icon}
77
- onClick={onBack}
90
+ onClick={handleBack}
78
91
  style={{
79
92
  color: cssVar.colorTextDescription,
80
93
  }}
@@ -3,7 +3,7 @@
3
3
  import { Block, Button, Flexbox, Icon, Input, Text } from '@lobehub/ui';
4
4
  import { cssVar } from 'antd-style';
5
5
  import { BriefcaseIcon, Undo2Icon } from 'lucide-react';
6
- import { memo, useCallback, useMemo, useState } from 'react';
6
+ import { memo, useCallback, useMemo, useRef, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
9
  import { useUserStore } from '@/store/user';
@@ -25,6 +25,8 @@ const InterestsStep = memo<InterestsStepProps>(({ onBack, onNext }) => {
25
25
  const [selectedInterests, setSelectedInterests] = useState<string[]>(existingInterests);
26
26
  const [customInput, setCustomInput] = useState('');
27
27
  const [showCustomInput, setShowCustomInput] = useState(false);
28
+ const [isNavigating, setIsNavigating] = useState(false);
29
+ const isNavigatingRef = useRef(false);
28
30
 
29
31
  const areas = useMemo(
30
32
  () =>
@@ -49,7 +51,11 @@ const InterestsStep = memo<InterestsStepProps>(({ onBack, onNext }) => {
49
51
  }
50
52
  }, [customInput, selectedInterests]);
51
53
 
52
- const handleNext = useCallback(async () => {
54
+ const handleNext = useCallback(() => {
55
+ if (isNavigatingRef.current) return;
56
+ isNavigatingRef.current = true;
57
+ setIsNavigating(true);
58
+
53
59
  // Include custom input value if "other" is active and has content
54
60
  const finalInterests = [...selectedInterests];
55
61
  const trimmedCustom = customInput.trim();
@@ -60,12 +66,17 @@ const InterestsStep = memo<InterestsStepProps>(({ onBack, onNext }) => {
60
66
  // Deduplicate
61
67
  const uniqueInterests = [...new Set(finalInterests)];
62
68
 
63
- if (uniqueInterests.length > 0) {
64
- await updateInterests(uniqueInterests);
65
- }
69
+ updateInterests(uniqueInterests);
66
70
  onNext();
67
71
  }, [selectedInterests, customInput, showCustomInput, updateInterests, onNext]);
68
72
 
73
+ const handleBack = useCallback(() => {
74
+ if (isNavigatingRef.current) return;
75
+ isNavigatingRef.current = true;
76
+ setIsNavigating(true);
77
+ onBack();
78
+ }, [onBack]);
79
+
69
80
  return (
70
81
  <Flexbox gap={16}>
71
82
  <LobeMessage
@@ -138,14 +149,15 @@ const InterestsStep = memo<InterestsStepProps>(({ onBack, onNext }) => {
138
149
  )}
139
150
  <Flexbox horizontal justify={'space-between'} style={{ marginTop: 32 }}>
140
151
  <Button
152
+ disabled={isNavigating}
141
153
  icon={Undo2Icon}
142
- onClick={onBack}
154
+ onClick={handleBack}
143
155
  style={{ color: cssVar.colorTextDescription }}
144
156
  type={'text'}
145
157
  >
146
158
  {t('back')}
147
159
  </Button>
148
- <Button onClick={handleNext} type={'primary'}>
160
+ <Button disabled={isNavigating} onClick={handleNext} type={'primary'}>
149
161
  {t('next')}
150
162
  </Button>
151
163
  </Flexbox>
@@ -3,7 +3,7 @@
3
3
  import { Button, Flexbox, Text } from '@lobehub/ui';
4
4
  import { cssVar } from 'antd-style';
5
5
  import { Undo2Icon } from 'lucide-react';
6
- import { memo } from 'react';
6
+ import { memo, useCallback, useRef, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { useNavigate } from 'react-router-dom';
9
9
 
@@ -34,14 +34,30 @@ const ProSettingsStep = memo<ProSettingsStepProps>(({ onBack }) => {
34
34
  (s) => settingsSelectors.currentSettings(s).defaultAgent?.config,
35
35
  );
36
36
 
37
- const handleFinish = () => {
37
+ const [isNavigating, setIsNavigating] = useState(false);
38
+ const isNavigatingRef = useRef(false);
39
+
40
+ const handleFinish = useCallback(() => {
41
+ if (isNavigatingRef.current) return;
42
+ isNavigatingRef.current = true;
43
+ setIsNavigating(true);
38
44
  finishOnboarding();
39
45
  navigate('/');
40
- };
46
+ }, [finishOnboarding, navigate]);
47
+
48
+ const handleBack = useCallback(() => {
49
+ if (isNavigatingRef.current) return;
50
+ isNavigatingRef.current = true;
51
+ setIsNavigating(true);
52
+ onBack();
53
+ }, [onBack]);
41
54
 
42
- const handleModelChange = ({ model, provider }: { model: string; provider: string }) => {
43
- updateDefaultModel(model, provider);
44
- };
55
+ const handleModelChange = useCallback(
56
+ ({ model, provider }: { model: string; provider: string }) => {
57
+ updateDefaultModel(model, provider);
58
+ },
59
+ [updateDefaultModel],
60
+ );
45
61
 
46
62
  return (
47
63
  <Flexbox gap={16}>
@@ -70,8 +86,9 @@ const ProSettingsStep = memo<ProSettingsStepProps>(({ onBack }) => {
70
86
 
71
87
  <Flexbox align={'center'} horizontal justify={'space-between'} style={{ marginTop: 16 }}>
72
88
  <Button
89
+ disabled={isNavigating}
73
90
  icon={Undo2Icon}
74
- onClick={onBack}
91
+ onClick={handleBack}
75
92
  style={{
76
93
  color: cssVar.colorTextDescription,
77
94
  }}
@@ -79,7 +96,12 @@ const ProSettingsStep = memo<ProSettingsStepProps>(({ onBack }) => {
79
96
  >
80
97
  {t('back')}
81
98
  </Button>
82
- <Button onClick={handleFinish} style={{ minWidth: 120 }} type="primary">
99
+ <Button
100
+ disabled={isNavigating}
101
+ onClick={handleFinish}
102
+ style={{ minWidth: 120 }}
103
+ type="primary"
104
+ >
83
105
  {t('finish')}
84
106
  </Button>
85
107
  </Flexbox>
@@ -4,7 +4,7 @@ import { SendButton } from '@lobehub/editor/react';
4
4
  import { Button, Flexbox, Select, Text } from '@lobehub/ui';
5
5
  import { cssVar } from 'antd-style';
6
6
  import { Undo2Icon } from 'lucide-react';
7
- import { memo, useCallback, useState } from 'react';
7
+ import { memo, useCallback, useRef, useState } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
10
10
  import { type Locales, localeOptions, normalizeLocale } from '@/locales/resources';
@@ -24,11 +24,23 @@ const ResponseLanguageStep = memo<ResponseLanguageStepProps>(({ onBack, onNext }
24
24
  const setSettings = useUserStore((s) => s.setSettings);
25
25
 
26
26
  const [value, setValue] = useState<Locales | ''>(normalizeLocale(navigator.language));
27
+ const [isNavigating, setIsNavigating] = useState(false);
28
+ const isNavigatingRef = useRef(false);
27
29
 
28
- const handleNext = () => {
30
+ const handleNext = useCallback(() => {
31
+ if (isNavigatingRef.current) return;
32
+ isNavigatingRef.current = true;
33
+ setIsNavigating(true);
29
34
  setSettings({ general: { responseLanguage: value || '' } });
30
35
  onNext();
31
- };
36
+ }, [value, setSettings, onNext]);
37
+
38
+ const handleBack = useCallback(() => {
39
+ if (isNavigatingRef.current) return;
40
+ isNavigatingRef.current = true;
41
+ setIsNavigating(true);
42
+ onBack();
43
+ }, [onBack]);
32
44
 
33
45
  const Message = useCallback(
34
46
  () => (
@@ -73,6 +85,7 @@ const ResponseLanguageStep = memo<ResponseLanguageStepProps>(({ onBack, onNext }
73
85
  value={value}
74
86
  />
75
87
  <SendButton
88
+ disabled={isNavigating}
76
89
  onClick={handleNext}
77
90
  style={{
78
91
  zoom: 1.5,
@@ -85,8 +98,9 @@ const ResponseLanguageStep = memo<ResponseLanguageStepProps>(({ onBack, onNext }
85
98
  </Text>
86
99
  <Flexbox horizontal justify={'flex-start'} style={{ marginTop: 32 }}>
87
100
  <Button
101
+ disabled={isNavigating}
88
102
  icon={Undo2Icon}
89
- onClick={onBack}
103
+ onClick={handleBack}
90
104
  style={{
91
105
  color: cssVar.colorTextDescription,
92
106
  }}
@@ -7,7 +7,7 @@ import { LoadingDots } from '@lobehub/ui/chat';
7
7
  import { Steps, Switch } from 'antd';
8
8
  import { cssVar } from 'antd-style';
9
9
  import { BrainIcon, HeartHandshakeIcon, PencilRulerIcon, ShieldCheck } from 'lucide-react';
10
- import { memo, useCallback, useState } from 'react';
10
+ import { memo, useCallback, useRef, useState } from 'react';
11
11
  import { Trans, useTranslation } from 'react-i18next';
12
12
 
13
13
  import { ProductLogo } from '@/components/Branding';
@@ -21,12 +21,20 @@ interface TelemetryStepProps {
21
21
  const TelemetryStep = memo<TelemetryStepProps>(({ onNext }) => {
22
22
  const { t } = useTranslation('onboarding');
23
23
  const [check, setCheck] = useState(true);
24
+ const [isNavigating, setIsNavigating] = useState(false);
25
+ const isNavigatingRef = useRef(false);
24
26
  const updateGeneralConfig = useUserStore((s) => s.updateGeneralConfig);
25
27
 
26
- const handleChoice = (enabled: boolean) => {
27
- updateGeneralConfig({ telemetry: enabled });
28
- onNext();
29
- };
28
+ const handleChoice = useCallback(
29
+ (enabled: boolean) => {
30
+ if (isNavigatingRef.current) return;
31
+ isNavigatingRef.current = true;
32
+ setIsNavigating(true);
33
+ updateGeneralConfig({ telemetry: enabled });
34
+ onNext();
35
+ },
36
+ [updateGeneralConfig, onNext],
37
+ );
30
38
 
31
39
  const IconAvatar = useCallback(({ icon }: { icon: IconProps['icon'] }) => {
32
40
  return (
@@ -123,6 +131,7 @@ const TelemetryStep = memo<TelemetryStepProps>(({ onNext }) => {
123
131
  </Flexbox>
124
132
  </Flexbox>
125
133
  <Button
134
+ disabled={isNavigating}
126
135
  onClick={() => handleChoice(check)}
127
136
  size={'large'}
128
137
  style={{
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { MAX_ONBOARDING_STEPS } from '@lobechat/types';
3
4
  import { Flexbox } from '@lobehub/ui';
4
5
  import { memo } from 'react';
5
6
 
@@ -40,7 +41,7 @@ const OnboardingPage = memo(() => {
40
41
  case 4: {
41
42
  return <ResponseLanguageStep onBack={goToPreviousStep} onNext={goToNextStep} />;
42
43
  }
43
- case 5: {
44
+ case MAX_ONBOARDING_STEPS: {
44
45
  return <ProSettingsStep onBack={goToPreviousStep} />;
45
46
  }
46
47
  default: {
@@ -47,7 +47,7 @@ export class ExaImpl implements SearchServiceImpl {
47
47
  };
48
48
  })()
49
49
  : {}),
50
- category: // Exa 只支持 news 类型
50
+ category: // Exa only supports news type
51
51
  params?.searchCategories?.filter((cat) => ['news'].includes(cat))?.[0],
52
52
  };
53
53
 
@@ -41,7 +41,7 @@ export class Search1APIImpl implements SearchServiceImpl {
41
41
  const { searchEngines } = params;
42
42
 
43
43
  const defaultQueryParams: Search1APIQueryParams = {
44
- crawl_results: 0, // 默认不做抓取
44
+ crawl_results: 0, // Default is no crawling
45
45
  image: false,
46
46
  max_results: 15, // Default max results
47
47
  query,
@@ -42,7 +42,7 @@ export class TavilyImpl implements SearchServiceImpl {
42
42
  params?.searchTimeRange && params.searchTimeRange !== 'anytime'
43
43
  ? params.searchTimeRange
44
44
  : undefined,
45
- topic: // Tavily 只支持 news general 两种类型
45
+ topic: // Tavily only supports news and general types
46
46
  params?.searchCategories?.filter((cat) => ['news', 'general'].includes(cat))?.[0],
47
47
  };
48
48