@lobehub/lobehub 2.0.0-next.240 → 2.0.0-next.242

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.
Files changed (123) hide show
  1. package/.github/workflows/test.yml +13 -5
  2. package/CHANGELOG.md +50 -0
  3. package/apps/desktop/build/Icon-beta.Assets.car +0 -0
  4. package/apps/desktop/build/Icon-beta.icns +0 -0
  5. package/apps/desktop/build/icon-beta.ico +0 -0
  6. package/apps/desktop/build/icon-beta.png +0 -0
  7. package/apps/desktop/resources/locales/ar/menu.json +5 -1
  8. package/apps/desktop/resources/locales/bg-BG/menu.json +5 -1
  9. package/apps/desktop/resources/locales/de-DE/menu.json +5 -1
  10. package/apps/desktop/resources/locales/es-ES/menu.json +5 -1
  11. package/apps/desktop/resources/locales/fa-IR/menu.json +5 -1
  12. package/apps/desktop/resources/locales/fr-FR/menu.json +5 -1
  13. package/apps/desktop/resources/locales/it-IT/menu.json +5 -1
  14. package/apps/desktop/resources/locales/ja-JP/menu.json +5 -1
  15. package/apps/desktop/resources/locales/ko-KR/menu.json +5 -1
  16. package/apps/desktop/resources/locales/nl-NL/menu.json +5 -1
  17. package/apps/desktop/resources/locales/pl-PL/menu.json +5 -1
  18. package/apps/desktop/resources/locales/pt-BR/menu.json +5 -1
  19. package/apps/desktop/resources/locales/ru-RU/menu.json +5 -1
  20. package/apps/desktop/resources/locales/tr-TR/menu.json +5 -1
  21. package/apps/desktop/resources/locales/vi-VN/menu.json +5 -1
  22. package/apps/desktop/resources/locales/zh-CN/menu.json +5 -1
  23. package/apps/desktop/resources/locales/zh-TW/menu.json +5 -1
  24. package/apps/desktop/src/main/locales/default/menu.ts +5 -1
  25. package/apps/desktop/src/main/menus/impls/linux.ts +30 -0
  26. package/apps/desktop/src/main/menus/impls/macOS.test.ts +17 -0
  27. package/apps/desktop/src/main/menus/impls/macOS.ts +33 -0
  28. package/apps/desktop/src/main/menus/impls/windows.ts +30 -0
  29. package/changelog/v1.json +10 -0
  30. package/locales/ar/electron.json +24 -0
  31. package/locales/ar/models.json +48 -7
  32. package/locales/ar/plugin.json +9 -0
  33. package/locales/ar/providers.json +1 -0
  34. package/locales/bg-BG/electron.json +24 -0
  35. package/locales/bg-BG/models.json +35 -7
  36. package/locales/bg-BG/plugin.json +9 -0
  37. package/locales/bg-BG/providers.json +1 -0
  38. package/locales/de-DE/electron.json +24 -0
  39. package/locales/de-DE/models.json +26 -6
  40. package/locales/de-DE/plugin.json +9 -0
  41. package/locales/de-DE/providers.json +1 -0
  42. package/locales/en-US/electron.json +24 -0
  43. package/locales/en-US/models.json +10 -10
  44. package/locales/en-US/oauth.json +0 -1
  45. package/locales/en-US/providers.json +1 -0
  46. package/locales/en-US/subscription.json +2 -2
  47. package/locales/es-ES/electron.json +24 -0
  48. package/locales/es-ES/models.json +42 -7
  49. package/locales/es-ES/plugin.json +9 -0
  50. package/locales/es-ES/providers.json +1 -0
  51. package/locales/fa-IR/electron.json +24 -0
  52. package/locales/fa-IR/models.json +52 -10
  53. package/locales/fa-IR/plugin.json +9 -0
  54. package/locales/fa-IR/providers.json +1 -0
  55. package/locales/fr-FR/electron.json +24 -0
  56. package/locales/fr-FR/models.json +36 -7
  57. package/locales/fr-FR/plugin.json +9 -0
  58. package/locales/fr-FR/providers.json +1 -0
  59. package/locales/it-IT/electron.json +24 -0
  60. package/locales/it-IT/models.json +42 -7
  61. package/locales/it-IT/plugin.json +9 -0
  62. package/locales/it-IT/providers.json +1 -0
  63. package/locales/ja-JP/electron.json +24 -0
  64. package/locales/ja-JP/models.json +35 -6
  65. package/locales/ja-JP/plugin.json +9 -0
  66. package/locales/ja-JP/providers.json +1 -0
  67. package/locales/ko-KR/electron.json +24 -0
  68. package/locales/ko-KR/models.json +28 -7
  69. package/locales/ko-KR/plugin.json +9 -0
  70. package/locales/ko-KR/providers.json +1 -0
  71. package/locales/nl-NL/electron.json +24 -0
  72. package/locales/nl-NL/models.json +35 -6
  73. package/locales/nl-NL/plugin.json +9 -0
  74. package/locales/nl-NL/providers.json +1 -0
  75. package/locales/pl-PL/electron.json +24 -0
  76. package/locales/pl-PL/models.json +36 -7
  77. package/locales/pl-PL/plugin.json +9 -0
  78. package/locales/pl-PL/providers.json +1 -0
  79. package/locales/pt-BR/electron.json +24 -0
  80. package/locales/pt-BR/models.json +35 -6
  81. package/locales/pt-BR/plugin.json +9 -0
  82. package/locales/pt-BR/providers.json +1 -0
  83. package/locales/ru-RU/electron.json +24 -0
  84. package/locales/ru-RU/models.json +35 -7
  85. package/locales/ru-RU/plugin.json +9 -0
  86. package/locales/ru-RU/providers.json +1 -0
  87. package/locales/tr-TR/electron.json +24 -0
  88. package/locales/tr-TR/models.json +5 -7
  89. package/locales/tr-TR/plugin.json +9 -0
  90. package/locales/tr-TR/providers.json +1 -0
  91. package/locales/vi-VN/electron.json +24 -0
  92. package/locales/vi-VN/models.json +5 -5
  93. package/locales/vi-VN/plugin.json +9 -0
  94. package/locales/vi-VN/providers.json +1 -0
  95. package/locales/zh-CN/electron.json +24 -0
  96. package/locales/zh-CN/models.json +48 -6
  97. package/locales/zh-CN/oauth.json +0 -1
  98. package/locales/zh-CN/providers.json +1 -0
  99. package/locales/zh-CN/subscription.json +1 -1
  100. package/locales/zh-TW/electron.json +24 -0
  101. package/locales/zh-TW/models.json +10 -10
  102. package/locales/zh-TW/plugin.json +9 -0
  103. package/locales/zh-TW/providers.json +1 -0
  104. package/package.json +1 -1
  105. package/packages/electron-client-ipc/src/events/navigation.ts +12 -0
  106. package/src/components/PageTitle/index.tsx +11 -1
  107. package/src/features/ElectronTitlebar/NavigationBar/RecentlyViewed.tsx +137 -0
  108. package/src/features/ElectronTitlebar/NavigationBar/index.tsx +86 -0
  109. package/src/features/ElectronTitlebar/helpers/routeMetadata.ts +214 -0
  110. package/src/features/ElectronTitlebar/hooks/useNavigationHistory.ts +152 -0
  111. package/src/features/ElectronTitlebar/index.tsx +13 -5
  112. package/src/features/NavHeader/index.tsx +4 -2
  113. package/src/features/NavPanel/components/NavPanelDraggable.tsx +174 -0
  114. package/src/features/NavPanel/hooks/useNavPanel.ts +11 -35
  115. package/src/features/NavPanel/index.tsx +2 -126
  116. package/src/hooks/useTypeScriptHappyCallback.ts +7 -0
  117. package/src/locales/default/electron.ts +24 -0
  118. package/src/locales/default/subscription.ts +2 -3
  119. package/src/server/services/memory/userMemory/extract.ts +46 -6
  120. package/src/store/electron/actions/navigationHistory.ts +247 -0
  121. package/src/store/electron/initialState.ts +7 -1
  122. package/src/store/electron/store.ts +9 -2
  123. package/src/store/global/selectors/systemStatus.ts +4 -1
