@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 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2025-12-05",
5
+ "version": "2.0.0-next.160"
6
+ },
2
7
  {
3
8
  "children": {
4
9
  "features": [
@@ -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": "Pin",
258
- "pinOff": "Unpin",
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": "Text-to-Speech",
424
- "clear": "Clear Speech"
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": "Auto-generate agent description based on prompts",
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": "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": "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": "Password",
313
- "pin": "Pin",
314
- "pinOff": "Unpin",
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": "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": "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.159",
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
- // 确保 globalThis 有我们的命名空间
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
- * 返回 true 表示成功获取锁,false 表示已有其他实例正在运行
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
- // 创建锁文件并写入当前进程 ID
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
- // 检查锁文件是否过期(超过5分钟未更新)
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
- // 如果锁文件超过5分钟未更新,视为过期锁
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
- // 如果表数量大于0,则认为数据库已正确初始化
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
- // 尝试关闭现有的 PGlite 实例 (如果客户端有 close 方法)
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
- // 有初始化进行中的Promise,等待它完成
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
- // 创建新的 Promise 等待初始化完成
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
- // 创建初始化Promise并保存
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
- // 创建新的 PGlite 实例
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
- // 创建 Drizzle 数据库实例
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
- // 清空初始化Promise,允许下次重试
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 级默认表(只在本地内置模型没给出 settings.searchImpl settings.searchProvider 时使用)
28
- * 注意:不在 DB 存储,纯读取时注入
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: 默认 params,但对 -search- 型号做 internal 特判
43
+ // openai: defaults to params, but -search- models use internal as special case
44
44
  openai: { searchImpl: 'params' },
45
- // perplexity: 默认 internal
45
+ // perplexity: defaults to internal
46
46
  perplexity: { searchImpl: 'internal' },
47
47
  qwen: { searchImpl: 'params' },
48
- spark: { searchImpl: 'params' }, // 某些模型(如 max-32k)若内置标了 internal,会优先使用内置
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
- // 根据 providerId + modelId 推断默认 settings
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
- // 仅在读取时注入 settings; 根据 abilities.search 来添加或删去settings 中的 search 相关字段
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
- // 模型显式关闭搜索能力:移除 settings 中的 search 相关字段,确保 UI 不显示启用模型内置搜索
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
- // 模型显式开启搜索能力:添加 settings 中的 search 相关字段
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
- // 否则按 providerId + modelId
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
- // 兼容老版本中数据库没有存储 abilities.search 字段的情况
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. 先创建一个基于 DEFAULT_MODEL_PROVIDER_LIST id 顺序的映射
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. 根据 orderMap 排序
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
- // 这里不修改搜索设置不影响使用,但是为了get数据统一
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
- // 如果表有userId字段并且不是users表,添加用户过滤
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
- // 只对使用 userId 查询的表移除 userId 字段
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
- // 默认使用 userId 查询,特殊情况使用 userField
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
- // 只对使用 userId 查询的表移除 userId 字段
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.