@lobehub/chat 1.133.6 → 1.134.0
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 +25 -0
- package/apps/desktop/src/main/appBrowsers.ts +51 -0
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +72 -1
- package/apps/desktop/src/main/core/browser/BrowserManager.ts +88 -18
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/windows.ts +39 -0
- package/src/app/[variants]/(main)/_layout/Desktop/DesktopLayoutContainer.tsx +4 -2
- package/src/app/[variants]/(main)/_layout/Desktop/SideBar/index.tsx +3 -1
- package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +3 -1
- package/src/app/[variants]/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicItem/TopicContent.tsx +25 -1
- package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/Actions.tsx +19 -2
- package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +27 -1
- package/src/app/[variants]/(main)/chat/_layout/Desktop/SessionPanel.tsx +11 -1
- package/src/app/[variants]/(main)/chat/features/TogglePanelButton.tsx +6 -0
- package/src/config/featureFlags/index.ts +2 -2
- package/src/config/featureFlags/schema.test.ts +165 -9
- package/src/config/featureFlags/schema.ts +68 -46
- package/src/features/ElectronTitlebar/Connection/index.tsx +0 -1
- package/src/hooks/useIsSingleMode.test.ts +66 -0
- package/src/hooks/useIsSingleMode.ts +29 -0
- package/src/server/featureFlags/index.ts +56 -0
- package/src/server/modules/EdgeConfig/index.ts +43 -4
- package/src/store/global/actions/general.ts +46 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 1.134.0](https://github.com/lobehub/lobe-chat/compare/v1.133.6...v1.134.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-10-04**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **misc**: Support double-click to open multi agent window on the desktop.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **misc**: Support double-click to open multi agent window on the desktop, closes [#9331](https://github.com/lobehub/lobe-chat/issues/9331) ([a060901](https://github.com/lobehub/lobe-chat/commit/a060901))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
### [Version 1.133.6](https://github.com/lobehub/lobe-chat/compare/v1.133.5...v1.133.6)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2025-10-04**</sup>
|
|
@@ -46,4 +46,55 @@ export const appBrowsers = {
|
|
|
46
46
|
},
|
|
47
47
|
} satisfies Record<string, BrowserWindowOpts>;
|
|
48
48
|
|
|
49
|
+
// Window templates for multi-instance windows
|
|
50
|
+
export interface WindowTemplate {
|
|
51
|
+
allowMultipleInstances: boolean;
|
|
52
|
+
// Include common BrowserWindow options
|
|
53
|
+
autoHideMenuBar?: boolean;
|
|
54
|
+
baseIdentifier: string;
|
|
55
|
+
basePath: string;
|
|
56
|
+
devTools?: boolean;
|
|
57
|
+
height?: number;
|
|
58
|
+
keepAlive?: boolean;
|
|
59
|
+
minWidth?: number;
|
|
60
|
+
parentIdentifier?: string;
|
|
61
|
+
showOnInit?: boolean;
|
|
62
|
+
title?: string;
|
|
63
|
+
titleBarStyle?: 'hidden' | 'default' | 'hiddenInset' | 'customButtonsOnHover';
|
|
64
|
+
vibrancy?:
|
|
65
|
+
| 'appearance-based'
|
|
66
|
+
| 'content'
|
|
67
|
+
| 'fullscreen-ui'
|
|
68
|
+
| 'header'
|
|
69
|
+
| 'hud'
|
|
70
|
+
| 'menu'
|
|
71
|
+
| 'popover'
|
|
72
|
+
| 'selection'
|
|
73
|
+
| 'sheet'
|
|
74
|
+
| 'sidebar'
|
|
75
|
+
| 'titlebar'
|
|
76
|
+
| 'tooltip'
|
|
77
|
+
| 'under-page'
|
|
78
|
+
| 'under-window'
|
|
79
|
+
| 'window';
|
|
80
|
+
width?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const windowTemplates = {
|
|
84
|
+
chatSingle: {
|
|
85
|
+
allowMultipleInstances: true,
|
|
86
|
+
autoHideMenuBar: true,
|
|
87
|
+
baseIdentifier: 'chatSingle',
|
|
88
|
+
basePath: '/chat',
|
|
89
|
+
height: 600,
|
|
90
|
+
keepAlive: false, // Multi-instance windows don't need to stay alive
|
|
91
|
+
minWidth: 400,
|
|
92
|
+
parentIdentifier: 'chat',
|
|
93
|
+
titleBarStyle: 'hidden',
|
|
94
|
+
vibrancy: 'under-window',
|
|
95
|
+
width: 900,
|
|
96
|
+
},
|
|
97
|
+
} satisfies Record<string, WindowTemplate>;
|
|
98
|
+
|
|
49
99
|
export type AppBrowsersIdentifiers = keyof typeof appBrowsers;
|
|
100
|
+
export type WindowTemplateIdentifiers = keyof typeof windowTemplates;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InterceptRouteParams } from '@lobechat/electron-client-ipc';
|
|
2
2
|
import { extractSubPath, findMatchingRoute } from '~common/routes';
|
|
3
3
|
|
|
4
|
-
import { AppBrowsersIdentifiers, BrowsersIdentifiers } from '@/appBrowsers';
|
|
4
|
+
import { AppBrowsersIdentifiers, BrowsersIdentifiers, WindowTemplateIdentifiers } from '@/appBrowsers';
|
|
5
5
|
import { IpcClientEventSender } from '@/types/ipcClientEvent';
|
|
6
6
|
|
|
7
7
|
import { ControllerModule, ipcClientEvent, shortcut } from './index';
|
|
@@ -100,6 +100,77 @@ export default class BrowserWindowsCtr extends ControllerModule {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Create a new multi-instance window
|
|
105
|
+
*/
|
|
106
|
+
@ipcClientEvent('createMultiInstanceWindow')
|
|
107
|
+
async createMultiInstanceWindow(params: {
|
|
108
|
+
templateId: WindowTemplateIdentifiers;
|
|
109
|
+
path: string;
|
|
110
|
+
uniqueId?: string;
|
|
111
|
+
}) {
|
|
112
|
+
try {
|
|
113
|
+
console.log('[BrowserWindowsCtr] Creating multi-instance window:', params);
|
|
114
|
+
|
|
115
|
+
const result = this.app.browserManager.createMultiInstanceWindow(
|
|
116
|
+
params.templateId,
|
|
117
|
+
params.path,
|
|
118
|
+
params.uniqueId,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Show the window
|
|
122
|
+
result.browser.show();
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
windowId: result.identifier,
|
|
127
|
+
};
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('[BrowserWindowsCtr] Failed to create multi-instance window:', error);
|
|
130
|
+
return {
|
|
131
|
+
error: error.message,
|
|
132
|
+
success: false,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get all windows by template
|
|
139
|
+
*/
|
|
140
|
+
@ipcClientEvent('getWindowsByTemplate')
|
|
141
|
+
async getWindowsByTemplate(templateId: string) {
|
|
142
|
+
try {
|
|
143
|
+
const windowIds = this.app.browserManager.getWindowsByTemplate(templateId);
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
windowIds,
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('[BrowserWindowsCtr] Failed to get windows by template:', error);
|
|
150
|
+
return {
|
|
151
|
+
error: error.message,
|
|
152
|
+
success: false,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Close all windows by template
|
|
159
|
+
*/
|
|
160
|
+
@ipcClientEvent('closeWindowsByTemplate')
|
|
161
|
+
async closeWindowsByTemplate(templateId: string) {
|
|
162
|
+
try {
|
|
163
|
+
this.app.browserManager.closeWindowsByTemplate(templateId);
|
|
164
|
+
return { success: true };
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error('[BrowserWindowsCtr] Failed to close windows by template:', error);
|
|
167
|
+
return {
|
|
168
|
+
error: error.message,
|
|
169
|
+
success: false,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
103
174
|
/**
|
|
104
175
|
* Open target window and navigate to specified sub-path
|
|
105
176
|
*/
|
|
@@ -3,7 +3,7 @@ import { WebContents } from 'electron';
|
|
|
3
3
|
|
|
4
4
|
import { createLogger } from '@/utils/logger';
|
|
5
5
|
|
|
6
|
-
import { AppBrowsersIdentifiers, appBrowsers } from '../../appBrowsers';
|
|
6
|
+
import { AppBrowsersIdentifiers, appBrowsers, WindowTemplate, WindowTemplateIdentifiers, windowTemplates } from '../../appBrowsers';
|
|
7
7
|
import type { App } from '../App';
|
|
8
8
|
import type { BrowserWindowOpts } from './Browser';
|
|
9
9
|
import Browser from './Browser';
|
|
@@ -14,9 +14,9 @@ const logger = createLogger('core:BrowserManager');
|
|
|
14
14
|
export class BrowserManager {
|
|
15
15
|
app: App;
|
|
16
16
|
|
|
17
|
-
browsers: Map<
|
|
17
|
+
browsers: Map<string, Browser> = new Map();
|
|
18
18
|
|
|
19
|
-
private webContentsMap = new Map<WebContents,
|
|
19
|
+
private webContentsMap = new Map<WebContents, string>();
|
|
20
20
|
|
|
21
21
|
constructor(app: App) {
|
|
22
22
|
logger.debug('Initializing BrowserManager');
|
|
@@ -51,12 +51,12 @@ export class BrowserManager {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
broadcastToWindow = <T extends MainBroadcastEventKey>(
|
|
54
|
-
identifier:
|
|
54
|
+
identifier: string,
|
|
55
55
|
event: T,
|
|
56
56
|
data: MainBroadcastParams<T>,
|
|
57
57
|
) => {
|
|
58
58
|
logger.debug(`Broadcasting event ${event} to window: ${identifier}`);
|
|
59
|
-
this.browsers.get(identifier)
|
|
59
|
+
this.browsers.get(identifier)?.broadcast(event, data);
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
/**
|
|
@@ -87,13 +87,21 @@ export class BrowserManager {
|
|
|
87
87
|
* @param identifier Window identifier
|
|
88
88
|
* @param subPath Sub-path, such as 'agent', 'about', etc.
|
|
89
89
|
*/
|
|
90
|
-
async redirectToPage(identifier:
|
|
90
|
+
async redirectToPage(identifier: string, subPath?: string) {
|
|
91
91
|
try {
|
|
92
92
|
// Ensure window is retrieved or created
|
|
93
93
|
const browser = this.retrieveByIdentifier(identifier);
|
|
94
94
|
browser.hide();
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
// Handle both static and dynamic windows
|
|
97
|
+
let baseRoute: string;
|
|
98
|
+
if (identifier in appBrowsers) {
|
|
99
|
+
baseRoute = appBrowsers[identifier as AppBrowsersIdentifiers].path;
|
|
100
|
+
} else {
|
|
101
|
+
// For dynamic windows, extract base route from the browser options
|
|
102
|
+
const browserOptions = browser.options;
|
|
103
|
+
baseRoute = browserOptions.path;
|
|
104
|
+
}
|
|
97
105
|
|
|
98
106
|
// Build complete URL path
|
|
99
107
|
const fullPath = subPath ? `${baseRoute}/${subPath}` : baseRoute;
|
|
@@ -114,13 +122,75 @@ export class BrowserManager {
|
|
|
114
122
|
/**
|
|
115
123
|
* get Browser by identifier
|
|
116
124
|
*/
|
|
117
|
-
retrieveByIdentifier(identifier:
|
|
125
|
+
retrieveByIdentifier(identifier: string) {
|
|
118
126
|
const browser = this.browsers.get(identifier);
|
|
119
127
|
|
|
120
128
|
if (browser) return browser;
|
|
121
129
|
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
// Check if it's a static browser
|
|
131
|
+
if (identifier in appBrowsers) {
|
|
132
|
+
logger.debug(`Browser ${identifier} not found, initializing new instance`);
|
|
133
|
+
return this.retrieveOrInitialize(appBrowsers[identifier as AppBrowsersIdentifiers]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
throw new Error(`Browser ${identifier} not found and is not a static browser`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Create a multi-instance window from template
|
|
141
|
+
* @param templateId Template identifier
|
|
142
|
+
* @param path Full path with query parameters
|
|
143
|
+
* @param uniqueId Optional unique identifier, will be generated if not provided
|
|
144
|
+
* @returns The window identifier and Browser instance
|
|
145
|
+
*/
|
|
146
|
+
createMultiInstanceWindow(templateId: WindowTemplateIdentifiers, path: string, uniqueId?: string) {
|
|
147
|
+
const template = windowTemplates[templateId];
|
|
148
|
+
if (!template) {
|
|
149
|
+
throw new Error(`Window template ${templateId} not found`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Generate unique identifier
|
|
153
|
+
const windowId = uniqueId || `${template.baseIdentifier}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
154
|
+
|
|
155
|
+
// Create browser options from template
|
|
156
|
+
const browserOpts: BrowserWindowOpts = {
|
|
157
|
+
...template,
|
|
158
|
+
identifier: windowId,
|
|
159
|
+
path: path,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
logger.debug(`Creating multi-instance window: ${windowId} with path: ${path}`);
|
|
163
|
+
|
|
164
|
+
const browser = this.retrieveOrInitialize(browserOpts);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
identifier: windowId,
|
|
168
|
+
browser: browser,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get all windows based on template
|
|
174
|
+
* @param templateId Template identifier
|
|
175
|
+
* @returns Array of window identifiers matching the template
|
|
176
|
+
*/
|
|
177
|
+
getWindowsByTemplate(templateId: string): string[] {
|
|
178
|
+
const prefix = `${templateId}_`;
|
|
179
|
+
return Array.from(this.browsers.keys()).filter(id => id.startsWith(prefix));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Close all windows based on template
|
|
184
|
+
* @param templateId Template identifier
|
|
185
|
+
*/
|
|
186
|
+
closeWindowsByTemplate(templateId: string): void {
|
|
187
|
+
const windowIds = this.getWindowsByTemplate(templateId);
|
|
188
|
+
windowIds.forEach(id => {
|
|
189
|
+
const browser = this.browsers.get(id);
|
|
190
|
+
if (browser) {
|
|
191
|
+
browser.close();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
124
194
|
}
|
|
125
195
|
|
|
126
196
|
/**
|
|
@@ -144,7 +214,7 @@ export class BrowserManager {
|
|
|
144
214
|
* @param options Browser window options
|
|
145
215
|
*/
|
|
146
216
|
private retrieveOrInitialize(options: BrowserWindowOpts) {
|
|
147
|
-
let browser = this.browsers.get(options.identifier
|
|
217
|
+
let browser = this.browsers.get(options.identifier);
|
|
148
218
|
if (browser) {
|
|
149
219
|
logger.debug(`Retrieved existing browser: ${options.identifier}`);
|
|
150
220
|
return browser;
|
|
@@ -153,7 +223,7 @@ export class BrowserManager {
|
|
|
153
223
|
logger.debug(`Creating new browser: ${options.identifier}`);
|
|
154
224
|
browser = new Browser(options, this.app);
|
|
155
225
|
|
|
156
|
-
const identifier = options.identifier
|
|
226
|
+
const identifier = options.identifier;
|
|
157
227
|
this.browsers.set(identifier, browser);
|
|
158
228
|
|
|
159
229
|
// 记录 WebContents 和 identifier 的映射
|
|
@@ -166,32 +236,32 @@ export class BrowserManager {
|
|
|
166
236
|
|
|
167
237
|
browser.browserWindow.on('show', () => {
|
|
168
238
|
if (browser.webContents)
|
|
169
|
-
this.webContentsMap.set(browser.webContents, browser.identifier
|
|
239
|
+
this.webContentsMap.set(browser.webContents, browser.identifier);
|
|
170
240
|
});
|
|
171
241
|
|
|
172
242
|
return browser;
|
|
173
243
|
}
|
|
174
244
|
|
|
175
245
|
closeWindow(identifier: string) {
|
|
176
|
-
const browser = this.browsers.get(identifier
|
|
246
|
+
const browser = this.browsers.get(identifier);
|
|
177
247
|
browser?.close();
|
|
178
248
|
}
|
|
179
249
|
|
|
180
250
|
minimizeWindow(identifier: string) {
|
|
181
|
-
const browser = this.browsers.get(identifier
|
|
251
|
+
const browser = this.browsers.get(identifier);
|
|
182
252
|
browser?.browserWindow.minimize();
|
|
183
253
|
}
|
|
184
254
|
|
|
185
255
|
maximizeWindow(identifier: string) {
|
|
186
|
-
const browser = this.browsers.get(identifier
|
|
187
|
-
if (browser
|
|
256
|
+
const browser = this.browsers.get(identifier);
|
|
257
|
+
if (browser?.browserWindow.isMaximized()) {
|
|
188
258
|
browser?.browserWindow.unmaximize();
|
|
189
259
|
} else {
|
|
190
260
|
browser?.browserWindow.maximize();
|
|
191
261
|
}
|
|
192
262
|
}
|
|
193
263
|
|
|
194
|
-
getIdentifierByWebContents(webContents: WebContents):
|
|
264
|
+
getIdentifierByWebContents(webContents: WebContents): string | null {
|
|
195
265
|
return this.webContentsMap.get(webContents) || null;
|
|
196
266
|
}
|
|
197
267
|
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.134.0",
|
|
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",
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { InterceptRouteParams, InterceptRouteResponse } from '../types/route';
|
|
2
2
|
|
|
3
|
+
export interface CreateMultiInstanceWindowParams {
|
|
4
|
+
templateId: string;
|
|
5
|
+
path: string;
|
|
6
|
+
uniqueId?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CreateMultiInstanceWindowResponse {
|
|
10
|
+
success: boolean;
|
|
11
|
+
windowId?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GetWindowsByTemplateResponse {
|
|
16
|
+
success: boolean;
|
|
17
|
+
windowIds?: string[];
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
3
21
|
export interface WindowsDispatchEvents {
|
|
4
22
|
/**
|
|
5
23
|
* 拦截客户端路由导航请求
|
|
@@ -14,4 +32,25 @@ export interface WindowsDispatchEvents {
|
|
|
14
32
|
openDevtools: () => void;
|
|
15
33
|
|
|
16
34
|
openSettingsWindow: (tab?: string) => void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a new multi-instance window
|
|
38
|
+
* @param params Window creation parameters
|
|
39
|
+
* @returns Creation result
|
|
40
|
+
*/
|
|
41
|
+
createMultiInstanceWindow: (params: CreateMultiInstanceWindowParams) => CreateMultiInstanceWindowResponse;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get all windows by template
|
|
45
|
+
* @param templateId Template identifier
|
|
46
|
+
* @returns List of window identifiers
|
|
47
|
+
*/
|
|
48
|
+
getWindowsByTemplate: (templateId: string) => GetWindowsByTemplateResponse;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Close all windows by template
|
|
52
|
+
* @param templateId Template identifier
|
|
53
|
+
* @returns Operation result
|
|
54
|
+
*/
|
|
55
|
+
closeWindowsByTemplate: (templateId: string) => { success: boolean; error?: string };
|
|
17
56
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useTheme } from 'antd-style';
|
|
2
2
|
import { usePathname } from 'next/navigation';
|
|
3
|
-
import { PropsWithChildren, memo } from 'react';
|
|
3
|
+
import { PropsWithChildren, Suspense, memo } from 'react';
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
|
6
6
|
import SideBar from './SideBar';
|
|
@@ -11,7 +11,9 @@ const DesktopLayoutContainer = memo<PropsWithChildren>(({ children }) => {
|
|
|
11
11
|
const hideSideBar = pathname.startsWith('/settings');
|
|
12
12
|
return (
|
|
13
13
|
<>
|
|
14
|
-
|
|
14
|
+
<Suspense>
|
|
15
|
+
{!hideSideBar && <SideBar />}
|
|
16
|
+
</Suspense>
|
|
15
17
|
<Flexbox
|
|
16
18
|
style={{
|
|
17
19
|
background: theme.colorBgLayout,
|
|
@@ -6,6 +6,7 @@ import { Suspense, memo } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { isDesktop } from '@/const/version';
|
|
8
8
|
import { useActiveTabKey } from '@/hooks/useActiveTabKey';
|
|
9
|
+
import { useIsSingleMode } from '@/hooks/useIsSingleMode';
|
|
9
10
|
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
|
|
10
11
|
import { useGlobalStore } from '@/store/global';
|
|
11
12
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
@@ -26,11 +27,12 @@ const Top = () => {
|
|
|
26
27
|
|
|
27
28
|
const Nav = memo(() => {
|
|
28
29
|
const theme = useTheme();
|
|
30
|
+
const isSingleMode = useIsSingleMode()
|
|
29
31
|
const inZenMode = useGlobalStore(systemStatusSelectors.inZenMode);
|
|
30
32
|
const { showPinList } = useServerConfigStore(featureFlagsSelectors);
|
|
31
33
|
|
|
32
34
|
return (
|
|
33
|
-
!inZenMode && (
|
|
35
|
+
!inZenMode && !isSingleMode && (
|
|
34
36
|
<SideNav
|
|
35
37
|
avatar={
|
|
36
38
|
<div className={electronStylish.nodrag}>
|
|
@@ -2,6 +2,7 @@ import { ActionIcon, Dropdown, EditableText, Icon, type MenuProps, Text } from '
|
|
|
2
2
|
import { App } from 'antd';
|
|
3
3
|
import { createStyles } from 'antd-style';
|
|
4
4
|
import {
|
|
5
|
+
ExternalLink,
|
|
5
6
|
LucideCopy,
|
|
6
7
|
LucideLoader2,
|
|
7
8
|
MoreVertical,
|
|
@@ -16,8 +17,10 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
16
17
|
|
|
17
18
|
import BubblesLoading from '@/components/BubblesLoading';
|
|
18
19
|
import { LOADING_FLAT } from '@/const/message';
|
|
20
|
+
import { isDesktop } from '@/const/version';
|
|
19
21
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
|
20
22
|
import { useChatStore } from '@/store/chat';
|
|
23
|
+
import { useGlobalStore } from '@/store/global';
|
|
21
24
|
|
|
22
25
|
const useStyles = createStyles(({ css }) => ({
|
|
23
26
|
content: css`
|
|
@@ -45,6 +48,8 @@ const TopicContent = memo<TopicContentProps>(({ id, title, fav, showMore }) => {
|
|
|
45
48
|
|
|
46
49
|
const mobile = useIsMobile();
|
|
47
50
|
|
|
51
|
+
const openTopicInNewWindow = useGlobalStore((s) => s.openTopicInNewWindow);
|
|
52
|
+
|
|
48
53
|
const [
|
|
49
54
|
editing,
|
|
50
55
|
favoriteTopic,
|
|
@@ -53,6 +58,7 @@ const TopicContent = memo<TopicContentProps>(({ id, title, fav, showMore }) => {
|
|
|
53
58
|
autoRenameTopicTitle,
|
|
54
59
|
duplicateTopic,
|
|
55
60
|
isLoading,
|
|
61
|
+
activeId,
|
|
56
62
|
] = useChatStore((s) => [
|
|
57
63
|
s.topicRenamingId === id,
|
|
58
64
|
s.favoriteTopic,
|
|
@@ -61,6 +67,7 @@ const TopicContent = memo<TopicContentProps>(({ id, title, fav, showMore }) => {
|
|
|
61
67
|
s.autoRenameTopicTitle,
|
|
62
68
|
s.duplicateTopic,
|
|
63
69
|
s.topicLoadingIds.includes(id),
|
|
70
|
+
s.activeId,
|
|
64
71
|
]);
|
|
65
72
|
const { styles, theme } = useStyles();
|
|
66
73
|
|
|
@@ -88,6 +95,18 @@ const TopicContent = memo<TopicContentProps>(({ id, title, fav, showMore }) => {
|
|
|
88
95
|
toggleEditing(true);
|
|
89
96
|
},
|
|
90
97
|
},
|
|
98
|
+
...(isDesktop
|
|
99
|
+
? [
|
|
100
|
+
{
|
|
101
|
+
icon: <Icon icon={ExternalLink} />,
|
|
102
|
+
key: 'openInNewWindow',
|
|
103
|
+
label: '单独打开页面',
|
|
104
|
+
onClick: () => {
|
|
105
|
+
openTopicInNewWindow(activeId, id);
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
]
|
|
109
|
+
: []),
|
|
91
110
|
{
|
|
92
111
|
type: 'divider',
|
|
93
112
|
},
|
|
@@ -134,7 +153,7 @@ const TopicContent = memo<TopicContentProps>(({ id, title, fav, showMore }) => {
|
|
|
134
153
|
},
|
|
135
154
|
},
|
|
136
155
|
],
|
|
137
|
-
[],
|
|
156
|
+
[id, activeId, autoRenameTopicTitle, duplicateTopic, removeTopic, t, toggleEditing, openTopicInNewWindow],
|
|
138
157
|
);
|
|
139
158
|
|
|
140
159
|
return (
|
|
@@ -169,6 +188,11 @@ const TopicContent = memo<TopicContentProps>(({ id, title, fav, showMore }) => {
|
|
|
169
188
|
<Text
|
|
170
189
|
className={styles.title}
|
|
171
190
|
ellipsis={{ rows: 1, tooltip: { placement: 'left', title } }}
|
|
191
|
+
onDoubleClick={() => {
|
|
192
|
+
if (isDesktop) {
|
|
193
|
+
openTopicInNewWindow(activeId, id)
|
|
194
|
+
}
|
|
195
|
+
}}
|
|
172
196
|
style={{ margin: 0 }}
|
|
173
197
|
>
|
|
174
198
|
{title}
|
package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/Actions.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { ItemType } from 'antd/es/menu/interface';
|
|
|
5
5
|
import isEqual from 'fast-deep-equal';
|
|
6
6
|
import {
|
|
7
7
|
Check,
|
|
8
|
+
ExternalLink,
|
|
8
9
|
HardDriveDownload,
|
|
9
10
|
ListTree,
|
|
10
11
|
LucideCopy,
|
|
@@ -17,8 +18,9 @@ import {
|
|
|
17
18
|
import { memo, useMemo } from 'react';
|
|
18
19
|
import { useTranslation } from 'react-i18next';
|
|
19
20
|
|
|
20
|
-
import { isServerMode } from '@/const/version';
|
|
21
|
+
import { isDesktop, isServerMode } from '@/const/version';
|
|
21
22
|
import { configService } from '@/services/config';
|
|
23
|
+
import { useGlobalStore } from '@/store/global';
|
|
22
24
|
import { useSessionStore } from '@/store/session';
|
|
23
25
|
import { sessionHelpers } from '@/store/session/helpers';
|
|
24
26
|
import { sessionGroupSelectors, sessionSelectors } from '@/store/session/selectors';
|
|
@@ -41,6 +43,8 @@ const Actions = memo<ActionProps>(({ group, id, openCreateGroupModal, setOpen })
|
|
|
41
43
|
const { styles } = useStyles();
|
|
42
44
|
const { t } = useTranslation('chat');
|
|
43
45
|
|
|
46
|
+
const openSessionInNewWindow = useGlobalStore((s) => s.openSessionInNewWindow);
|
|
47
|
+
|
|
44
48
|
const sessionCustomGroups = useSessionStore(sessionGroupSelectors.sessionGroupItems, isEqual);
|
|
45
49
|
const [pin, removeSession, pinSession, duplicateSession, updateSessionGroup] = useSessionStore(
|
|
46
50
|
(s) => {
|
|
@@ -82,6 +86,19 @@ const Actions = memo<ActionProps>(({ group, id, openCreateGroupModal, setOpen })
|
|
|
82
86
|
duplicateSession(id);
|
|
83
87
|
},
|
|
84
88
|
},
|
|
89
|
+
...(isDesktop
|
|
90
|
+
? [
|
|
91
|
+
{
|
|
92
|
+
icon: <Icon icon={ExternalLink} />,
|
|
93
|
+
key: 'openInNewWindow',
|
|
94
|
+
label: '单独打开页面',
|
|
95
|
+
onClick: ({ domEvent }: { domEvent: Event }) => {
|
|
96
|
+
domEvent.stopPropagation();
|
|
97
|
+
openSessionInNewWindow(id);
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
: []),
|
|
85
102
|
{
|
|
86
103
|
type: 'divider',
|
|
87
104
|
},
|
|
@@ -167,7 +184,7 @@ const Actions = memo<ActionProps>(({ group, id, openCreateGroupModal, setOpen })
|
|
|
167
184
|
},
|
|
168
185
|
] as ItemType[]
|
|
169
186
|
).filter(Boolean),
|
|
170
|
-
[id, pin],
|
|
187
|
+
[id, pin, openSessionInNewWindow],
|
|
171
188
|
);
|
|
172
189
|
|
|
173
190
|
return (
|