@@ -0,0 +1,152 @@
1
+ 'use client';
2
+
3
+ import { useWatchBroadcast } from '@lobechat/electron-client-ipc';
4
+ import { useCallback, useEffect, useRef } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { useLocation, useNavigate } from 'react-router-dom';
7
+
8
+ import { useElectronStore } from '@/store/electron';
9
+
10
+ import { getRouteMetadata } from '../helpers/routeMetadata';
11
+
12
+ /**
13
+ * Hook to manage navigation history in Electron desktop app
14
+ * Provides browser-like back/forward functionality
15
+ */
16
+ export const useNavigationHistory = () => {
17
+ const { t } = useTranslation('electron');
18
+ const navigate = useNavigate();
19
+ const location = useLocation();
20
+
21
+ // Get store state and actions
22
+ const isNavigatingHistory = useElectronStore((s) => s.isNavigatingHistory);
23
+ const historyCurrentIndex = useElectronStore((s) => s.historyCurrentIndex);
24
+ const historyEntries = useElectronStore((s) => s.historyEntries);
25
+ const currentPageTitle = useElectronStore((s) => s.currentPageTitle);
26
+ const pushHistory = useElectronStore((s) => s.pushHistory);
27
+ const replaceHistory = useElectronStore((s) => s.replaceHistory);
28
+ const setIsNavigatingHistory = useElectronStore((s) => s.setIsNavigatingHistory);
29
+ const storeGoBack = useElectronStore((s) => s.goBack);
30
+ const storeGoForward = useElectronStore((s) => s.goForward);
31
+ const canGoBackFn = useElectronStore((s) => s.canGoBack);
32
+ const canGoForwardFn = useElectronStore((s) => s.canGoForward);
33
+ const getCurrentEntry = useElectronStore((s) => s.getCurrentEntry);
34
+
35
+ // Track previous location to avoid duplicate entries
36
+ const prevLocationRef = useRef<string | null>(null);
37
+
38
+ // Calculate can go back/forward
39
+ const canGoBack = historyCurrentIndex > 0;
40
+ const canGoForward = historyCurrentIndex < historyEntries.length - 1;
41
+
42
+ /**
43
+ * Go back in history
44
+ */
45
+ const goBack = useCallback(() => {
46
+ if (!canGoBackFn()) return;
47
+
48
+ const targetEntry = storeGoBack();
49
+ if (targetEntry) {
50
+ navigate(targetEntry.url);
51
+ }
52
+ }, [canGoBackFn, storeGoBack, navigate]);
53
+
54
+ /**
55
+ * Go forward in history
56
+ */
57
+ const goForward = useCallback(() => {
58
+ if (!canGoForwardFn()) return;
59
+
60
+ const targetEntry = storeGoForward();
61
+ if (targetEntry) {
62
+ navigate(targetEntry.url);
63
+ }
64
+ }, [canGoForwardFn, storeGoForward, navigate]);
65
+
66
+ // Listen to route changes and push history
67
+ useEffect(() => {
68
+ const currentUrl = location.pathname + location.search;
69
+
70
+ // Skip if this is a back/forward navigation
71
+ if (isNavigatingHistory) {
72
+ setIsNavigatingHistory(false);
73
+ prevLocationRef.current = currentUrl;
74
+ return;
75
+ }
76
+
77
+ // Skip if same as previous location
78
+ if (prevLocationRef.current === currentUrl) {
79
+ return;
80
+ }
81
+
82
+ // Skip if same as current entry
83
+ const currentEntry = getCurrentEntry();
84
+ if (currentEntry?.url === currentUrl) {
85
+ prevLocationRef.current = currentUrl;
86
+ return;
87
+ }
88
+
89
+ // Get metadata for this route
90
+ const metadata = getRouteMetadata(location.pathname);
91
+ const presetTitle = t(metadata.titleKey as any) as string;
92
+
93
+ // Push history with preset title (will be updated by PageTitle if useDynamicTitle)
94
+ pushHistory({
95
+ metadata: {
96
+ timestamp: Date.now(),
97
+ },
98
+ title: presetTitle,
99
+ url: currentUrl,
100
+ });
101
+
102
+ prevLocationRef.current = currentUrl;
103
+ }, [
104
+ location.pathname,
105
+ location.search,
106
+ isNavigatingHistory,
107
+ setIsNavigatingHistory,
108
+ getCurrentEntry,
109
+ pushHistory,
110
+ t,
111
+ ]);
112
+
113
+ // Update current history entry title when PageTitle component updates
114
+ useEffect(() => {
115
+ if (!currentPageTitle) return;
116
+
117
+ const currentEntry = getCurrentEntry();
118
+ if (!currentEntry) return;
119
+
120
+ // Check if current route supports dynamic title
121
+ const metadata = getRouteMetadata(location.pathname);
122
+ if (!metadata.useDynamicTitle) return;
123
+
124
+ // Skip if title is already the same
125
+ if (currentEntry.title === currentPageTitle) return;
126
+
127
+ // Update the current history entry with the dynamic title
128
+ replaceHistory({
129
+ ...currentEntry,
130
+ title: currentPageTitle,
131
+ });
132
+ }, [currentPageTitle, getCurrentEntry, replaceHistory, location.pathname]);
133
+
134
+ // Listen to broadcast events from main process (Electron menu)
135
+ useWatchBroadcast('historyGoBack', () => {
136
+ goBack();
137
+ });
138
+
139
+ useWatchBroadcast('historyGoForward', () => {
140
+ goForward();
141
+ });
142
+
143
+ return {
144
+ canGoBack,
145
+ canGoForward,
146
+ currentEntry: getCurrentEntry(),
147
+ goBack,
148
+ goForward,
149
+ historyEntries,
150
+ historyIndex: historyCurrentIndex,
151
+ };
152
+ };
@@ -1,12 +1,13 @@
1
1
  import { Flexbox } from '@lobehub/ui';
