@lobehub/lobehub 2.0.0-next.312 → 2.0.0-next.314

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 (93) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/apps/desktop/src/main/appBrowsers.ts +4 -1
  3. package/apps/desktop/src/main/controllers/AuthCtr.ts +75 -7
  4. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +15 -3
  5. package/apps/desktop/src/main/core/browser/Browser.ts +14 -4
  6. package/apps/desktop/src/main/core/browser/BrowserManager.ts +7 -2
  7. package/changelog/v1.json +18 -0
  8. package/docs/usage/providers/internlm.mdx +2 -2
  9. package/docs/usage/providers/internlm.zh-CN.mdx +3 -3
  10. package/e2e/src/steps/community/detail-pages.steps.ts +2 -2
  11. package/e2e/src/steps/community/interactions.steps.ts +6 -6
  12. package/e2e/src/steps/hooks.ts +19 -3
  13. package/locales/en-US/error.json +10 -1
  14. package/locales/en-US/subscription.json +1 -1
  15. package/locales/zh-CN/desktop-onboarding.json +5 -0
  16. package/locales/zh-CN/error.json +10 -1
  17. package/locales/zh-CN/subscription.json +1 -1
  18. package/package.json +1 -1
  19. package/packages/agent-runtime/src/agents/GeneralChatAgent.ts +14 -2
  20. package/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts +275 -1
  21. package/packages/builtin-tool-cloud-sandbox/package.json +1 -0
  22. package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +105 -134
  23. package/packages/builtin-tool-cloud-sandbox/src/executor/index.ts +254 -0
  24. package/packages/builtin-tool-cloud-sandbox/src/index.ts +1 -0
  25. package/packages/builtin-tool-cloud-sandbox/src/types/api.ts +22 -0
  26. package/packages/builtin-tool-cloud-sandbox/src/types/index.ts +4 -0
  27. package/packages/builtin-tool-cloud-sandbox/src/types/params.ts +85 -0
  28. package/packages/builtin-tool-cloud-sandbox/src/types/service.ts +48 -0
  29. package/packages/builtin-tool-cloud-sandbox/src/{types.ts → types/state.ts} +0 -23
  30. package/packages/builtin-tool-memory/src/manifest.ts +5 -5
  31. package/packages/desktop-bridge/src/index.ts +5 -0
  32. package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +1 -1
  33. package/packages/editor-runtime/src/__tests__/EditorRuntime.test.ts +1 -1
  34. package/packages/electron-client-ipc/src/events/index.ts +5 -1
  35. package/packages/electron-client-ipc/src/events/remoteServer.ts +23 -0
  36. package/packages/electron-client-ipc/src/types/window.ts +3 -2
  37. package/packages/memory-user-memory/src/schemas/index.ts +0 -1
  38. package/packages/model-bank/src/modelProviders/internlm.ts +1 -1
  39. package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +5 -15
  40. package/packages/model-runtime/src/providers/internlm/index.test.ts +15 -15
  41. package/packages/model-runtime/src/providers/internlm/index.ts +1 -1
  42. package/packages/types/src/tool/intervention.ts +4 -2
  43. package/packages/types/src/user/preference.ts +1 -0
  44. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +6 -3
  45. package/src/app/[variants]/(desktop)/desktop-onboarding/components/OnboardingFooterActions.tsx +38 -0
  46. package/src/app/[variants]/(desktop)/desktop-onboarding/features/DataModeStep.tsx +19 -14
  47. package/src/app/[variants]/(desktop)/desktop-onboarding/features/LoginStep.tsx +121 -29
  48. package/src/app/[variants]/(desktop)/desktop-onboarding/features/PermissionsStep.tsx +19 -14
  49. package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +8 -7
  50. package/src/app/[variants]/(main)/_layout/DesktopAutoOidcOnFirstOpen.tsx +4 -0
  51. package/src/app/manifest.ts +1 -1
  52. package/src/business/server/user.ts +4 -0
  53. package/src/features/Conversation/Messages/Task/Actions/index.tsx +0 -2
  54. package/src/features/Conversation/Messages/Task/index.tsx +1 -1
  55. package/src/features/Conversation/Messages/Tasks/shared/ProcessingState.tsx +0 -2
  56. package/src/features/Electron/titlebar/NavigationBar.tsx +1 -2
  57. package/src/features/NavPanel/components/NavPanelDraggable.tsx +0 -14
  58. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +4 -3
  59. package/src/features/SharePopover/index.tsx +5 -3
  60. package/src/hooks/useAppOrigin.ts +16 -0
  61. package/src/layout/GlobalProvider/useUserStateRedirect.ts +37 -24
  62. package/src/libs/trusted-client/index.ts +2 -5
  63. package/src/locales/default/desktop-onboarding.ts +5 -0
  64. package/src/locales/default/error.ts +11 -0
  65. package/src/locales/default/subscription.ts +1 -1
  66. package/src/server/manifest.ts +2 -2
  67. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -0
  68. package/src/server/routers/lambda/user.ts +24 -10
  69. package/src/server/services/agentRuntime/AgentRuntimeService.test.ts +3 -0
  70. package/src/server/services/agentRuntime/AgentRuntimeService.ts +8 -5
  71. package/src/server/services/agentRuntime/types.ts +7 -0
  72. package/src/server/services/aiAgent/__tests__/execGroupSubAgentTask.test.ts +3 -0
  73. package/src/server/services/aiAgent/index.ts +10 -4
  74. package/src/server/services/market/index.ts +20 -0
  75. package/src/server/services/sandbox/index.ts +186 -0
  76. package/src/server/services/toolExecution/builtin.ts +12 -18
  77. package/src/server/services/toolExecution/index.ts +1 -1
  78. package/src/server/services/toolExecution/serverRuntimes/cloudSandbox.ts +38 -0
  79. package/src/server/services/toolExecution/serverRuntimes/index.ts +55 -0
  80. package/src/server/services/toolExecution/serverRuntimes/types.ts +14 -0
  81. package/src/server/services/toolExecution/serverRuntimes/webBrowsing.ts +20 -0
  82. package/src/server/services/toolExecution/types.ts +2 -0
  83. package/src/services/{codeInterpreter.ts → cloudSandbox.ts} +3 -3
  84. package/src/services/electron/remoteServer.ts +8 -0
  85. package/src/services/electron/system.ts +5 -5
  86. package/src/store/chat/agents/GroupOrchestration/__tests__/batch-exec-async-tasks.test.ts +626 -0
  87. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +294 -0
  88. package/src/store/chat/slices/plugin/action.test.ts +0 -48
  89. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +0 -131
  90. package/src/store/tool/slices/builtin/executors/index.ts +2 -0
  91. package/src/store/user/slices/settings/selectors/toolIntervention.test.ts +143 -0
  92. package/src/store/user/slices/settings/selectors/toolIntervention.ts +11 -2
  93. package/packages/memory-user-memory/src/schemas/jsonSchemas.ts +0 -37
