@lobehub/chat 1.141.4 → 1.141.6

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 (72) hide show
  1. package/.github/workflows/test.yml +6 -8
  2. package/CHANGELOG.md +42 -0
  3. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +41 -10
  4. package/apps/desktop/src/main/controllers/__tests__/BrowserWindowsCtr.test.ts +14 -7
  5. package/apps/desktop/src/main/core/browser/BrowserManager.ts +56 -18
  6. package/changelog/v1.json +14 -0
  7. package/package.json +3 -1
  8. package/packages/electron-client-ipc/src/events/index.ts +2 -0
  9. package/packages/electron-client-ipc/src/events/windows.ts +32 -19
  10. package/packages/utils/src/server/responsive.ts +1 -1
  11. package/playwright.config.ts +1 -1
  12. package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +5 -0
  13. package/src/app/[variants]/(main)/discover/(detail)/_layout/DetailLayout.tsx +22 -0
  14. package/src/app/[variants]/(main)/discover/(detail)/_layout/Mobile/Header.tsx +6 -7
  15. package/src/app/[variants]/(main)/discover/(detail)/assistant/AssistantDetailPage.tsx +47 -0
  16. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Header.tsx +10 -9
  17. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Related/index.tsx +3 -3
  18. package/src/app/[variants]/(main)/discover/(detail)/components/NotFound.tsx +23 -0
  19. package/src/app/[variants]/(main)/discover/(detail)/features/Back.tsx +2 -2
  20. package/src/app/[variants]/(main)/discover/(detail)/features/Breadcrumb.tsx +3 -4
  21. package/src/app/[variants]/(main)/discover/(detail)/mcp/McpDetailPage.tsx +51 -0
  22. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/Related/index.tsx +3 -3
  23. package/src/app/[variants]/(main)/discover/(detail)/model/ModelDetailPage.tsx +44 -0
  24. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/Related/index.tsx +3 -3
  25. package/src/app/[variants]/(main)/discover/(detail)/provider/ProviderDetailPage.tsx +44 -0
  26. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/ActionButton/ProviderConfig.tsx +16 -2
  27. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/Related/index.tsx +3 -3
  28. package/src/app/[variants]/(main)/discover/(list)/(home)/HomePage.tsx +45 -0
  29. package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/Nav.tsx +7 -8
  30. package/src/app/[variants]/(main)/discover/(list)/_layout/ListLayout.tsx +22 -0
  31. package/src/app/[variants]/(main)/discover/(list)/_layout/Mobile/Nav.tsx +4 -5
  32. package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantLayout.tsx +21 -0
  33. package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantPage.tsx +44 -0
  34. package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/index.tsx +5 -6
  35. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +8 -8
  36. package/src/app/[variants]/(main)/discover/(list)/features/Pagination.tsx +7 -8
  37. package/src/app/[variants]/(main)/discover/(list)/mcp/McpLayout.tsx +21 -0
  38. package/src/app/[variants]/(main)/discover/(list)/mcp/McpPage.tsx +44 -0
  39. package/src/app/[variants]/(main)/discover/(list)/mcp/features/Category/index.tsx +5 -6
  40. package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/Item.tsx +12 -8
  41. package/src/app/[variants]/(main)/discover/(list)/model/ModelLayout.tsx +21 -0
  42. package/src/app/[variants]/(main)/discover/(list)/model/ModelPage.tsx +44 -0
  43. package/src/app/[variants]/(main)/discover/(list)/model/_layout/Desktop.tsx +1 -1
  44. package/src/app/[variants]/(main)/discover/(list)/model/features/Category/index.tsx +5 -6
  45. package/src/app/[variants]/(main)/discover/(list)/model/features/List/Item.tsx +5 -6
  46. package/src/app/[variants]/(main)/discover/(list)/provider/ProviderPage.tsx +43 -0
  47. package/src/app/[variants]/(main)/discover/(list)/provider/features/List/Item.tsx +16 -11
  48. package/src/app/[variants]/(main)/discover/DiscoverRouter.tsx +167 -0
  49. package/src/app/[variants]/(main)/discover/[[...path]]/page.tsx +11 -0
  50. package/src/app/[variants]/(main)/discover/_layout/Desktop/Header.tsx +2 -2
  51. package/src/app/[variants]/(main)/discover/_layout/DiscoverLayout.tsx +22 -0
  52. package/src/app/[variants]/(main)/discover/components/Title.tsx +5 -2
  53. package/src/app/[variants]/(main)/discover/features/Title.tsx +35 -12
  54. package/src/app/[variants]/(main)/discover/features/useNav.tsx +11 -12
  55. package/src/app/[variants]/(main)/labs/components/LabCard.tsx +6 -7
  56. package/src/layout/GlobalProvider/Query.tsx +5 -1
  57. package/vercel.json +1 -1
  58. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/page.tsx +0 -110
  59. package/src/app/[variants]/(main)/discover/(detail)/layout.tsx +0 -12
  60. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/page.tsx +0 -103
  61. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/page.tsx +0 -104
  62. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/page.tsx +0 -103
  63. package/src/app/[variants]/(main)/discover/(list)/(home)/page.tsx +0 -44
  64. package/src/app/[variants]/(main)/discover/(list)/assistant/layout.tsx +0 -12
  65. package/src/app/[variants]/(main)/discover/(list)/assistant/page.tsx +0 -46
  66. package/src/app/[variants]/(main)/discover/(list)/layout.tsx +0 -12
  67. package/src/app/[variants]/(main)/discover/(list)/mcp/layout.tsx +0 -12
  68. package/src/app/[variants]/(main)/discover/(list)/mcp/page.tsx +0 -46
  69. package/src/app/[variants]/(main)/discover/(list)/model/layout.tsx +0 -12
  70. package/src/app/[variants]/(main)/discover/(list)/model/page.tsx +0 -44
  71. package/src/app/[variants]/(main)/discover/(list)/provider/page.tsx +0 -44
  72. package/src/app/[variants]/(main)/discover/layout.tsx +0 -12
