@lobehub/lobehub 2.0.0-next.286 → 2.0.0-next.287
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/src/main/const/theme.ts +0 -3
- package/apps/desktop/src/main/core/browser/Browser.ts +1 -1
- package/apps/desktop/src/main/core/browser/WindowThemeManager.ts +3 -2
- package/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +0 -1
- package/apps/desktop/src/main/core/browser/__tests__/WindowThemeManager.test.ts +8 -5
- package/changelog/v1.json +5 -0
- package/package.json +1 -1
- package/packages/desktop-bridge/src/index.ts +3 -0
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +15 -2
- package/src/features/ElectronTitlebar/Connection/ConnectionMode.tsx +2 -2
- package/src/features/ElectronTitlebar/SimpleTitleBar.tsx +1 -2
- package/src/features/ElectronTitlebar/index.tsx +2 -2
- package/src/hooks/useUserAvatar.test.ts +23 -4
- package/src/store/electron/selectors/sync.ts +17 -1
- package/src/features/ElectronTitlebar/const.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.287](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.286...v2.0.0-next.287)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-14**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **desktop**: Unify TITLE_BAR_HEIGHT constant to desktop-bridge.
|
|
12
|
+
|
|
13
|
+
#### 🐛 Bug Fixes
|
|
14
|
+
|
|
15
|
+
- **desktop**: Return OFFICIAL_URL in cloud mode for remoteServerUrl selector.
|
|
16
|
+
|
|
17
|
+
<br/>
|
|
18
|
+
|
|
19
|
+
<details>
|
|
20
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
21
|
+
|
|
22
|
+
#### Code refactoring
|
|
23
|
+
|
|
24
|
+
- **desktop**: Unify TITLE_BAR_HEIGHT constant to desktop-bridge, closes [#11496](https://github.com/lobehub/lobe-chat/issues/11496) ([e7739e5](https://github.com/lobehub/lobe-chat/commit/e7739e5))
|
|
25
|
+
|
|
26
|
+
#### What's fixed
|
|
27
|
+
|
|
28
|
+
- **desktop**: Return OFFICIAL_URL in cloud mode for remoteServerUrl selector, closes [#11502](https://github.com/lobehub/lobe-chat/issues/11502) ([1d11fac](https://github.com/lobehub/lobe-chat/commit/1d11fac))
|
|
29
|
+
|
|
30
|
+
</details>
|
|
31
|
+
|
|
32
|
+
<div align="right">
|
|
33
|
+
|
|
34
|
+
[](#readme-top)
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
|
|
5
38
|
## [Version 2.0.0-next.286](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.285...v2.0.0-next.286)
|
|
6
39
|
|
|
7
40
|
<sup>Released on **2026-01-14**</sup>
|
|
@@ -4,8 +4,5 @@ export const BACKGROUND_LIGHT = '#f8f8f8';
|
|
|
4
4
|
export const SYMBOL_COLOR_DARK = '#ffffff80';
|
|
5
5
|
export const SYMBOL_COLOR_LIGHT = '#00000080';
|
|
6
6
|
|
|
7
|
-
// Window dimensions and constraints
|
|
8
|
-
export const TITLE_BAR_HEIGHT = 29;
|
|
9
|
-
|
|
10
7
|
// Default window configuration
|
|
11
8
|
export const THEME_CHANGE_DELAY = 100;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
|
|
1
2
|
import { MainBroadcastEventKey, MainBroadcastParams } from '@lobechat/electron-client-ipc';
|
|
2
3
|
import {
|
|
3
4
|
BrowserWindow,
|
|
@@ -12,7 +13,6 @@ import { join } from 'node:path';
|
|
|
12
13
|
import { preloadDir, resourcesDir } from '@/const/dir';
|
|
13
14
|
import { isMac } from '@/const/env';
|
|
14
15
|
import { ELECTRON_BE_PROTOCOL_SCHEME } from '@/const/protocol';
|
|
15
|
-
import { TITLE_BAR_HEIGHT } from '@/const/theme';
|
|
16
16
|
import RemoteServerConfigCtr from '@/controllers/RemoteServerConfigCtr';
|
|
17
17
|
import { backendProxyProtocolManager } from '@/core/infrastructure/BackendProxyProtocolManager';
|
|
18
18
|
import { setResponseHeader } from '@/utils/http-headers';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
|
|
1
2
|
import { BrowserWindow, nativeTheme } from 'electron';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
|
|
@@ -9,7 +10,6 @@ import {
|
|
|
9
10
|
SYMBOL_COLOR_DARK,
|
|
10
11
|
SYMBOL_COLOR_LIGHT,
|
|
11
12
|
THEME_CHANGE_DELAY,
|
|
12
|
-
TITLE_BAR_HEIGHT,
|
|
13
13
|
} from '@/const/theme';
|
|
14
14
|
import { createLogger } from '@/utils/logger';
|
|
15
15
|
|
|
@@ -91,7 +91,8 @@ export class WindowThemeManager {
|
|
|
91
91
|
icon: isDev ? join(buildDir, 'icon-dev.ico') : undefined,
|
|
92
92
|
titleBarOverlay: {
|
|
93
93
|
color: isDarkMode ? BACKGROUND_DARK : BACKGROUND_LIGHT,
|
|
94
|
-
|
|
94
|
+
// Reduce 2px to prevent blocking the container border edge
|
|
95
|
+
height: TITLE_BAR_HEIGHT - 2,
|
|
95
96
|
symbolColor: isDarkMode ? SYMBOL_COLOR_DARK : SYMBOL_COLOR_LIGHT,
|
|
96
97
|
},
|
|
97
98
|
titleBarStyle: 'hidden',
|
|
@@ -38,13 +38,16 @@ vi.mock('@/const/env', () => ({
|
|
|
38
38
|
isWindows: true,
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
|
+
vi.mock('@lobechat/desktop-bridge', () => ({
|
|
42
|
+
TITLE_BAR_HEIGHT: 38,
|
|
43
|
+
}));
|
|
44
|
+
|
|
41
45
|
vi.mock('@/const/theme', () => ({
|
|
42
46
|
BACKGROUND_DARK: '#1a1a1a',
|
|
43
47
|
BACKGROUND_LIGHT: '#ffffff',
|
|
44
48
|
SYMBOL_COLOR_DARK: '#ffffff',
|
|
45
49
|
SYMBOL_COLOR_LIGHT: '#000000',
|
|
46
50
|
THEME_CHANGE_DELAY: 0,
|
|
47
|
-
TITLE_BAR_HEIGHT: 32,
|
|
48
51
|
}));
|
|
49
52
|
|
|
50
53
|
describe('WindowThemeManager', () => {
|
|
@@ -89,7 +92,7 @@ describe('WindowThemeManager', () => {
|
|
|
89
92
|
icon: undefined,
|
|
90
93
|
titleBarOverlay: {
|
|
91
94
|
color: '#1a1a1a',
|
|
92
|
-
height:
|
|
95
|
+
height: 36,
|
|
93
96
|
symbolColor: '#ffffff',
|
|
94
97
|
},
|
|
95
98
|
titleBarStyle: 'hidden',
|
|
@@ -106,7 +109,7 @@ describe('WindowThemeManager', () => {
|
|
|
106
109
|
icon: undefined,
|
|
107
110
|
titleBarOverlay: {
|
|
108
111
|
color: '#ffffff',
|
|
109
|
-
height:
|
|
112
|
+
height: 36,
|
|
110
113
|
symbolColor: '#000000',
|
|
111
114
|
},
|
|
112
115
|
titleBarStyle: 'hidden',
|
|
@@ -183,7 +186,7 @@ describe('WindowThemeManager', () => {
|
|
|
183
186
|
expect(mockBrowserWindow.setBackgroundColor).toHaveBeenCalledWith('#1a1a1a');
|
|
184
187
|
expect(mockBrowserWindow.setTitleBarOverlay).toHaveBeenCalledWith({
|
|
185
188
|
color: '#1a1a1a',
|
|
186
|
-
height:
|
|
189
|
+
height: 36,
|
|
187
190
|
symbolColor: '#ffffff',
|
|
188
191
|
});
|
|
189
192
|
});
|
|
@@ -195,7 +198,7 @@ describe('WindowThemeManager', () => {
|
|
|
195
198
|
expect(mockBrowserWindow.setBackgroundColor).toHaveBeenCalledWith('#ffffff');
|
|
196
199
|
expect(mockBrowserWindow.setTitleBarOverlay).toHaveBeenCalledWith({
|
|
197
200
|
color: '#ffffff',
|
|
198
|
-
height:
|
|
201
|
+
height: 36,
|
|
199
202
|
symbolColor: '#000000',
|
|
200
203
|
});
|
|
201
204
|
});
|
package/changelog/v1.json
CHANGED
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.287",
|
|
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",
|
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
import { useMemo } from 'react';
|
|
27
27
|
import { useTranslation } from 'react-i18next';
|
|
28
28
|
|
|
29
|
+
import { useElectronStore } from '@/store/electron';
|
|
30
|
+
import { electronSyncSelectors } from '@/store/electron/selectors';
|
|
29
31
|
import { SettingsTabs } from '@/store/global/initialState';
|
|
30
32
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
31
33
|
import { useUserStore } from '@/store/user';
|
|
@@ -63,13 +65,24 @@ export const useCategory = () => {
|
|
|
63
65
|
userProfileSelectors.userAvatar(s),
|
|
64
66
|
userProfileSelectors.nickName(s),
|
|
65
67
|
]);
|
|
68
|
+
const remoteServerUrl = useElectronStore(electronSyncSelectors.remoteServerUrl);
|
|
69
|
+
|
|
70
|
+
// Process avatar URL for desktop environment
|
|
71
|
+
const avatarUrl = useMemo(() => {
|
|
72
|
+
if (!avatar) return undefined;
|
|
73
|
+
if (isDesktop && avatar.startsWith('/') && remoteServerUrl) {
|
|
74
|
+
return remoteServerUrl + avatar;
|
|
75
|
+
}
|
|
76
|
+
return avatar;
|
|
77
|
+
}, [avatar, remoteServerUrl]);
|
|
78
|
+
|
|
66
79
|
const categoryGroups: CategoryGroup[] = useMemo(() => {
|
|
67
80
|
const groups: CategoryGroup[] = [];
|
|
68
81
|
|
|
69
82
|
// 个人资料组 - Profile 相关设置
|
|
70
83
|
const profileItems: CategoryItem[] = [
|
|
71
84
|
{
|
|
72
|
-
icon:
|
|
85
|
+
icon: avatarUrl ? <Avatar avatar={avatarUrl} shape={'square'} size={26} /> : UserCircle,
|
|
73
86
|
key: SettingsTabs.Profile,
|
|
74
87
|
label: username ? username : tAuth('tab.profile'),
|
|
75
88
|
},
|
|
@@ -227,7 +240,7 @@ export const useCategory = () => {
|
|
|
227
240
|
showAiImage,
|
|
228
241
|
showApiKeyManage,
|
|
229
242
|
isLoginWithClerk,
|
|
230
|
-
|
|
243
|
+
avatarUrl,
|
|
231
244
|
username,
|
|
232
245
|
]);
|
|
233
246
|
|
|
@@ -82,12 +82,12 @@ const ConnectionMode = memo<ConnectionModeProps>(({ setWaiting }) => {
|
|
|
82
82
|
|
|
83
83
|
const connect = useElectronStore((s) => s.connectRemoteServer);
|
|
84
84
|
const storageMode = useElectronStore(electronSyncSelectors.storageMode);
|
|
85
|
-
const
|
|
85
|
+
const rawRemoteServerUrl = useElectronStore(electronSyncSelectors.rawRemoteServerUrl);
|
|
86
86
|
|
|
87
87
|
const [selectedOption, setSelectedOption] = useState<RemoteStorageMode>(
|
|
88
88
|
storageMode === StorageModeEnum.SelfHost ? StorageModeEnum.SelfHost : StorageModeEnum.Cloud,
|
|
89
89
|
);
|
|
90
|
-
const [selfHostedUrl, setSelfHostedUrl] = useState(
|
|
90
|
+
const [selfHostedUrl, setSelfHostedUrl] = useState(rawRemoteServerUrl);
|
|
91
91
|
|
|
92
92
|
const validateUrl = useCallback((url: string) => {
|
|
93
93
|
if (!url) {
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
|
|
3
4
|
import { Flexbox } from '@lobehub/ui';
|
|
4
5
|
import { type FC } from 'react';
|
|
5
6
|
|
|
6
7
|
import { ProductLogo } from '@/components/Branding/ProductLogo';
|
|
7
8
|
import { electronStylish } from '@/styles/electron';
|
|
8
9
|
|
|
9
|
-
import { TITLE_BAR_HEIGHT } from './const';
|
|
10
|
-
|
|
11
10
|
/**
|
|
12
11
|
* A simple, minimal TitleBar for Electron windows.
|
|
13
12
|
* Provides draggable area without business logic (navigation, updates, etc.)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
|
|
1
2
|
import { Flexbox } from '@lobehub/ui';
|
|
2
3
|
import { Divider } from 'antd';
|
|
3
4
|
import { memo, useMemo } from 'react';
|
|
@@ -11,7 +12,6 @@ import NavigationBar from './NavigationBar';
|
|
|
11
12
|
import { UpdateModal } from './UpdateModal';
|
|
12
13
|
import { UpdateNotification } from './UpdateNotification';
|
|
13
14
|
import WinControl from './WinControl';
|
|
14
|
-
import { TITLE_BAR_HEIGHT } from './const';
|
|
15
15
|
import { useWatchThemeUpdate } from './hooks/useWatchThemeUpdate';
|
|
16
16
|
|
|
17
17
|
const isMac = isMacOS();
|
|
@@ -66,5 +66,5 @@ const TitleBar = memo(() => {
|
|
|
66
66
|
|
|
67
67
|
export default TitleBar;
|
|
68
68
|
|
|
69
|
-
export { TITLE_BAR_HEIGHT } from './const';
|
|
70
69
|
export { default as SimpleTitleBar } from './SimpleTitleBar';
|
|
70
|
+
export { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
|
|
@@ -19,6 +19,7 @@ vi.mock('@lobechat/const', async (importOriginal) => {
|
|
|
19
19
|
return mockIsDesktop;
|
|
20
20
|
},
|
|
21
21
|
DEFAULT_USER_AVATAR: 'default-avatar.png',
|
|
22
|
+
OFFICIAL_URL: 'https://app.lobehub.com',
|
|
22
23
|
};
|
|
23
24
|
});
|
|
24
25
|
|
|
@@ -77,7 +78,7 @@ describe('useUserAvatar', () => {
|
|
|
77
78
|
expect(result.current).toBe(mockAvatar);
|
|
78
79
|
});
|
|
79
80
|
|
|
80
|
-
it('should prepend remote server URL when avatar starts with / in desktop environment', () => {
|
|
81
|
+
it('should prepend remote server URL when avatar starts with / in desktop environment (selfHost mode)', () => {
|
|
81
82
|
mockIsDesktop = true;
|
|
82
83
|
const mockAvatar = '/api/avatar.png';
|
|
83
84
|
const mockServerUrl = 'https://server.com';
|
|
@@ -85,7 +86,7 @@ describe('useUserAvatar', () => {
|
|
|
85
86
|
act(() => {
|
|
86
87
|
useUserStore.setState({ user: { avatar: mockAvatar } as any });
|
|
87
88
|
useElectronStore.setState({
|
|
88
|
-
dataSyncConfig: { remoteServerUrl: mockServerUrl, storageMode: '
|
|
89
|
+
dataSyncConfig: { remoteServerUrl: mockServerUrl, storageMode: 'selfHost' },
|
|
89
90
|
});
|
|
90
91
|
});
|
|
91
92
|
|
|
@@ -102,7 +103,7 @@ describe('useUserAvatar', () => {
|
|
|
102
103
|
act(() => {
|
|
103
104
|
useUserStore.setState({ user: { avatar: mockAvatar } as any });
|
|
104
105
|
useElectronStore.setState({
|
|
105
|
-
dataSyncConfig: { remoteServerUrl: mockServerUrl, storageMode: '
|
|
106
|
+
dataSyncConfig: { remoteServerUrl: mockServerUrl, storageMode: 'selfHost' },
|
|
106
107
|
});
|
|
107
108
|
});
|
|
108
109
|
|
|
@@ -111,7 +112,7 @@ describe('useUserAvatar', () => {
|
|
|
111
112
|
expect(result.current).toBe(mockAvatar);
|
|
112
113
|
});
|
|
113
114
|
|
|
114
|
-
it('should
|
|
115
|
+
it('should use OFFICIAL_URL when storageMode is cloud in desktop environment', () => {
|
|
115
116
|
mockIsDesktop = true;
|
|
116
117
|
const mockAvatar = '/api/avatar.png';
|
|
117
118
|
|
|
@@ -124,6 +125,24 @@ describe('useUserAvatar', () => {
|
|
|
124
125
|
|
|
125
126
|
const { result } = renderHook(() => useUserAvatar());
|
|
126
127
|
|
|
128
|
+
// In cloud mode, selector returns OFFICIAL_URL regardless of remoteServerUrl config
|
|
129
|
+
expect(result.current).toBe('https://app.lobehub.com/api/avatar.png');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should return original avatar when storageMode is selfHost but no URL configured', () => {
|
|
133
|
+
mockIsDesktop = true;
|
|
134
|
+
const mockAvatar = '/api/avatar.png';
|
|
135
|
+
|
|
136
|
+
act(() => {
|
|
137
|
+
useUserStore.setState({ user: { avatar: mockAvatar } as any });
|
|
138
|
+
useElectronStore.setState({
|
|
139
|
+
dataSyncConfig: { remoteServerUrl: '', storageMode: 'selfHost' },
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const { result } = renderHook(() => useUserAvatar());
|
|
144
|
+
|
|
145
|
+
// In selfHost mode with empty URL, avatar is not prepended
|
|
127
146
|
expect(result.current).toBe(mockAvatar);
|
|
128
147
|
});
|
|
129
148
|
});
|
|
@@ -1,12 +1,28 @@
|
|
|
1
|
+
import { OFFICIAL_URL } from '@lobechat/const';
|
|
2
|
+
|
|
1
3
|
import { type ElectronState } from '../initialState';
|
|
2
4
|
|
|
3
5
|
const isSyncActive = (s: ElectronState) => s.dataSyncConfig.active;
|
|
4
6
|
|
|
5
7
|
const storageMode = (s: ElectronState) => s.dataSyncConfig.storageMode;
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns the effective remote server URL based on storage mode:
|
|
11
|
+
* - Cloud mode: returns OFFICIAL_URL
|
|
12
|
+
* - SelfHost mode: returns the configured remoteServerUrl
|
|
13
|
+
*/
|
|
14
|
+
const remoteServerUrl = (s: ElectronState) =>
|
|
15
|
+
s.dataSyncConfig.storageMode === 'cloud' ? OFFICIAL_URL : s.dataSyncConfig.remoteServerUrl || '';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns the raw remoteServerUrl from config without transformation.
|
|
19
|
+
* Use this when you need the original configured value (e.g., for editing forms).
|
|
20
|
+
*/
|
|
21
|
+
const rawRemoteServerUrl = (s: ElectronState) => s.dataSyncConfig.remoteServerUrl || '';
|
|
7
22
|
|
|
8
23
|
export const electronSyncSelectors = {
|
|
9
24
|
isSyncActive,
|
|
25
|
+
rawRemoteServerUrl,
|
|
10
26
|
remoteServerUrl,
|
|
11
27
|
storageMode,
|
|
12
28
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const TITLE_BAR_HEIGHT = 30;
|