@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.
- package/CHANGELOG.md +33 -0
- package/apps/desktop/build/entitlements.mac.plist +9 -0
- package/apps/desktop/resources/locales/zh-CN/dialog.json +5 -1
- package/apps/desktop/resources/locales/zh-CN/menu.json +7 -0
- package/apps/desktop/src/main/controllers/SystemCtr.ts +186 -94
- package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +200 -31
- package/apps/desktop/src/main/core/browser/Browser.ts +9 -0
- package/apps/desktop/src/main/locales/default/dialog.ts +7 -2
- package/apps/desktop/src/main/locales/default/menu.ts +7 -0
- package/apps/desktop/src/main/menus/impls/macOS.ts +44 -1
- package/apps/desktop/src/main/utils/fullDiskAccess.ts +121 -0
- package/changelog/v1.json +5 -0
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/system.ts +1 -0
- package/src/app/[variants]/(desktop)/desktop-onboarding/features/PermissionsStep.tsx +16 -30
- package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +19 -9
- package/src/app/[variants]/(desktop)/desktop-onboarding/storage.ts +49 -0
- package/src/features/ChatInput/ActionBar/Params/Controls.tsx +7 -6
- package/apps/desktop/src/main/controllers/scripts/full-disk-access.applescript +0 -85
|
@@ -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
|
|
101
|
-
if (p.id === 2)
|
|
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
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
//
|
|
46
|
+
// 持久化当前步骤到 localStorage
|
|
37
47
|
useEffect(() => {
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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
|