@@ -145,26 +145,24 @@ jobs:
145
145
  node-version: 22
146
146
  package-manager-cache: false
147
147
 
148
- - name: Install bun
149
- uses: oven-sh/setup-bun@v2
150
- with:
151
- bun-version: 1.2.23
148
+ - name: Install pnpm
149
+ uses: pnpm/action-setup@v4
152
150
 
153
151
  - name: Install deps
154
- run: bun i
152
+ run: pnpm i
155
153
 
156
154
  - name: Lint
157
- run: bun run lint
155
+ run: npm run lint
158
156
 
159
157
  - name: Test Client DB
160
- run: bun run --filter @lobechat/database test:client-db
158
+ run: pnpm --filter @lobechat/database test:client-db
161
159
  env:
162
160
  KEY_VAULTS_SECRET: LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=
163
161
  S3_PUBLIC_DOMAIN: https://example.com
164
162
  APP_URL: https://home.com
165
163
 
166
164
  - name: Test Coverage
167
- run: bun run --filter @lobechat/database test:coverage
165
+ run: pnpm --filter @lobechat/database test:coverage
168
166
  env:
169
167
  DATABASE_TEST_URL: postgresql://postgres:postgres@localhost:5432/postgres
170
168
  DATABASE_DRIVER: node
package/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.141.6](https://github.com/lobehub/lobe-chat/compare/v1.141.5...v1.141.6)
6
+
7
+ <sup>Released on **2025-10-22**</sup>
8
+
9
+ <br/>
10
+
11
+ <details>
12
+ <summary><kbd>Improvements and Fixes</kbd></summary>
13
+
14
+ </details>
15
+
16
+ <div align="right">
17
+
18
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
19
+
20
+ </div>
21
+
22
+ ### [Version 1.141.5](https://github.com/lobehub/lobe-chat/compare/v1.141.4...v1.141.5)
23
+
24
+ <sup>Released on **2025-10-22**</sup>
25
+
26
+ #### ♻ Code Refactoring
27
+
28
+ - **misc**: Change discover page from RSC to SPA to improve performance.
29
+
30
+ <br/>
31
+
32
+ <details>
33
+ <summary><kbd>Improvements and Fixes</kbd></summary>
34
+
35
+ #### Code refactoring
36
+
37
+ - **misc**: Change discover page from RSC to SPA to improve performance, closes [#9828](https://github.com/lobehub/lobe-chat/issues/9828) ([b59ee0a](https://github.com/lobehub/lobe-chat/commit/b59ee0a))
38
+
39
+ </details>
40
+
41
+ <div align="right">
42
+
43
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
44
+
45
+ </div>
46
+
5
47
  ### [Version 1.141.4](https://github.com/lobehub/lobe-chat/compare/v1.141.3...v1.141.4)
6
48
 
7
49
  <sup>Released on **2025-10-22**</sup>
@@ -1,7 +1,11 @@
1
- import { InterceptRouteParams } from '@lobechat/electron-client-ipc';
1
+ import { InterceptRouteParams, OpenSettingsWindowOptions } from '@lobechat/electron-client-ipc';
2
2
  import { extractSubPath, findMatchingRoute } from '~common/routes';
3
3
 
4
- import { AppBrowsersIdentifiers, BrowsersIdentifiers, WindowTemplateIdentifiers } from '@/appBrowsers';
4
+ import {
5
+ AppBrowsersIdentifiers,
6
+ BrowsersIdentifiers,
7
+ WindowTemplateIdentifiers,
8
+ } from '@/appBrowsers';
5
9
  import { IpcClientEventSender } from '@/types/ipcClientEvent';
6
10
 
7
11
  import { ControllerModule, ipcClientEvent, shortcut } from './index';
@@ -14,11 +18,16 @@ export default class BrowserWindowsCtr extends ControllerModule {
14
18
  }
15
19
 
16
20
  @ipcClientEvent('openSettingsWindow')
17
- async openSettingsWindow(tab?: string) {
18
- console.log('[BrowserWindowsCtr] Received request to open settings window', tab);
21
+ async openSettingsWindow(options?: string | OpenSettingsWindowOptions) {
22
+ const normalizedOptions: OpenSettingsWindowOptions =
23
+ typeof options === 'string' || options === undefined
24
+ ? { tab: typeof options === 'string' ? options : undefined }
25
+ : options;
26
+
27
+ console.log('[BrowserWindowsCtr] Received request to open settings window', normalizedOptions);
19
28
 
20
29
  try {
21
- await this.app.browserManager.showSettingsWindowWithTab(tab);
30
+ await this.app.browserManager.showSettingsWindowWithTab(normalizedOptions);
22
31
 
23
32
  return { success: true };
24
33
  } catch (error) {
@@ -68,15 +77,37 @@ export default class BrowserWindowsCtr extends ControllerModule {
68
77
 
69
78
  try {
70
79
  if (matchedRoute.targetWindow === BrowsersIdentifiers.settings) {
71
- const subPath = extractSubPath(path, matchedRoute.pathPrefix);
72
-
73
- await this.app.browserManager.showSettingsWindowWithTab(subPath);
80
+ const extractedSubPath = extractSubPath(path, matchedRoute.pathPrefix);
81
+ const sanitizedSubPath =
82
+ extractedSubPath && !extractedSubPath.startsWith('?') ? extractedSubPath : undefined;
83
+ let searchParams: Record<string, string> | undefined;
84
+ try {
85
+ const url = new URL(params.url);
86
+ const entries = Array.from(url.searchParams.entries());
87
+ if (entries.length > 0) {
88
+ searchParams = entries.reduce<Record<string, string>>((acc, [key, value]) => {
89
+ acc[key] = value;
90
+ return acc;
91
+ }, {});
92
+ }
93
+ } catch (error) {
94
+ console.warn(
95
+ '[BrowserWindowsCtr] Failed to parse URL for settings route interception:',
96
+ params.url,
97
+ error,
98
+ );
99
+ }
100
+
101
+ await this.app.browserManager.showSettingsWindowWithTab({
102
+ searchParams,
103
+ tab: sanitizedSubPath,
104
+ });
74
105
 
75
106
  return {
76
107
  intercepted: true,
77
108
  path,
78
109
  source,
79
- subPath,
110
+ subPath: sanitizedSubPath,
80
111
  targetWindow: matchedRoute.targetWindow,
81
112
  };
82
113
  } else {
@@ -105,8 +136,8 @@ export default class BrowserWindowsCtr extends ControllerModule {
105
136
  */
106
137
  @ipcClientEvent('createMultiInstanceWindow')
107
138
  async createMultiInstanceWindow(params: {
108
- templateId: WindowTemplateIdentifiers;
109
139
  path: string;
140
+ templateId: WindowTemplateIdentifiers;
110
141
  uniqueId?: string;
111
142
  }) {
112
143
  try {
@@ -64,7 +64,7 @@ describe('BrowserWindowsCtr', () => {
64
64
  it('should show the settings window with the specified tab', async () => {
65
65
  const tab = 'appearance';
66
66
  const result = await browserWindowsCtr.openSettingsWindow(tab);
67
- expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith(tab);
67
+ expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith({ tab });
68
68
  expect(result).toEqual({ success: true });
69
69
  });
70
70
 
@@ -120,11 +120,11 @@ describe('BrowserWindowsCtr', () => {
120
120
  it('should show settings window if matched route target is settings', async () => {
121
121
  const params: InterceptRouteParams = {
122
122
  ...baseParams,
123
- path: '/settings?active=common',
124
- url: 'app://host/settings?active=common',
123
+ path: '/settings/provider',
124
+ url: 'app://host/settings/provider?active=provider&provider=ollama',
125
125
  };
126
126
  const matchedRoute = { targetWindow: BrowsersIdentifiers.settings, pathPrefix: '/settings' };
127
- const subPath = 'common';
127
+ const subPath = 'provider';
128
128
  (findMatchingRoute as Mock).mockReturnValue(matchedRoute);
129
129
  (extractSubPath as Mock).mockReturnValue(subPath);
130
130
 
@@ -132,7 +132,10 @@ describe('BrowserWindowsCtr', () => {
132
132
 
133
133
  expect(findMatchingRoute).toHaveBeenCalledWith(params.path);
134
134
  expect(extractSubPath).toHaveBeenCalledWith(params.path, matchedRoute.pathPrefix);
135
- expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith(subPath);
135
+ expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith({
136
+ searchParams: { active: 'provider', provider: 'ollama' },
137
+ tab: subPath,
138
+ });
136
139
  expect(result).toEqual({
137
140
  intercepted: true,
138
141
  path: params.path,
@@ -170,11 +173,11 @@ describe('BrowserWindowsCtr', () => {
170
173
  it('should return error if processing route interception fails for settings', async () => {
171
174
  const params: InterceptRouteParams = {
172
175
  ...baseParams,
173
- path: '/settings?active=general',
176
+ path: '/settings',
174
177
  url: 'app://host/settings?active=general',
175
178
  };
176
179
  const matchedRoute = { targetWindow: BrowsersIdentifiers.settings, pathPrefix: '/settings' };
177
- const subPath = 'general';
180
+ const subPath = undefined;
178
181
  const errorMessage = 'Processing error for settings';
179
182
  (findMatchingRoute as Mock).mockReturnValue(matchedRoute);
180
183
  (extractSubPath as Mock).mockReturnValue(subPath);
@@ -182,6 +185,10 @@ describe('BrowserWindowsCtr', () => {
182
185
 
183
186
  const result = await browserWindowsCtr.interceptRoute(params);
184
187
 
188
+ expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith({
189
+ searchParams: { active: 'general' },
190
+ tab: subPath,
191
+ });
185
192
  expect(result).toEqual({
186
193
  error: errorMessage,
187
194
  intercepted: false,
@@ -1,9 +1,18 @@
1
- import { MainBroadcastEventKey, MainBroadcastParams } from '@lobechat/electron-client-ipc';
1
+ import {
2
+ MainBroadcastEventKey,
3
+ MainBroadcastParams,
4
+ OpenSettingsWindowOptions,
5
+ } from '@lobechat/electron-client-ipc';
2
6
  import { WebContents } from 'electron';
3
7
 
4
8
  import { createLogger } from '@/utils/logger';
5
9
 
6
- import { AppBrowsersIdentifiers, appBrowsers, WindowTemplate, WindowTemplateIdentifiers, windowTemplates } from '../../appBrowsers';
10
+ import {
11
+ AppBrowsersIdentifiers,
12
+ WindowTemplateIdentifiers,
13
+ appBrowsers,
14
+ windowTemplates,
15
+ } from '../../appBrowsers';
7
16
  import type { App } from '../App';
8
17
  import type { BrowserWindowOpts } from './Browser';
9
18
  import Browser from './Browser';
@@ -63,14 +72,35 @@ export class BrowserManager {
63
72
  * Display the settings window and navigate to a specific tab
64
73
  * @param tab Settings window sub-path tab
65
74
  */
66
- async showSettingsWindowWithTab(tab?: string) {
67
- logger.debug(`Showing settings window with tab: ${tab || 'default'}`);
68
- // common is the main path for settings route
69
- if (tab && tab !== 'common') {
70
- const browser = await this.redirectToPage('settings', tab);
75
+ async showSettingsWindowWithTab(options?: OpenSettingsWindowOptions) {
76
+ const tab = options?.tab;
77
+ const searchParams = options?.searchParams;
78
+
79
+ const query = new URLSearchParams();
80
+ if (searchParams) {
81
+ Object.entries(searchParams).forEach(([key, value]) => {
82
+ if (value !== undefined) query.set(key, value);
83
+ });
84
+ }
85
+
86
+ if (tab && tab !== 'common' && !query.has('active')) {
87
+ query.set('active', tab);
88
+ }
89
+
90
+ const queryString = query.toString();
91
+ const activeTab = query.get('active') ?? tab;
92
+
93
+ logger.debug(
94
+ `Showing settings window with navigation: active=${activeTab || 'default'}, query=${
95
+ queryString || 'none'
96
+ }`,
97
+ );
98
+
99
+ if (queryString) {
100
+ const browser = await this.redirectToPage('settings', undefined, queryString);
71
101
 
72
102
  // make provider page more large
73
- if (tab.startsWith('provider/')) {
103
+ if (activeTab?.startsWith('provider')) {
74
104
  logger.debug('Resizing window for provider settings');
75
105
  browser.setWindowSize({ height: 1000, width: 1400 });
76
106
  browser.moveToCenter();
@@ -87,7 +117,7 @@ export class BrowserManager {
87
117
  * @param identifier Window identifier
88
118
  * @param subPath Sub-path, such as 'agent', 'about', etc.
89
119
  */
90
- async redirectToPage(identifier: string, subPath?: string) {
120
+ async redirectToPage(identifier: string, subPath?: string, search?: string) {
91
121
  try {
92
122
  // Ensure window is retrieved or created
93
123
  const browser = this.retrieveByIdentifier(identifier);
@@ -105,11 +135,14 @@ export class BrowserManager {
105
135
 
106
136
  // Build complete URL path
107
137
  const fullPath = subPath ? `${baseRoute}/${subPath}` : baseRoute;
138
+ const normalizedSearch =
139
+ search && search.length > 0 ? (search.startsWith('?') ? search : `?${search}`) : '';
140
+ const fullUrl = `${fullPath}${normalizedSearch}`;
108
141
 
109
- logger.debug(`Redirecting to: ${fullPath}`);
142
+ logger.debug(`Redirecting to: ${fullUrl}`);
110
143
 
111
144
  // Load URL and show window
112
- await browser.loadUrl(fullPath);
145
+ await browser.loadUrl(fullUrl);
113
146
  browser.show();
114
147
 
115
148
  return browser;
@@ -143,14 +176,20 @@ export class BrowserManager {
143
176
  * @param uniqueId Optional unique identifier, will be generated if not provided
144
177
  * @returns The window identifier and Browser instance
145
178
  */
146
- createMultiInstanceWindow(templateId: WindowTemplateIdentifiers, path: string, uniqueId?: string) {
179
+ createMultiInstanceWindow(
180
+ templateId: WindowTemplateIdentifiers,
181
+ path: string,
182
+ uniqueId?: string,
183
+ ) {
147
184
  const template = windowTemplates[templateId];
148
185
  if (!template) {
149
186
  throw new Error(`Window template ${templateId} not found`);
150
187
  }
151
188
 
152
189
  // Generate unique identifier
153
- const windowId = uniqueId || `${template.baseIdentifier}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
190
+ const windowId =
191
+ uniqueId ||
192
+ `${template.baseIdentifier}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
154
193
 
155
194
  // Create browser options from template
156
195
  const browserOpts: BrowserWindowOpts = {
@@ -164,8 +203,8 @@ export class BrowserManager {
164
203
  const browser = this.retrieveOrInitialize(browserOpts);
165
204
 
166
205
  return {
167
- identifier: windowId,
168
206
  browser: browser,
207
+ identifier: windowId,
169
208
  };
170
209
  }
171
210
 
@@ -176,7 +215,7 @@ export class BrowserManager {
176
215
  */
177
216
  getWindowsByTemplate(templateId: string): string[] {
178
217
  const prefix = `${templateId}_`;
179
- return Array.from(this.browsers.keys()).filter(id => id.startsWith(prefix));
218
+ return Array.from(this.browsers.keys()).filter((id) => id.startsWith(prefix));
180
219
  }
181
220
 
182
221
  /**
@@ -185,7 +224,7 @@ export class BrowserManager {
185
224
  */
186
225
  closeWindowsByTemplate(templateId: string): void {
187
226
  const windowIds = this.getWindowsByTemplate(templateId);
188
- windowIds.forEach(id => {
227
+ windowIds.forEach((id) => {
189
228
  const browser = this.browsers.get(id);
190
229
  if (browser) {
191
230
  browser.close();
@@ -235,8 +274,7 @@ export class BrowserManager {
235
274
  });
236
275
 
237
276
  browser.browserWindow.on('show', () => {
238
- if (browser.webContents)
239
- this.webContentsMap.set(browser.webContents, browser.identifier);
277
+ if (browser.webContents) this.webContentsMap.set(browser.webContents, browser.identifier);
240
278
  });
241
279
 
242
280
  return browser;
package/changelog/v1.json CHANGED
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2025-10-22",
5
+ "version": "1.141.6"
6
+ },
7
+ {
8
+ "children": {
9
+ "improvements": [
10
+ "Change discover page from RSC to SPA to improve performance."
11
+ ]
12
+ },
13
+ "date": "2025-10-22",
14
+ "version": "1.141.5"
15
+ },
2
16
  {
3
17
  "children": {
4
18
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.141.4",
3
+ "version": "1.141.6",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot 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",
@@ -266,7 +266,9 @@
266
266
  "react-layout-kit": "^2.0.0",
267
267
  "react-lazy-load": "^4.0.1",
268
268
  "react-pdf": "^9.2.1",
269
+ "react-responsive": "^10.0.1",
269
270
  "react-rnd": "^10.5.2",
271
+ "react-router-dom": "^7.9.4",
270
272
  "react-scan": "^0.4.3",
271
273
  "react-virtuoso": "^4.14.1",
272
274
  "react-wrap-balancer": "^1.1.1",
@@ -50,3 +50,5 @@ export type MainBroadcastEventKey = keyof MainBroadcastEvents;
50
50
  export type MainBroadcastParams<T extends MainBroadcastEventKey> = Parameters<
51
51
  MainBroadcastEvents[T]
52
52
  >[0];
53
+
54
+ export type { OpenSettingsWindowOptions } from './windows';
@@ -1,44 +1,50 @@
1
1
  import { InterceptRouteParams, InterceptRouteResponse } from '../types/route';
2
2
 
3
+ export interface OpenSettingsWindowOptions {
4
+ /**
5
+ * Query parameters that should be appended to the settings URL.
6
+ */
7
+ searchParams?: Record<string, string | undefined>;
8
+ /**
9
+ * Settings page tab path or identifier.
10
+ */
11
+ tab?: string;
12
+ }
13
+
3
14
  export interface CreateMultiInstanceWindowParams {
4
- templateId: string;
5
15
  path: string;
16
+ templateId: string;
6
17
  uniqueId?: string;
7
18
  }
8
19
 
9
20
  export interface CreateMultiInstanceWindowResponse {
21
+ error?: string;
10
22
  success: boolean;
11
23
  windowId?: string;
12
- error?: string;
13
24
  }
14
25
 
15
26
  export interface GetWindowsByTemplateResponse {
27
+ error?: string;
16
28
  success: boolean;
17
29
  windowIds?: string[];
18
- error?: string;
19
30
  }
20
31
 
21
32
  export interface WindowsDispatchEvents {
22
33
  /**
23
- * 拦截客户端路由导航请求
24
- * @param params 包含路径和来源信息的参数对象
25
- * @returns 路由拦截结果
26
- */
27
- interceptRoute: (params: InterceptRouteParams) => InterceptRouteResponse;
28
-
29
- /**
30
- * open the LobeHub Devtools
34
+ * Close all windows by template
35
+ * @param templateId Template identifier
36
+ * @returns Operation result
31
37
  */
32
- openDevtools: () => void;
33
-
34
- openSettingsWindow: (tab?: string) => void;
38
+ closeWindowsByTemplate: (templateId: string) => { error?: string, success: boolean; };
35
39
 
36
40
  /**
37
41
  * Create a new multi-instance window
38
42
  * @param params Window creation parameters
39
43
  * @returns Creation result
40
44
  */
41
- createMultiInstanceWindow: (params: CreateMultiInstanceWindowParams) => CreateMultiInstanceWindowResponse;
45
+ createMultiInstanceWindow: (
46
+ params: CreateMultiInstanceWindowParams,
47
+ ) => CreateMultiInstanceWindowResponse;
42
48
 
43
49
  /**
44
50
  * Get all windows by template
@@ -48,9 +54,16 @@ export interface WindowsDispatchEvents {
48
54
  getWindowsByTemplate: (templateId: string) => GetWindowsByTemplateResponse;
49
55
 
50
56
  /**
51
- * Close all windows by template
52
- * @param templateId Template identifier
53
- * @returns Operation result
57
+ * 拦截客户端路由导航请求
58
+ * @param params 包含路径和来源信息的参数对象
59
+ * @returns 路由拦截结果
54
60
  */
55
- closeWindowsByTemplate: (templateId: string) => { success: boolean; error?: string };
61
+ interceptRoute: (params: InterceptRouteParams) => InterceptRouteResponse;
62
+
63
+ /**
64
+ * open the LobeHub Devtools
65
+ */
66
+ openDevtools: () => void;
67
+
68
+ openSettingsWindow: (options?: OpenSettingsWindowOptions | string) => void;
56
69
  }
@@ -4,7 +4,7 @@ import { UAParser } from 'ua-parser-js';
4
4
  /**
5
5
  * check mobile device in server
6
6
  */
7
- const isMobileDevice = async () => {
7
+ export const isMobileDevice = async () => {
8
8
  if (typeof process === 'undefined') {
9
9
  throw new Error('[Server method] you are importing a server-only module outside of server');
10
10
  }
@@ -14,7 +14,7 @@ export default defineConfig({
14
14
  reporter: 'list',
15
15
  retries: 0,
16
16
  testDir: './e2e',
17
- timeout: 60_000,
17
+ timeout: 1_200_000,
18
18
  use: {
19
19
  baseURL: `http://localhost:${PORT}`,
20
20
  trace: 'on-first-retry',
@@ -20,6 +20,11 @@ const handler = (req: NextRequest) =>
20
20
  },
21
21
 
22
22
  req,
23
+ responseMeta({ ctx }) {
24
+ const headers = ctx?.resHeaders;
25
+
26
+ return { headers };
27
+ },
23
28
  router: desktopRouter,
24
29
  });
25
30
 
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { PropsWithChildren, memo } from 'react';
4
+
5
+ import Desktop from './Desktop';
6
+ import Mobile from './Mobile';
7
+
8
+ interface DetailLayoutProps extends PropsWithChildren {
9
+ mobile?: boolean;
10
+ }
11
+
12
+ const DetailLayout = memo<DetailLayoutProps>(({ children, mobile }) => {
13
+ if (mobile) {
14
+ return <Mobile>{children}</Mobile>;
15
+ }
16
+
17
+ return <Desktop>{children}</Desktop>;
18
+ });
19
+
20
+ DetailLayout.displayName = 'DetailLayout';
21
+
22
+ export default DetailLayout;
@@ -1,22 +1,21 @@
1
1
  'use client';
2
2
 
3
3
  import { ChatHeader } from '@lobehub/ui/mobile';
4
- import { usePathname } from 'next/navigation';
5
- import { useRouter } from 'nextjs-toploader/app';
6
4
  import { memo } from 'react';
7
- import urlJoin from 'url-join';
5
+ import { useLocation, useNavigate } from 'react-router-dom';
8
6
 
9
7
  import { mobileHeaderSticky } from '@/styles/mobileHeader';
10
8
 
11
9
  const Header = memo(() => {
12
- const pathname = usePathname();
13
- const router = useRouter();
10
+ const location = useLocation();
11
+ const navigate = useNavigate();
14
12
 
15
- const path = pathname.split('/').filter(Boolean)[1];
13
+ // Extract the path segment (assistant, model, provider, mcp)
14
+ const path = location.pathname.split('/').find(Boolean);
16
15
 
17
16
  return (
18
17
  <ChatHeader
19
- onBackClick={() => router.push(urlJoin('/discover', path))}
18
+ onBackClick={() => navigate(`/${path}`)}
20
19
  showBackButton
21
20
  style={mobileHeaderSticky}
22
21
  />
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+ import { useParams } from 'react-router-dom';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { withSuspense } from '@/components/withSuspense';
8
+ import { useDiscoverStore } from '@/store/discover';
9
+ import { DiscoverTab } from '@/types/discover';
10
+
11
+ import Breadcrumb from '../features/Breadcrumb';
12
+ import { TocProvider } from '../features/Toc/useToc';
13
+ import NotFound from '../components/NotFound';
14
+ import { DetailProvider } from './[...slugs]/features/DetailProvider';
15
+ import Details from './[...slugs]/features/Details';
16
+ import Header from './[...slugs]/features/Header';
17
+ import Loading from './[...slugs]/loading';
18
+
19
+ interface AssistantDetailPageProps {
20
+ mobile?: boolean;
21
+ }
22
+
23
+ const AssistantDetailPage = memo<AssistantDetailPageProps>(({ mobile }) => {
24
+ const params = useParams();
25
+ const slugs = params['*']?.split('/') || [];
26
+ const identifier = decodeURIComponent(slugs.join('/'));
27
+
28
+ const useAssistantDetail = useDiscoverStore((s) => s.useAssistantDetail);
29
+ const { data, isLoading } = useAssistantDetail({ identifier });
30
+
31
+ if (isLoading) return <Loading />;
32
+ if (!data) return <NotFound />;
33
+
34
+ return (
35
+ <TocProvider>
36
+ <DetailProvider config={data}>
37
+ {!mobile && <Breadcrumb identifier={identifier} tab={DiscoverTab.Assistants} />}
38
+ <Flexbox gap={16}>
39
+ <Header mobile={mobile} />
40
+ <Details mobile={mobile} />
41
+ </Flexbox>
42
+ </DetailProvider>
43
+ </TocProvider>
44
+ );
45
+ });
46
+
47
+ export default withSuspense(AssistantDetailPage);