@lobehub/lobehub 2.0.0-next.304 → 2.0.0-next.305

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 (55) hide show
  1. package/.github/workflows/manual-build-desktop.yml +11 -1
  2. package/CHANGELOG.md +25 -0
  3. package/apps/desktop/.i18nrc.js +3 -3
  4. package/apps/desktop/electron.vite.config.ts +0 -2
  5. package/apps/desktop/resources/locales/ar/dialog.json +5 -1
  6. package/apps/desktop/resources/locales/ar/menu.json +16 -0
  7. package/apps/desktop/resources/locales/bg-BG/dialog.json +5 -1
  8. package/apps/desktop/resources/locales/bg-BG/menu.json +16 -0
  9. package/apps/desktop/resources/locales/de-DE/dialog.json +5 -1
  10. package/apps/desktop/resources/locales/de-DE/menu.json +16 -0
  11. package/apps/desktop/resources/locales/en/common.json +26 -0
  12. package/apps/desktop/resources/locales/en/dialog.json +27 -0
  13. package/apps/desktop/resources/locales/en/menu.json +73 -0
  14. package/apps/desktop/resources/locales/es-ES/dialog.json +5 -1
  15. package/apps/desktop/resources/locales/es-ES/menu.json +16 -0
  16. package/apps/desktop/resources/locales/fa-IR/dialog.json +5 -1
  17. package/apps/desktop/resources/locales/fa-IR/menu.json +16 -0
  18. package/apps/desktop/resources/locales/fr-FR/dialog.json +5 -1
  19. package/apps/desktop/resources/locales/fr-FR/menu.json +16 -0
  20. package/apps/desktop/resources/locales/it-IT/dialog.json +5 -1
  21. package/apps/desktop/resources/locales/it-IT/menu.json +16 -0
  22. package/apps/desktop/resources/locales/ja-JP/dialog.json +5 -1
  23. package/apps/desktop/resources/locales/ja-JP/menu.json +16 -0
  24. package/apps/desktop/resources/locales/ko-KR/dialog.json +5 -1
  25. package/apps/desktop/resources/locales/ko-KR/menu.json +16 -0
  26. package/apps/desktop/resources/locales/nl-NL/dialog.json +5 -1
  27. package/apps/desktop/resources/locales/nl-NL/menu.json +16 -0
  28. package/apps/desktop/resources/locales/pl-PL/dialog.json +5 -1
  29. package/apps/desktop/resources/locales/pl-PL/menu.json +16 -0
  30. package/apps/desktop/resources/locales/pt-BR/dialog.json +5 -1
  31. package/apps/desktop/resources/locales/pt-BR/menu.json +16 -0
  32. package/apps/desktop/resources/locales/ru-RU/dialog.json +5 -1
  33. package/apps/desktop/resources/locales/ru-RU/menu.json +16 -0
  34. package/apps/desktop/resources/locales/tr-TR/dialog.json +5 -1
  35. package/apps/desktop/resources/locales/tr-TR/menu.json +16 -0
  36. package/apps/desktop/resources/locales/vi-VN/dialog.json +5 -1
  37. package/apps/desktop/resources/locales/vi-VN/menu.json +16 -0
  38. package/apps/desktop/resources/locales/zh-TW/dialog.json +5 -1
  39. package/apps/desktop/resources/locales/zh-TW/menu.json +16 -0
  40. package/apps/desktop/scripts/update-test/README.md +15 -0
  41. package/apps/desktop/src/main/core/infrastructure/BackendProxyProtocolManager.ts +7 -6
  42. package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +38 -5
  43. package/apps/desktop/src/main/utils/logger.ts +2 -2
  44. package/changelog/v1.json +5 -0
  45. package/locales/en-US/auth.json +5 -0
  46. package/locales/zh-CN/auth.json +5 -0
  47. package/package.json +6 -5
  48. package/packages/builtin-tool-agent-builder/src/ExecutionRuntime/index.ts +362 -30
  49. package/packages/builtin-tool-agent-builder/src/client/Intervention/InstallPlugin.tsx +28 -4
  50. package/scripts/electronWorkflow/buildDesktopChannel.ts +135 -0
  51. package/src/app/[variants]/(main)/_layout/index.tsx +2 -0
  52. package/src/features/DesktopNavigationBridge/index.tsx +0 -9
  53. package/src/features/Electron/AuthRequiredModal/index.tsx +151 -0
  54. package/src/locales/default/auth.ts +6 -0
  55. package/src/utils/errorResponse.ts +21 -1
@@ -4,8 +4,6 @@ import { useWatchBroadcast } from '@lobechat/electron-client-ipc';
4
4
  import { memo, useCallback } from 'react';
