@lobehub/lobehub 2.0.0-next.252 → 2.0.0-next.253

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.
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { useWatchBroadcast } from '@lobechat/electron-client-ipc';
3
4
  import { Block, Button, Flexbox, Icon, Text } from '@lobehub/ui';
4
5
  import { cssVar } from 'antd-style';
5
6
  import {
@@ -93,12 +94,15 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
93
94
  const micStatus = await ipc.system.getMediaAccessStatus('microphone');
94
95
  const screenStatus = await ipc.system.getMediaAccessStatus('screen');
95
96
  const accessibilityStatus = await ipc.system.getAccessibilityStatus();
97
+ // Full Disk Access can now be checked by attempting to read protected directories
98
+ const fullDiskStatus = await ipc.system.getFullDiskAccessStatus();
96
99
 
97
100
  setPermissions((prev) =>
98
101
  prev.map((p) => {
99
102
  if (p.id === 1) return { ...p, granted: notifStatus === 'authorized' };
100
- // Full Disk Access cannot be checked programmatically, so it remains manual
101
- if (p.id === 2) return { ...p, buttonKey: 'screen3.actions.openSettings', granted: false };
103
+ // Full Disk Access status is detected by reading protected directories
104
+ if (p.id === 2)
105
+ return { ...p, buttonKey: 'screen3.actions.openSettings', granted: fullDiskStatus };
102
106
  if (p.id === 3)
103
107
  return { ...p, granted: micStatus === 'granted' && screenStatus === 'granted' };
104
108
  if (p.id === 4) return { ...p, granted: accessibilityStatus };
@@ -111,28 +115,11 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
111
115
  checkAllPermissions();
112
116
  }, [checkAllPermissions]);
113
117
 
114
- // When this page regains focus (e.g. back from System Settings), re-check permission states and refresh UI.
115
- useEffect(() => {
116
- if (typeof window === 'undefined') return;
117
-
118
- const handleFocus = () => {
119
- checkAllPermissions();
120
- };
121
-
122
- const handleVisibilityChange = () => {
123
- if (document.visibilityState === 'visible') {
124
- checkAllPermissions();
125
- }
126
- };
127
-
128
- window.addEventListener('focus', handleFocus);
129
- document.addEventListener('visibilitychange', handleVisibilityChange);
130
-
131
- return () => {
132
- window.removeEventListener('focus', handleFocus);
133
- document.removeEventListener('visibilitychange', handleVisibilityChange);
134
- };
135
- }, [checkAllPermissions]);
118
+ // Listen for window focus event from Electron main process
119
+ // This is more reliable than browser focus events in Electron environment
120
+ useWatchBroadcast('windowFocused', () => {
121
+ checkAllPermissions();
122
+ });
136
123
 
137
124
  const handlePermissionRequest = async (permissionId: number) => {
138
125
  const ipc = ensureElectronIpc();
@@ -143,7 +130,8 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
143
130
  break;
144
131
  }
145
132
  case 2: {
146
- await ipc.system.openFullDiskAccessSettings({ autoAdd: true });
133
+ // Use native prompt dialog for Full Disk Access
134
+ await ipc.system.promptFullDiskAccessIfNotGranted();
147
135
  break;
148
136
  }
149
137
  case 3: {
@@ -175,7 +163,7 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
175
163
  {permissions.map((permission) => (
176
164
  <Block
177
165
  align={'center'}
178
- clickable={!permission.granted || permission.id === 2}
166
+ clickable={!permission.granted}
179
167
  gap={16}
180
168
  horizontal
181
169
  key={permission.id}
@@ -197,7 +185,7 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
197
185
  {t(permission.descriptionKey as any)}
198
186
  </Text>
199
187
  </Flexbox>
200
- {permission.granted && permission.id !== 2 ? (
188
+ {permission.granted ? (
201
189
  <Icon color={cssVar.colorSuccess} icon={Check} size={20} />
202
190
  ) : (
203
191
  <Button
@@ -213,9 +201,7 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
213
201
  }}
214
202
  type={'text'}
215
203
  >
216
- {permission.granted && permission.id === 2
217
- ? t('screen3.actions.granted')
218
- : t(permission.buttonKey)}
204
+ {t(permission.buttonKey)}
219
205
  </Button>
220
206
  )}
221
207
  </Block>
@@ -2,27 +2,37 @@
2
2
 
3
3
  import { Flexbox, Skeleton } from '@lobehub/ui';
4
4
  import { Suspense, memo, useCallback, useEffect, useState } from 'react';
5
- import { useNavigate, useSearchParams } from 'react-router-dom';
5
+ import { useSearchParams } from 'react-router-dom';
6
6
 
7
7
  import Loading from '@/components/Loading/BrandTextLoading';
8
8
  import { electronSystemService } from '@/services/electron/system';
9
- import { isDev } from '@/utils/env';
10
9
 
11
10
  import OnboardingContainer from './_layout';
12
11
  import DataModeStep from './features/DataModeStep';
13
12
  import LoginStep from './features/LoginStep';
14
13
  import PermissionsStep from './features/PermissionsStep';
15
14
  import WelcomeStep from './features/WelcomeStep';
16
- import { getDesktopOnboardingCompleted, setDesktopOnboardingCompleted } from './storage';
15
+ import {
16
+ clearDesktopOnboardingStep,
17
+ getDesktopOnboardingStep,
18
+ setDesktopOnboardingCompleted,
19
+ setDesktopOnboardingStep,
20
+ } from './storage';
17
21
 
18
22
  const DesktopOnboardingPage = memo(() => {
19
- const navigate = useNavigate();
20
23
  const [searchParams, setSearchParams] = useSearchParams();
21
24
  const [isMac, setIsMac] = useState(true);
22
25
  const [isLoading, setIsLoading] = useState(true);
23
26
 
24
- // 从 URL query 参数获取初始步骤,默认为 1
27
+ // 从 localStorage 或 URL query 参数获取初始步骤
28
+ // 优先使用 localStorage 以支持重启后恢复
25
29
  const getInitialStep = useCallback(() => {
30
+ // First try localStorage (for app restart scenario)
31
+ const savedStep = getDesktopOnboardingStep();
32
+ if (savedStep !== null) {
33
+ return savedStep;
34
+ }
35
+ // Then try URL params
26
36
  const stepParam = searchParams.get('step');
27
37
  if (stepParam) {
28
38
  const step = parseInt(stepParam, 10);
@@ -33,11 +43,10 @@ const DesktopOnboardingPage = memo(() => {
33
43
 
34
44
  const [currentStep, setCurrentStep] = useState(getInitialStep);
35
45
 
36
- // 检查是否已完成 onboarding
46
+ // 持久化当前步骤到 localStorage
37
47
  useEffect(() => {
38
- if (isDev) return;
39
- if (getDesktopOnboardingCompleted()) navigate('/', { replace: true });
40
- }, [navigate]);
48
+ setDesktopOnboardingStep(currentStep);
49
+ }, [currentStep]);
41
50
 
42
51
  // 设置窗口大小和可调整性
43
52
  useEffect(() => {
@@ -117,6 +126,7 @@ const DesktopOnboardingPage = memo(() => {
117
126
  case 4: {
118
127
  // 如果是第4步(LoginStep),完成 onboarding
119
128
  setDesktopOnboardingCompleted();
129
+ clearDesktopOnboardingStep(); // Clear persisted step since onboarding is complete
120
130
  // Restore window resizable before hard reload (cleanup won't run due to hard navigation)
121
131
  electronSystemService
122
132
  .setWindowResizable({ resizable: true })
@@ -1,4 +1,5 @@
1
1
  export const DESKTOP_ONBOARDING_STORAGE_KEY = 'lobechat:desktop:onboarding:completed:v1';
2
+ export const DESKTOP_ONBOARDING_STEP_KEY = 'lobechat:desktop:onboarding:step:v1';
2
3
 
3
4
  export const getDesktopOnboardingCompleted = () => {
4
5
  if (typeof window === 'undefined') return true;
@@ -32,3 +33,51 @@ export const clearDesktopOnboardingCompleted = () => {
32
33
  return false;
33
34
  }
34
35
  };
36
+
37
+ /**
38
+ * Get the persisted onboarding step (for restoring after app restart)
39
+ */
40
+ export const getDesktopOnboardingStep = (): number | null => {
41
+ if (typeof window === 'undefined') return null;
42
+
43
+ try {
44
+ const step = window.localStorage.getItem(DESKTOP_ONBOARDING_STEP_KEY);
45
+ if (step) {
46
+ const parsedStep = Number.parseInt(step, 10);
47
+ if (parsedStep >= 1 && parsedStep <= 4) {
48
+ return parsedStep;
49
+ }
50
+ }
51
+ return null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Persist the current onboarding step
59
+ */
60
+ export const setDesktopOnboardingStep = (step: number) => {
61
+ if (typeof window === 'undefined') return false;
62
+
63
+ try {
64
+ window.localStorage.setItem(DESKTOP_ONBOARDING_STEP_KEY, step.toString());
65
+ return true;
66
+ } catch {
67
+ return false;
68
+ }
69
+ };
70
+
71
+ /**
72
+ * Clear the persisted onboarding step (called when onboarding completes)
73
+ */
74
+ export const clearDesktopOnboardingStep = () => {
75
+ if (typeof window === 'undefined') return false;
76
+
77
+ try {
78
+ window.localStorage.removeItem(DESKTOP_ONBOARDING_STEP_KEY);
79
+ return true;
80
+ } catch {
81
+ return false;
82
+ }
83
+ };
@@ -80,10 +80,10 @@ const ParamControlWrapper = memo<ParamControlWrapperProps>(
80
80
  <Checkbox
81
81
  checked={checked}
82
82
  className={styles.checkbox}
83
- onChange={(v) => {
84
- onToggle(v);
83
+ onClick={(e) => {
84
+ e.stopPropagation();
85
+ onToggle(!checked);
85
86
  }}
86
- onClick={(e) => e.stopPropagation()}
87
87
  />
88
88
  <div style={{ flex: 1 }}>
89
89
  <Component disabled={disabled} onChange={onChange} value={value} />
@@ -275,6 +275,7 @@ const Controls = memo<ControlsProps>(({ setUpdating }) => {
275
275
  disabled={!enabled}
276
276
  onToggle={(checked) => handleToggle(key, checked)}
277
277
  styles={styles}
278
+ value={form.getFieldValue(PARAM_NAME_MAP[key])}
278
279
  />
279
280
  ),
280
281
  label: (
@@ -297,9 +298,9 @@ const Controls = memo<ControlsProps>(({ setUpdating }) => {
297
298
  mobile
298
299
  ? baseItems
299
300
  : baseItems.map(({ tag, ...item }) => ({
300
- ...item,
301
- desc: <Tag size={'small'}>{tag}</Tag>,
302
- }))
301
+ ...item,
302
+ desc: <Tag size={'small'}>{tag}</Tag>,
303
+ }))
303
304
  }
304
305
  itemsType={'flat'}
305
306
  onValuesChange={handleValuesChange}
@@ -1,85 +0,0 @@
1
- on run argv
2
- set appBundlePath to item 1 of argv
3
-
4
- set settingsBundleIds to {"com.apple.SystemSettings", "com.apple.systempreferences"}
5
-
6
- -- Bring System Settings/Preferences to front (Ventura+ / older). If it doesn't exist, ignore.
7
- repeat with bundleId in settingsBundleIds
8
- try
9
- tell application id bundleId to activate
10
- exit repeat
11
- end try
12
- end repeat
13
-
14
- tell application "System Events"
15
- set settingsProcess to missing value
16
- repeat 30 times
17
- repeat with bundleId in settingsBundleIds
18
- try
19
- if exists (first process whose bundle identifier is bundleId) then
20
- set settingsProcess to first process whose bundle identifier is bundleId
21
- exit repeat
22
- end if
23
- end try
24
- end repeat
25
-
26
- if settingsProcess is not missing value then exit repeat
27
- delay 0.2
28
- end repeat
29
-
30
- if settingsProcess is missing value then return "no-settings-process"
31
-
32
- tell settingsProcess
33
- set frontmost to true
34
-
35
- repeat 30 times
36
- if exists window 1 then exit repeat
37
- delay 0.2
38
- end repeat
39
- if not (exists window 1) then return "no-window"
40
-
41
- -- Best-effort: find an "add" button in the front window and click it.
42
- set clickedAdd to false
43
- repeat 30 times
44
- try
45
- repeat with b in (buttons of window 1)
46
- set bDesc to ""
47
- set bName to ""
48
- set bTitle to ""
49
- try set bDesc to description of b end try
50
- try set bName to name of b end try
51
- try set bTitle to title of b end try
52
-
53
- if (bDesc is "Add") or (bTitle is "Add") or (bName is "+") or (bTitle is "+") then
54
- click b
55
- set clickedAdd to true
56
- exit repeat
57
- end if
58
- end repeat
59
- end try
60
-
61
- if clickedAdd is true then exit repeat
62
- delay 0.2
63
- end repeat
64
-
65
- if clickedAdd is false then return "no-add-button"
66
-
67
- -- Wait for open panel / sheet
68
- repeat 30 times
69
- if exists sheet 1 of window 1 then exit repeat
70
- delay 0.2
71
- end repeat
72
- if not (exists sheet 1 of window 1) then return "no-sheet"
73
-
74
- -- Open "Go to the folder" and input the app bundle path, then confirm.
75
- keystroke "G" using {command down, shift down}
76
- delay 0.3
77
- keystroke appBundlePath
78
- key code 36
79
- delay 0.6
80
- -- Confirm "Open" in the panel (Enter usually triggers default)
81
- key code 36
82
- return "ok"
83
- end tell
84
- end tell
85
- end run