@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 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
- height: TITLE_BAR_HEIGHT,
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',
@@ -108,7 +108,6 @@ vi.mock('@/const/theme', () => ({
108
108
  SYMBOL_COLOR_DARK: '#ffffff',
109
109
  SYMBOL_COLOR_LIGHT: '#000000',
110
110
  THEME_CHANGE_DELAY: 0,
111
- TITLE_BAR_HEIGHT: 32,
112
111
  }));
113
112
 
114
113
  describe('Browser', () => {
@@ -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: 32,
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: 32,
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: 32,
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: 32,
201
+ height: 36,
199
202
  symbolColor: '#000000',
200
203
  });
201
204
  });
package/changelog/v1.json CHANGED
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-14",
5
+ "version": "2.0.0-next.287"
6
+ },
2
7
  {
3
8
  "children": {
4
9
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.286",
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",
@@ -7,3 +7,6 @@ export {
7
7
  locales,
8
8
  RouteVariants,
9
9
  } from './routeVariants';
10
+
11
+ // Desktop window constants
12
+ export const TITLE_BAR_HEIGHT = 38;
@@ -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: avatar ? <Avatar avatar={avatar} shape={'square'} size={26} /> : UserCircle,
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
- avatar,
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 remoteServerUrl = useElectronStore(electronSyncSelectors.remoteServerUrl);
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(remoteServerUrl);
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: 'cloud' },
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: 'cloud' },
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 handle empty remote server URL in desktop environment', () => {
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
- const remoteServerUrl = (s: ElectronState) => s.dataSyncConfig.remoteServerUrl || '';
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;