@lobehub/lobehub 2.0.0-next.319 → 2.0.0-next.320

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.320](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.319...v2.0.0-next.320)
6
+
7
+ <sup>Released on **2026-01-20**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **ShareModal**: Wrap ShareMessageModal with Provider in context menu.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **ShareModal**: Wrap ShareMessageModal with Provider in context menu, closes [#11434](https://github.com/lobehub/lobe-chat/issues/11434) [#11382](https://github.com/lobehub/lobe-chat/issues/11382) ([0d30e5f](https://github.com/lobehub/lobe-chat/commit/0d30e5f))
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.319](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.318...v2.0.0-next.319)
6
31
 
7
32
  <sup>Released on **2026-01-20**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-20",
5
+ "version": "2.0.0-next.320"
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.319",
3
+ "version": "2.0.0-next.320",
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",
@@ -1,9 +1,9 @@
1
1
  import { Flexbox } from '@lobehub/ui';
2
- import { BrainCircuit, LibraryBigIcon, Settings } from 'lucide-react';
3
2
  import { memo, useMemo } from 'react';
4
3
  import { useTranslation } from 'react-i18next';
5
4
  import { Link, useNavigate } from 'react-router-dom';
6
5
 
6
+ import { getRouteById } from '@/config/routes';
7
7
  import NavItem from '@/features/NavPanel/components/NavItem';
8
8
  import { useActiveTabKey } from '@/hooks/useActiveTabKey';
9
9
  import { SidebarTabKey } from '@/store/global/initialState';
