@lobehub/lobehub 2.0.0-next.235 → 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.
- package/.devcontainer/devcontainer.json +4 -2
- package/CHANGELOG.md +58 -0
- package/changelog/v1.json +10 -0
- package/locales/zh-CN/subscription.json +1 -1
- package/package.json +1 -1
- package/packages/model-bank/src/aiModels/anthropic.ts +0 -30
- package/packages/model-bank/src/aiModels/volcengine.ts +2 -1
- package/packages/types/src/user/onboarding.ts +3 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Header/Nav.tsx +19 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Header/index.tsx +1 -2
- package/src/app/[variants]/(main)/settings/profile/features/KlavisAuthorizationList/index.tsx +6 -24
- package/src/app/[variants]/(main)/settings/profile/index.tsx +17 -3
- package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +1 -7
- package/src/app/[variants]/onboarding/features/FullNameStep.tsx +18 -5
- package/src/app/[variants]/onboarding/features/InterestsStep.tsx +19 -7
- package/src/app/[variants]/onboarding/features/ProSettingsStep.tsx +30 -8
- package/src/app/[variants]/onboarding/features/ResponseLanguageStep.tsx +18 -4
- package/src/app/[variants]/onboarding/features/TelemetryStep.tsx +14 -5
- package/src/app/[variants]/onboarding/index.tsx +2 -1
- package/src/server/services/search/impls/exa/index.ts +1 -1
- package/src/server/services/search/impls/search1api/index.ts +1 -1
- package/src/server/services/search/impls/tavily/index.ts +1 -1
- package/src/store/tool/slices/klavisStore/action.test.ts +167 -2
- package/src/store/tool/slices/klavisStore/action.ts +9 -8
- package/src/store/tool/slices/klavisStore/initialState.ts +3 -0
- package/src/store/user/slices/auth/action.ts +1 -0
- package/src/store/user/slices/onboarding/action.test.ts +342 -0
- package/src/store/user/slices/onboarding/action.ts +4 -9
- package/src/store/user/slices/onboarding/selectors.test.ts +222 -0
- package/src/store/user/slices/onboarding/selectors.ts +6 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"features": {
|
|
3
3
|
"ghcr.io/devcontainer-community/devcontainer-features/bun.sh:1": {},
|
|
4
|
-
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
|
|
4
|
+
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
|
|
5
|
+
"moby": false
|
|
6
|
+
}
|
|
5
7
|
},
|
|
6
8
|
"image": "mcr.microsoft.com/devcontainers/typescript-node"
|
|
7
|
-
}
|
|
9
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
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
|
+
[](#readme-top)
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
## [Version 2.0.0-next.236](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.235...v2.0.0-next.236)
|
|
39
|
+
|
|
40
|
+
<sup>Released on **2026-01-08**</sup>
|
|
41
|
+
|
|
42
|
+
#### 🐛 Bug Fixes
|
|
43
|
+
|
|
44
|
+
- **provider-config**: Update isFetchOnClient Switch component.
|
|
45
|
+
|
|
46
|
+
<br/>
|
|
47
|
+
|
|
48
|
+
<details>
|
|
49
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
50
|
+
|
|
51
|
+
#### What's fixed
|
|
52
|
+
|
|
53
|
+
- **provider-config**: Update isFetchOnClient Switch component, closes [#11215](https://github.com/lobehub/lobe-chat/issues/11215) ([5bb038b](https://github.com/lobehub/lobe-chat/commit/5bb038b))
|
|
54
|
+
|
|
55
|
+
</details>
|
|
56
|
+
|
|
57
|
+
<div align="right">
|
|
58
|
+
|
|
59
|
+
[](#readme-top)
|
|
60
|
+
|
|
61
|
+
</div>
|
|
62
|
+
|
|
5
63
|
## [Version 2.0.0-next.235](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.234...v2.0.0-next.235)
|
|
6
64
|
|
|
7
65
|
<sup>Released on **2026-01-08**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
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",
|
|
@@ -292,36 +292,6 @@ const anthropicChatModels: AIChatModelCard[] = [
|
|
|
292
292
|
},
|
|
293
293
|
type: 'chat',
|
|
294
294
|
},
|
|
295
|
-
{
|
|
296
|
-
abilities: {
|
|
297
|
-
functionCall: true,
|
|
298
|
-
vision: true,
|
|
299
|
-
},
|
|
300
|
-
contextWindowTokens: 200_000,
|
|
301
|
-
description:
|
|
302
|
-
'Claude 3 Opus is Anthropic’s most powerful model for highly complex tasks, excelling in performance, intelligence, fluency, and comprehension.',
|
|
303
|
-
displayName: 'Claude 3 Opus',
|
|
304
|
-
id: 'claude-3-opus-20240229',
|
|
305
|
-
maxOutput: 4096,
|
|
306
|
-
pricing: {
|
|
307
|
-
units: [
|
|
308
|
-
{ name: 'textInput_cacheRead', rate: 1.5, strategy: 'fixed', unit: 'millionTokens' },
|
|
309
|
-
{ name: 'textInput', rate: 15, strategy: 'fixed', unit: 'millionTokens' },
|
|
310
|
-
{ name: 'textOutput', rate: 75, strategy: 'fixed', unit: 'millionTokens' },
|
|
311
|
-
{
|
|
312
|
-
lookup: { prices: { '1h': 30, '5m': 18.75 }, pricingParams: ['ttl'] },
|
|
313
|
-
name: 'textInput_cacheWrite',
|
|
314
|
-
strategy: 'lookup',
|
|
315
|
-
unit: 'millionTokens',
|
|
316
|
-
},
|
|
317
|
-
],
|
|
318
|
-
},
|
|
319
|
-
releasedAt: '2024-02-29',
|
|
320
|
-
settings: {
|
|
321
|
-
extendParams: ['disableContextCaching'],
|
|
322
|
-
},
|
|
323
|
-
type: 'chat',
|
|
324
|
-
},
|
|
325
295
|
];
|
|
326
296
|
|
|
327
297
|
export const allModels = [...anthropicChatModels];
|
|
@@ -10,12 +10,13 @@ const doubaoChatModels: AIChatModelCard[] = [
|
|
|
10
10
|
vision: true,
|
|
11
11
|
},
|
|
12
12
|
config: {
|
|
13
|
-
deploymentName: 'doubao-seed-1-8-
|
|
13
|
+
deploymentName: 'doubao-seed-1-8-251228',
|
|
14
14
|
},
|
|
15
15
|
contextWindowTokens: 256_000,
|
|
16
16
|
description:
|
|
17
17
|
'Doubao-Seed-1.8 有着更强的多模态理解能力和 Agent 能力,支持文本/图片/视频输入与上下文缓存,可在复杂任务中提供更出色的表现。',
|
|
18
18
|
displayName: 'Doubao Seed 1.8',
|
|
19
|
+
enabled: true,
|
|
19
20
|
id: 'doubao-seed-1.8',
|
|
20
21
|
maxOutput: 64_000,
|
|
21
22
|
pricing: {
|
|
@@ -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 />}
|
|
13
|
+
<SideBarHeaderLayout left={<Agent />} />
|
|
15
14
|
<Nav />
|
|
16
15
|
</>
|
|
17
16
|
);
|
package/src/app/[variants]/(main)/settings/profile/features/KlavisAuthorizationList/index.tsx
CHANGED
|
@@ -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
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
{
|
|
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 =
|
|
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
|
)}
|
|
@@ -150,7 +150,6 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
|
150
150
|
enabled,
|
|
151
151
|
isLoading,
|
|
152
152
|
configUpdating,
|
|
153
|
-
isFetchOnClient,
|
|
154
153
|
enableResponseApi,
|
|
155
154
|
isProviderEndpointNotEmpty,
|
|
156
155
|
isProviderApiKeyNotEmpty,
|
|
@@ -160,7 +159,6 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
|
160
159
|
aiProviderSelectors.isProviderEnabled(id)(s),
|
|
161
160
|
aiProviderSelectors.isAiProviderConfigLoading(id)(s),
|
|
162
161
|
aiProviderSelectors.isProviderConfigUpdating(id)(s),
|
|
163
|
-
aiProviderSelectors.isProviderFetchOnClient(id)(s),
|
|
164
162
|
aiProviderSelectors.isProviderEnableResponseApi(id)(s),
|
|
165
163
|
aiProviderSelectors.isActiveProviderEndpointNotEmpty(s),
|
|
166
164
|
aiProviderSelectors.isActiveProviderApiKeyNotEmpty(s),
|
|
@@ -301,11 +299,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
|
301
299
|
(showEndpoint && isProviderEndpointNotEmpty) ||
|
|
302
300
|
(showApiKey && isProviderApiKeyNotEmpty));
|
|
303
301
|
const clientFetchItem = showClientFetch && {
|
|
304
|
-
children: isLoading ?
|
|
305
|
-
<SkeletonSwitch />
|
|
306
|
-
) : (
|
|
307
|
-
<Switch checked={isFetchOnClient} disabled={configUpdating} />
|
|
308
|
-
),
|
|
302
|
+
children: isLoading ? <SkeletonSwitch /> : <Switch loading={configUpdating} />,
|
|
309
303
|
desc: t('providerModels.config.fetchOnClient.desc'),
|
|
310
304
|
label: t('providerModels.config.fetchOnClient.title'),
|
|
311
305
|
minWidth: undefined,
|
|
@@ -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={
|
|
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(
|
|
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
|
-
|
|
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={
|
|
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
|
|
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 = (
|
|
43
|
-
|
|
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={
|
|
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
|
|
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={
|
|
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 = (
|
|
27
|
-
|
|
28
|
-
|
|
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={{
|