@lobehub/lobehub 2.0.0-next.159 → 2.0.0-next.160
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 +17 -0
- package/changelog/v1.json +5 -0
- package/locales/tr-TR/chat.json +4 -4
- package/locales/tr-TR/common.json +8 -8
- package/package.json +1 -1
- package/packages/database/src/core/electron.ts +42 -42
- package/packages/database/src/repositories/aiInfra/index.ts +21 -21
- package/packages/database/src/repositories/dataExporter/index.ts +13 -13
- package/packages/database/src/repositories/tableViewer/index.ts +17 -17
- package/src/auth.ts +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.160](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.159...v2.0.0-next.160)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-12-05**</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
|
+
|
|
5
22
|
## [Version 2.0.0-next.159](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.158...v2.0.0-next.159)
|
|
6
23
|
|
|
7
24
|
<sup>Released on **2025-12-04**</sup>
|
package/changelog/v1.json
CHANGED
package/locales/tr-TR/chat.json
CHANGED
|
@@ -254,8 +254,8 @@
|
|
|
254
254
|
"noSelectedAgents": "Henüz üye seçilmedi",
|
|
255
255
|
"openInNewWindow": "Sayfayı yeni pencerede aç",
|
|
256
256
|
"owner": "Grup sahibi",
|
|
257
|
-
"pin": "
|
|
258
|
-
"pinOff": "
|
|
257
|
+
"pin": "Sabitle",
|
|
258
|
+
"pinOff": "Sabitlemeyi Kaldır",
|
|
259
259
|
"rag": {
|
|
260
260
|
"referenceChunks": "Referans Parçaları",
|
|
261
261
|
"userQuery": {
|
|
@@ -420,8 +420,8 @@
|
|
|
420
420
|
"clear": "Çeviriyi Temizle"
|
|
421
421
|
},
|
|
422
422
|
"tts": {
|
|
423
|
-
"action": "
|
|
424
|
-
"clear": "
|
|
423
|
+
"action": "Metinden Sese",
|
|
424
|
+
"clear": "Sesi Temizle"
|
|
425
425
|
},
|
|
426
426
|
"untitledAgent": "Adsız Asistan",
|
|
427
427
|
"updateAgent": "Asistan Bilgilerini Güncelle",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"showDetail": "Detayları Görüntüle"
|
|
26
26
|
},
|
|
27
27
|
"autoGenerate": "Otomatik Oluştur",
|
|
28
|
-
"autoGenerateTooltip": "
|
|
28
|
+
"autoGenerateTooltip": "İpuçlarına göre asistan açıklamasını otomatik oluştur",
|
|
29
29
|
"autoGenerateTooltipDisabled": "Otomatik tamamlama işlevini kullanmadan önce ipucu kelimesini girin",
|
|
30
30
|
"back": "Geri",
|
|
31
31
|
"batchDelete": "Toplu Sil",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"branchingDisable": "“Alt Konu” özelliği mevcut modda kullanılamaz. Bu özelliği kullanmak için lütfen Postgres/Pglite DB moduna geçin veya LobeHub Cloud'u kullanın.",
|
|
35
35
|
"branchingRequiresSavedTopic": "Geçerli konu kaydedilmedi, alt konu özelliğini kullanmak için önce kaydedilmelidir",
|
|
36
36
|
"cancel": "İptal",
|
|
37
|
-
"changelog": "
|
|
37
|
+
"changelog": "Değişiklik Günlüğü",
|
|
38
38
|
"clientDB": {
|
|
39
39
|
"autoInit": {
|
|
40
40
|
"title": "PGlite veritabanı başlatılıyor"
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
"allAgentWithMessage": "Tüm Asistan ve Mesajları Dışa Aktar",
|
|
184
184
|
"globalSetting": "Ayarları Dışa Aktar"
|
|
185
185
|
},
|
|
186
|
-
"feedback": "
|
|
186
|
+
"feedback": "Geri Bildirim",
|
|
187
187
|
"follow": "Bizi {{name}} üzerinde takip edin",
|
|
188
188
|
"footer": {
|
|
189
189
|
"action": {
|
|
@@ -309,14 +309,14 @@
|
|
|
309
309
|
"officialSite": "Resmi Site",
|
|
310
310
|
"ok": "Tamam",
|
|
311
311
|
"or": "veya",
|
|
312
|
-
"password": "
|
|
313
|
-
"pin": "
|
|
314
|
-
"pinOff": "
|
|
312
|
+
"password": "Şifre",
|
|
313
|
+
"pin": "Sabitle",
|
|
314
|
+
"pinOff": "Sabitlemeyi Kaldır",
|
|
315
315
|
"privacy": "Gizlilik Politikası",
|
|
316
316
|
"regenerate": "Tekrarla",
|
|
317
317
|
"releaseNotes": "Sürüm Detayları",
|
|
318
318
|
"rename": "Yeniden İsimlendir",
|
|
319
|
-
"reset": "
|
|
319
|
+
"reset": "Sıfırla",
|
|
320
320
|
"retry": "Yeniden Dene",
|
|
321
321
|
"run": "Çalıştır",
|
|
322
322
|
"save": "Kaydet",
|
|
@@ -359,7 +359,7 @@
|
|
|
359
359
|
},
|
|
360
360
|
"tab": {
|
|
361
361
|
"aiImage": "Yapay Zeka Resim",
|
|
362
|
-
"chat": "
|
|
362
|
+
"chat": "Sohbet",
|
|
363
363
|
"discover": "Keşfet",
|
|
364
364
|
"files": "Dosyalar",
|
|
365
365
|
"knowledgeBase": "Bilgi Tabanı",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.160",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -12,7 +12,7 @@ import * as schema from '../schemas';
|
|
|
12
12
|
import { LobeChatDatabase } from '../type';
|
|
13
13
|
import migrations from './migrations.json';
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// Global object for instance management
|
|
16
16
|
interface LobeGlobal {
|
|
17
17
|
pgDB?: LobeChatDatabase;
|
|
18
18
|
pgDBInitPromise?: Promise<LobeChatDatabase>;
|
|
@@ -22,7 +22,7 @@ interface LobeGlobal {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// Ensure globalThis has our namespace
|
|
26
26
|
declare global {
|
|
27
27
|
// eslint-disable-next-line no-var
|
|
28
28
|
var __LOBE__: LobeGlobal;
|
|
@@ -33,20 +33,20 @@ if (!globalThis.__LOBE__) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* Attempt to create a file lock to ensure singleton pattern
|
|
37
|
+
* Returns true if lock acquired successfully, false if another instance is already running
|
|
38
38
|
*/
|
|
39
39
|
const acquireLock = async (dbPath: string): Promise<boolean> => {
|
|
40
40
|
try {
|
|
41
|
-
//
|
|
41
|
+
// Database lock file path
|
|
42
42
|
const lockPath = `${dbPath}.lock`;
|
|
43
43
|
|
|
44
|
-
//
|
|
44
|
+
// Attempt to create lock file
|
|
45
45
|
if (!fs.existsSync(lockPath)) {
|
|
46
|
-
//
|
|
46
|
+
// Create lock file and write current process ID
|
|
47
47
|
fs.writeFileSync(lockPath, process.pid.toString(), 'utf8');
|
|
48
48
|
|
|
49
|
-
//
|
|
49
|
+
// Save lock information to global object
|
|
50
50
|
if (!globalThis.__LOBE__.pgDBLock) {
|
|
51
51
|
globalThis.__LOBE__.pgDBLock = {
|
|
52
52
|
acquired: true,
|
|
@@ -58,19 +58,19 @@ const acquireLock = async (dbPath: string): Promise<boolean> => {
|
|
|
58
58
|
return true;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
//
|
|
61
|
+
// Check if lock file has expired (not updated for over 5 minutes)
|
|
62
62
|
const stats = fs.statSync(lockPath);
|
|
63
63
|
const currentTime = Date.now();
|
|
64
64
|
const modifiedTime = stats.mtime.getTime();
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// If lock file hasn't been updated for over 5 minutes, consider it expired
|
|
67
67
|
if (currentTime - modifiedTime > 5 * 60 * 1000) {
|
|
68
|
-
//
|
|
68
|
+
// Delete expired lock file
|
|
69
69
|
fs.unlinkSync(lockPath);
|
|
70
|
-
//
|
|
70
|
+
// Recreate lock file
|
|
71
71
|
fs.writeFileSync(lockPath, process.pid.toString(), 'utf8');
|
|
72
72
|
|
|
73
|
-
//
|
|
73
|
+
// Save lock information to global object
|
|
74
74
|
if (!globalThis.__LOBE__.pgDBLock) {
|
|
75
75
|
globalThis.__LOBE__.pgDBLock = {
|
|
76
76
|
acquired: true,
|
|
@@ -91,7 +91,7 @@ const acquireLock = async (dbPath: string): Promise<boolean> => {
|
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
*
|
|
94
|
+
* Release file lock
|
|
95
95
|
*/
|
|
96
96
|
const releaseLock = () => {
|
|
97
97
|
if (globalThis.__LOBE__.pgDBLock?.acquired && globalThis.__LOBE__.pgDBLock.lockPath) {
|
|
@@ -105,7 +105,7 @@ const releaseLock = () => {
|
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
-
//
|
|
108
|
+
// Release lock on process exit
|
|
109
109
|
process.on('exit', releaseLock);
|
|
110
110
|
process.on('SIGINT', () => {
|
|
111
111
|
releaseLock();
|
|
@@ -129,15 +129,15 @@ const migrateDatabase = async (db: LobeChatDatabase): Promise<void> => {
|
|
|
129
129
|
|
|
130
130
|
console.log('schemaHash:', hash);
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// If hash is the same, check if all tables exist
|
|
133
133
|
if (hash === cacheHash) {
|
|
134
134
|
try {
|
|
135
135
|
const drizzleMigration = new DrizzleMigrationModel(db);
|
|
136
136
|
|
|
137
|
-
//
|
|
137
|
+
// Check if tables exist in the database
|
|
138
138
|
const tableCount = await drizzleMigration.getTableCounts();
|
|
139
139
|
|
|
140
|
-
//
|
|
140
|
+
// If table count is greater than 0, assume database is properly initialized
|
|
141
141
|
if (tableCount > 0) {
|
|
142
142
|
console.log('✅ Electron DB schema already synced');
|
|
143
143
|
return;
|
|
@@ -152,7 +152,7 @@ const migrateDatabase = async (db: LobeChatDatabase): Promise<void> => {
|
|
|
152
152
|
console.log('🚀 Starting Electron DB migration...');
|
|
153
153
|
|
|
154
154
|
try {
|
|
155
|
-
//
|
|
155
|
+
// Execute migration
|
|
156
156
|
// @ts-expect-error
|
|
157
157
|
await db.dialect.migrate(migrations, db.session, {});
|
|
158
158
|
|
|
@@ -162,10 +162,10 @@ const migrateDatabase = async (db: LobeChatDatabase): Promise<void> => {
|
|
|
162
162
|
} catch (error) {
|
|
163
163
|
console.error('❌ Electron database schema migration failed', error);
|
|
164
164
|
|
|
165
|
-
//
|
|
165
|
+
// Attempt to query migration table data
|
|
166
166
|
let migrationsTableData: MigrationTableItem[] = [];
|
|
167
167
|
try {
|
|
168
|
-
//
|
|
168
|
+
// Attempt to query migration table
|
|
169
169
|
const drizzleMigration = new DrizzleMigrationModel(db);
|
|
170
170
|
migrationsTableData = await drizzleMigration.getMigrationList();
|
|
171
171
|
} catch (queryError) {
|
|
@@ -184,12 +184,12 @@ const migrateDatabase = async (db: LobeChatDatabase): Promise<void> => {
|
|
|
184
184
|
};
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
|
-
*
|
|
187
|
+
* Check if there's an active database instance and attempt to close it if exists
|
|
188
188
|
*/
|
|
189
189
|
const checkAndCleanupExistingInstance = async () => {
|
|
190
190
|
if (globalThis.__LOBE__.pgDB) {
|
|
191
191
|
try {
|
|
192
|
-
//
|
|
192
|
+
// Attempt to close existing PGlite instance (if client has close method)
|
|
193
193
|
// @ts-expect-error
|
|
194
194
|
const client = globalThis.__LOBE__.pgDB?.dialect?.client;
|
|
195
195
|
|
|
@@ -198,11 +198,11 @@ const checkAndCleanupExistingInstance = async () => {
|
|
|
198
198
|
console.log('✅ Successfully closed previous PGlite instance');
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
//
|
|
201
|
+
// Reset global reference
|
|
202
202
|
globalThis.__LOBE__.pgDB = undefined;
|
|
203
203
|
} catch (error) {
|
|
204
204
|
console.error('❌ Failed to close previous PGlite instance:', error);
|
|
205
|
-
//
|
|
205
|
+
// Continue execution and create new instance
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
};
|
|
@@ -220,19 +220,19 @@ export const getPgliteInstance = async (): Promise<LobeChatDatabase> => {
|
|
|
220
220
|
}),
|
|
221
221
|
);
|
|
222
222
|
|
|
223
|
-
//
|
|
223
|
+
// Already initialized, return instance directly
|
|
224
224
|
if (globalThis.__LOBE__.pgDB) return globalThis.__LOBE__.pgDB;
|
|
225
225
|
|
|
226
|
-
//
|
|
226
|
+
// Initialization promise in progress, wait for it to complete
|
|
227
227
|
if (globalThis.__LOBE__.pgDBInitPromise) {
|
|
228
228
|
console.log('Waiting for existing initialization promise to complete');
|
|
229
229
|
return globalThis.__LOBE__.pgDBInitPromise;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
//
|
|
232
|
+
// Prevent race conditions from multiple calls
|
|
233
233
|
if (isInitializing) {
|
|
234
234
|
console.log('Already initializing, waiting for result');
|
|
235
|
-
//
|
|
235
|
+
// Create new Promise to wait for initialization to complete
|
|
236
236
|
return new Promise((resolve, reject) => {
|
|
237
237
|
const checkInterval = setInterval(() => {
|
|
238
238
|
if (globalThis.__LOBE__.pgDB) {
|
|
@@ -248,12 +248,12 @@ export const getPgliteInstance = async (): Promise<LobeChatDatabase> => {
|
|
|
248
248
|
|
|
249
249
|
isInitializing = true;
|
|
250
250
|
|
|
251
|
-
//
|
|
251
|
+
// Create and save initialization Promise
|
|
252
252
|
globalThis.__LOBE__.pgDBInitPromise = (async () => {
|
|
253
|
-
//
|
|
253
|
+
// Check again in case another call succeeded during wait
|
|
254
254
|
if (globalThis.__LOBE__.pgDB) return globalThis.__LOBE__.pgDB;
|
|
255
255
|
|
|
256
|
-
//
|
|
256
|
+
// Get database path first
|
|
257
257
|
let dbPath: string = '';
|
|
258
258
|
try {
|
|
259
259
|
dbPath = await electronIpcClient.getDatabasePath();
|
|
@@ -263,34 +263,34 @@ export const getPgliteInstance = async (): Promise<LobeChatDatabase> => {
|
|
|
263
263
|
|
|
264
264
|
console.log('Database path:', dbPath);
|
|
265
265
|
try {
|
|
266
|
-
//
|
|
266
|
+
// Attempt to acquire database lock
|
|
267
267
|
const lockAcquired = await acquireLock(dbPath);
|
|
268
268
|
if (!lockAcquired) {
|
|
269
269
|
throw new Error('Cannot acquire database lock. Another instance might be using it.');
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
//
|
|
272
|
+
// Check and cleanup any existing old instances
|
|
273
273
|
await checkAndCleanupExistingInstance();
|
|
274
274
|
|
|
275
|
-
//
|
|
275
|
+
// Create new PGlite instance
|
|
276
276
|
console.log('Creating new PGlite instance');
|
|
277
277
|
const client = new PGlite(dbPath, {
|
|
278
278
|
extensions: { vector },
|
|
279
|
-
//
|
|
279
|
+
// Add options to improve stability
|
|
280
280
|
relaxedDurability: true,
|
|
281
281
|
});
|
|
282
282
|
|
|
283
|
-
//
|
|
283
|
+
// Wait for database to be ready
|
|
284
284
|
await client.waitReady;
|
|
285
285
|
console.log('PGlite state:', client.ready);
|
|
286
286
|
|
|
287
|
-
//
|
|
287
|
+
// Create Drizzle database instance
|
|
288
288
|
const db = pgliteDrizzle({ client, schema }) as unknown as LobeChatDatabase;
|
|
289
289
|
|
|
290
|
-
//
|
|
290
|
+
// Execute migration
|
|
291
291
|
await migrateDatabase(db);
|
|
292
292
|
|
|
293
|
-
//
|
|
293
|
+
// Save instance reference
|
|
294
294
|
globalThis.__LOBE__.pgDB = db;
|
|
295
295
|
|
|
296
296
|
console.log('✅ PGlite instance successfully initialized');
|
|
@@ -298,9 +298,9 @@ export const getPgliteInstance = async (): Promise<LobeChatDatabase> => {
|
|
|
298
298
|
return db;
|
|
299
299
|
} catch (error) {
|
|
300
300
|
console.error('❌ Failed to initialize PGlite instance:', error);
|
|
301
|
-
//
|
|
301
|
+
// Clear initialization Promise to allow retry next time
|
|
302
302
|
globalThis.__LOBE__.pgDBInitPromise = undefined;
|
|
303
|
-
//
|
|
303
|
+
// Release potentially acquired lock
|
|
304
304
|
releaseLock();
|
|
305
305
|
throw error;
|
|
306
306
|
} finally {
|
|
@@ -24,8 +24,8 @@ import { LobeChatDatabase } from '../../type';
|
|
|
24
24
|
type DecryptUserKeyVaults = (encryptKeyVaultsStr: string | null) => Promise<any>;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Provider
|
|
28
|
-
*
|
|
27
|
+
* Provider-level search defaults (only used when built-in models don't provide settings.searchImpl and settings.searchProvider)
|
|
28
|
+
* Note: Not stored in DB, only injected during read
|
|
29
29
|
*/
|
|
30
30
|
const PROVIDER_SEARCH_DEFAULTS: Record<
|
|
31
31
|
string,
|
|
@@ -40,12 +40,12 @@ const PROVIDER_SEARCH_DEFAULTS: Record<
|
|
|
40
40
|
hunyuan: { searchImpl: 'params' },
|
|
41
41
|
jina: { searchImpl: 'internal' },
|
|
42
42
|
minimax: { searchImpl: 'params' },
|
|
43
|
-
// openai:
|
|
43
|
+
// openai: defaults to params, but -search- models use internal as special case
|
|
44
44
|
openai: { searchImpl: 'params' },
|
|
45
|
-
// perplexity:
|
|
45
|
+
// perplexity: defaults to internal
|
|
46
46
|
perplexity: { searchImpl: 'internal' },
|
|
47
47
|
qwen: { searchImpl: 'params' },
|
|
48
|
-
spark: { searchImpl: 'params' }, //
|
|
48
|
+
spark: { searchImpl: 'params' }, // Some models (like max-32k) will prioritize built-in if marked as internal
|
|
49
49
|
stepfun: { searchImpl: 'params' },
|
|
50
50
|
vertexai: { searchImpl: 'params', searchProvider: 'google' },
|
|
51
51
|
wenxin: { searchImpl: 'params' },
|
|
@@ -53,7 +53,7 @@ const PROVIDER_SEARCH_DEFAULTS: Record<
|
|
|
53
53
|
zhipu: { searchImpl: 'params' },
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
//
|
|
56
|
+
// Special model configuration - model-level settings override provider defaults
|
|
57
57
|
const MODEL_SEARCH_DEFAULTS: Record<
|
|
58
58
|
string,
|
|
59
59
|
Record<string, { searchImpl?: 'tool' | 'params' | 'internal'; searchProvider?: string }>
|
|
@@ -61,15 +61,15 @@ const MODEL_SEARCH_DEFAULTS: Record<
|
|
|
61
61
|
openai: {
|
|
62
62
|
'gpt-4o-mini-search-preview': { searchImpl: 'internal' },
|
|
63
63
|
'gpt-4o-search-preview': { searchImpl: 'internal' },
|
|
64
|
-
//
|
|
64
|
+
// Add other special model configurations here
|
|
65
65
|
},
|
|
66
66
|
spark: {
|
|
67
67
|
'max-32k': { searchImpl: 'internal' },
|
|
68
68
|
},
|
|
69
|
-
//
|
|
69
|
+
// Add special model configurations for other providers here
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
//
|
|
72
|
+
// Infer default settings based on providerId + modelId
|
|
73
73
|
const inferProviderSearchDefaults = (
|
|
74
74
|
providerId: string | undefined,
|
|
75
75
|
modelId: string,
|
|
@@ -82,11 +82,11 @@ const inferProviderSearchDefaults = (
|
|
|
82
82
|
return (providerId && PROVIDER_SEARCH_DEFAULTS[providerId]) || PROVIDER_SEARCH_DEFAULTS.default;
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
//
|
|
85
|
+
// Only inject settings during read; add or remove search-related fields in settings based on abilities.search
|
|
86
86
|
const injectSearchSettings = (providerId: string, item: any) => {
|
|
87
87
|
const abilities = item?.abilities || {};
|
|
88
88
|
|
|
89
|
-
//
|
|
89
|
+
// Model explicitly disables search capability: remove search-related fields from settings to prevent UI from showing built-in search
|
|
90
90
|
if (abilities.search === false) {
|
|
91
91
|
if (item?.settings?.searchImpl || item?.settings?.searchProvider) {
|
|
92
92
|
const next = { ...item } as any;
|
|
@@ -100,12 +100,12 @@ const injectSearchSettings = (providerId: string, item: any) => {
|
|
|
100
100
|
return item;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
//
|
|
103
|
+
// Model explicitly enables search capability: add search-related fields to settings
|
|
104
104
|
else if (abilities.search === true) {
|
|
105
|
-
//
|
|
105
|
+
// If built-in (local) model already has either field, preserve it without overriding
|
|
106
106
|
if (item?.settings?.searchImpl || item?.settings?.searchProvider) return item;
|
|
107
107
|
|
|
108
|
-
//
|
|
108
|
+
// Otherwise use providerId + modelId
|
|
109
109
|
const searchSettings = inferProviderSearchDefaults(providerId, item.id);
|
|
110
110
|
|
|
111
111
|
return {
|
|
@@ -117,7 +117,7 @@ const injectSearchSettings = (providerId: string, item: any) => {
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
//
|
|
120
|
+
// Compatibility for legacy versions where database doesn't store abilities.search field
|
|
121
121
|
return item;
|
|
122
122
|
};
|
|
123
123
|
|
|
@@ -146,7 +146,7 @@ export class AiInfraRepos {
|
|
|
146
146
|
getAiProviderList = async () => {
|
|
147
147
|
const userProviders = await this.aiProviderModel.getAiProviderList();
|
|
148
148
|
|
|
149
|
-
// 1.
|
|
149
|
+
// 1. First create a mapping based on DEFAULT_MODEL_PROVIDER_LIST id order
|
|
150
150
|
const orderMap = new Map(DEFAULT_MODEL_PROVIDER_LIST.map((item, index) => [item.id, index]));
|
|
151
151
|
|
|
152
152
|
const builtinProviders = DEFAULT_MODEL_PROVIDER_LIST.map((item) => ({
|
|
@@ -161,7 +161,7 @@ export class AiInfraRepos {
|
|
|
161
161
|
|
|
162
162
|
const mergedProviders = mergeArrayById(builtinProviders, userProviders);
|
|
163
163
|
|
|
164
|
-
// 3.
|
|
164
|
+
// 3. Sort based on orderMap
|
|
165
165
|
return mergedProviders.sort((a, b) => {
|
|
166
166
|
const orderA = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
|
|
167
167
|
const orderB = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -205,7 +205,7 @@ export class AiInfraRepos {
|
|
|
205
205
|
.map<EnabledAiModel & { enabled?: boolean | null }>((item) => {
|
|
206
206
|
const user = allModels.find((m) => m.id === item.id && m.providerId === provider.id);
|
|
207
207
|
|
|
208
|
-
//
|
|
208
|
+
// User hasn't modified local model
|
|
209
209
|
if (!user)
|
|
210
210
|
return {
|
|
211
211
|
...item,
|
|
@@ -229,7 +229,7 @@ export class AiInfraRepos {
|
|
|
229
229
|
sort: user.sort || undefined,
|
|
230
230
|
type: user.type || item.type,
|
|
231
231
|
};
|
|
232
|
-
return injectSearchSettings(provider.id, mergedModel); //
|
|
232
|
+
return injectSearchSettings(provider.id, mergedModel); // User modified local model, check search settings
|
|
233
233
|
})
|
|
234
234
|
.filter((item) => (filterEnabled ? item.enabled : true));
|
|
235
235
|
},
|
|
@@ -237,7 +237,7 @@ export class AiInfraRepos {
|
|
|
237
237
|
);
|
|
238
238
|
|
|
239
239
|
const enabledProviderIds = new Set(enabledProviders.map((item) => item.id));
|
|
240
|
-
//
|
|
240
|
+
// User database models, check search settings
|
|
241
241
|
const appendedUserModels = allModels
|
|
242
242
|
.filter((item) =>
|
|
243
243
|
filterEnabled ? enabledProviderIds.has(item.providerId) && item.enabled : true,
|
|
@@ -284,7 +284,7 @@ export class AiInfraRepos {
|
|
|
284
284
|
|
|
285
285
|
const defaultModels: AiProviderModelListItem[] =
|
|
286
286
|
(await this.fetchBuiltinModels(providerId)) || [];
|
|
287
|
-
//
|
|
287
|
+
// Not modifying search settings here doesn't affect usage, but done for data consistency on get
|
|
288
288
|
const mergedModel = mergeArrayById(defaultModels, aiModels) as AiProviderModelListItem[];
|
|
289
289
|
|
|
290
290
|
return mergedModel.map((m) => injectSearchSettings(providerId, m));
|
|
@@ -105,11 +105,11 @@ export class DataExporterRepos {
|
|
|
105
105
|
try {
|
|
106
106
|
const conditions = [];
|
|
107
107
|
|
|
108
|
-
//
|
|
108
|
+
// Process each relation condition
|
|
109
109
|
for (const relation of config.relations) {
|
|
110
110
|
const sourceData = existingData[relation.sourceTable] || [];
|
|
111
111
|
|
|
112
|
-
//
|
|
112
|
+
// If source data is empty, this table may not be able to query any data
|
|
113
113
|
if (sourceData.length === 0) {
|
|
114
114
|
console.log(
|
|
115
115
|
`Source table ${relation.sourceTable} has no data, skipping query for ${table}`,
|
|
@@ -121,18 +121,18 @@ export class DataExporterRepos {
|
|
|
121
121
|
conditions.push(inArray(tableObj[relation.field], sourceIds));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
//
|
|
124
|
+
// If table has userId field and is not the users table, add user filter
|
|
125
125
|
if ('userId' in tableObj && table !== 'users' && !config.relations) {
|
|
126
126
|
conditions.push(eq(tableObj.userId, this.userId));
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
//
|
|
129
|
+
// Combine all conditions
|
|
130
130
|
const where = conditions.length === 1 ? conditions[0] : and(...conditions);
|
|
131
131
|
|
|
132
132
|
// @ts-expect-error query
|
|
133
133
|
const result = await this.db.query[table].findMany({ where });
|
|
134
134
|
|
|
135
|
-
//
|
|
135
|
+
// Only remove userId field for tables queried with userId
|
|
136
136
|
console.log(`Successfully exported table: ${table}, count: ${result.length}`);
|
|
137
137
|
return config.relations ? result : this.removeUserId(result);
|
|
138
138
|
} catch (error) {
|
|
@@ -147,16 +147,16 @@ export class DataExporterRepos {
|
|
|
147
147
|
if (!tableObj) throw new Error(`Table ${table} not found`);
|
|
148
148
|
|
|
149
149
|
try {
|
|
150
|
-
//
|
|
150
|
+
// If there's relation config, use relation query
|
|
151
151
|
|
|
152
|
-
//
|
|
152
|
+
// Default to querying with userId, use userField for special cases
|
|
153
153
|
const userField = config.userField || 'userId';
|
|
154
154
|
const where = eq(tableObj[userField], this.userId);
|
|
155
155
|
|
|
156
156
|
// @ts-expect-error query
|
|
157
157
|
const result = await this.db.query[table].findMany({ where });
|
|
158
158
|
|
|
159
|
-
//
|
|
159
|
+
// Only remove userId field for tables queried with userId
|
|
160
160
|
console.log(`Successfully exported table: ${table}, count: ${result.length}`);
|
|
161
161
|
return this.removeUserId(result);
|
|
162
162
|
} catch (error) {
|
|
@@ -168,7 +168,7 @@ export class DataExporterRepos {
|
|
|
168
168
|
async export(concurrency = 10) {
|
|
169
169
|
const result: Record<string, any[]> = {};
|
|
170
170
|
|
|
171
|
-
// 1.
|
|
171
|
+
// 1. First query all base tables concurrently
|
|
172
172
|
console.log('Querying base tables...');
|
|
173
173
|
const baseResults = await pMap(
|
|
174
174
|
DATA_EXPORT_CONFIG.baseTables,
|
|
@@ -176,17 +176,17 @@ export class DataExporterRepos {
|
|
|
176
176
|
{ concurrency },
|
|
177
177
|
);
|
|
178
178
|
|
|
179
|
-
//
|
|
179
|
+
// Update result set
|
|
180
180
|
baseResults.forEach(({ table, data }) => {
|
|
181
181
|
result[table] = data;
|
|
182
182
|
});
|
|
183
183
|
|
|
184
|
-
// 2.
|
|
184
|
+
// 2. Then query all relation tables concurrently
|
|
185
185
|
|
|
186
186
|
const relationResults = await pMap(
|
|
187
187
|
DATA_EXPORT_CONFIG.relationTables,
|
|
188
188
|
async (config) => {
|
|
189
|
-
//
|
|
189
|
+
// Check if all dependent source tables have data
|
|
190
190
|
const allSourcesHaveData = config.relations.every(
|
|
191
191
|
(relation) => (result[relation.sourceTable] || []).length > 0,
|
|
192
192
|
);
|
|
@@ -204,7 +204,7 @@ export class DataExporterRepos {
|
|
|
204
204
|
{ concurrency },
|
|
205
205
|
);
|
|
206
206
|
|
|
207
|
-
//
|
|
207
|
+
// Update result set
|
|
208
208
|
relationResults.forEach(({ table, data }) => {
|
|
209
209
|
result[table] = data;
|
|
210
210
|
});
|
|
@@ -19,7 +19,7 @@ export class TableViewerRepo {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Get all tables in the database
|
|
23
23
|
*/
|
|
24
24
|
async getAllTables(schema = 'public'): Promise<TableBasicInfo[]> {
|
|
25
25
|
const query = sql`
|
|
@@ -47,7 +47,7 @@ export class TableViewerRepo {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Get detailed structure information for a specified table
|
|
51
51
|
*/
|
|
52
52
|
async getTableDetails(tableName: string): Promise<TableColumnInfo[]> {
|
|
53
53
|
const query = sql`
|
|
@@ -56,7 +56,7 @@ export class TableViewerRepo {
|
|
|
56
56
|
c.data_type,
|
|
57
57
|
c.is_nullable,
|
|
58
58
|
c.column_default,
|
|
59
|
-
--
|
|
59
|
+
-- Primary key information
|
|
60
60
|
(
|
|
61
61
|
SELECT true
|
|
62
62
|
FROM information_schema.table_constraints tc
|
|
@@ -66,7 +66,7 @@ export class TableViewerRepo {
|
|
|
66
66
|
AND kcu.column_name = c.column_name
|
|
67
67
|
AND tc.constraint_type = 'PRIMARY KEY'
|
|
68
68
|
) is_primary_key,
|
|
69
|
-
--
|
|
69
|
+
-- Foreign key information
|
|
70
70
|
(
|
|
71
71
|
SELECT json_build_object(
|
|
72
72
|
'table', ccu.table_name,
|
|
@@ -100,15 +100,15 @@ export class TableViewerRepo {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
|
-
*
|
|
103
|
+
* Get table data with support for pagination, sorting, and filtering
|
|
104
104
|
*/
|
|
105
105
|
async getTableData(tableName: string, pagination: PaginationParams, filters?: FilterCondition[]) {
|
|
106
106
|
const offset = (pagination.page - 1) * pagination.pageSize;
|
|
107
107
|
|
|
108
|
-
//
|
|
108
|
+
// Build base query
|
|
109
109
|
let baseQuery = sql`SELECT * FROM ${sql.identifier(tableName)}`;
|
|
110
110
|
|
|
111
|
-
//
|
|
111
|
+
// Add filter conditions
|
|
112
112
|
if (filters && filters.length > 0) {
|
|
113
113
|
const whereConditions = filters.map((filter) => {
|
|
114
114
|
const column = sql.identifier(filter.column);
|
|
@@ -135,19 +135,19 @@ export class TableViewerRepo {
|
|
|
135
135
|
baseQuery = sql`${baseQuery} WHERE ${sql.join(whereConditions, sql` AND `)}`;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
//
|
|
138
|
+
// Add sorting
|
|
139
139
|
if (pagination.sortBy) {
|
|
140
140
|
const direction = pagination.sortOrder === 'desc' ? sql`DESC` : sql`ASC`;
|
|
141
141
|
baseQuery = sql`${baseQuery} ORDER BY ${sql.identifier(pagination.sortBy)} ${direction}`;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
//
|
|
144
|
+
// Add pagination
|
|
145
145
|
const query = sql`${baseQuery} LIMIT ${pagination.pageSize} OFFSET ${offset}`;
|
|
146
146
|
|
|
147
|
-
//
|
|
147
|
+
// Get total count
|
|
148
148
|
const countQuery = sql`SELECT COUNT(*) as total FROM ${sql.identifier(tableName)}`;
|
|
149
149
|
|
|
150
|
-
//
|
|
150
|
+
// Execute queries in parallel
|
|
151
151
|
const [data, count] = await Promise.all([this.db.execute(query), this.db.execute(countQuery)]);
|
|
152
152
|
|
|
153
153
|
return {
|
|
@@ -161,7 +161,7 @@ export class TableViewerRepo {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
|
-
*
|
|
164
|
+
* Update a row in the table
|
|
165
165
|
*/
|
|
166
166
|
async updateRow(
|
|
167
167
|
tableName: string,
|
|
@@ -185,7 +185,7 @@ export class TableViewerRepo {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
|
-
*
|
|
188
|
+
* Delete a row from the table
|
|
189
189
|
*/
|
|
190
190
|
async deleteRow(tableName: string, id: string, primaryKeyColumn: string) {
|
|
191
191
|
const query = sql`
|
|
@@ -197,7 +197,7 @@ export class TableViewerRepo {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/**
|
|
200
|
-
*
|
|
200
|
+
* Insert new row data
|
|
201
201
|
*/
|
|
202
202
|
async insertRow(tableName: string, data: Record<string, any>) {
|
|
203
203
|
const columns = Object.keys(data).map((key) => sql.identifier(key));
|
|
@@ -218,7 +218,7 @@ export class TableViewerRepo {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
|
-
*
|
|
221
|
+
* Get total record count of a table
|
|
222
222
|
*/
|
|
223
223
|
async getTableCount(tableName: string): Promise<number> {
|
|
224
224
|
const query = sql`SELECT COUNT(*) as total FROM ${sql.identifier(tableName)}`;
|
|
@@ -227,7 +227,7 @@ export class TableViewerRepo {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
/**
|
|
230
|
-
*
|
|
230
|
+
* Batch delete data
|
|
231
231
|
*/
|
|
232
232
|
async batchDelete(tableName: string, ids: string[], primaryKeyColumn: string) {
|
|
233
233
|
const query = sql`
|
|
@@ -239,7 +239,7 @@ export class TableViewerRepo {
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
/**
|
|
242
|
-
*
|
|
242
|
+
* Export table data (supports paginated export)
|
|
243
243
|
*/
|
|
244
244
|
async exportTableData(
|
|
245
245
|
tableName: string,
|
package/src/auth.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
2
2
|
import { createNanoId, idGenerator, serverDB } from '@lobechat/database';
|
|
3
|
-
import { betterAuth } from 'better-auth';
|
|
3
|
+
import { betterAuth } from 'better-auth/minimal';
|
|
4
4
|
import { emailHarmony } from 'better-auth-harmony';
|
|
5
5
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
|
6
6
|
import { admin, genericOAuth, magicLink } from 'better-auth/plugins';
|
|
@@ -122,6 +122,13 @@ export const auth = betterAuth({
|
|
|
122
122
|
database: drizzleAdapter(serverDB, {
|
|
123
123
|
provider: 'pg',
|
|
124
124
|
}),
|
|
125
|
+
/**
|
|
126
|
+
* Database joins is useful when Better-Auth needs to fetch related data from multiple tables in a single query.
|
|
127
|
+
* Endpoints like /get-session, /get-full-organization and many others benefit greatly from this feature,
|
|
128
|
+
* seeing upwards of 2x to 3x performance improvements depending on database latency.
|
|
129
|
+
* Ref: https://www.better-auth.com/docs/adapters/drizzle#joins-experimental
|
|
130
|
+
*/
|
|
131
|
+
experimental: { joins: true },
|
|
125
132
|
/**
|
|
126
133
|
* Run user bootstrap for every newly created account (email, magic link, OAuth/social, etc.).
|
|
127
134
|
* Using Better Auth database hooks ensures we catch social flows that bypass /sign-up/* routes.
|