@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.
- package/.github/workflows/test.yml +6 -8
- package/CHANGELOG.md +42 -0
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +41 -10
- package/apps/desktop/src/main/controllers/__tests__/BrowserWindowsCtr.test.ts +14 -7
- package/apps/desktop/src/main/core/browser/BrowserManager.ts +56 -18
- package/changelog/v1.json +14 -0
- package/package.json +3 -1
- package/packages/electron-client-ipc/src/events/index.ts +2 -0
- package/packages/electron-client-ipc/src/events/windows.ts +32 -19
- package/packages/utils/src/server/responsive.ts +1 -1
- package/playwright.config.ts +1 -1
- package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +5 -0
- package/src/app/[variants]/(main)/discover/(detail)/_layout/DetailLayout.tsx +22 -0
- package/src/app/[variants]/(main)/discover/(detail)/_layout/Mobile/Header.tsx +6 -7
- package/src/app/[variants]/(main)/discover/(detail)/assistant/AssistantDetailPage.tsx +47 -0
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Header.tsx +10 -9
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Related/index.tsx +3 -3
- package/src/app/[variants]/(main)/discover/(detail)/components/NotFound.tsx +23 -0
- package/src/app/[variants]/(main)/discover/(detail)/features/Back.tsx +2 -2
- package/src/app/[variants]/(main)/discover/(detail)/features/Breadcrumb.tsx +3 -4
- package/src/app/[variants]/(main)/discover/(detail)/mcp/McpDetailPage.tsx +51 -0
- package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/Related/index.tsx +3 -3
- package/src/app/[variants]/(main)/discover/(detail)/model/ModelDetailPage.tsx +44 -0
- package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/Related/index.tsx +3 -3
- package/src/app/[variants]/(main)/discover/(detail)/provider/ProviderDetailPage.tsx +44 -0
- package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/ActionButton/ProviderConfig.tsx +16 -2
- package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/Related/index.tsx +3 -3
- package/src/app/[variants]/(main)/discover/(list)/(home)/HomePage.tsx +45 -0
- package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/Nav.tsx +7 -8
- package/src/app/[variants]/(main)/discover/(list)/_layout/ListLayout.tsx +22 -0
- package/src/app/[variants]/(main)/discover/(list)/_layout/Mobile/Nav.tsx +4 -5
- package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantLayout.tsx +21 -0
- package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantPage.tsx +44 -0
- package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/index.tsx +5 -6
- package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +8 -8
- package/src/app/[variants]/(main)/discover/(list)/features/Pagination.tsx +7 -8
- package/src/app/[variants]/(main)/discover/(list)/mcp/McpLayout.tsx +21 -0
- package/src/app/[variants]/(main)/discover/(list)/mcp/McpPage.tsx +44 -0
- package/src/app/[variants]/(main)/discover/(list)/mcp/features/Category/index.tsx +5 -6
- package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/Item.tsx +12 -8
- package/src/app/[variants]/(main)/discover/(list)/model/ModelLayout.tsx +21 -0
- package/src/app/[variants]/(main)/discover/(list)/model/ModelPage.tsx +44 -0
- package/src/app/[variants]/(main)/discover/(list)/model/_layout/Desktop.tsx +1 -1
- package/src/app/[variants]/(main)/discover/(list)/model/features/Category/index.tsx +5 -6
- package/src/app/[variants]/(main)/discover/(list)/model/features/List/Item.tsx +5 -6
- package/src/app/[variants]/(main)/discover/(list)/provider/ProviderPage.tsx +43 -0
- package/src/app/[variants]/(main)/discover/(list)/provider/features/List/Item.tsx +16 -11
- package/src/app/[variants]/(main)/discover/DiscoverRouter.tsx +167 -0
- package/src/app/[variants]/(main)/discover/[[...path]]/page.tsx +11 -0
- package/src/app/[variants]/(main)/discover/_layout/Desktop/Header.tsx +2 -2
- package/src/app/[variants]/(main)/discover/_layout/DiscoverLayout.tsx +22 -0
- package/src/app/[variants]/(main)/discover/components/Title.tsx +5 -2
- package/src/app/[variants]/(main)/discover/features/Title.tsx +35 -12
- package/src/app/[variants]/(main)/discover/features/useNav.tsx +11 -12
- package/src/app/[variants]/(main)/labs/components/LabCard.tsx +6 -7
- package/src/layout/GlobalProvider/Query.tsx +5 -1
- package/vercel.json +1 -1
- package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/page.tsx +0 -110
- package/src/app/[variants]/(main)/discover/(detail)/layout.tsx +0 -12
- package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/page.tsx +0 -103
- package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/page.tsx +0 -104
- package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/page.tsx +0 -103
- package/src/app/[variants]/(main)/discover/(list)/(home)/page.tsx +0 -44
- package/src/app/[variants]/(main)/discover/(list)/assistant/layout.tsx +0 -12
- package/src/app/[variants]/(main)/discover/(list)/assistant/page.tsx +0 -46
- package/src/app/[variants]/(main)/discover/(list)/layout.tsx +0 -12
- package/src/app/[variants]/(main)/discover/(list)/mcp/layout.tsx +0 -12
- package/src/app/[variants]/(main)/discover/(list)/mcp/page.tsx +0 -46
- package/src/app/[variants]/(main)/discover/(list)/model/layout.tsx +0 -12
- package/src/app/[variants]/(main)/discover/(list)/model/page.tsx +0 -44
- package/src/app/[variants]/(main)/discover/(list)/provider/page.tsx +0 -44
- 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
|
|
149
|
-
uses:
|
|
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:
|
|
152
|
+
run: pnpm i
|
|
155
153
|
|
|
156
154
|
- name: Lint
|
|
157
|
-
run:
|
|
155
|
+
run: npm run lint
|
|
158
156
|
|
|
159
157
|
- name: Test Client DB
|
|
160
|
-
run:
|
|
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:
|
|
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
|
+
[](#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
|
+
[](#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 {
|
|
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(
|
|
18
|
-
|
|
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(
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
124
|
-
url: 'app://host/settings?active=
|
|
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 = '
|
|
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(
|
|
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
|
|
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 =
|
|
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 {
|
|
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 {
|
|
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(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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 (
|
|
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: ${
|
|
142
|
+
logger.debug(`Redirecting to: ${fullUrl}`);
|
|
110
143
|
|
|
111
144
|
// Load URL and show window
|
|
112
|
-
await browser.loadUrl(
|
|
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(
|
|
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 =
|
|
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.
|
|
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",
|
|
@@ -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
|
|
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
|
-
|
|
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: (
|
|
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
|
-
*
|
|
52
|
-
* @param
|
|
53
|
-
* @returns
|
|
57
|
+
* 拦截客户端路由导航请求
|
|
58
|
+
* @param params 包含路径和来源信息的参数对象
|
|
59
|
+
* @returns 路由拦截结果
|
|
54
60
|
*/
|
|
55
|
-
|
|
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
|
}
|
package/playwright.config.ts
CHANGED
|
@@ -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
|
|
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
|
|
13
|
-
const
|
|
10
|
+
const location = useLocation();
|
|
11
|
+
const navigate = useNavigate();
|
|
14
12
|
|
|
15
|
-
|
|
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={() =>
|
|
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);
|