@@ -25,19 +25,19 @@ const BottomMenu = memo(() => {
25
25
  () =>
26
26
  [
27
27
  {
28
- icon: Settings,
28
+ icon: getRouteById('settings')!.icon,
29
29
  key: SidebarTabKey.Setting,
30
30
  title: t('tab.setting'),
31
31
  url: '/settings',
32
32
  },
33
33
  {
34
- icon: LibraryBigIcon,
34
+ icon: getRouteById('resource')!.icon,
35
35
  key: SidebarTabKey.Resource,
36
36
  title: t('tab.resource'),
37
37
  url: '/resource',
38
38
  },
39
39
  {
40
- icon: BrainCircuit,
40
+ icon: getRouteById('memory')!.icon,
41
41
  key: SidebarTabKey.Memory,
42
42
  title: t('tab.memory'),
43
43
  url: '/memory',
@@ -1,11 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import { Flexbox } from '@lobehub/ui';
4
- import { FilePenIcon, HomeIcon, ImageIcon, SearchIcon, ShapesIcon } from 'lucide-react';
4
+ import { HomeIcon, SearchIcon } from 'lucide-react';
5
5
  import { memo, useMemo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Link, useNavigate } from 'react-router-dom';
8
8
 
9
+ import { getRouteById } from '@/config/routes';
9
10
  import { useActiveTabKey } from '@/hooks/useActiveTabKey';
10
11
  import { useGlobalStore } from '@/store/global';
11
12
  import { SidebarTabKey } from '@/store/global/initialState';
@@ -48,21 +49,21 @@ const Nav = memo(() => {
48
49
  url: '/',
49
50
  },
50
51
  {
51
- icon: FilePenIcon,
52
+ icon: getRouteById('page')!.icon,
52
53
  key: SidebarTabKey.Pages,
53
54
  title: t('tab.pages'),
54
55
  url: '/page',
55
56
  },
56
57
  {
57
58
  hidden: !showAiImage,
58
- icon: ImageIcon,
59
+ icon: getRouteById('image')!.icon,
59
60
  key: SidebarTabKey.Image,
60
61
  title: t('tab.aiImage'),
61
62
  url: '/image',
62
63
  },
63
64
  {
64
65
  hidden: !showMarket,
65
- icon: ShapesIcon,
66
+ icon: getRouteById('community')!.icon,
66
67
  key: SidebarTabKey.Community,
67
68
  title: t('tab.community'),
68
69
  url: '/community',
@@ -0,0 +1,104 @@
1
+ import {
2
+ BrainCircuit,
3
+ FilePenIcon,
4
+ Image,
5
+ LibraryBigIcon,
6
+ type LucideIcon,
7
+ Settings,
8
+ ShapesIcon,
9
+ } from 'lucide-react';
10
+
11
+ export interface NavigationRoute {
12
+ /** CMDK i18n key in common namespace */
13
+ cmdkKey: string;
14
+ /** Electron i18n key in electron namespace */
15
+ electronKey: string;
16
+ /** Route icon component */
17
+ icon: LucideIcon;
18
+ /** Unique route identifier */
19
+ id: string;
20
+ /** Keywords for CMDK search */
21
+ keywords?: string[];
22
+ /** Route path */
23
+ path: string;
24
+ /** Path prefix for checking current location */
25
+ pathPrefix: string;
26
+ /** Whether route supports dynamic titles (for specific items) */
27
+ useDynamicTitle?: boolean;
28
+ }
29
+
30
+ /**
31
+ * Shared navigation route configuration
32
+ * Used by both Electron navigation and CommandMenu (CMDK)
33
+ */
34
+ export const NAVIGATION_ROUTES: NavigationRoute[] = [
35
+ {
36
+ cmdkKey: 'cmdk.community',
37
+ electronKey: 'navigation.discover',
38
+ icon: ShapesIcon,
39
+ id: 'community',
40
+ keywords: ['discover', 'market', 'assistant', 'model', 'provider', 'mcp'],
41
+ path: '/community',
42
+ pathPrefix: '/community',
43
+ },
44
+ {
45
+ cmdkKey: 'cmdk.painting',
46
+ electronKey: 'navigation.image',
47
+ icon: Image,
48
+ id: 'image',
49
+ keywords: ['painting', 'art', 'generate', 'draw'],
50
+ path: '/image',
51
+ pathPrefix: '/image',
52
+ },
53
+ {
54
+ cmdkKey: 'cmdk.resource',
55
+ electronKey: 'navigation.resources',
56
+ icon: LibraryBigIcon,
57
+ id: 'resource',
58
+ keywords: ['knowledge', 'files', 'library', 'documents'],
59
+ path: '/resource',
60
+ pathPrefix: '/resource',
61
+ },
62
+ {
63
+ cmdkKey: 'cmdk.pages',
64
+ electronKey: 'navigation.pages',
65
+ icon: FilePenIcon,
66
+ id: 'page',
67
+ keywords: ['documents', 'write', 'notes'],
68
+ path: '/page',
69
+ pathPrefix: '/page',
70
+ useDynamicTitle: true,
71
+ },
72
+ {
73
+ cmdkKey: 'cmdk.memory',
74
+ electronKey: 'navigation.memory',
75
+ icon: BrainCircuit,
76
+ id: 'memory',
77
+ keywords: ['identities', 'contexts', 'preferences', 'experiences'],
78
+ path: '/memory',
79
+ pathPrefix: '/memory',
80
+ },
81
+ {
82
+ cmdkKey: 'cmdk.settings',
83
+ electronKey: 'navigation.settings',
84
+ icon: Settings,
85
+ id: 'settings',
86
+ keywords: ['settings', 'preferences', 'configuration', 'options'],
87
+ path: '/settings',
88
+ pathPrefix: '/settings',
89
+ },
90
+ ];
91
+
92
+ /**
93
+ * Get route configuration by id
94
+ */
95
+ export const getRouteById = (id: string): NavigationRoute | undefined =>
96
+ NAVIGATION_ROUTES.find((r) => r.id === id);
97
+
98
+ /**
99
+ * Get navigable routes for CMDK (excludes settings which has separate handling)
100
+ */
101
+ export const getNavigableRoutes = (): NavigationRoute[] =>
102
+ NAVIGATION_ROUTES.filter((r) =>
103
+ ['community', 'image', 'resource', 'page', 'memory'].includes(r.id),
104
+ );
@@ -3,21 +3,18 @@ import { DiscordIcon } from '@lobehub/ui/icons';
3
3
  import { Command } from 'cmdk';
4
4
  import {
5
5
  Bot,
6
- BrainCircuit,
7
6
  FilePen,
8
7
  Github,
9
- Image,
10
8
  LibraryBig,
11
9
  MailIcon,
12
10
  MessageSquarePlusIcon,
13
11
  Monitor,
14
- Settings,
15
- Shapes,
16
12
  Star,
17
13
  } from 'lucide-react';
18
14
  import { memo } from 'react';
19
15
  import { useTranslation } from 'react-i18next';
20
16
 
17
+ import { getNavigableRoutes, getRouteById } from '@/config/routes';
21
18
  import { FEEDBACK } from '@/const/url';
22
19
  import { useFeedbackModal } from '@/hooks/useFeedbackModal';
23
20
 
@@ -88,16 +85,20 @@ const MainMenu = memo(() => {
88
85
  {t('cmdk.newLibrary')}
89
86
  </CommandItem>
90
87
 
91
- {menuContext !== 'settings' && (
92
- <CommandItem
93
- icon={<Settings />}
94
- keywords={['settings', 'preferences', 'configuration', 'options']}
95
- onSelect={() => handleNavigate('/settings')}
96
- value="settings"
97
- >
98
- {t('cmdk.settings')}
99
- </CommandItem>
100
- )}
88
+ {menuContext !== 'settings' && (() => {
89
+ const settingsRoute = getRouteById('settings');
90
+ const SettingsIcon = settingsRoute?.icon;
91
+ return (
92
+ <CommandItem
93
+ icon={SettingsIcon && <SettingsIcon />}
94
+ keywords={settingsRoute?.keywords}
95
+ onSelect={() => handleNavigate(settingsRoute?.path || '/settings')}
96
+ value="settings"
97
+ >
98
+ {t('cmdk.settings')}
99
+ </CommandItem>
100
+ );
101
+ })()}
101
102
 
102
103
  <CommandItem
103
104
  icon={<Monitor />}
@@ -109,47 +110,22 @@ const MainMenu = memo(() => {
109
110
  </Command.Group>
110
111
 
111
112
  <Command.Group heading={t('cmdk.navigate')}>
112
- {!pathname?.startsWith('/community') && (
113
- <CommandItem
114
- icon={<Shapes />}
115
- onSelect={() => handleNavigate('/community')}
116
- value="community"
117
- >
118
- {t('cmdk.community')}
119
- </CommandItem>
120
- )}
121
- {!pathname?.startsWith('/image') && (
122
- <CommandItem icon={<Image />} onSelect={() => handleNavigate('/image')} value="painting">
123
- {t('cmdk.painting')}
124
- </CommandItem>
125
- )}
126
- {!pathname?.startsWith('/knowledge') && (
127
- <CommandItem
128
- icon={<LibraryBig />}
129
- onSelect={() => handleNavigate('/resource')}
130
- value="resource"
131
- >
132
- {t('cmdk.resource')}
133
- </CommandItem>
134
- )}
135
- {!pathname?.startsWith('/page') && (
136
- <CommandItem
137
- icon={<FilePen />}
138
- onSelect={() => handleNavigate('/page')}
139
- value="page documents write"
140
- >
141
- {t('cmdk.pages')}
142
- </CommandItem>
143
- )}
144
- {!pathname?.startsWith('/memory') && (
145
- <CommandItem
146
- icon={<BrainCircuit />}
147
- onSelect={() => handleNavigate('/memory')}
148
- value="memory"
149
- >
150
- {t('cmdk.memory')}
151
- </CommandItem>
152
- )}
113
+ {getNavigableRoutes().map((route) => {
114
+ const RouteIcon = route.icon;
115
+ return (
116
+ !pathname?.startsWith(route.pathPrefix) && (
117
+ <CommandItem
118
+ icon={<RouteIcon />}
119
+ key={route.id}
120
+ keywords={route.keywords}
121
+ onSelect={() => handleNavigate(route.path)}
122
+ value={route.id}
123
+ >
124
+ {t(route.cmdkKey as any)}
125
+ </CommandItem>
126
+ )
127
+ );
128
+ })}
153
129
  </Command.Group>
154
130
 
155
131
  <Command.Group heading={t('cmdk.about')}>
@@ -18,8 +18,10 @@ import { sessionSelectors } from '@/store/session/selectors';
18
18
  import { useUserStore } from '@/store/user';
19
19
  import { userGeneralSettingsSelectors } from '@/store/user/selectors';
20
20
 
21
- import ShareMessageModal from '../components/ShareMessageModal';
21
+ import ShareMessageModal, { type ShareModalProps } from '../components/ShareMessageModal';
22
22
  import {
23
+ Provider,
24
+ createStore,
23
25
  dataSelectors,
24
26
  messageStateSelectors,
25
27
  useConversationStore,
@@ -187,13 +189,26 @@ export const useChatItemContextMenu = ({
187
189
  if (!item || item.role !== 'assistant') return;
188
190
 
189
191
  createRawModal(
190
- ShareMessageModal,
192
+ (props: ShareModalProps) => (
193
+ <Provider
194
+ createStore={() => {
195
+ const state = storeApi.getState();
196
+ return createStore({
197
+ context: state.context,
198
+ hooks: state.hooks,
199
+ skipFetch: state.skipFetch,
200
+ });
201
+ }}
202
+ >
203
+ <ShareMessageModal {...props} />
204
+ </Provider>
205
+ ),
191
206
  {
192
207
  message: item,
193
208
  },
194
209
  { onCloseKey: 'onCancel', openKey: 'open' },
195
210
  );
196
- }, [getMessage]);
211
+ }, [getMessage, storeApi]);
197
212
 
198
213
  const handleAction = useCallback(
199
214
  async (action: ContextMenuEvent) => {
@@ -3,20 +3,17 @@
3
3
  * Provides title and icon information based on route path
4
4
  */
5
5
  import {
6
- Brain,
7
6
  Circle,
8
- Compass,
9
- Database,
10
- FileText,
11
7
  Home,
12
- Image,
13
8
  type LucideIcon,
14
9
  MessageSquare,
15
10
  Rocket,
16
- Settings,
11
+ ShapesIcon,
17
12
  Users,
18
13
  } from 'lucide-react';
19
14
 
15
+ import { getRouteById } from '@/config/routes';
16
+
20
17
  export interface RouteMetadata {
21
18
  icon?: LucideIcon;
22
19
  /** i18n key for the title (namespace: electron) */
@@ -34,18 +31,26 @@ interface RoutePattern {
34
31
  useDynamicTitle?: boolean;
35
32
  }
36
33
 
34
+ // Get shared icons
35
+ const communityIcon = getRouteById('community')?.icon;
36
+ const resourceIcon = getRouteById('resource')?.icon;
37
+ const memoryIcon = getRouteById('memory')?.icon;
38
+ const imageIcon = getRouteById('image')?.icon;
39
+ const pageIcon = getRouteById('page')?.icon;
40
+ const settingsIcon = getRouteById('settings')?.icon;
41
+
37
42
  /**
38
43
  * Route patterns ordered by specificity (most specific first)
39
44
  */
40
45
  const routePatterns: RoutePattern[] = [
41
46
  // Settings routes
42
47
  {
43
- icon: Settings,
48
+ icon: settingsIcon,
44
49
  test: (p) => p.startsWith('/settings/provider'),
45
50
  titleKey: 'navigation.provider',
46
51
  },
47
52
  {
48
- icon: Settings,
53
+ icon: settingsIcon,
49
54
  test: (p) => p.startsWith('/settings'),
50
55
  titleKey: 'navigation.settings',
51
56
  },
@@ -78,86 +83,86 @@ const routePatterns: RoutePattern[] = [
78
83
 
79
84
  // Community/Discover routes
80
85
  {
81
- icon: Compass,
86
+ icon: ShapesIcon,
82
87
  test: (p) => p.startsWith('/community/agent'),
83
88
  titleKey: 'navigation.discoverAssistants',
84
89
  },
85
90
  {
86
- icon: Compass,
91
+ icon: communityIcon,
87
92
  test: (p) => p.startsWith('/community/model'),
88
93
  titleKey: 'navigation.discoverModels',
89
94
  },
90
95
  {
91
- icon: Compass,
96
+ icon: communityIcon,
92
97
  test: (p) => p.startsWith('/community/provider'),
93
98
  titleKey: 'navigation.discoverProviders',
94
99
  },
95
100
  {
96
- icon: Compass,
101
+ icon: communityIcon,
97
102
  test: (p) => p.startsWith('/community/mcp'),
98
103
  titleKey: 'navigation.discoverMcp',
99
104
  },
100
105
  {
101
- icon: Compass,
106
+ icon: communityIcon,
102
107
  test: (p) => p.startsWith('/community'),
103
108
  titleKey: 'navigation.discover',
104
109
  },
105
110
 
106
111
  // Resource/Knowledge routes
107
112
  {
108
- icon: Database,
113
+ icon: resourceIcon,
109
114
  test: (p) => p.startsWith('/resource/library'),
110
115
  titleKey: 'navigation.knowledgeBase',
111
116
  },
112
117
  {
113
- icon: Database,
118
+ icon: resourceIcon,
114
119
  test: (p) => p.startsWith('/resource'),
115
120
  titleKey: 'navigation.resources',
116
121
  },
117
122
 
118
123
  // Memory routes
119
124
  {
120
- icon: Brain,
125
+ icon: memoryIcon,
121
126
  test: (p) => p.startsWith('/memory/identities'),
122
127
  titleKey: 'navigation.memoryIdentities',
123
128
  },
124
129
  {
125
- icon: Brain,
130
+ icon: memoryIcon,
126
131
  test: (p) => p.startsWith('/memory/contexts'),
127
132
  titleKey: 'navigation.memoryContexts',
128
133
  },
129
134
  {
130
- icon: Brain,
135
+ icon: memoryIcon,
131
136
  test: (p) => p.startsWith('/memory/preferences'),
132
137
  titleKey: 'navigation.memoryPreferences',
133
138
  },
134
139
  {
135
- icon: Brain,
140
+ icon: memoryIcon,
136
141
  test: (p) => p.startsWith('/memory/experiences'),
137
142
  titleKey: 'navigation.memoryExperiences',
138
143
  },
139
144
  {
140
- icon: Brain,
145
+ icon: memoryIcon,
141
146
  test: (p) => p.startsWith('/memory'),
142
147
  titleKey: 'navigation.memory',
143
148
  },
144
149
 
145
150
  // Image routes
146
151
  {
147
- icon: Image,
152
+ icon: imageIcon,
148
153
  test: (p) => p.startsWith('/image'),
149
154
  titleKey: 'navigation.image',
150
155
  },
151
156
 
152
157
  // Page routes - use dynamic title for specific page names
153
158
  {
154
- icon: FileText,
159
+ icon: pageIcon,
155
160
  test: (p) => p.startsWith('/page/'),
156
161
  titleKey: 'navigation.page',
157
162
  useDynamicTitle: true,
158
163
  },
159
164
  {
160
- icon: FileText,
165
+ icon: pageIcon,
161
166
  test: (p) => p === '/page',
162
167
  titleKey: 'navigation.pages',
163
168
  },