2
2
  import { Divider } from 'antd';
3
- import { memo } from 'react';
3
+ import { memo, useMemo } from 'react';
4
4
 
5
5
  import { useElectronStore } from '@/store/electron';
6
6
  import { electronStylish } from '@/styles/electron';
7
7
  import { isMacOS } from '@/utils/platform';
8
8
 
9
9
  import Connection from './Connection';
10
+ import NavigationBar from './NavigationBar';
10
11
  import { UpdateModal } from './UpdateModal';
11
12
  import { UpdateNotification } from './UpdateNotification';
12
13
  import WinControl from './WinControl';
@@ -25,6 +26,15 @@ const TitleBar = memo(() => {
25
26
  useWatchThemeUpdate();
26
27
 
27
28
  const showWinControl = isAppStateInit && !isMac;
29
+
30
+ const padding = useMemo(() => {
31
+ if (showWinControl) {
32
+ return '0 12px 0 0';
33
+ }
34
+
35
+ return '0 12px';
36
+ }, [showWinControl, isMac]);
37
+
28
38
  return (
29
39
  <Flexbox
30
40
  align={'center'}
@@ -32,12 +42,10 @@ const TitleBar = memo(() => {
32
42
  height={TITLE_BAR_HEIGHT}
33
43
  horizontal
34
44
  justify={'space-between'}
35
- paddingInline={showWinControl ? '12px 0' : 12}
36
- style={{ minHeight: TITLE_BAR_HEIGHT }}
45
+ style={{ minHeight: TITLE_BAR_HEIGHT, padding }}
37
46
  width={'100%'}
38
47
  >
39
- <div />
40
- <div>{/* TODO */}</div>
48
+ <NavigationBar />
41
49
 
42
50
  <Flexbox align={'center'} gap={4} horizontal>
43
51
  <Flexbox className={electronStylish.nodrag} gap={8} horizontal>
@@ -2,7 +2,8 @@ import { Flexbox, type FlexboxProps, TooltipGroup } from '@lobehub/ui';
2
2
  import { type CSSProperties, type ReactNode, memo } from 'react';
3
3
 
4
4
  import ToggleLeftPanelButton from '@/features/NavPanel/ToggleLeftPanelButton';
5
- import { useNavPanel } from '@/features/NavPanel/hooks/useNavPanel';
5
+ import { useGlobalStore } from '@/store/global';
6
+ import { systemStatusSelectors } from '@/store/global/selectors';
6
7
 
7
8
  export interface NavHeaderProps extends Omit<FlexboxProps, 'children'> {
8
9
  children?: ReactNode;
@@ -18,7 +19,8 @@ export interface NavHeaderProps extends Omit<FlexboxProps, 'children'> {
18
19
 
19
20
  const NavHeader = memo<NavHeaderProps>(
20
21
  ({ showTogglePanelButton = true, style, children, left, right, styles, ...rest }) => {
21
- const { expand } = useNavPanel();
22
+ const expand = useGlobalStore(systemStatusSelectors.showLeftPanel);
23
+
22
24
  const noContent = !left && !right;
23
25
 
24
26
  if (noContent && expand) return;
@@ -0,0 +1,174 @@
1
+ 'use client';
2
+
3
+ import { DraggablePanel } from '@lobehub/ui';
4
+ import { createStaticStyles, cssVar } from 'antd-style';
5
+ import { AnimatePresence, motion } from 'motion/react';
6
+ import { type ReactNode, memo, useMemo, useRef } from 'react';
7
+
8
+ import { USER_DROPDOWN_ICON_ID } from '@/app/[variants]/(main)/home/_layout/Header/components/User';
9
+ import { isDesktop } from '@/const/version';
10
+ import { TOGGLE_BUTTON_ID } from '@/features/NavPanel/ToggleLeftPanelButton';
11
+ import { useGlobalStore } from '@/store/global';
12
+ import { systemStatusSelectors } from '@/store/global/selectors';
13
+ import { isMacOS } from '@/utils/platform';
14
+
15
+ import { useNavPanelSizeChangeHandler } from '../hooks/useNavPanel';
16
+ import { BACK_BUTTON_ID } from './BackButton';
17
+
18
+ const motionVariants = {
19
+ animate: { opacity: 1, x: 0 },
20
+ exit: {
21
+ opacity: 0,
22
+ x: '-20%',
23
+ },
24
+ initial: {
25
+ opacity: 0,
26
+ x: 0,
27
+ },
28
+ transition: {
29
+ duration: 0.4,
30
+ ease: [0.4, 0, 0.2, 1],
31
+ },
32
+ } as const;
33
+
34
+ const draggableStyles = createStaticStyles(({ css, cssVar }) => ({
35
+ content: css`
36
+ position: relative;
37
+
38
+ overflow: hidden;
39
+ display: flex;
40
+
41
+ height: 100%;
42
+ min-height: 100%;
43
+ max-height: 100%;
44
+ `,
45
+ inner: css`
46
+ position: relative;
47
+ inset: 0;
48
+
49
+ overflow: hidden;
50
+ flex: 1;
51
+ flex-direction: column;
52
+
53
+ min-width: 240px;
54
+ `,
55
+ panel: css`
56
+ user-select: none;
57
+ height: 100%;
58
+ color: ${cssVar.colorTextSecondary};
59
+ background: ${isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout};
60
+
61
+ * {
62
+ user-select: none;
63
+ }
64
+
65
+ #${TOGGLE_BUTTON_ID} {
66
+ width: 0 !important;
67
+ opacity: 0;
68
+ transition:
69
+ opacity,
70
+ width 0.2s ${cssVar.motionEaseOut};
71
+ }
72
+
73
+ #${USER_DROPDOWN_ICON_ID} {
74
+ width: 0 !important;
75
+ opacity: 0;
76
+ transition:
77
+ opacity,
78
+ width 0.2s ${cssVar.motionEaseOut};
79
+ }
80
+
81
+ #${BACK_BUTTON_ID} {
82
+ width: 0 !important;
83
+ opacity: 0;
84
+ transition: all 0.2s ${cssVar.motionEaseOut};
85
+ }
86
+
87
+ &:hover {
88
+ #${TOGGLE_BUTTON_ID} {
89
+ width: 32px !important;
90
+ opacity: 1;
91
+ }
92
+
93
+ #${USER_DROPDOWN_ICON_ID} {
94
+ width: 14px !important;
95
+ opacity: 1;
96
+ }
97
+
98
+ &:hover {
99
+ #${BACK_BUTTON_ID} {
100
+ width: 24px !important;
101
+ opacity: 1;
102
+ }
103
+ }
104
+ }
105
+ `,
106
+ }));
107
+
108
+ interface NavPanelDraggableProps {
109
+ activeContent: {
110
+ key: string;
111
+ node: ReactNode;
112
+ };
113
+ }
114
+
115
+ const classNames = {
116
+ content: draggableStyles.content,
117
+ };
118
+
119
+ export const NavPanelDraggable = memo<NavPanelDraggableProps>(({ activeContent }) => {
120
+ const [expand, togglePanel] = useGlobalStore((s) => [
121
+ systemStatusSelectors.showLeftPanel(s),
122
+ s.toggleLeftPanel,
123
+ ]);
124
+ const handleSizeChange = useNavPanelSizeChangeHandler();
125
+
126
+ const defaultWidthRef = useRef(0);
127
+ if (defaultWidthRef.current === 0) {
128
+ defaultWidthRef.current = systemStatusSelectors.leftPanelWidth(useGlobalStore.getState());
129
+ }
130
+
131
+ const defaultSize = useMemo(
132
+ () => ({
133
+ height: '100%',
134
+ width: defaultWidthRef.current,
135
+ }),
136
+ [defaultWidthRef.current],
137
+ );
138
+ const styles = useMemo(
139
+ () => ({
140
+ background: isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout,
141
+ zIndex: 11,
142
+ }),
143
+ [isDesktop, isMacOS()],
144
+ );
145
+ return (
146
+ <DraggablePanel
147
+ className={draggableStyles.panel}
148
+ classNames={classNames}
149
+ defaultSize={defaultSize}
150
+ expand={expand}
151
+ expandable={false}
152
+ maxWidth={400}
153
+ minWidth={240}
154
+ onExpandChange={togglePanel}
155
+ onSizeDragging={handleSizeChange}
156
+ placement="left"
157
+ showBorder={false}
158
+ style={styles}
159
+ >
160
+ <AnimatePresence initial={false} mode="popLayout">
161
+ <motion.div
162
+ animate={motionVariants.animate}
163
+ className={draggableStyles.inner}
164
+ exit={motionVariants.exit}
165
+ initial={motionVariants.initial}
166
+ key={activeContent.key}
167
+ transition={motionVariants.transition}
168
+ >
169
+ {activeContent.node}
170
+ </motion.div>
171
+ </AnimatePresence>
172
+ </DraggablePanel>
173
+ );
174
+ });
@@ -2,49 +2,25 @@
2
2
 
3
3
  import { type DraggablePanelProps } from '@lobehub/ui';
4
4
  import isEqual from 'fast-deep-equal';
5
- import { useCallback, useState } from 'react';
6
5
 
6
+ import { useTypeScriptHappyCallback } from '@/hooks/useTypeScriptHappyCallback';
7
7
  import { useGlobalStore } from '@/store/global';
8
8
  import { systemStatusSelectors } from '@/store/global/selectors';
9
9
 
10
- export const useNavPanel = () => {
11
- const [leftPanelWidth, sessionExpandable, togglePanel, updatePreference] = useGlobalStore((s) => [
12
- systemStatusSelectors.leftPanelWidth(s),
13
- systemStatusSelectors.showLeftPanel(s),
14
- s.toggleLeftPanel,
15
- s.updateSystemStatus,
16
- ]);
17
-
18
- const [tmpWidth, setWidth] = useState(leftPanelWidth);
19
-
20
- if (tmpWidth !== leftPanelWidth) setWidth(leftPanelWidth);
21
-
22
- const handleSizeChange: DraggablePanelProps['onSizeChange'] = useCallback(
23
- (_: any, size: any) => {
24
- const width = size?.width;
10
+ export const useNavPanelSizeChangeHandler = (onChange?: (width: number) => void) => {
11
+ const handleSizeChange: DraggablePanelProps['onSizeChange'] = useTypeScriptHappyCallback(
12
+ (_, size) => {
13
+ const width = typeof size?.width === 'string' ? Number.parseInt(size.width) : size?.width;
25
14
  if (!width || width < 64) return;
15
+ const s = useGlobalStore.getState();
16
+ const leftPanelWidth = systemStatusSelectors.leftPanelWidth(s);
17
+ const updatePreference = s.updateSystemStatus;
26
18
  if (isEqual(width, leftPanelWidth)) return;
27
- setWidth(width);
19
+ onChange?.(width);
28
20
  updatePreference({ leftPanelWidth: width });
29
21
  },
30
- [sessionExpandable, leftPanelWidth, updatePreference],
22
+ [],
31
23
  );
32
24
 
33
- const openPanel = useCallback(() => {
34
- togglePanel(true);
35
- }, [togglePanel]);
36
-
37
- const closePanel = useCallback(() => {
38
- togglePanel(false);
39
- }, [togglePanel]);
40
-
41
- return {
42
- closePanel,
43
- defaultWidth: tmpWidth,
44
- expand: sessionExpandable,
45
- handleSizeChange,
46
- openPanel,
47
- togglePanel,
48
- width: leftPanelWidth,
49
- };
25
+ return handleSizeChange;
50
26
  };
@@ -1,8 +1,5 @@
1
1
  'use client';
2
2
 
3
- import { DraggablePanel } from '@lobehub/ui';
4
- import { createStaticStyles, cssVar } from 'antd-style';
5
- import { AnimatePresence, motion } from 'motion/react';
6
3
  import {
7
4
  type PropsWithChildren,
8
5
  type ReactNode,
@@ -11,14 +8,8 @@ import {
11
8
  useSyncExternalStore,
12
9
  } from 'react';
13
10
 
14
- import { USER_DROPDOWN_ICON_ID } from '@/app/[variants]/(main)/home/_layout/Header/components/User';
15
- import { isDesktop } from '@/const/version';
16
- import { TOGGLE_BUTTON_ID } from '@/features/NavPanel/ToggleLeftPanelButton';
17
- import { isMacOS } from '@/utils/platform';
18
-
19
11
  import Sidebar from '../../app/[variants]/(main)/home/_layout/Sidebar';
20
- import { BACK_BUTTON_ID } from './components/BackButton';
21
- import { useNavPanel } from './hooks/useNavPanel';
12
+ import { NavPanelDraggable } from './components/NavPanelDraggable';
22
13
 
23
14
  export const NAV_PANEL_RIGHT_DRAWER_ID = 'nav-panel-drawer';
24
15
 
@@ -41,82 +32,7 @@ const setNavPanelSnapshot = (snapshot: NavPanelSnapshot) => {
41
32
  listeners.forEach((listener) => listener());
42
33
  };
43
34
 
44
- export const styles = createStaticStyles(({ css, cssVar }) => ({
45
- content: css`
46
- position: relative;
47
-
48
- overflow: hidden;
49
- display: flex;
50
-
51
- height: 100%;
52
- min-height: 100%;
53
- max-height: 100%;
54
- `,
55
- inner: css`
56
- position: relative;
57
- inset: 0;
58
-
59
- overflow: hidden;
60
- flex: 1;
61
- flex-direction: column;
62
-
63
- min-width: 240px;
64
- `,
65
- panel: css`
66
- user-select: none;
67
- height: 100%;
68
- color: ${cssVar.colorTextSecondary};
69
- background: ${isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout};
70
-
71
- * {
72
- user-select: none;
73
- }
74
-
75
- #${TOGGLE_BUTTON_ID} {
76
- width: 0 !important;
77
- opacity: 0;
78
- transition:
79
- opacity,
80
- width 0.2s ${cssVar.motionEaseOut};
81
- }
82
-
83
- #${USER_DROPDOWN_ICON_ID} {
84
- width: 0 !important;
85
- opacity: 0;
86
- transition:
87
- opacity,
88
- width 0.2s ${cssVar.motionEaseOut};
89
- }
90
-
91
- #${BACK_BUTTON_ID} {
92
- width: 0 !important;
93
- opacity: 0;
94
- transition: all 0.2s ${cssVar.motionEaseOut};
95
- }
96
-
97
- &:hover {
98
- #${TOGGLE_BUTTON_ID} {
99
- width: 32px !important;
100
- opacity: 1;
101
- }
102
-
103
- #${USER_DROPDOWN_ICON_ID} {
104
- width: 14px !important;
105
- opacity: 1;
106
- }
107
-
108
- &:hover {
109
- #${BACK_BUTTON_ID} {
110
- width: 24px !important;
111
- opacity: 1;
112
- }
113
- }
114
- }
115
- `,
116
- }));
117
-
118
35
  const NavPanel = memo(() => {
119
- const { expand, handleSizeChange, width, togglePanel } = useNavPanel();
120
36
  const panelContent = useSyncExternalStore(
121
37
  subscribeNavPanel,
122
38
  getNavPanelSnapshot,
@@ -128,47 +44,7 @@ const NavPanel = memo(() => {
128
44
 
129
45
  return (
130
46
  <>
131
- <DraggablePanel
132
- className={styles.panel}
133
- classNames={{
134
- content: styles.content,
135
- }}
136
- defaultSize={{ height: '100%', width }}
137
- expand={expand}
138
- expandable={false}
139
- maxWidth={400}
140
- minWidth={240}
141
- onExpandChange={(expand) => togglePanel(expand)}
142
- onSizeChange={handleSizeChange}
143
- placement="left"
144
- showBorder={false}
145
- style={{
146
- background: isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout,
147
- zIndex: 11,
148
- }}
149
- >
150
- <AnimatePresence initial={false} mode="popLayout">
151
- <motion.div
152
- animate={{ opacity: 1, x: 0 }}
153
- className={styles.inner}
154
- exit={{
155
- opacity: 0,
156
- x: '-20%',
157
- }}
158
- initial={{
159
- opacity: 0,
160
- x: 0,
161
- }}
162
- key={activeContent.key}
163
- transition={{
164
- duration: 0.4,
165
- ease: [0.4, 0, 0.2, 1],
166
- }}
167
- >
168
- {activeContent.node}
169
- </motion.div>
170
- </AnimatePresence>
171
- </DraggablePanel>
47
+ <NavPanelDraggable activeContent={activeContent} />
172
48
  <div
173
49
  id={NAV_PANEL_RIGHT_DRAWER_ID}
174
50
  style={{
@@ -0,0 +1,7 @@
1
+ import type { DependencyList } from 'react';
2
+ import { useCallback } from 'react';
3
+
4
+ export const useTypeScriptHappyCallback: <Args extends unknown[], R>(
5
+ fn: (...args: Args) => R,
6
+ deps: DependencyList,
7
+ ) => (...args: Args) => R = useCallback;
@@ -1,4 +1,28 @@
1
1
  export default {
2
+ 'navigation.chat': 'Chat',
3
+ 'navigation.discover': 'Discover',
4
+ 'navigation.discoverAssistants': 'Discover Assistants',
5
+ 'navigation.discoverMcp': 'Discover MCP',
6
+ 'navigation.discoverModels': 'Discover Models',
7
+ 'navigation.discoverProviders': 'Discover Providers',
8
+ 'navigation.group': 'Group',
9
+ 'navigation.groupChat': 'Group Chat',
10
+ 'navigation.home': 'Home',
11
+ 'navigation.image': 'Image',
12
+ 'navigation.knowledgeBase': 'Knowledge Base',
13
+ 'navigation.lobehub': 'LobeHub',
14
+ 'navigation.memory': 'Memory',
15
+ 'navigation.memoryContexts': 'Memory - Contexts',
16
+ 'navigation.memoryExperiences': 'Memory - Experiences',
17
+ 'navigation.memoryIdentities': 'Memory - Identities',
18
+ 'navigation.memoryPreferences': 'Memory - Preferences',
19
+ 'navigation.onboarding': 'Onboarding',
20
+ 'navigation.page': 'Page',
21
+ 'navigation.pages': 'Pages',
22
+ 'navigation.provider': 'Provider',
23
+ 'navigation.recentView': 'Recent pages',
24
+ 'navigation.resources': 'Resources',
25
+ 'navigation.settings': 'Settings',
2
26
  'notification.finishChatGeneration': 'AI message generation completed',
3
27
  'proxy.auth': 'Authentication Required',
4
28
  'proxy.authDesc': 'If the proxy server requires a username and password',
@@ -132,10 +132,9 @@ export default {
132
132
  'Your {{plan}} computing credits have been exhausted. Upgrade now to get more credits.',
133
133
  'limitation.limited.descUltimate':
134
134
  'Your {{plan}} computing credits have been exhausted. Please top up credits to continue.',
135
- 'limitation.limited.referralTip':
136
- 'Invite new users to register, and you and your friend will each receive {{reward}}M credits',
135
+ 'limitation.limited.referralTip': 'Invite friends, both get {{reward}}M',
137
136
  'limitation.limited.title': 'Computing Credits Exhausted',
138
- 'limitation.limited.topup': 'Top Up Credits',
137
+ 'limitation.limited.topup': 'Top-Up Credits',
139
138
  'limitation.limited.upgrade': 'Upgrade to Higher Plan',
140
139
  'limitation.providers.lock.addNew': 'Subscribe now to create custom AI providers',
141
140
  'limitation.providers.lock.enableProvider': 'Subscribe now to enable this AI provider',