@lobehub/lobehub 2.0.0-next.303 → 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.
- package/.github/workflows/manual-build-desktop.yml +11 -1
- package/CHANGELOG.md +50 -0
- package/apps/desktop/.i18nrc.js +3 -3
- package/apps/desktop/electron.vite.config.ts +0 -2
- package/apps/desktop/resources/locales/ar/dialog.json +5 -1
- package/apps/desktop/resources/locales/ar/menu.json +16 -0
- package/apps/desktop/resources/locales/bg-BG/dialog.json +5 -1
- package/apps/desktop/resources/locales/bg-BG/menu.json +16 -0
- package/apps/desktop/resources/locales/de-DE/dialog.json +5 -1
- package/apps/desktop/resources/locales/de-DE/menu.json +16 -0
- package/apps/desktop/resources/locales/en/common.json +26 -0
- package/apps/desktop/resources/locales/en/dialog.json +27 -0
- package/apps/desktop/resources/locales/en/menu.json +73 -0
- package/apps/desktop/resources/locales/es-ES/dialog.json +5 -1
- package/apps/desktop/resources/locales/es-ES/menu.json +16 -0
- package/apps/desktop/resources/locales/fa-IR/dialog.json +5 -1
- package/apps/desktop/resources/locales/fa-IR/menu.json +16 -0
- package/apps/desktop/resources/locales/fr-FR/dialog.json +5 -1
- package/apps/desktop/resources/locales/fr-FR/menu.json +16 -0
- package/apps/desktop/resources/locales/it-IT/dialog.json +5 -1
- package/apps/desktop/resources/locales/it-IT/menu.json +16 -0
- package/apps/desktop/resources/locales/ja-JP/dialog.json +5 -1
- package/apps/desktop/resources/locales/ja-JP/menu.json +16 -0
- package/apps/desktop/resources/locales/ko-KR/dialog.json +5 -1
- package/apps/desktop/resources/locales/ko-KR/menu.json +16 -0
- package/apps/desktop/resources/locales/nl-NL/dialog.json +5 -1
- package/apps/desktop/resources/locales/nl-NL/menu.json +16 -0
- package/apps/desktop/resources/locales/pl-PL/dialog.json +5 -1
- package/apps/desktop/resources/locales/pl-PL/menu.json +16 -0
- package/apps/desktop/resources/locales/pt-BR/dialog.json +5 -1
- package/apps/desktop/resources/locales/pt-BR/menu.json +16 -0
- package/apps/desktop/resources/locales/ru-RU/dialog.json +5 -1
- package/apps/desktop/resources/locales/ru-RU/menu.json +16 -0
- package/apps/desktop/resources/locales/tr-TR/dialog.json +5 -1
- package/apps/desktop/resources/locales/tr-TR/menu.json +16 -0
- package/apps/desktop/resources/locales/vi-VN/dialog.json +5 -1
- package/apps/desktop/resources/locales/vi-VN/menu.json +16 -0
- package/apps/desktop/resources/locales/zh-TW/dialog.json +5 -1
- package/apps/desktop/resources/locales/zh-TW/menu.json +16 -0
- package/apps/desktop/scripts/update-test/README.md +15 -0
- package/apps/desktop/src/common/routes.ts +8 -8
- package/apps/desktop/src/main/const/dir.ts +2 -2
- package/apps/desktop/src/main/const/env.ts +4 -4
- package/apps/desktop/src/main/const/store.ts +3 -3
- package/apps/desktop/src/main/controllers/AuthCtr.ts +1 -1
- package/apps/desktop/src/main/controllers/McpInstallCtr.ts +8 -8
- package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +9 -9
- package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +8 -8
- package/apps/desktop/src/main/core/App.ts +9 -9
- package/apps/desktop/src/main/core/infrastructure/BackendProxyProtocolManager.ts +7 -6
- package/apps/desktop/src/main/core/infrastructure/StaticFileServerManager.ts +2 -2
- package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +38 -5
- package/apps/desktop/src/main/core/ui/ShortcutManager.ts +10 -10
- package/apps/desktop/src/main/core/ui/TrayManager.ts +12 -12
- package/apps/desktop/src/main/locales/resources.ts +4 -4
- package/apps/desktop/src/main/menus/impls/macOS.ts +1 -1
- package/apps/desktop/src/main/menus/types.ts +5 -5
- package/apps/desktop/src/main/modules/updater/configs.ts +10 -10
- package/apps/desktop/src/main/modules/updater/utils.ts +9 -9
- package/apps/desktop/src/main/services/fileSrv.ts +62 -62
- package/apps/desktop/src/main/shortcuts/config.ts +3 -3
- package/apps/desktop/src/main/types/protocol.ts +12 -12
- package/apps/desktop/src/main/utils/file-system.ts +2 -2
- package/apps/desktop/src/main/utils/logger.ts +4 -4
- package/apps/desktop/src/main/utils/protocol.ts +32 -32
- package/changelog/v1.json +14 -0
- package/locales/en-US/auth.json +5 -0
- package/locales/en-US/plugin.json +1 -0
- package/locales/zh-CN/auth.json +5 -0
- package/locales/zh-CN/discover.json +4 -4
- package/locales/zh-CN/plugin.json +1 -0
- package/package.json +6 -5
- package/packages/builtin-tool-agent-builder/src/ExecutionRuntime/index.ts +362 -30
- package/packages/builtin-tool-agent-builder/src/client/Intervention/InstallPlugin.tsx +28 -4
- package/packages/builtin-tool-group-management/src/client/Inspector/ExecuteAgentTask/index.tsx +78 -0
- package/packages/builtin-tool-group-management/src/client/Inspector/{ExecuteTasks → ExecuteAgentTasks}/index.tsx +1 -5
- package/packages/builtin-tool-group-management/src/client/Inspector/index.ts +4 -2
- package/packages/database/src/schemas/relations.ts +4 -4
- package/scripts/electronWorkflow/buildDesktopChannel.ts +135 -0
- package/src/app/[variants]/(main)/_layout/index.tsx +2 -0
- package/src/features/Conversation/ChatList/components/AutoScroll.tsx +3 -9
- package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +2 -6
- package/src/features/DesktopNavigationBridge/index.tsx +0 -9
- package/src/features/Electron/AuthRequiredModal/index.tsx +151 -0
- package/src/locales/default/auth.ts +6 -0
- package/src/locales/default/plugin.ts +1 -0
- package/src/utils/errorResponse.ts +21 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-process-exit */
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
type ReleaseChannel = 'stable' | 'beta' | 'nightly';
|
|
7
|
+
|
|
8
|
+
const rootDir = path.resolve(__dirname, '../..');
|
|
9
|
+
const desktopDir = path.join(rootDir, 'apps/desktop');
|
|
10
|
+
const desktopPackageJsonPath = path.join(desktopDir, 'package.json');
|
|
11
|
+
const buildDir = path.join(desktopDir, 'build');
|
|
12
|
+
|
|
13
|
+
const iconTargets = ['icon.png', 'Icon.icns', 'icon.ico'];
|
|
14
|
+
|
|
15
|
+
const isFlag = (value: string) => value.startsWith('-');
|
|
16
|
+
|
|
17
|
+
const parseArgs = (args: string[]) => {
|
|
18
|
+
let channel = '';
|
|
19
|
+
let version = '';
|
|
20
|
+
let keepChanges = false;
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
23
|
+
const arg = args[i];
|
|
24
|
+
|
|
25
|
+
if (arg === '--channel' || arg === '-c') {
|
|
26
|
+
channel = args[i + 1] ?? '';
|
|
27
|
+
i += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (arg === '--version' || arg === '-v') {
|
|
32
|
+
version = args[i + 1] ?? '';
|
|
33
|
+
i += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (arg === '--keep-changes') {
|
|
38
|
+
keepChanges = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!isFlag(arg)) {
|
|
43
|
+
if (!channel) {
|
|
44
|
+
channel = arg;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!version) {
|
|
49
|
+
version = arg;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { channel, keepChanges, version };
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const resolveDefaultVersion = () => {
|
|
58
|
+
const rootPackageJsonPath = path.join(rootDir, 'package.json');
|
|
59
|
+
const rootPackageJson = fs.readJsonSync(rootPackageJsonPath);
|
|
60
|
+
return rootPackageJson.version as string | undefined;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const backupFile = async (filePath: string) => {
|
|
64
|
+
try {
|
|
65
|
+
return await fs.readFile(filePath);
|
|
66
|
+
} catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const restoreFile = async (filePath: string, content?: Buffer) => {
|
|
72
|
+
if (!content) return;
|
|
73
|
+
await fs.writeFile(filePath, content);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const validateChannel = (channel: string): channel is ReleaseChannel =>
|
|
77
|
+
channel === 'stable' || channel === 'beta' || channel === 'nightly';
|
|
78
|
+
|
|
79
|
+
const runCommand = (command: string, env?: Record<string, string | undefined>) => {
|
|
80
|
+
execSync(command, {
|
|
81
|
+
cwd: rootDir,
|
|
82
|
+
env: { ...process.env, ...env },
|
|
83
|
+
stdio: 'inherit',
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const main = async () => {
|
|
88
|
+
const { channel, version: rawVersion, keepChanges } = parseArgs(process.argv.slice(2));
|
|
89
|
+
|
|
90
|
+
if (!validateChannel(channel)) {
|
|
91
|
+
console.error(
|
|
92
|
+
'Missing or invalid channel. Usage: npm run desktop:build-channel -- <stable|beta|nightly> [version] [--keep-changes]',
|
|
93
|
+
);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const version = rawVersion || resolveDefaultVersion();
|
|
98
|
+
|
|
99
|
+
if (!version) {
|
|
100
|
+
console.error('Missing version. Provide it or ensure root package.json has a version.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const packageJsonBackup = await backupFile(desktopPackageJsonPath);
|
|
105
|
+
const iconBackups = await Promise.all(
|
|
106
|
+
iconTargets.map(async (fileName) => ({
|
|
107
|
+
content: await backupFile(path.join(buildDir, fileName)),
|
|
108
|
+
fileName,
|
|
109
|
+
})),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
console.log(`🚦 CI-style build channel: ${channel}`);
|
|
113
|
+
console.log(`🏷️ Desktop version: ${version}`);
|
|
114
|
+
console.log(`🧩 Keep local changes: ${keepChanges ? 'yes' : 'no'}`);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
runCommand(`npm run workflow:set-desktop-version ${version} ${channel}`);
|
|
118
|
+
runCommand('npm run desktop:build', { UPDATE_CHANNEL: channel });
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('❌ Build failed:', error);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
} finally {
|
|
123
|
+
if (!keepChanges) {
|
|
124
|
+
await restoreFile(desktopPackageJsonPath, packageJsonBackup);
|
|
125
|
+
await Promise.all(
|
|
126
|
+
iconBackups.map(({ fileName, content }) =>
|
|
127
|
+
restoreFile(path.join(buildDir, fileName), content),
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
console.log('🧹 Restored local desktop package metadata and icons.');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
main();
|
|
@@ -12,6 +12,7 @@ import Loading from '@/components/Loading/BrandTextLoading';
|
|
|
12
12
|
import { isDesktop } from '@/const/version';
|
|
13
13
|
import { BANNER_HEIGHT } from '@/features/AlertBanner/CloudBanner';
|
|
14
14
|
import DesktopNavigationBridge from '@/features/DesktopNavigationBridge';
|
|
15
|
+
import AuthRequiredModal from '@/features/Electron/AuthRequiredModal';
|
|
15
16
|
import TitleBar from '@/features/Electron/titlebar/TitleBar';
|
|
16
17
|
import HotkeyHelperPanel from '@/features/HotkeyHelperPanel';
|
|
17
18
|
import NavPanel from '@/features/NavPanel';
|
|
@@ -45,6 +46,7 @@ const Layout: FC = () => {
|
|
|
45
46
|
{isDesktop && <TitleBar />}
|
|
46
47
|
{isDesktop && <DesktopAutoOidcOnFirstOpen />}
|
|
47
48
|
{isDesktop && <DesktopNavigationBridge />}
|
|
49
|
+
{isDesktop && <AuthRequiredModal />}
|
|
48
50
|
{showCloudPromotion && <CloudBanner />}
|
|
49
51
|
</Suspense>
|
|
50
52
|
<DndContextWrapper>
|
|
@@ -2,19 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { memo, useEffect } from 'react';
|
|
4
4
|
|
|
5
|
-
import { useConversationStore, virtuaListSelectors } from '../../store';
|
|
5
|
+
import { messageStateSelectors, useConversationStore, virtuaListSelectors } from '../../store';
|
|
6
6
|
import BackBottom from './BackBottom';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Whether AI is generating (for auto-scroll during generation)
|
|
11
|
-
*/
|
|
12
|
-
isGenerating?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const AutoScroll = memo<AutoScrollProps>(({ isGenerating }) => {
|
|
8
|
+
const AutoScroll = memo(() => {
|
|
16
9
|
const atBottom = useConversationStore(virtuaListSelectors.atBottom);
|
|
17
10
|
const isScrolling = useConversationStore(virtuaListSelectors.isScrolling);
|
|
11
|
+
const isGenerating = useConversationStore(messageStateSelectors.isAIGenerating);
|
|
18
12
|
const scrollToBottom = useConversationStore((s) => s.scrollToBottom);
|
|
19
13
|
|
|
20
14
|
useEffect(() => {
|
|
@@ -10,10 +10,6 @@ import AutoScroll from './AutoScroll';
|
|
|
10
10
|
|
|
11
11
|
interface VirtualizedListProps {
|
|
12
12
|
dataSource: string[];
|
|
13
|
-
/**
|
|
14
|
-
* Whether AI is generating (for auto-scroll)
|
|
15
|
-
*/
|
|
16
|
-
isGenerating?: boolean;
|
|
17
13
|
itemContent: (index: number, data: string) => ReactNode;
|
|
18
14
|
}
|
|
19
15
|
|
|
@@ -22,7 +18,7 @@ interface VirtualizedListProps {
|
|
|
22
18
|
*
|
|
23
19
|
* Based on ConversationStore data flow, no dependency on global ChatStore.
|
|
24
20
|
*/
|
|
25
|
-
const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent
|
|
21
|
+
const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent }) => {
|
|
26
22
|
const virtuaRef = useRef<VListHandle>(null);
|
|
27
23
|
const prevDataLengthRef = useRef(dataSource.length);
|
|
28
24
|
const scrollEndTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -154,7 +150,7 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent, i
|
|
|
154
150
|
position: 'relative',
|
|
155
151
|
}}
|
|
156
152
|
>
|
|
157
|
-
<AutoScroll
|
|
153
|
+
<AutoScroll />
|
|
158
154
|
</WideScreenContainer>
|
|
159
155
|
</>
|
|
160
156
|
);
|
|
@@ -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',
|
|
@@ -70,6 +70,7 @@ export default {
|
|
|
70
70
|
'builtins.lobe-group-management.apiName.summarize': 'Summarize conversation',
|
|
71
71
|
'builtins.lobe-group-management.apiName.vote': 'Start vote',
|
|
72
72
|
'builtins.lobe-group-management.inspector.broadcast.title': 'Following Agents speak:',
|
|
73
|
+
'builtins.lobe-group-management.inspector.executeAgentTask.title': 'Assigning task to:',
|
|
73
74
|
'builtins.lobe-group-management.inspector.executeAgentTasks.title': 'Assigning tasks to:',
|
|
74
75
|
'builtins.lobe-group-management.inspector.speak.title': 'Designated Agent speaks:',
|
|
75
76
|
'builtins.lobe-group-management.title': 'Group Coordinator',
|
|
@@ -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
|
-
|
|
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
|
};
|