@lobehub/lobehub 2.0.0-next.340 → 2.0.0-next.341
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 +25 -0
- package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +2 -1
- package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +8 -8
- package/changelog/v1.json +9 -0
- package/locales/en-US/common.json +6 -0
- package/locales/zh-CN/common.json +6 -0
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/types/dataSync.ts +1 -1
- package/src/app/[variants]/(main)/settings/about/features/Version.tsx +13 -2
- package/src/hooks/useUserAvatar.test.ts +2 -2
- package/src/layout/GlobalProvider/ServerVersionOutdatedAlert.tsx +131 -0
- package/src/layout/GlobalProvider/StoreInitialization.tsx +8 -1
- package/src/layout/GlobalProvider/index.tsx +4 -0
- package/src/locales/default/common.ts +8 -0
- package/src/services/global.ts +25 -0
- package/src/store/global/actions/general.ts +46 -0
- package/src/store/global/initialState.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.341](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.340...v2.0.0-next.341)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-22**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **misc**: Add server version check for desktop app.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **misc**: Add server version check for desktop app, closes [#11710](https://github.com/lobehub/lobe-chat/issues/11710) ([0cf2723](https://github.com/lobehub/lobe-chat/commit/0cf2723))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.340](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.339...v2.0.0-next.340)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2026-01-22**</sup>
|
|
@@ -50,7 +50,8 @@ export default class RemoteServerConfigCtr extends ControllerModule {
|
|
|
50
50
|
* Local mode has been removed; fall back to cloud.
|
|
51
51
|
*/
|
|
52
52
|
private normalizeConfig = (config: DataSyncConfig): DataSyncConfig => {
|
|
53
|
-
|
|
53
|
+
// Use type assertion to handle legacy 'local' value from stored data
|
|
54
|
+
if ((config.storageMode as string) !== 'local') return config;
|
|
54
55
|
|
|
55
56
|
const nextConfig: DataSyncConfig = {
|
|
56
57
|
...config,
|
|
@@ -60,7 +60,7 @@ describe('RemoteServerConfigCtr', () => {
|
|
|
60
60
|
ipcMainHandleMock.mockClear();
|
|
61
61
|
mockStoreManager.get.mockReturnValue({
|
|
62
62
|
active: false,
|
|
63
|
-
storageMode: '
|
|
63
|
+
storageMode: 'cloud',
|
|
64
64
|
});
|
|
65
65
|
controller = new RemoteServerConfigCtr(mockApp);
|
|
66
66
|
});
|
|
@@ -85,7 +85,7 @@ describe('RemoteServerConfigCtr', () => {
|
|
|
85
85
|
it('should update configuration', async () => {
|
|
86
86
|
const prevConfig: DataSyncConfig = {
|
|
87
87
|
active: false,
|
|
88
|
-
storageMode: '
|
|
88
|
+
storageMode: 'cloud',
|
|
89
89
|
};
|
|
90
90
|
mockStoreManager.get.mockReturnValue(prevConfig);
|
|
91
91
|
|
|
@@ -195,7 +195,7 @@ describe('RemoteServerConfigCtr', () => {
|
|
|
195
195
|
refreshToken: Buffer.from('stored-refresh-token').toString('base64'),
|
|
196
196
|
};
|
|
197
197
|
}
|
|
198
|
-
return { active: false, storageMode: '
|
|
198
|
+
return { active: false, storageMode: 'cloud' };
|
|
199
199
|
});
|
|
200
200
|
|
|
201
201
|
// Create new controller to test loading from store
|
|
@@ -210,7 +210,7 @@ describe('RemoteServerConfigCtr', () => {
|
|
|
210
210
|
if (key === 'encryptedTokens') {
|
|
211
211
|
return null;
|
|
212
212
|
}
|
|
213
|
-
return { active: false, storageMode: '
|
|
213
|
+
return { active: false, storageMode: 'cloud' };
|
|
214
214
|
});
|
|
215
215
|
|
|
216
216
|
const newController = new RemoteServerConfigCtr(mockApp);
|
|
@@ -243,7 +243,7 @@ describe('RemoteServerConfigCtr', () => {
|
|
|
243
243
|
refreshToken: 'invalid-encrypted-token',
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
|
-
return { active: false, storageMode: '
|
|
246
|
+
return { active: false, storageMode: 'cloud' };
|
|
247
247
|
});
|
|
248
248
|
|
|
249
249
|
const newController = new RemoteServerConfigCtr(mockApp);
|
|
@@ -273,7 +273,7 @@ describe('RemoteServerConfigCtr', () => {
|
|
|
273
273
|
if (key === 'encryptedTokens') {
|
|
274
274
|
return null;
|
|
275
275
|
}
|
|
276
|
-
return { active: false, storageMode: '
|
|
276
|
+
return { active: false, storageMode: 'cloud' };
|
|
277
277
|
});
|
|
278
278
|
|
|
279
279
|
const newController = new RemoteServerConfigCtr(mockApp);
|
|
@@ -417,7 +417,7 @@ describe('RemoteServerConfigCtr', () => {
|
|
|
417
417
|
it('should return error when remote server is not active', async () => {
|
|
418
418
|
mockStoreManager.get.mockImplementation((key) => {
|
|
419
419
|
if (key === 'dataSyncConfig') {
|
|
420
|
-
return { active: false, storageMode: '
|
|
420
|
+
return { active: false, storageMode: 'cloud' };
|
|
421
421
|
}
|
|
422
422
|
return null;
|
|
423
423
|
});
|
|
@@ -648,7 +648,7 @@ describe('RemoteServerConfigCtr', () => {
|
|
|
648
648
|
refreshToken: 'stored-refresh',
|
|
649
649
|
};
|
|
650
650
|
}
|
|
651
|
-
return { active: false, storageMode: '
|
|
651
|
+
return { active: false, storageMode: 'cloud' };
|
|
652
652
|
});
|
|
653
653
|
|
|
654
654
|
const newController = new RemoteServerConfigCtr(mockApp);
|
package/changelog/v1.json
CHANGED
|
@@ -332,6 +332,11 @@
|
|
|
332
332
|
"run": "Run",
|
|
333
333
|
"save": "Save",
|
|
334
334
|
"send": "Send",
|
|
335
|
+
"serverVersionOutdated.desc": "Your client version (v{{version}}) requires a newer server version.",
|
|
336
|
+
"serverVersionOutdated.dismiss": "Continue Anyway",
|
|
337
|
+
"serverVersionOutdated.title": "Server Version Outdated",
|
|
338
|
+
"serverVersionOutdated.upgrade": "Upgrade Guide",
|
|
339
|
+
"serverVersionOutdated.warning": "Some features may not work properly or behave unexpectedly. Please update your server for the best experience.",
|
|
335
340
|
"setting": "Settings",
|
|
336
341
|
"share": "Share",
|
|
337
342
|
"stop": "Stop",
|
|
@@ -380,6 +385,7 @@
|
|
|
380
385
|
"upgradeVersion.action": "Upgrade",
|
|
381
386
|
"upgradeVersion.hasNew": "Update available",
|
|
382
387
|
"upgradeVersion.newVersion": "Update available: {{version}}",
|
|
388
|
+
"upgradeVersion.serverVersion": "Server: {{version}}",
|
|
383
389
|
"userPanel.anonymousNickName": "Anonymous User",
|
|
384
390
|
"userPanel.billing": "Billing Management",
|
|
385
391
|
"userPanel.cloud": "Launch {{name}}",
|
|
@@ -332,6 +332,11 @@
|
|
|
332
332
|
"run": "运行",
|
|
333
333
|
"save": "保存",
|
|
334
334
|
"send": "发送",
|
|
335
|
+
"serverVersionOutdated.desc": "当前客户端版本(v{{version}})需要更新的服务端版本。",
|
|
336
|
+
"serverVersionOutdated.dismiss": "继续使用",
|
|
337
|
+
"serverVersionOutdated.title": "服务端版本过旧",
|
|
338
|
+
"serverVersionOutdated.upgrade": "升级指南",
|
|
339
|
+
"serverVersionOutdated.warning": "部分功能可能无法正常使用或出现非预期行为。建议更新服务端以获得最佳体验。",
|
|
335
340
|
"setting": "设置",
|
|
336
341
|
"share": "分享",
|
|
337
342
|
"stop": "停止",
|
|
@@ -380,6 +385,7 @@
|
|
|
380
385
|
"upgradeVersion.action": "升级",
|
|
381
386
|
"upgradeVersion.hasNew": "有可用更新",
|
|
382
387
|
"upgradeVersion.newVersion": "可用更新版本:{{version}}",
|
|
388
|
+
"upgradeVersion.serverVersion": "服务端:{{version}}",
|
|
383
389
|
"userPanel.anonymousNickName": "匿名用户",
|
|
384
390
|
"userPanel.billing": "账单管理",
|
|
385
391
|
"userPanel.cloud": "体验 {{name}}",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.341",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
|
|
7
7
|
import { ProductLogo } from '@/components/Branding';
|
|
8
8
|
import { CHANGELOG_URL, MANUAL_UPGRADE_URL, OFFICIAL_SITE } from '@/const/url';
|
|
9
|
-
import { CURRENT_VERSION } from '@/const/version';
|
|
9
|
+
import { CURRENT_VERSION, isDesktop } from '@/const/version';
|
|
10
10
|
import { useNewVersion } from '@/features/User/UserPanel/useNewVersion';
|
|
11
11
|
import { useGlobalStore } from '@/store/global';
|
|
12
12
|
|
|
@@ -18,9 +18,17 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
|
18
18
|
|
|
19
19
|
const Version = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
20
20
|
const hasNewVersion = useNewVersion();
|
|
21
|
-
const [latestVersion] = useGlobalStore((s) => [
|
|
21
|
+
const [latestVersion, serverVersion, useCheckServerVersion] = useGlobalStore((s) => [
|
|
22
|
+
s.latestVersion,
|
|
23
|
+
s.serverVersion,
|
|
24
|
+
s.useCheckServerVersion,
|
|
25
|
+
]);
|
|
22
26
|
const { t } = useTranslation('common');
|
|
23
27
|
|
|
28
|
+
useCheckServerVersion(isDesktop);
|
|
29
|
+
|
|
30
|
+
const showServerVersion = serverVersion && serverVersion !== CURRENT_VERSION;
|
|
31
|
+
|
|
24
32
|
return (
|
|
25
33
|
<Flexbox
|
|
26
34
|
align={mobile ? 'stretch' : 'center'}
|
|
@@ -46,6 +54,9 @@ const Version = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
46
54
|
<div style={{ fontSize: 18, fontWeight: 'bolder' }}>{BRANDING_NAME}</div>
|
|
47
55
|
<Flexbox gap={6} horizontal={!mobile}>
|
|
48
56
|
<Tag>v{CURRENT_VERSION}</Tag>
|
|
57
|
+
{showServerVersion && (
|
|
58
|
+
<Tag>{t('upgradeVersion.serverVersion', { version: `v${serverVersion}` })}</Tag>
|
|
59
|
+
)}
|
|
49
60
|
{hasNewVersion && (
|
|
50
61
|
<Tag color={'info'}>
|
|
51
62
|
{t('upgradeVersion.newVersion', { version: `v${latestVersion}` })}
|
|
@@ -62,14 +62,14 @@ describe('useUserAvatar', () => {
|
|
|
62
62
|
expect(result.current).toBe(mockAvatar);
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
it('should return original avatar when no remote server URL in desktop environment', () => {
|
|
65
|
+
it('should return original avatar when no remote server URL in desktop environment (selfHost mode)', () => {
|
|
66
66
|
mockIsDesktop = true;
|
|
67
67
|
const mockAvatar = '/api/avatar.png';
|
|
68
68
|
|
|
69
69
|
act(() => {
|
|
70
70
|
useUserStore.setState({ user: { avatar: mockAvatar } as any });
|
|
71
71
|
useElectronStore.setState({
|
|
72
|
-
dataSyncConfig: { remoteServerUrl: undefined, storageMode: '
|
|
72
|
+
dataSyncConfig: { remoteServerUrl: undefined, storageMode: 'selfHost' },
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
75
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button, Flexbox, Icon } from '@lobehub/ui';
|
|
4
|
+
import { createStyles } from 'antd-style';
|
|
5
|
+
import { TriangleAlert, X } from 'lucide-react';
|
|
6
|
+
import { useState } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
|
|
9
|
+
import { MANUAL_UPGRADE_URL } from '@/const/url';
|
|
10
|
+
import { CURRENT_VERSION } from '@/const/version';
|
|
11
|
+
import { useElectronStore } from '@/store/electron';
|
|
12
|
+
import { electronSyncSelectors } from '@/store/electron/selectors';
|
|
13
|
+
import { useGlobalStore } from '@/store/global';
|
|
14
|
+
|
|
15
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
16
|
+
closeButton: css`
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
|
|
19
|
+
position: absolute;
|
|
20
|
+
inset-block-start: 20px;
|
|
21
|
+
inset-inline-end: 20px;
|
|
22
|
+
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
|
|
27
|
+
width: 28px;
|
|
28
|
+
height: 28px;
|
|
29
|
+
border-radius: ${token.borderRadius}px;
|
|
30
|
+
|
|
31
|
+
color: ${token.colorTextSecondary};
|
|
32
|
+
|
|
33
|
+
transition: all 0.2s;
|
|
34
|
+
|
|
35
|
+
&:hover {
|
|
36
|
+
color: ${token.colorText};
|
|
37
|
+
background: ${token.colorFillSecondary};
|
|
38
|
+
}
|
|
39
|
+
`,
|
|
40
|
+
container: css`
|
|
41
|
+
position: fixed;
|
|
42
|
+
z-index: 9999;
|
|
43
|
+
inset: 0;
|
|
44
|
+
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
|
|
49
|
+
background: ${token.colorBgMask};
|
|
50
|
+
`,
|
|
51
|
+
content: css`
|
|
52
|
+
position: relative;
|
|
53
|
+
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
|
|
56
|
+
max-width: 480px;
|
|
57
|
+
padding: 24px;
|
|
58
|
+
border: 1px solid ${token.yellowBorder};
|
|
59
|
+
border-radius: ${token.borderRadiusLG}px;
|
|
60
|
+
|
|
61
|
+
background: ${token.colorBgContainer};
|
|
62
|
+
box-shadow: ${token.boxShadowSecondary};
|
|
63
|
+
`,
|
|
64
|
+
desc: css`
|
|
65
|
+
line-height: 1.6;
|
|
66
|
+
color: ${token.colorTextSecondary};
|
|
67
|
+
`,
|
|
68
|
+
title: css`
|
|
69
|
+
font-size: 16px;
|
|
70
|
+
font-weight: bold;
|
|
71
|
+
color: ${token.colorWarningText};
|
|
72
|
+
`,
|
|
73
|
+
titleIcon: css`
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
color: ${token.colorWarning};
|
|
76
|
+
`,
|
|
77
|
+
warning: css`
|
|
78
|
+
padding: 12px;
|
|
79
|
+
border-radius: ${token.borderRadius}px;
|
|
80
|
+
color: ${token.colorWarningText};
|
|
81
|
+
background: ${token.yellowBg};
|
|
82
|
+
`,
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
const ServerVersionOutdatedAlert = () => {
|
|
86
|
+
const { styles } = useStyles();
|
|
87
|
+
const { t } = useTranslation('common');
|
|
88
|
+
const [dismissed, setDismissed] = useState(false);
|
|
89
|
+
const isServerVersionOutdated = useGlobalStore((s) => s.isServerVersionOutdated);
|
|
90
|
+
const storageMode = useElectronStore(electronSyncSelectors.storageMode);
|
|
91
|
+
|
|
92
|
+
// Only show alert when using self-hosted server, not cloud
|
|
93
|
+
if (storageMode !== 'selfHost') return null;
|
|
94
|
+
if (!isServerVersionOutdated || dismissed) return null;
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className={styles.container}>
|
|
98
|
+
<div className={styles.content}>
|
|
99
|
+
<div className={styles.closeButton} onClick={() => setDismissed(true)}>
|
|
100
|
+
<Icon icon={X} />
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<Flexbox gap={16}>
|
|
104
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
105
|
+
<Icon className={styles.titleIcon} icon={TriangleAlert} />
|
|
106
|
+
<div className={styles.title}>{t('serverVersionOutdated.title')}</div>
|
|
107
|
+
</Flexbox>
|
|
108
|
+
|
|
109
|
+
<div className={styles.desc}>
|
|
110
|
+
{t('serverVersionOutdated.desc', { version: CURRENT_VERSION })}
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div className={styles.warning}>{t('serverVersionOutdated.warning')}</div>
|
|
114
|
+
|
|
115
|
+
<Flexbox gap={8} horizontal justify="flex-end" style={{ marginTop: 8 }}>
|
|
116
|
+
<a href={MANUAL_UPGRADE_URL} rel="noreferrer" target="_blank">
|
|
117
|
+
<Button size="small" type="primary">
|
|
118
|
+
{t('serverVersionOutdated.upgrade')}
|
|
119
|
+
</Button>
|
|
120
|
+
</a>
|
|
121
|
+
<Button onClick={() => setDismissed(true)} size="small">
|
|
122
|
+
{t('serverVersionOutdated.dismiss')}
|
|
123
|
+
</Button>
|
|
124
|
+
</Flexbox>
|
|
125
|
+
</Flexbox>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default ServerVersionOutdatedAlert;
|
|
@@ -5,6 +5,7 @@ import { memo } from 'react';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { createStoreUpdater } from 'zustand-utils';
|
|
7
7
|
|
|
8
|
+
import { isDesktop } from '@/const/version';
|
|
8
9
|
import { enableNextAuth } from '@/envs/auth';
|
|
9
10
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
|
10
11
|
import { useAgentStore } from '@/store/agent';
|
|
@@ -32,7 +33,10 @@ const StoreInitialization = memo(() => {
|
|
|
32
33
|
|
|
33
34
|
const { serverConfig } = useServerConfigStore();
|
|
34
35
|
|
|
35
|
-
const useInitSystemStatus = useGlobalStore((s) =>
|
|
36
|
+
const [useInitSystemStatus, useCheckServerVersion] = useGlobalStore((s) => [
|
|
37
|
+
s.useInitSystemStatus,
|
|
38
|
+
s.useCheckServerVersion,
|
|
39
|
+
]);
|
|
36
40
|
|
|
37
41
|
const useInitBuiltinAgent = useAgentStore((s) => s.useInitBuiltinAgent);
|
|
38
42
|
const useInitAiProviderKeyVaults = useAiInfraStore((s) => s.useFetchAiProviderRuntimeState);
|
|
@@ -41,6 +45,9 @@ const StoreInitialization = memo(() => {
|
|
|
41
45
|
// init the system preference
|
|
42
46
|
useInitSystemStatus();
|
|
43
47
|
|
|
48
|
+
// check server version in desktop app
|
|
49
|
+
useCheckServerVersion(isDesktop);
|
|
50
|
+
|
|
44
51
|
// fetch server config
|
|
45
52
|
const useFetchServerConfig = useServerConfigStore((s) => s.useInitServerConfig);
|
|
46
53
|
useFetchServerConfig();
|
|
@@ -7,6 +7,7 @@ import { ReferralProvider } from '@/business/client/ReferralProvider';
|
|
|
7
7
|
import { LobeAnalyticsProviderWrapper } from '@/components/Analytics/LobeAnalyticsProviderWrapper';
|
|
8
8
|
import { DragUploadProvider } from '@/components/DragUploadZone/DragUploadProvider';
|
|
9
9
|
import { getServerFeatureFlagsValue } from '@/config/featureFlags';
|
|
10
|
+
import { isDesktop } from '@/const/version';
|
|
10
11
|
import { appEnv } from '@/envs/app';
|
|
11
12
|
import DevPanel from '@/features/DevPanel';
|
|
12
13
|
import { getServerGlobalConfig } from '@/server/globalConfig';
|
|
@@ -20,6 +21,7 @@ import ImportSettings from './ImportSettings';
|
|
|
20
21
|
import Locale from './Locale';
|
|
21
22
|
import NextThemeProvider from './NextThemeProvider';
|
|
22
23
|
import QueryProvider from './Query';
|
|
24
|
+
import ServerVersionOutdatedAlert from './ServerVersionOutdatedAlert';
|
|
23
25
|
import StoreInitialization from './StoreInitialization';
|
|
24
26
|
import StyleRegistry from './StyleRegistry';
|
|
25
27
|
|
|
@@ -66,6 +68,8 @@ const GlobalLayout = async ({
|
|
|
66
68
|
>
|
|
67
69
|
<QueryProvider>
|
|
68
70
|
<StoreInitialization />
|
|
71
|
+
|
|
72
|
+
{isDesktop && <ServerVersionOutdatedAlert />}
|
|
69
73
|
<FaviconProvider>
|
|
70
74
|
<GroupWizardProvider>
|
|
71
75
|
<DragUploadProvider>
|
|
@@ -350,6 +350,13 @@ export default {
|
|
|
350
350
|
'run': 'Run',
|
|
351
351
|
'save': 'Save',
|
|
352
352
|
'send': 'Send',
|
|
353
|
+
'serverVersionOutdated.desc':
|
|
354
|
+
'Your client version (v{{version}}) requires a newer server version.',
|
|
355
|
+
'serverVersionOutdated.dismiss': 'Continue Anyway',
|
|
356
|
+
'serverVersionOutdated.title': 'Server Version Outdated',
|
|
357
|
+
'serverVersionOutdated.upgrade': 'Upgrade Guide',
|
|
358
|
+
'serverVersionOutdated.warning':
|
|
359
|
+
'Some features may not work properly or behave unexpectedly. Please update your server for the best experience.',
|
|
353
360
|
'setting': 'Settings',
|
|
354
361
|
'share': 'Share',
|
|
355
362
|
'stop': 'Stop',
|
|
@@ -401,6 +408,7 @@ export default {
|
|
|
401
408
|
'upgradeVersion.action': 'Upgrade',
|
|
402
409
|
'upgradeVersion.hasNew': 'Update available',
|
|
403
410
|
'upgradeVersion.newVersion': 'Update available: {{version}}',
|
|
411
|
+
'upgradeVersion.serverVersion': 'Server: {{version}}',
|
|
404
412
|
'userPanel.anonymousNickName': 'Anonymous User',
|
|
405
413
|
'userPanel.billing': 'Billing Management',
|
|
406
414
|
'userPanel.cloud': 'Launch {{name}}',
|
package/src/services/global.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { PartialDeep } from 'type-fest';
|
|
2
2
|
|
|
3
|
+
import type { VersionResponseData } from '@/app/(backend)/api/version/route';
|
|
3
4
|
import { BusinessGlobalService } from '@/business/client/services/BusinessGlobalService';
|
|
4
5
|
import { lambdaClient } from '@/libs/trpc/client';
|
|
5
6
|
import { type LobeAgentConfig } from '@/types/agent';
|
|
6
7
|
import { type GlobalRuntimeConfig } from '@/types/serverConfig';
|
|
7
8
|
|
|
8
9
|
const VERSION_URL = 'https://registry.npmmirror.com/@lobehub/chat/latest';
|
|
10
|
+
const SERVER_VERSION_URL = '/api/version';
|
|
9
11
|
|
|
10
12
|
class GlobalService extends BusinessGlobalService {
|
|
11
13
|
/**
|
|
@@ -18,6 +20,29 @@ class GlobalService extends BusinessGlobalService {
|
|
|
18
20
|
return data['version'];
|
|
19
21
|
};
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* get server version from /api/version
|
|
25
|
+
* @returns version string if available, null only if server returns 404 (API doesn't exist on old server)
|
|
26
|
+
* @throws Error for other failures (network errors, 500s, etc.) to allow SWR retry
|
|
27
|
+
*/
|
|
28
|
+
getServerVersion = async (): Promise<string | null> => {
|
|
29
|
+
const res = await fetch(SERVER_VERSION_URL);
|
|
30
|
+
|
|
31
|
+
// Only treat 404 as "server doesn't support version API"
|
|
32
|
+
// Other errors (500, network issues) should throw to allow retry
|
|
33
|
+
if (res.status === 404) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`Failed to fetch server version: ${res.status}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const data: VersionResponseData = await res.json();
|
|
42
|
+
|
|
43
|
+
return data.version;
|
|
44
|
+
};
|
|
45
|
+
|
|
21
46
|
getGlobalConfig = async (): Promise<GlobalRuntimeConfig> => {
|
|
22
47
|
return lambdaClient.config.getGlobalConfig.query();
|
|
23
48
|
};
|
|
@@ -23,6 +23,7 @@ export interface GlobalGeneralAction {
|
|
|
23
23
|
updateResourceManagerColumnWidth: (column: 'name' | 'date' | 'size', width: number) => void;
|
|
24
24
|
updateSystemStatus: (status: Partial<SystemStatus>, action?: any) => void;
|
|
25
25
|
useCheckLatestVersion: (enabledCheck?: boolean) => SWRResponse<string>;
|
|
26
|
+
useCheckServerVersion: (enabledCheck?: boolean) => SWRResponse<string | null>;
|
|
26
27
|
useInitSystemStatus: () => SWRResponse;
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -160,6 +161,51 @@ export const generalActionSlice: StateCreator<
|
|
|
160
161
|
},
|
|
161
162
|
),
|
|
162
163
|
|
|
164
|
+
useCheckServerVersion: (enabledCheck = true) =>
|
|
165
|
+
useOnlyFetchOnceSWR(
|
|
166
|
+
enabledCheck ? 'checkServerVersion' : null,
|
|
167
|
+
async () => globalService.getServerVersion(),
|
|
168
|
+
{
|
|
169
|
+
onSuccess: (data: string | null) => {
|
|
170
|
+
if (data === null) {
|
|
171
|
+
set({ isServerVersionOutdated: true }, false);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
set({ serverVersion: data }, false);
|
|
176
|
+
|
|
177
|
+
if (!valid(CURRENT_VERSION) || !valid(data)) return;
|
|
178
|
+
|
|
179
|
+
const clientVersion = parse(CURRENT_VERSION);
|
|
180
|
+
const serverVersion = parse(data);
|
|
181
|
+
|
|
182
|
+
if (!clientVersion || !serverVersion) return;
|
|
183
|
+
|
|
184
|
+
const DIFF_THRESHOLD = 5;
|
|
185
|
+
// 版本差异计算规则
|
|
186
|
+
// ┌─────────────────┬────────┬─────────┐
|
|
187
|
+
// │ 客户端 → 服务端 │ 差异值 │ 结果 │
|
|
188
|
+
// ├─────────────────┼────────┼─────────┤
|
|
189
|
+
// │ 1.0.5 → 1.0.0 │ 5 │ ⚠️ 过旧 │
|
|
190
|
+
// ├─────────────────┼────────┼─────────┤
|
|
191
|
+
// │ 1.1.0 → 1.0.5 │ 5 │ ⚠️ 过旧 │
|
|
192
|
+
// ├─────────────────┼────────┼─────────┤
|
|
193
|
+
// │ 2.0.0 → 1.9.9 │ 91 │ ⚠️ 过旧 │
|
|
194
|
+
// ├─────────────────┼────────┼─────────┤
|
|
195
|
+
// │ 1.0.4 → 1.0.0 │ 4 │ ✅ 正常 │
|
|
196
|
+
// └─────────────────┴────────┴─────────┘
|
|
197
|
+
const versionDiff =
|
|
198
|
+
(clientVersion.major - serverVersion.major) * 100 +
|
|
199
|
+
(clientVersion.minor - serverVersion.minor) * 10 +
|
|
200
|
+
(clientVersion.patch - serverVersion.patch);
|
|
201
|
+
|
|
202
|
+
if (versionDiff >= DIFF_THRESHOLD) {
|
|
203
|
+
set({ isServerVersionOutdated: true }, false);
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
),
|
|
208
|
+
|
|
163
209
|
useInitSystemStatus: () =>
|
|
164
210
|
useOnlyFetchOnceSWR<SystemStatus>(
|
|
165
211
|
'initSystemStatus',
|
|
@@ -180,9 +180,18 @@ export interface GlobalState {
|
|
|
180
180
|
*/
|
|
181
181
|
initClientDBStage: DatabaseLoadingState;
|
|
182
182
|
isMobile?: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* 服务端版本过旧,不支持 /api/version 接口
|
|
185
|
+
* 需要提示用户更新服务端
|
|
186
|
+
*/
|
|
187
|
+
isServerVersionOutdated?: boolean;
|
|
183
188
|
isStatusInit?: boolean;
|
|
184
189
|
latestVersion?: string;
|
|
185
190
|
navigate?: NavigateFunction;
|
|
191
|
+
/**
|
|
192
|
+
* 服务端版本号,用于检测客户端与服务端版本是否一致
|
|
193
|
+
*/
|
|
194
|
+
serverVersion?: string;
|
|
186
195
|
sidebarKey: SidebarTabKey;
|
|
187
196
|
status: SystemStatus;
|
|
188
197
|
statusStorage: AsyncLocalStorage<SystemStatus>;
|