@@ -9,7 +9,7 @@ import { LobeInternLMAI, params } from './index';
9
9
  testProvider({
10
10
  Runtime: LobeInternLMAI,
11
11
  provider: ModelProvider.InternLM,
12
- defaultBaseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
12
+ defaultBaseURL: 'https://chat.intern-ai.org.cn/api/v1',
13
13
  chatDebugEnv: 'DEBUG_INTERNLM_CHAT_COMPLETION',
14
14
  chatModel: 'internlm2_5-7b-chat',
15
15
  test: {
@@ -30,7 +30,7 @@ describe('LobeInternLMAI - custom features', () => {
30
30
 
31
31
  describe('params object', () => {
32
32
  it('should export params with correct baseURL', () => {
33
- expect(params.baseURL).toBe('https://internlm-chat.intern-ai.org.cn/puyu/api/v1');
33
+ expect(params.baseURL).toBe('https://chat.intern-ai.org.cn/api/v1');
34
34
  });
35
35
 
36
36
  it('should have correct provider', () => {
@@ -116,7 +116,7 @@ describe('LobeInternLMAI - custom features', () => {
116
116
  describe('models', () => {
117
117
  it('should fetch and process models', async () => {
118
118
  const mockClient = {
119
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
119
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
120
120
  apiKey: 'test',
121
121
  models: {
122
122
  list: vi.fn().mockResolvedValue({
@@ -137,7 +137,7 @@ describe('LobeInternLMAI - custom features', () => {
137
137
 
138
138
  it('should detect function call capability from model name', async () => {
139
139
  const mockClient = {
140
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
140
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
141
141
  apiKey: 'test',
142
142
  models: {
143
143
  list: vi.fn().mockResolvedValue({
@@ -156,7 +156,7 @@ describe('LobeInternLMAI - custom features', () => {
156
156
 
157
157
  it('should detect vision capability from model name', async () => {
158
158
  const mockClient = {
159
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
159
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
160
160
  apiKey: 'test',
161
161
  models: {
162
162
  list: vi.fn().mockResolvedValue({
@@ -175,7 +175,7 @@ describe('LobeInternLMAI - custom features', () => {
175
175
 
176
176
  it('should handle case-insensitive keyword matching', async () => {
177
177
  const mockClient = {
178
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
178
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
179
179
  apiKey: 'test',
180
180
  models: {
181
181
  list: vi.fn().mockResolvedValue({
@@ -194,7 +194,7 @@ describe('LobeInternLMAI - custom features', () => {
194
194
 
195
195
  it('should merge with known model data from model-bank', async () => {
196
196
  const mockClient = {
197
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
197
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
198
198
  apiKey: 'test',
199
199
  models: {
200
200
  list: vi.fn().mockResolvedValue({
@@ -215,7 +215,7 @@ describe('LobeInternLMAI - custom features', () => {
215
215
 
216
216
  it('should handle models not in model-bank', async () => {
217
217
  const mockClient = {
218
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
218
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
219
219
  apiKey: 'test',
220
220
  models: {
221
221
  list: vi.fn().mockResolvedValue({
@@ -238,7 +238,7 @@ describe('LobeInternLMAI - custom features', () => {
238
238
 
239
239
  it('should set enabled flag from known model', async () => {
240
240
  const mockClient = {
241
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
241
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
242
242
  apiKey: 'test',
243
243
  models: {
244
244
  list: vi.fn().mockResolvedValue({
@@ -255,7 +255,7 @@ describe('LobeInternLMAI - custom features', () => {
255
255
 
256
256
  it('should inherit abilities from known model', async () => {
257
257
  const mockClient = {
258
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
258
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
259
259
  apiKey: 'test',
260
260
  models: {
261
261
  list: vi.fn().mockResolvedValue({
@@ -291,7 +291,7 @@ describe('LobeInternLMAI - custom features', () => {
291
291
 
292
292
  it('should handle empty model list', async () => {
293
293
  const mockClient = {
294
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
294
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
295
295
  apiKey: 'test',
296
296
  models: {
297
297
  list: vi.fn().mockResolvedValue({
@@ -306,7 +306,7 @@ describe('LobeInternLMAI - custom features', () => {
306
306
 
307
307
  it('should filter out null/undefined models', async () => {
308
308
  const mockClient = {
309
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
309
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
310
310
  apiKey: 'test',
311
311
  models: {
312
312
  list: vi.fn().mockResolvedValue({
@@ -322,7 +322,7 @@ describe('LobeInternLMAI - custom features', () => {
322
322
 
323
323
  it('should set contextWindowTokens from known model', async () => {
324
324
  const mockClient = {
325
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
325
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
326
326
  apiKey: 'test',
327
327
  models: {
328
328
  list: vi.fn().mockResolvedValue({
@@ -340,7 +340,7 @@ describe('LobeInternLMAI - custom features', () => {
340
340
 
341
341
  it('should set displayName from known model', async () => {
342
342
  const mockClient = {
343
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
343
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
344
344
  apiKey: 'test',
345
345
  models: {
346
346
  list: vi.fn().mockResolvedValue({
@@ -358,7 +358,7 @@ describe('LobeInternLMAI - custom features', () => {
358
358
 
359
359
  it('should combine keyword detection with known model abilities', async () => {
360
360
  const mockClient = {
361
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
361
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
362
362
  apiKey: 'test',
363
363
  models: {
364
364
  list: vi.fn().mockResolvedValue({
@@ -11,7 +11,7 @@ export interface InternLMModelCard {
11
11
  }
12
12
 
13
13
  export const params = {
14
- baseURL: 'https://internlm-chat.intern-ai.org.cn/puyu/api/v1',
14
+ baseURL: 'https://chat.intern-ai.org.cn/api/v1',
15
15
  chatCompletion: {
16
16
  handlePayload: (payload) => {
17
17
  return {
@@ -138,13 +138,15 @@ export interface UserInterventionConfig {
138
138
  * - auto-run: Automatically approve all tools without user consent
139
139
  * - allow-list: Only approve tools in the allow list
140
140
  * - manual: Use tool's own humanIntervention config (default)
141
+ * - headless: Fully automated mode for async tasks - all tools execute automatically,
142
+ * security blacklist tools are skipped (not blocked)
141
143
  */
142
- approvalMode: 'auto-run' | 'allow-list' | 'manual';
144
+ approvalMode: 'auto-run' | 'allow-list' | 'manual' | 'headless';
143
145
  }
144
146
 
145
147
  export const UserInterventionConfigSchema = z.object({
146
148
  allowList: z.array(z.string()).optional(),
147
- approvalMode: z.enum(['auto-run', 'allow-list', 'manual']),
149
+ approvalMode: z.enum(['auto-run', 'allow-list', 'manual', 'headless']),
148
150
  });
149
151
 
150
152
  /**
@@ -84,6 +84,7 @@ export interface UserInitializationState {
84
84
  hasConversation?: boolean;
85
85
  interests?: string[];
86
86
  isFreePlan?: boolean;
87
+ isInviteCodeRequired?: boolean;
87
88
  isInWaitList?: boolean;
88
89
  /** @deprecated Use onboarding field instead */
89
90
  isOnboard?: boolean;
@@ -3,7 +3,7 @@
3
3
  import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
4
4
  import { Center, Flexbox, Text } from '@lobehub/ui';
5
5
  import { Divider } from 'antd';
6
- import { cx } from 'antd-style';
6
+ import { css, cx } from 'antd-style';
7
7
  import type { FC, PropsWithChildren } from 'react';
8
8
 
9
9
  import SimpleTitleBar from '@/features/Electron/titlebar/SimpleTitleBar';
@@ -13,6 +13,9 @@ import { useIsDark } from '@/hooks/useIsDark';
13
13
 
14
14
  import { styles } from './style';
15
15
 
16
+ const contentContainer = css`
17
+ overflow: auto;
18
+ `;
16
19
  const OnboardingContainer: FC<PropsWithChildren> = ({ children }) => {
17
20
  const isDarkMode = useIsDark();
18
21
  return (
@@ -44,9 +47,9 @@ const OnboardingContainer: FC<PropsWithChildren> = ({ children }) => {
44
47
  <ThemeButton placement={'bottomRight'} size={18} />
45
48
  </Flexbox>
46
49
  </Flexbox>
47
- <Center height={'100%'} padding={16} width={'100%'}>
50
+ <Flexbox align={'center'} className={cx(contentContainer)} height={'100%'} width={'100%'}>
48
51
  {children}
49
- </Center>
52
+ </Flexbox>
50
53
  <Center padding={24}>
51
54
  <Text align={'center'} type={'secondary'}>
52
55
  © 2025 LobeHub. All rights reserved.
@@ -0,0 +1,38 @@
1
+ import { Flexbox, type FlexboxProps } from '@lobehub/ui';
2
+ import { cssVar } from 'antd-style';
3
+ import { type ReactNode, memo } from 'react';
4
+
5
+ interface OnboardingFooterActionsProps extends Omit<FlexboxProps, 'children'> {
6
+ left?: ReactNode;
7
+ right?: ReactNode;
8
+ }
9
+
10
+ const OnboardingFooterActions = memo<OnboardingFooterActionsProps>(
11
+ ({ left, right, style, ...rest }) => {
12
+ return (
13
+ <Flexbox
14
+ align={'center'}
15
+ horizontal
16
+ justify={'space-between'}
17
+ style={{
18
+ background: cssVar.colorBgContainer,
19
+ bottom: 0,
20
+ marginTop: 'auto',
21
+ paddingTop: 16,
22
+ position: 'sticky',
23
+ width: '100%',
24
+ zIndex: 10,
25
+ ...style,
26
+ }}
27
+ {...rest}
28
+ >
29
+ <div>{left}</div>
30
+ <div>{right}</div>
31
+ </Flexbox>
32
+ );
33
+ },
34
+ );
35
+
36
+ OnboardingFooterActions.displayName = 'OnboardingFooterActions';
37
+
38
+ export default OnboardingFooterActions;
@@ -10,6 +10,7 @@ import { useUserStore } from '@/store/user';
10
10
  import { userGeneralSettingsSelectors } from '@/store/user/selectors';
11
11
 
12
12
  import LobeMessage from '../components/LobeMessage';
13
+ import OnboardingFooterActions from '../components/OnboardingFooterActions';
13
14
 
14
15
  type DataMode = 'share' | 'privacy';
15
16
 
@@ -48,7 +49,7 @@ const DataModeStep = memo<DataModeStepProps>(({ onBack, onNext }) => {
48
49
  );
49
50
 
50
51
  return (
51
- <Flexbox gap={16}>
52
+ <Flexbox gap={16} style={{ height: '100%', minHeight: '100%' }}>
52
53
  <Flexbox>
53
54
  <LobeMessage sentences={[t('screen4.title'), t('screen4.title2'), t('screen4.title3')]} />
54
55
  <Text as={'p'}>{t('screen4.description')}</Text>
@@ -113,19 +114,23 @@ const DataModeStep = memo<DataModeStepProps>(({ onBack, onNext }) => {
113
114
  <Text color={cssVar.colorTextSecondary} fontSize={12} style={{ marginTop: 16 }}>
114
115
  {t('screen4.footerNote')}
115
116
  </Text>
116
- <Flexbox horizontal justify={'space-between'} style={{ marginTop: 32 }}>
117
- <Button
118
- icon={Undo2Icon}
119
- onClick={onBack}
120
- style={{ color: cssVar.colorTextDescription }}
121
- type={'text'}
122
- >
123
- {t('back')}
124
- </Button>
125
- <Button onClick={onNext} type={'primary'}>
126
- {t('next')}
127
- </Button>
128
- </Flexbox>
117
+ <OnboardingFooterActions
118
+ left={
119
+ <Button
120
+ icon={Undo2Icon}
121
+ onClick={onBack}
122
+ style={{ color: cssVar.colorTextDescription }}
123
+ type={'text'}
124
+ >
125
+ {t('back')}
126
+ </Button>
127
+ }
128
+ right={
129
+ <Button onClick={onNext} type={'primary'}>
130
+ {t('next')}
131
+ </Button>
132
+ }
133
+ />
129
134
  </Flexbox>
130
135
  );
131
136
  });
@@ -1,6 +1,10 @@
1
1
  'use client';
2
2
 
3
- import { useWatchBroadcast } from '@lobechat/electron-client-ipc';
3
+ import {
4
+ AuthorizationPhase,
5
+ AuthorizationProgress,
6
+ useWatchBroadcast,
7
+ } from '@lobechat/electron-client-ipc';
4
8
  import { Alert, Button, Center, Flexbox, Icon, Input, Text } from '@lobehub/ui';
5
9
  import { Divider } from 'antd';
6
10
  import { cssVar } from 'antd-style';
@@ -9,6 +13,8 @@ import { memo, useEffect, useState } from 'react';
9
13
  import { useTranslation } from 'react-i18next';
10
14
 
11
15
  import { isDesktop } from '@/const/version';
16
+ import UserInfo from '@/features/User/UserInfo';
17
+ import { remoteServerService } from '@/services/electron/remoteServer';
12
18
  import { useElectronStore } from '@/store/electron';
13
19
  import { setDesktopAutoOidcFirstOpenHandled } from '@/utils/electron/autoOidc';
14
20
 
@@ -20,6 +26,13 @@ type LoginMethod = 'cloud' | 'selfhost';
20
26
  // 登录状态类型
21
27
  type LoginStatus = 'idle' | 'loading' | 'success' | 'error';
22
28
 
29
+ const authorizationPhaseI18nKeyMap: Record<AuthorizationPhase, string> = {
30
+ browser_opened: 'screen5.auth.phase.browserOpened',
31
+ cancelled: 'screen5.actions.cancel',
32
+ verifying: 'screen5.auth.phase.verifying',
33
+ waiting_for_auth: 'screen5.auth.phase.waitingForAuth',
34
+ };
35
+
23
36
  const loginMethodMetas = {
24
37
  cloud: {
25
38
  descriptionKey: 'screen5.methods.cloud.description',
@@ -44,6 +57,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
44
57
  const { t } = useTranslation('desktop-onboarding');
45
58
  const [endpoint, setEndpoint] = useState('');
46
59
  const [cloudLoginStatus, setCloudLoginStatus] = useState<LoginStatus>('idle');
60
+ const [authProgress, setAuthProgress] = useState<AuthorizationProgress | null>(null);
47
61
  const [selfhostLoginStatus, setSelfhostLoginStatus] = useState<LoginStatus>('idle');
48
62
  const [remoteError, setRemoteError] = useState<string | null>(null);
49
63
  const [isSigningOut, setIsSigningOut] = useState(false);
@@ -164,29 +178,61 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
164
178
  useWatchBroadcast('authorizationSuccessful', async () => {
165
179
  setRemoteError(null);
166
180
  clearRemoteServerSyncError();
181
+ setAuthProgress(null);
167
182
  await refreshServerConfig();
168
183
  });
169
184
 
170
185
  useWatchBroadcast('authorizationFailed', ({ error }) => {
171
186
  setRemoteError(error);
187
+ setAuthProgress(null);
172
188
  if (cloudLoginStatus === 'loading') setCloudLoginStatus('error');
173
189
  if (selfhostLoginStatus === 'loading') setSelfhostLoginStatus('error');
174
190
  });
175
191
 
192
+ useWatchBroadcast('authorizationProgress', (progress) => {
193
+ setAuthProgress(progress);
194
+ if (progress.phase === 'cancelled') {
195
+ setCloudLoginStatus('idle');
196
+ setSelfhostLoginStatus('idle');
197
+ setAuthProgress(null);
198
+ }
199
+ });
200
+
201
+ const handleCancelAuth = async () => {
202
+ await remoteServerService.cancelAuthorization();
203
+ setCloudLoginStatus('idle');
204
+ setSelfhostLoginStatus('idle');
205
+ setAuthProgress(null);
206
+ };
207
+
176
208
  // 渲染 Cloud 登录内容
177
209
  const renderCloudContent = () => {
178
210
  if (cloudLoginStatus === 'success') {
179
211
  return (
180
- <Button
181
- block
182
- disabled={isSigningOut || isConnectingServer}
183
- icon={Cloud}
184
- onClick={handleSignOut}
185
- size={'large'}
186
- type={'default'}
187
- >
188
- {isSigningOut ? t('screen5.actions.signingOut') : t('screen5.actions.signOut')}
189
- </Button>
212
+ <Flexbox gap={16} style={{ width: '100%' }}>
213
+ <Alert
214
+ description={t('authResult.success.desc')}
215
+ style={{ width: '100%' }}
216
+ title={t('authResult.success.title')}
217
+ type={'success'}
218
+ />
219
+ <UserInfo
220
+ style={{
221
+ background: cssVar.colorFillSecondary,
222
+ borderRadius: 8,
223
+ }}
224
+ />
225
+ <Button
226
+ block
227
+ disabled={isSigningOut || isConnectingServer}
228
+ icon={Cloud}
229
+ onClick={handleSignOut}
230
+ size={'large'}
231
+ type={'default'}
232
+ >
233
+ {isSigningOut ? t('screen5.actions.signingOut') : t('screen5.actions.signOut')}
234
+ </Button>
235
+ </Flexbox>
190
236
  );
191
237
  }
192
238
 
@@ -212,19 +258,51 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
212
258
  );
213
259
  }
214
260
 
261
+ if (cloudLoginStatus === 'loading') {
262
+ const phaseText = t(authorizationPhaseI18nKeyMap[authProgress?.phase ?? 'browser_opened'], {
263
+ defaultValue: t('screen5.actions.signingIn'),
264
+ });
265
+ const remainingSeconds = authProgress
266
+ ? Math.max(0, Math.ceil((authProgress.maxPollTime - authProgress.elapsed) / 1000))
267
+ : null;
268
+
269
+ return (
270
+ <Flexbox gap={8} style={{ width: '100%' }}>
271
+ <Button block disabled={true} icon={Cloud} loading={true} size={'large'} type={'primary'}>
272
+ {t('screen5.actions.signingIn')}
273
+ </Button>
274
+ <Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
275
+ {phaseText}
276
+ </Text>
277
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
278
+ {remainingSeconds !== null ? (
279
+ <Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
280
+ {t('screen5.auth.remaining', {
281
+ time: remainingSeconds,
282
+ })}
283
+ </Text>
284
+ ) : (
285
+ <div />
286
+ )}
287
+ <Button onClick={handleCancelAuth} size={'small'} type={'text'}>
288
+ {t('screen5.actions.cancel')}
289
+ </Button>
290
+ </Flexbox>
291
+ </Flexbox>
292
+ );
293
+ }
294
+
215
295
  return (
216
296
  <Button
217
297
  block
218
- disabled={cloudLoginStatus === 'loading' || isConnectingServer}
298
+ disabled={isConnectingServer}
219
299
  icon={Cloud}
220
- loading={cloudLoginStatus === 'loading'}
300
+ loading={false}
221
301
  onClick={handleCloudLogin}
222
302
  size={'large'}
223
303
  type={'primary'}
224
304
  >
225
- {cloudLoginStatus === 'loading'
226
- ? t('screen5.actions.signingIn')
227
- : t('screen5.actions.signInCloud')}
305
+ {t('screen5.actions.signInCloud')}
228
306
  </Button>
229
307
  );
230
308
  };
@@ -233,16 +311,30 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
233
311
  const renderSelfhostContent = () => {
234
312
  if (selfhostLoginStatus === 'success') {
235
313
  return (
236
- <Button
237
- block
238
- disabled={isSigningOut || isConnectingServer}
239
- icon={Server}
240
- onClick={handleSignOut}
241
- size={'large'}
242
- type={'default'}
243
- >
244
- {isSigningOut ? t('screen5.actions.signingOut') : t('screen5.actions.signOut')}
245
- </Button>
314
+ <Flexbox gap={16} style={{ width: '100%' }}>
315
+ <Alert
316
+ description={t('authResult.success.desc')}
317
+ style={{ width: '100%' }}
318
+ title={t('authResult.success.title')}
319
+ type={'success'}
320
+ />
321
+ <UserInfo
322
+ style={{
323
+ background: cssVar.colorFillSecondary,
324
+ borderRadius: 8,
325
+ }}
326
+ />
327
+ <Button
328
+ block
329
+ disabled={isSigningOut || isConnectingServer}
330
+ icon={Server}
331
+ onClick={handleSignOut}
332
+ size={'large'}
333
+ type={'default'}
334
+ >
335
+ {isSigningOut ? t('screen5.actions.signingOut') : t('screen5.actions.signOut')}
336
+ </Button>
337
+ </Flexbox>
246
338
  );
247
339
  }
248
340
 
@@ -296,8 +388,8 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
296
388
  };
297
389
 
298
390
  return (
299
- <Flexbox gap={32}>
300
- <Flexbox>
391
+ <Center gap={32} style={{ height: '100%', minHeight: '100%' }}>
392
+ <Flexbox align={'flex-start'} justify={'flex-start'} style={{ width: '100%' }}>
301
393
  <LobeMessage sentences={[t('screen5.title'), t('screen5.title2'), t('screen5.title3')]} />
302
394
  <Text as={'p'}>{t('screen5.description')}</Text>
303
395
  </Flexbox>
@@ -343,7 +435,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
343
435
  </Button>
344
436
  </Flexbox>
345
437
  )}
346
- </Flexbox>
438
+ </Center>
347
439
  );
348
440
  });
349
441
 
@@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next';
18
18
  import { ensureElectronIpc } from '@/utils/electron/ipc';
19
19
 
20
20
  import LobeMessage from '../components/LobeMessage';
21
+ import OnboardingFooterActions from '../components/OnboardingFooterActions';
21
22
 
22
23
  type PermissionMeta = {
23
24
  descriptionKey: string;
@@ -154,7 +155,7 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
154
155
  };
155
156
 
156
157
  return (
157
- <Flexbox gap={16}>
158
+ <Flexbox gap={16} style={{ height: '100%', minHeight: '100%' }}>
158
159
  <Flexbox>
159
160
  <LobeMessage sentences={[t('screen3.title'), t('screen3.title2'), t('screen3.title3')]} />
160
161
  <Text as={'p'}>{t('screen3.description')}</Text>
@@ -207,19 +208,23 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
207
208
  </Block>
208
209
  ))}
209
210
  </Block>
210
- <Flexbox horizontal justify={'space-between'} style={{ marginTop: 32 }}>
211
- <Button
212
- icon={Undo2Icon}
213
- onClick={onBack}
214
- style={{ color: cssVar.colorTextDescription }}
215
- type={'text'}
216
- >
217
- {t('back')}
218
- </Button>
219
- <Button onClick={onNext} type={'primary'}>
220
- {t('next')}
221
- </Button>
222
- </Flexbox>
211
+ <OnboardingFooterActions
212
+ left={
213
+ <Button
214
+ icon={Undo2Icon}
215
+ onClick={onBack}
216
+ style={{ color: cssVar.colorTextDescription }}
217
+ type={'text'}
218
+ >
219
+ {t('back')}
220
+ </Button>
221
+ }
222
+ right={
223
+ <Button onClick={onNext} type={'primary'}>
224
+ {t('next')}
225
+ </Button>
226
+ }
227
+ />
223
228
  </Flexbox>
224
229
  );
225
230
  });
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { APP_WINDOW_MIN_SIZE } from '@lobechat/desktop-bridge';
3
4
  import { Flexbox, Skeleton } from '@lobehub/ui';
4
5
  import { Suspense, memo, useCallback, useEffect, useState } from 'react';
5
6
  import { useSearchParams } from 'react-router-dom';
@@ -50,12 +51,11 @@ const DesktopOnboardingPage = memo(() => {
50
51
 
51
52
  // 设置窗口大小和可调整性
52
53
  useEffect(() => {
53
- const fixedSize = { height: 900, width: 1400 };
54
+ const minimumSize = { height: 900, width: 1200 };
54
55
 
55
56
  const applyWindowSettings = async () => {
56
57
  try {
57
- await electronSystemService.setWindowSize(fixedSize);
58
- await electronSystemService.setWindowResizable({ resizable: false });
58
+ await electronSystemService.setWindowMinimumSize(minimumSize);
59
59
  } catch (error) {
60
60
  console.error('[DesktopOnboarding] Failed to apply window settings:', error);
61
61
  }
@@ -64,7 +64,8 @@ const DesktopOnboardingPage = memo(() => {
64
64
  applyWindowSettings();
65
65
 
66
66
  return () => {
67
- electronSystemService.setWindowResizable({ resizable: true }).catch((error) => {
67
+ // Restore to app-level default minimum size preset
68
+ electronSystemService.setWindowMinimumSize(APP_WINDOW_MIN_SIZE).catch((error) => {
68
69
  console.error('[DesktopOnboarding] Failed to restore window settings:', error);
69
70
  });
70
71
  };
@@ -127,9 +128,9 @@ const DesktopOnboardingPage = memo(() => {
127
128
  // 如果是第4步(LoginStep),完成 onboarding
128
129
  setDesktopOnboardingCompleted();
129
130
  clearDesktopOnboardingStep(); // Clear persisted step since onboarding is complete
130
- // Restore window resizable before hard reload (cleanup won't run due to hard navigation)
131
+ // Restore window minimum size before hard reload (cleanup won't run due to hard navigation)
131
132
  electronSystemService
132
- .setWindowResizable({ resizable: true })
133
+ .setWindowMinimumSize(APP_WINDOW_MIN_SIZE)
133
134
  .catch(console.error)
134
135
  .finally(() => {
135
136
  // Use hard reload instead of SPA navigation to ensure the app boots with the new desktop state.
@@ -196,7 +197,7 @@ const DesktopOnboardingPage = memo(() => {
196
197
 
197
198
  return (
198
199
  <OnboardingContainer>
199
- <Flexbox gap={24} style={{ maxWidth: 560, width: '100%' }}>
200
+ <Flexbox gap={24} style={{ maxWidth: 560, minHeight: '100%', width: '100%' }}>
200
201
  <Suspense
201
202
  fallback={
202
203
  <Flexbox gap={8}>
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { memo, useEffect } from 'react';
4
4
 
5
+ import { getDesktopOnboardingCompleted } from '@/app/[variants]/(desktop)/desktop-onboarding/storage';
5
6
  import { useElectronStore } from '@/store/electron';
6
7
  import {
7
8
  getDesktopAutoOidcFirstOpenHandled,
@@ -28,6 +29,9 @@ const DesktopAutoOidcOnFirstOpen = memo(() => {
28
29
  useEffect(() => {
29
30
  if (!isInitRemoteServerConfig) return;
30
31
 
32
+ // Don't auto-trigger during onboarding flow.
33
+ if (!getDesktopOnboardingCompleted()) return;
34
+
31
35
  // If already connected or not in cloud mode, don't auto-trigger.
32
36
  if (dataSyncConfig.active) return;
33
37
  if (dataSyncConfig.storageMode !== 'cloud') return;
@@ -16,7 +16,7 @@ const manifest = async (): Promise<MetadataRoute.Manifest> => {
16
16
  ],
17
17
  name: 'LobeChat',
18
18
  short_name: 'LobeChat',
19
- start_url: '/agent',
19
+ start_url: '/',
20
20
  theme_color: '#000000',
21
21
  };
22
22
  }