5
5
  import { useNavigate } from 'react-router-dom';
6
6
 
7
- import { clearDesktopOnboardingCompleted } from '@/app/[variants]/(desktop)/desktop-onboarding/storage';
8
-
9
7
  const DesktopNavigationBridge = memo(() => {
10
8
  const navigate = useNavigate();
11
9
 
@@ -19,13 +17,6 @@ const DesktopNavigationBridge = memo(() => {
19
17
 
20
18
  useWatchBroadcast('navigate', handleNavigate);
21
19
 
22
- const handleAuthorizationRequired = useCallback(() => {
23
- clearDesktopOnboardingCompleted();
24
- navigate('/desktop-onboarding#5', { replace: true });
25
- }, [navigate]);
26
-
27
- useWatchBroadcast('authorizationRequired', handleAuthorizationRequired);
28
-
29
20
  return null;
30
21
  });
31
22
 
@@ -0,0 +1,151 @@
1
+ 'use client';
2
+
3
+ import { useWatchBroadcast } from '@lobechat/electron-client-ipc';
4
+ import { Button, Flexbox, Icon, type ModalInstance, createModal } from '@lobehub/ui';
5
+ import { AlertCircle, LogIn } from 'lucide-react';
6
+ import { type ReactNode, memo, useCallback, useRef, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ import { useElectronStore } from '@/store/electron';
10
+
11
+ interface ModalUpdateOptions {
12
+ closable?: boolean;
13
+ keyboard?: boolean;
14
+ maskClosable?: boolean;
15
+ title?: ReactNode;
16
+ }
17
+
18
+ interface AuthRequiredModalContentProps {
19
+ onClose: () => void;
20
+ setModalProps: (props: ModalUpdateOptions) => void;
21
+ }
22
+
23
+ const AuthRequiredModalContent = memo<AuthRequiredModalContentProps>(
24
+ ({ onClose, setModalProps }) => {
25
+ const { t } = useTranslation('auth');
26
+ const [isSigningIn, setIsSigningIn] = useState(false);
27
+ const isClosingRef = useRef(false);
28
+
29
+ const [dataSyncConfig, connectRemoteServer, refreshServerConfig, clearRemoteServerSyncError] =
30
+ useElectronStore((s) => [
31
+ s.dataSyncConfig,
32
+ s.connectRemoteServer,
33
+ s.refreshServerConfig,
34
+ s.clearRemoteServerSyncError,
35
+ ]);
36
+
37
+ // Update modal props based on signing in state
38
+ setModalProps({
39
+ closable: !isSigningIn,
40
+ keyboard: !isSigningIn,
41
+ maskClosable: !isSigningIn,
42
+ title: (
43
+ <Flexbox align="center" gap={8} horizontal>
44
+ <Icon icon={AlertCircle} />
45
+ {t('authModal.title')}
46
+ </Flexbox>
47
+ ),
48
+ });
49
+
50
+ // Listen for successful authorization to close the modal
51
+ useWatchBroadcast('authorizationSuccessful', async () => {
52
+ if (isClosingRef.current) return;
53
+ isClosingRef.current = true;
54
+ setIsSigningIn(false);
55
+ onClose();
56
+ await refreshServerConfig();
57
+ });
58
+
59
+ // Listen for authorization failure
60
+ useWatchBroadcast('authorizationFailed', () => {
61
+ setIsSigningIn(false);
62
+ });
63
+
64
+ const handleSignIn = useCallback(async () => {
65
+ setIsSigningIn(true);
66
+ clearRemoteServerSyncError();
67
+
68
+ await connectRemoteServer({
69
+ remoteServerUrl: dataSyncConfig?.remoteServerUrl,
70
+ storageMode: dataSyncConfig?.storageMode || 'cloud',
71
+ });
72
+ }, [clearRemoteServerSyncError, connectRemoteServer, dataSyncConfig]);
73
+
74
+ const handleLater = useCallback(() => {
75
+ if (isClosingRef.current) return;
76
+ isClosingRef.current = true;
77
+ onClose();
78
+ }, [onClose]);
79
+
80
+ return (
81
+ <Flexbox gap={16} style={{ padding: 16 }}>
82
+ <p style={{ margin: 0 }}>{t('authModal.description')}</p>
83
+ <Flexbox gap={8} horizontal justify="flex-end">
84
+ <Button disabled={isSigningIn} onClick={handleLater}>
85
+ {t('authModal.later')}
86
+ </Button>
87
+ <Button
88
+ icon={<Icon icon={LogIn} />}
89
+ loading={isSigningIn}
90
+ onClick={handleSignIn}
91
+ type="primary"
92
+ >
93
+ {isSigningIn ? t('authModal.signingIn') : t('authModal.signIn')}
94
+ </Button>
95
+ </Flexbox>
96
+ </Flexbox>
97
+ );
98
+ },
99
+ );
100
+
101
+ AuthRequiredModalContent.displayName = 'AuthRequiredModalContent';
102
+
103
+ /**
104
+ * Hook to create and manage the auth required modal
105
+ */
106
+ export const useAuthRequiredModal = () => {
107
+ const instanceRef = useRef<ModalInstance | null>(null);
108
+
109
+ const open = useCallback(() => {
110
+ // Don't open multiple modals
111
+ if (instanceRef.current) return;
112
+
113
+ const setModalProps = (nextProps: ModalUpdateOptions) => {
114
+ instanceRef.current?.update?.(nextProps);
115
+ };
116
+
117
+ const handleClose = () => {
118
+ instanceRef.current?.close();
119
+ instanceRef.current = null;
120
+ };
121
+
122
+ instanceRef.current = createModal({
123
+ children: <AuthRequiredModalContent onClose={handleClose} setModalProps={setModalProps} />,
124
+ closable: false,
125
+ footer: null,
126
+ keyboard: false,
127
+ maskClosable: false,
128
+ title: '',
129
+ });
130
+ }, []);
131
+
132
+ return { open };
133
+ };
134
+
135
+ /**
136
+ * Component that listens for authorizationRequired IPC events and opens the modal
137
+ */
138
+ const AuthRequiredModal = memo(() => {
139
+ const { open } = useAuthRequiredModal();
140
+
141
+ // Listen for IPC event to open the modal
142
+ useWatchBroadcast('authorizationRequired', () => {
143
+ open();
144
+ });
145
+
146
+ return null;
147
+ });
148
+
149
+ AuthRequiredModal.displayName = 'AuthRequiredModal';
150
+
151
+ export default AuthRequiredModal;
@@ -28,6 +28,12 @@ export default {
28
28
  'apikey.list.columns.status': 'Enabled Status',
29
29
  'apikey.list.title': 'API Key List',
30
30
  'apikey.validation.required': 'This field cannot be empty',
31
+ 'authModal.description':
32
+ 'Your login session has expired. Please sign in again to continue using cloud sync features.',
33
+ 'authModal.later': 'Later',
34
+ 'authModal.signIn': 'Sign In Again',
35
+ 'authModal.signingIn': 'Signing in...',
36
+ 'authModal.title': 'Session Expired',
31
37
  'betterAuth.errors.confirmPasswordRequired': 'Please confirm your password',
32
38
  'betterAuth.errors.emailExists': 'This email is already registered. Please sign in instead',
33
39
  'betterAuth.errors.emailInvalid': 'Please enter a valid email address or username',
@@ -1,6 +1,16 @@
1
1
  import { AgentRuntimeErrorType, type ILobeAgentRuntimeErrorType } from '@lobechat/model-runtime';
2
2
  import { ChatErrorType, type ErrorResponse, type ErrorType } from '@lobechat/types';
3
3
 
4
+ /**
5
+ * Error types that indicate a real authentication failure.
6
+ * When these errors occur, the response will include X-Auth-Required header
7
+ * to signal the client that re-authentication is needed.
8
+ */
9
+ const AUTH_REQUIRED_ERROR_TYPES = new Set<ErrorType>([
10
+ ChatErrorType.Unauthorized,
11
+ ChatErrorType.InvalidClerkUser,
12
+ ]);
13
+
4
14
  const getStatus = (errorType: ILobeAgentRuntimeErrorType | ErrorType) => {
5
15
  // InvalidAccessCode / InvalidAzureAPIKey / InvalidOpenAIAPIKey / InvalidZhipuAPIKey ....
6
16
  if (errorType.toString().includes('Invalid')) return 401;
@@ -71,5 +81,15 @@ export const createErrorResponse = (
71
81
  );
72
82
  }
73
83
 
74
- return new Response(JSON.stringify(data), { status: statusCode });
84
+ const headers: Record<string, string> = {
85
+ 'Content-Type': 'application/json',
86
+ };
87
+
88
+ // Add X-Auth-Required header for real authentication failures
89
+ // This allows the client to distinguish between auth failures and other 401 errors (e.g., invalid API keys)
90
+ if (AUTH_REQUIRED_ERROR_TYPES.has(errorType as ErrorType)) {
91
+ headers['X-Auth-Required'] = 'true';
92
+ }
93
+
94
+ return new Response(JSON.stringify(data), { headers, status: statusCode });
75
95
  };