@lobehub/chat 1.133.5 โ 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 +58 -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 +21 -0
- package/package.json +1 -1
- package/packages/database/src/models/__tests__/aiModel.test.ts +3 -0
- package/packages/database/src/models/aiModel.ts +18 -2
- package/packages/electron-client-ipc/src/events/windows.ts +39 -0
- package/packages/model-bank/src/aiModels/google.ts +64 -2
- package/packages/model-bank/src/types/aiModel.ts +13 -9
- package/packages/model-runtime/src/providers/google/createImage.ts +13 -4
- 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/app/[variants]/(main)/settings/provider/features/ModelList/ModelItem.tsx +6 -3
- 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/server/routers/lambda/aiModel.test.ts +2 -0
- package/src/store/global/actions/general.ts +46 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
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
|
+
|
|
30
|
+
### [Version 1.133.6](https://github.com/lobehub/lobe-chat/compare/v1.133.5...v1.133.6)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2025-10-04**</sup>
|
|
33
|
+
|
|
34
|
+
#### ๐ Bug Fixes
|
|
35
|
+
|
|
36
|
+
- **misc**: `type` not preserved when model is disabled or sorted.
|
|
37
|
+
|
|
38
|
+
#### ๐ Styles
|
|
39
|
+
|
|
40
|
+
- **misc**: Nano banana support `aspect_ratio`.
|
|
41
|
+
|
|
42
|
+
<br/>
|
|
43
|
+
|
|
44
|
+
<details>
|
|
45
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
46
|
+
|
|
47
|
+
#### What's fixed
|
|
48
|
+
|
|
49
|
+
- **misc**: `type` not preserved when model is disabled or sorted, closes [#9530](https://github.com/lobehub/lobe-chat/issues/9530) ([476b897](https://github.com/lobehub/lobe-chat/commit/476b897))
|
|
50
|
+
|
|
51
|
+
#### Styles
|
|
52
|
+
|
|
53
|
+
- **misc**: Nano banana support `aspect_ratio`, closes [#9528](https://github.com/lobehub/lobe-chat/issues/9528) ([ae3ed6e](https://github.com/lobehub/lobe-chat/commit/ae3ed6e))
|
|
54
|
+
|
|
55
|
+
</details>
|
|
56
|
+
|
|
57
|
+
<div align="right">
|
|
58
|
+
|
|
59
|
+
[](#readme-top)
|
|
60
|
+
|
|
61
|
+
</div>
|
|
62
|
+
|
|
5
63
|
### [Version 1.133.5](https://github.com/lobehub/lobe-chat/compare/v1.133.4...v1.133.5)
|
|
6
64
|
|
|
7
65
|
<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
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"children": {
|
|
4
|
+
"features": [
|
|
5
|
+
"Support double-click to open multi agent window on the desktop."
|
|
6
|
+
]
|
|
7
|
+
},
|
|
8
|
+
"date": "2025-10-04",
|
|
9
|
+
"version": "1.134.0"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"children": {
|
|
13
|
+
"fixes": [
|
|
14
|
+
"type not preserved when model is disabled or sorted."
|
|
15
|
+
],
|
|
16
|
+
"improvements": [
|
|
17
|
+
"Nano banana support aspect_ratio."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"date": "2025-10-04",
|
|
21
|
+
"version": "1.133.6"
|
|
22
|
+
},
|
|
2
23
|
{
|
|
3
24
|
"children": {
|
|
4
25
|
"fixes": [
|
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",
|
|
@@ -211,16 +211,19 @@ describe('AiModelModel', () => {
|
|
|
211
211
|
id: 'model1',
|
|
212
212
|
providerId: 'openai',
|
|
213
213
|
enabled: true,
|
|
214
|
+
type: 'image',
|
|
214
215
|
});
|
|
215
216
|
|
|
216
217
|
await aiProviderModel.toggleModelEnabled({
|
|
217
218
|
id: model.id,
|
|
218
219
|
providerId: 'openai',
|
|
219
220
|
enabled: false,
|
|
221
|
+
type: 'image',
|
|
220
222
|
});
|
|
221
223
|
|
|
222
224
|
const updatedModel = await aiProviderModel.findById(model.id);
|
|
223
225
|
expect(updatedModel?.enabled).toBe(false);
|
|
226
|
+
expect(updatedModel?.type).toBe('image');
|
|
224
227
|
});
|
|
225
228
|
});
|
|
226
229
|
|
|
@@ -122,11 +122,27 @@ export class AiModelModel {
|
|
|
122
122
|
};
|
|
123
123
|
|
|
124
124
|
toggleModelEnabled = async (value: ToggleAiModelEnableParams) => {
|
|
125
|
+
const now = new Date();
|
|
126
|
+
const insertValues = {
|
|
127
|
+
...value,
|
|
128
|
+
updatedAt: now,
|
|
129
|
+
userId: this.userId,
|
|
130
|
+
} as typeof aiModels.$inferInsert;
|
|
131
|
+
|
|
132
|
+
if (value.type) insertValues.type = value.type;
|
|
133
|
+
|
|
134
|
+
const updateValues: Partial<typeof aiModels.$inferInsert> = {
|
|
135
|
+
enabled: value.enabled,
|
|
136
|
+
updatedAt: now,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
if (value.type) updateValues.type = value.type;
|
|
140
|
+
|
|
125
141
|
return this.db
|
|
126
142
|
.insert(aiModels)
|
|
127
|
-
.values(
|
|
143
|
+
.values(insertValues)
|
|
128
144
|
.onConflictDoUpdate({
|
|
129
|
-
set:
|
|
145
|
+
set: updateValues,
|
|
130
146
|
target: [aiModels.id, aiModels.providerId, aiModels.userId],
|
|
131
147
|
});
|
|
132
148
|
};
|
|
@@ -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
|
}
|
|
@@ -366,6 +366,28 @@ const googleChatModels: AIChatModelCard[] = [
|
|
|
366
366
|
'Nano Banana ๆฏ Google ๆๆฐใๆๅฟซใๆ้ซๆ็ๅ็ๅคๆจกๆๆจกๅ๏ผๅฎๅ
่ฎธๆจ้่ฟๅฏน่ฏ็ๆๅ็ผ่พๅพๅใ',
|
|
367
367
|
displayName: 'Nano Banana',
|
|
368
368
|
enabled: true,
|
|
369
|
+
id: 'gemini-2.5-flash-image',
|
|
370
|
+
maxOutput: 8192,
|
|
371
|
+
pricing: {
|
|
372
|
+
units: [
|
|
373
|
+
{ name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
|
|
374
|
+
{ name: 'imageInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
|
|
375
|
+
{ name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
|
|
376
|
+
{ name: 'imageOutput', rate: 30, strategy: 'fixed', unit: 'millionTokens' },
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
releasedAt: '2025-08-26',
|
|
380
|
+
type: 'chat',
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
abilities: {
|
|
384
|
+
imageOutput: true,
|
|
385
|
+
vision: true,
|
|
386
|
+
},
|
|
387
|
+
contextWindowTokens: 32_768 + 8192,
|
|
388
|
+
description:
|
|
389
|
+
'Nano Banana ๆฏ Google ๆๆฐใๆๅฟซใๆ้ซๆ็ๅ็ๅคๆจกๆๆจกๅ๏ผๅฎๅ
่ฎธๆจ้่ฟๅฏน่ฏ็ๆๅ็ผ่พๅพๅใ',
|
|
390
|
+
displayName: 'Nano Banana (Preview)',
|
|
369
391
|
id: 'gemini-2.5-flash-image-preview',
|
|
370
392
|
maxOutput: 8192,
|
|
371
393
|
pricing: {
|
|
@@ -805,22 +827,62 @@ const imagenBaseParameters: ModelParamsSchema = {
|
|
|
805
827
|
prompt: { default: '' },
|
|
806
828
|
};
|
|
807
829
|
|
|
830
|
+
const NANO_BANANA_ASPECT_RATIOS = [
|
|
831
|
+
'1:1', // 1024x1024
|
|
832
|
+
'2:3', // 832x1248
|
|
833
|
+
'3:2', // 1248x832
|
|
834
|
+
'3:4', // 864x1184
|
|
835
|
+
'4:3', // 1184x864
|
|
836
|
+
'4:5', // 896x1152
|
|
837
|
+
'5:4', // 1152x896
|
|
838
|
+
'9:16', // 768x1344
|
|
839
|
+
'16:9', // 1344x768
|
|
840
|
+
'21:9', // 1536x672
|
|
841
|
+
];
|
|
842
|
+
|
|
843
|
+
const nanoBananaParameters: ModelParamsSchema = {
|
|
844
|
+
aspectRatio: {
|
|
845
|
+
default: '1:1',
|
|
846
|
+
enum: NANO_BANANA_ASPECT_RATIOS,
|
|
847
|
+
},
|
|
848
|
+
imageUrls: {
|
|
849
|
+
default: [],
|
|
850
|
+
},
|
|
851
|
+
prompt: { default: '' },
|
|
852
|
+
};
|
|
853
|
+
|
|
808
854
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
809
855
|
const googleImageModels: AIImageModelCard[] = [
|
|
810
856
|
{
|
|
811
857
|
displayName: 'Nano Banana',
|
|
812
|
-
id: 'gemini-2.5-flash-image
|
|
858
|
+
id: 'gemini-2.5-flash-image:image',
|
|
813
859
|
enabled: true,
|
|
814
860
|
type: 'image',
|
|
815
861
|
description:
|
|
816
862
|
'Nano Banana ๆฏ Google ๆๆฐใๆๅฟซใๆ้ซๆ็ๅ็ๅคๆจกๆๆจกๅ๏ผๅฎๅ
่ฎธๆจ้่ฟๅฏน่ฏ็ๆๅ็ผ่พๅพๅใ',
|
|
817
863
|
releasedAt: '2025-08-26',
|
|
864
|
+
parameters: nanoBananaParameters,
|
|
865
|
+
pricing: {
|
|
866
|
+
units: [
|
|
867
|
+
{ name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
|
|
868
|
+
{ name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
|
|
869
|
+
{ name: 'imageOutput', rate: 30, strategy: 'fixed', unit: 'millionTokens' },
|
|
870
|
+
],
|
|
871
|
+
},
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
displayName: 'Nano Banana (Preview)',
|
|
875
|
+
id: 'gemini-2.5-flash-image-preview:image',
|
|
876
|
+
type: 'image',
|
|
877
|
+
description:
|
|
878
|
+
'Nano Banana ๆฏ Google ๆๆฐใๆๅฟซใๆ้ซๆ็ๅ็ๅคๆจกๆๆจกๅ๏ผๅฎๅ
่ฎธๆจ้่ฟๅฏน่ฏ็ๆๅ็ผ่พๅพๅใ',
|
|
879
|
+
releasedAt: '2025-08-26',
|
|
818
880
|
parameters: CHAT_MODEL_IMAGE_GENERATION_PARAMS,
|
|
819
881
|
pricing: {
|
|
820
882
|
units: [
|
|
821
883
|
{ name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
|
|
822
884
|
{ name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
|
|
823
|
-
{ name: 'imageOutput', rate:
|
|
885
|
+
{ name: 'imageOutput', rate: 30, strategy: 'fixed', unit: 'millionTokens' },
|
|
824
886
|
],
|
|
825
887
|
},
|
|
826
888
|
},
|
|
@@ -12,15 +12,18 @@ export const AiModelSourceEnum = {
|
|
|
12
12
|
|
|
13
13
|
export type AiModelSourceType = (typeof AiModelSourceEnum)[keyof typeof AiModelSourceEnum];
|
|
14
14
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
export const AiModelTypeSchema = z.enum([
|
|
16
|
+
'chat',
|
|
17
|
+
'embedding',
|
|
18
|
+
'tts',
|
|
19
|
+
'stt',
|
|
20
|
+
'image',
|
|
21
|
+
'text2video',
|
|
22
|
+
'text2music',
|
|
23
|
+
'realtime',
|
|
24
|
+
] as const);
|
|
25
|
+
|
|
26
|
+
export type AiModelType = z.infer<typeof AiModelTypeSchema>;
|
|
24
27
|
|
|
25
28
|
export interface ModelAbilities {
|
|
26
29
|
/**
|
|
@@ -373,6 +376,7 @@ export const ToggleAiModelEnableSchema = z.object({
|
|
|
373
376
|
id: z.string(),
|
|
374
377
|
providerId: z.string(),
|
|
375
378
|
source: z.enum(['builtin', 'custom', 'remote']).optional(),
|
|
379
|
+
type: AiModelTypeSchema.optional(),
|
|
376
380
|
});
|
|
377
381
|
|
|
378
382
|
export type ToggleAiModelEnableParams = z.infer<typeof ToggleAiModelEnableSchema>;
|