@lobehub/lobehub 2.0.0-next.268 → 2.0.0-next.269

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,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.269](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.268...v2.0.0-next.269)
6
+
7
+ <sup>Released on **2026-01-12**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **electron**: Add custom titlebar for Electron windows.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **electron**: Add custom titlebar for Electron windows, closes [#11438](https://github.com/lobehub/lobe-chat/issues/11438) ([08f6ee3](https://github.com/lobehub/lobe-chat/commit/08f6ee3))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.268](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.267...v2.0.0-next.268)
6
31
 
7
32
  <sup>Released on **2026-01-12**</sup>
@@ -12,6 +12,7 @@ import { join } from 'node:path';
12
12
  import { preloadDir, resourcesDir } from '@/const/dir';
13
13
  import { isMac } from '@/const/env';
14
14
  import { ELECTRON_BE_PROTOCOL_SCHEME } from '@/const/protocol';
15
+ import { TITLE_BAR_HEIGHT } from '@/const/theme';
15
16
  import RemoteServerConfigCtr from '@/controllers/RemoteServerConfigCtr';
16
17
  import { backendProxyProtocolManager } from '@/core/infrastructure/BackendProxyProtocolManager';
17
18
  import { setResponseHeader } from '@/utils/http-headers';
@@ -119,6 +120,10 @@ export default class Browser {
119
120
  logger.info(`Creating new BrowserWindow instance: ${this.identifier}`);
120
121
  logger.debug(`[${this.identifier}] Resolved window state: ${JSON.stringify(resolvedState)}`);
121
122
 
123
+ // Calculate traffic light position to center vertically in title bar
124
+ // Traffic light buttons are approximately 12px tall
125
+ const trafficLightY = Math.round((TITLE_BAR_HEIGHT - 12) / 2);
126
+
122
127
  return new BrowserWindow({
123
128
  ...rest,
124
129
  autoHideMenuBar: true,
@@ -128,6 +133,7 @@ export default class Browser {
128
133
  height: resolvedState.height,
129
134
  show: false,
130
135
  title,
136
+ trafficLightPosition: isMac ? { x: 12, y: trafficLightY } : undefined,
131
137
  vibrancy: 'sidebar',
132
138
  visualEffectState: 'active',
133
139
  webPreferences: {
package/changelog/v1.json CHANGED
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-12",
5
+ "version": "2.0.0-next.269"
6
+ },
2
7
  {
3
8
  "children": {},
4
9
  "date": "2026-01-12",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.268",
3
+ "version": "2.0.0-next.269",
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",
@@ -10,6 +10,7 @@ export * from './merge';
10
10
  export * from './multimodalContent';
11
11
  export * from './number';
12
12
  export * from './object';
13
+ export * from './platform';
13
14
  export * from './pricing';
14
15
  export * from './safeParseJSON';
15
16
  export * from './sleep';
@@ -25,18 +25,50 @@ export const browserInfo = {
25
25
 
26
26
  export const isMacOS = () => getPlatform() === 'Mac OS';
27
27
 
28
+ /**
29
+ * Get macOS Darwin major version number
30
+ * @returns Darwin major version (e.g., 25, 26) or 0 if not available
31
+ */
32
+ export const getDarwinMajorVersion = (): number => {
33
+ if (isOnServerSide || typeof window === 'undefined') return 0;
34
+
35
+ // In Electron environment, use window.lobeEnv.darwinMajorVersion if available
36
+ if (typeof (window as any)?.lobeEnv?.darwinMajorVersion === 'number') {
37
+ return (window as any).lobeEnv.darwinMajorVersion;
38
+ }
39
+
40
+ // In web environment, try to parse from userAgent
41
+ if (typeof navigator !== 'undefined') {
42
+ const match = navigator.userAgent.match(/Mac OS X (\d+)[._](\d+)/);
43
+ if (match) {
44
+ return parseInt(match[1], 10);
45
+ }
46
+ }
47
+
48
+ return 0;
49
+ };
50
+
28
51
  /**
29
52
  *
30
53
  * We can't use it to detect the macOS real version, and we also don't know if it's macOS 26, only an estimated value.
31
- * @returns true if the current browser is macOS and the version is 10.15 or later
54
+ * @returns true if the current browser is macOS and the version is 10.15 or later (web) or darwinMajorVersion >= 25 (Electron)
32
55
  */
33
56
  export const isMacOSWithLargeWindowBorders = () => {
34
57
  if (isOnServerSide || typeof navigator === 'undefined') return false;
35
58
 
36
- // keep consistent with the original logic: only for macOS on web (exclude Electron)
59
+ // Check if we're in Electron environment
37
60
  const isElectron =
38
61
  /Electron\//.test(navigator.userAgent) || Boolean((window as any)?.process?.type);
39
- if (isElectron || !isMacOS()) return false;
62
+
63
+ // In Electron environment, check darwinMajorVersion from window.lobeEnv
64
+ if (isElectron) {
65
+ const darwinMajorVersion = getDarwinMajorVersion();
66
+ // macOS 25+ has large window borders
67
+ return darwinMajorVersion >= 25;
68
+ }
69
+
70
+ // keep consistent with the original logic: only for macOS on web (exclude Electron)
71
+ if (!isMacOS()) return false;
40
72
 
41
73
  const match = navigator.userAgent.match(/Mac OS X (\d+)[._](\d+)/);
42
74
  if (!match) return false;
@@ -5,6 +5,7 @@ import { Divider } from 'antd';
5
5
  import { cx } from 'antd-style';
6
6
  import type { FC, PropsWithChildren } from 'react';
7
7
 
8
+ import { SimpleTitleBar, TITLE_BAR_HEIGHT } from '@/features/ElectronTitlebar';
8
9
  import LangButton from '@/features/User/UserPanel/LangButton';
9
10
  import ThemeButton from '@/features/User/UserPanel/ThemeButton';
10
11
  import { useIsDark } from '@/hooks/useIsDark';
@@ -14,36 +15,43 @@ import { styles } from './style';
14
15
  const OnboardingContainer: FC<PropsWithChildren> = ({ children }) => {
15
16
  const isDarkMode = useIsDark();
16
17
  return (
17
- <Flexbox className={styles.outerContainer} height={'100%'} padding={8} width={'100%'}>
18
+ <Flexbox height={'100%'} width={'100%'}>
19
+ <SimpleTitleBar />
18
20
  <Flexbox
19
- className={cx(isDarkMode ? styles.innerContainerDark : styles.innerContainerLight)}
20
- height={'100%'}
21
+ className={styles.outerContainer}
22
+ height={`calc(100% - ${TITLE_BAR_HEIGHT}px)`}
23
+ style={{ paddingBottom: 8, paddingInline: 8 }}
21
24
  width={'100%'}
22
25
  >
23
26
  <Flexbox
24
- align={'center'}
25
- className={cx(styles.drag)}
26
- gap={8}
27
- horizontal
28
- justify={'space-between'}
29
- padding={16}
27
+ className={cx(isDarkMode ? styles.innerContainerDark : styles.innerContainerLight)}
28
+ height={'100%'}
30
29
  width={'100%'}
31
30
  >
32
- <div />
33
- <Flexbox align={'center'} horizontal>
34
- <LangButton placement={'bottomRight'} size={18} />
35
- <Divider className={styles.divider} orientation={'vertical'} />
36
- <ThemeButton placement={'bottomRight'} size={18} />
31
+ <Flexbox
32
+ align={'center'}
33
+ gap={8}
34
+ horizontal
35
+ justify={'space-between'}
36
+ padding={16}
37
+ width={'100%'}
38
+ >
39
+ <div />
40
+ <Flexbox align={'center'} horizontal>
41
+ <LangButton placement={'bottomRight'} size={18} />
42
+ <Divider className={styles.divider} orientation={'vertical'} />
43
+ <ThemeButton placement={'bottomRight'} size={18} />
44
+ </Flexbox>
37
45
  </Flexbox>
46
+ <Center height={'100%'} padding={16} width={'100%'}>
47
+ {children}
48
+ </Center>
49
+ <Center padding={24}>
50
+ <Text align={'center'} type={'secondary'}>
51
+ © 2025 LobeHub. All rights reserved.
52
+ </Text>
53
+ </Center>
38
54
  </Flexbox>
39
- <Center height={'100%'} padding={16} width={'100%'}>
40
- {children}
41
- </Center>
42
- <Center padding={24}>
43
- <Text align={'center'} type={'secondary'}>
44
- © 2025 LobeHub. All rights reserved.
45
- </Text>
46
- </Center>
47
55
  </Flexbox>
48
56
  </Flexbox>
49
57
  );
@@ -1,14 +1,13 @@
1
1
  import { createStaticStyles } from 'antd-style';
2
2
 
3
+ import { isMacOSWithLargeWindowBorders } from '@/utils/platform';
4
+
3
5
  export const styles = createStaticStyles(({ css, cssVar }) => ({
4
6
  // Divider 样式
5
7
  divider: css`
6
8
  height: 24px;
7
9
  `,
8
10
 
9
- drag: css`
10
- -webkit-app-region: drag;
11
- `,
12
11
  // 内层容器 - 深色模式
13
12
  innerContainerDark: css`
14
13
  position: relative;
@@ -16,7 +15,9 @@ export const styles = createStaticStyles(({ css, cssVar }) => ({
16
15
  overflow: hidden;
17
16
 
18
17
  border: 1px solid ${cssVar.colorBorderSecondary};
19
- border-radius: ${cssVar.borderRadius};
18
+ border-radius: ${!isMacOSWithLargeWindowBorders()
19
+ ? cssVar.borderRadius
20
+ : `${cssVar.borderRadius} 12px ${cssVar.borderRadius} 12px`};
20
21
 
21
22
  background: ${cssVar.colorBgContainer};
22
23
  `,
@@ -28,7 +29,9 @@ export const styles = createStaticStyles(({ css, cssVar }) => ({
28
29
  overflow: hidden;
29
30
 
30
31
  border: 1px solid ${cssVar.colorBorder};
31
- border-radius: ${cssVar.borderRadius};
32
+ border-radius: ${!isMacOSWithLargeWindowBorders()
33
+ ? cssVar.borderRadius
34
+ : `${cssVar.borderRadius} 12px ${cssVar.borderRadius} 12px`};
32
35
 
33
36
  background: ${cssVar.colorBgContainer};
34
37
  `,
@@ -6,7 +6,7 @@ import { isDesktop } from '@/const/version';
6
6
  import { useIsDark } from '@/hooks/useIsDark';
7
7
  import { useGlobalStore } from '@/store/global';
8
8
  import { systemStatusSelectors } from '@/store/global/selectors';
9
- import { isMacOSWithLargeWindowBorders } from '@/utils/platform';
9
+ import { getDarwinMajorVersion, isMacOSWithLargeWindowBorders } from '@/utils/platform';
10
10
 
11
11
  import { styles } from './DesktopLayoutContainer/style';
12
12
 
@@ -24,8 +24,7 @@ const DesktopLayoutContainer: FC<PropsWithChildren> = ({ children }) => {
24
24
  );
25
25
 
26
26
  const innerCssVariables = useMemo<Record<string, string>>(() => {
27
- const darwinMajorVersion =
28
- typeof window !== 'undefined' ? (window.lobeEnv?.darwinMajorVersion ?? 0) : 0;
27
+ const darwinMajorVersion = getDarwinMajorVersion();
29
28
 
30
29
  const borderRadius = darwinMajorVersion >= 25 ? '12px' : cssVar.borderRadius;
31
30
  const borderBottomRightRadius =
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { Flexbox } from '@lobehub/ui';
4
+ import { type FC } from 'react';
5
+
6
+ import { ProductLogo } from '@/components/Branding/ProductLogo';
7
+ import { electronStylish } from '@/styles/electron';
8
+
9
+ import { TITLE_BAR_HEIGHT } from './const';
10
+
11
+ /**
12
+ * A simple, minimal TitleBar for Electron windows.
13
+ * Provides draggable area without business logic (navigation, updates, etc.)
14
+ * Use this for secondary windows like onboarding, settings, etc.
15
+ */
16
+ const SimpleTitleBar: FC = () => {
17
+ return (
18
+ <Flexbox
19
+ align={'center'}
20
+ className={electronStylish.draggable}
21
+ height={TITLE_BAR_HEIGHT}
22
+ horizontal
23
+ justify={'center'}
24
+ width={'100%'}
25
+ >
26
+ <ProductLogo size={16} type={'text'} />
27
+ </Flexbox>
28
+ );
29
+ };
30
+
31
+ export default SimpleTitleBar;
@@ -67,3 +67,4 @@ const TitleBar = memo(() => {
67
67
  export default TitleBar;
68
68
 
69
69
  export { TITLE_BAR_HEIGHT } from './const';
70
+ export { default as SimpleTitleBar } from './SimpleTitleBar';
@@ -0,0 +1,2 @@
1
+ // Re-export platform utilities from packages/utils
2
+ export * from '../../packages/utils/src/platform';