@lobehub/lobehub 2.0.0-next.92 → 2.0.0-next.94

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/common.json +1 -0
  4. package/locales/bg-BG/common.json +1 -0
  5. package/locales/de-DE/common.json +1 -0
  6. package/locales/es-ES/common.json +1 -0
  7. package/locales/fa-IR/common.json +1 -0
  8. package/locales/fr-FR/common.json +1 -0
  9. package/locales/it-IT/common.json +1 -0
  10. package/locales/ja-JP/common.json +1 -0
  11. package/locales/ko-KR/common.json +1 -0
  12. package/locales/nl-NL/common.json +1 -0
  13. package/locales/pl-PL/common.json +1 -0
  14. package/locales/pt-BR/common.json +1 -0
  15. package/locales/ru-RU/common.json +1 -0
  16. package/locales/tr-TR/common.json +1 -0
  17. package/locales/vi-VN/common.json +1 -0
  18. package/locales/zh-TW/common.json +1 -0
  19. package/package.json +1 -1
  20. package/packages/database/src/client/pglite.ts +1 -1
  21. package/packages/database/src/core/db-adaptor.ts +4 -4
  22. package/packages/database/src/models/file.ts +32 -32
  23. package/packages/database/src/models/message.ts +7 -7
  24. package/packages/database/src/schemas/file.ts +12 -12
  25. package/packages/database/src/schemas/message.ts +4 -4
  26. package/packages/database/src/schemas/rag.ts +2 -2
  27. package/packages/model-bank/src/aiModels/xai.ts +85 -6
  28. package/src/features/ModelSwitchPanel/index.tsx +15 -13
  29. package/src/app/[variants]/(main)/profile/(home)/[[...slugs]]/page.tsx +0 -40
  30. package/src/app/[variants]/(main)/settings/error.tsx +0 -3
  31. package/src/app/[variants]/(main)/settings/loading.tsx +0 -3
  32. package/src/app/[variants]/(main)/settings/not-found.tsx +0 -1
  33. package/src/app/[variants]/(main)/settings/page.tsx +0 -41
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.94](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.93...v2.0.0-next.94)
6
+
7
+ <sup>Released on **2025-11-20**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Provider settings button unable to redirect.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Provider settings button unable to redirect, closes [#10319](https://github.com/lobehub/lobe-chat/issues/10319) ([e025fec](https://github.com/lobehub/lobe-chat/commit/e025fec))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.93](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.92...v2.0.0-next.93)
31
+
32
+ <sup>Released on **2025-11-20**</sup>
33
+
34
+ #### 💄 Styles
35
+
36
+ - **misc**: Update i18n.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Styles
44
+
45
+ - **misc**: Update i18n, closes [#10317](https://github.com/lobehub/lobe-chat/issues/10317) ([8fb9890](https://github.com/lobehub/lobe-chat/commit/8fb9890))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.92](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.91...v2.0.0-next.92)
6
56
 
7
57
  <sup>Released on **2025-11-19**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Provider settings button unable to redirect."
6
+ ]
7
+ },
8
+ "date": "2025-11-20",
9
+ "version": "2.0.0-next.94"
10
+ },
11
+ {
12
+ "children": {
13
+ "improvements": [
14
+ "Update i18n."
15
+ ]
16
+ },
17
+ "date": "2025-11-20",
18
+ "version": "2.0.0-next.93"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "improvements": [
@@ -283,6 +283,7 @@
283
283
  "business": "شراكات تجارية",
284
284
  "support": "الدعم عبر البريد الإلكتروني"
285
285
  },
286
+ "new": "جديد",
286
287
  "oauth": "تسجيل الدخول SSO",
287
288
  "officialSite": "الموقع الرسمي",
288
289
  "ok": "موافق",
@@ -283,6 +283,7 @@
283
283
  "business": "Бизнес сътрудничество",
284
284
  "support": "Поддръжка по имейл"
285
285
  },
286
+ "new": "Нов",
286
287
  "oauth": "SSO Вход",
287
288
  "officialSite": "Официален сайт",
288
289
  "ok": "Добре",
@@ -283,6 +283,7 @@
283
283
  "business": "Geschäftliche Zusammenarbeit",
284
284
  "support": "E-Mail-Support"
285
285
  },
286
+ "new": "Neu",
286
287
  "oauth": "SSO-Anmeldung",
287
288
  "officialSite": "Offizielle Website",
288
289
  "ok": "OK",
@@ -283,6 +283,7 @@
283
283
  "business": "Colaboración Comercial",
284
284
  "support": "Soporte por Correo"
285
285
  },
286
+ "new": "Nuevo",
286
287
  "oauth": "Inicio de sesión SSO",
287
288
  "officialSite": "Sitio oficial",
288
289
  "ok": "Aceptar",
@@ -283,6 +283,7 @@
283
283
  "business": "همکاری تجاری",
284
284
  "support": "پشتیبانی ایمیل"
285
285
  },
286
+ "new": "جدید",
286
287
  "oauth": "ورود با SSO",
287
288
  "officialSite": "وب‌سایت رسمی",
288
289
  "ok": "تأیید",
@@ -283,6 +283,7 @@
283
283
  "business": "Partenariats commerciaux",
284
284
  "support": "Support par e-mail"
285
285
  },
286
+ "new": "Nouveau",
286
287
  "oauth": "Connexion SSO",
287
288
  "officialSite": "Site officiel",
288
289
  "ok": "OK",
@@ -283,6 +283,7 @@
283
283
  "business": "Collaborazioni commerciali",
284
284
  "support": "Supporto via email"
285
285
  },
286
+ "new": "Nuovo",
286
287
  "oauth": "Accesso SSO",
287
288
  "officialSite": "Sito ufficiale",
288
289
  "ok": "OK",
@@ -283,6 +283,7 @@
283
283
  "business": "ビジネス提携",
284
284
  "support": "メールサポート"
285
285
  },
286
+ "new": "新規",
286
287
  "oauth": "SSO ログイン",
287
288
  "officialSite": "公式サイト",
288
289
  "ok": "OK",
@@ -283,6 +283,7 @@
283
283
  "business": "비즈니스 제휴",
284
284
  "support": "이메일 지원"
285
285
  },
286
+ "new": "새로운",
286
287
  "oauth": "SSO 로그인",
287
288
  "officialSite": "공식 웹사이트",
288
289
  "ok": "확인",
@@ -283,6 +283,7 @@
283
283
  "business": "Zakelijke samenwerking",
284
284
  "support": "E-mailondersteuning"
285
285
  },
286
+ "new": "Nieuw",
286
287
  "oauth": "SSO inloggen",
287
288
  "officialSite": "Officiële website",
288
289
  "ok": "Oké",
@@ -283,6 +283,7 @@
283
283
  "business": "Współpraca biznesowa",
284
284
  "support": "Wsparcie mailowe"
285
285
  },
286
+ "new": "Nowy",
286
287
  "oauth": "Logowanie SSO",
287
288
  "officialSite": "Oficjalna strona internetowa",
288
289
  "ok": "OK",
@@ -283,6 +283,7 @@
283
283
  "business": "Parcerias Comerciais",
284
284
  "support": "Suporte por E-mail"
285
285
  },
286
+ "new": "Novo",
286
287
  "oauth": "Login SSO",
287
288
  "officialSite": "Site Oficial",
288
289
  "ok": "OK",
@@ -283,6 +283,7 @@
283
283
  "business": "Деловое сотрудничество",
284
284
  "support": "Поддержка по электронной почте"
285
285
  },
286
+ "new": "Новый",
286
287
  "oauth": "Вход через единую учетную запись (SSO)",
287
288
  "officialSite": "Официальный сайт",
288
289
  "ok": "ОК",
@@ -283,6 +283,7 @@
283
283
  "business": "İşbirliği",
284
284
  "support": "E-posta Desteği"
285
285
  },
286
+ "new": "Yeni",
286
287
  "oauth": "SSO Girişi",
287
288
  "officialSite": "Resmi Site",
288
289
  "ok": "Tamam",
@@ -283,6 +283,7 @@
283
283
  "business": "Hợp tác kinh doanh",
284
284
  "support": "Hỗ trợ qua email"
285
285
  },
286
+ "new": "Mới",
286
287
  "oauth": "Đăng nhập SSO",
287
288
  "officialSite": "Trang web chính thức",
288
289
  "ok": "Đồng ý",
@@ -283,6 +283,7 @@
283
283
  "business": "商務合作",
284
284
  "support": "郵件支持"
285
285
  },
286
+ "new": "新增",
286
287
  "oauth": "單一登錄(SSO)",
287
288
  "officialSite": "官方網站",
288
289
  "ok": "確定",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.92",
3
+ "version": "2.0.0-next.94",
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",
@@ -8,7 +8,7 @@ export const initPgliteWorker = async (meta: InitMeta) => {
8
8
  { meta },
9
9
  );
10
10
 
11
- // 监听 worker 状态变化
11
+ // Listen for worker status changes
12
12
  worker.onLeaderChange(() => {
13
13
  console.log('Worker leader changed, isLeader:', worker?.isLeader);
14
14
  });
@@ -5,17 +5,17 @@ import { getPgliteInstance } from './electron';
5
5
  import { getDBInstance } from './web-server';
6
6
 
7
7
  /**
8
- * 懒加载数据库实例
9
- * 避免每次模块导入时都初始化数据库
8
+ * Lazy-load database instance
9
+ * Avoid initializing the database every time the module is imported
10
10
  */
11
11
  let cachedDB: LobeChatDatabase | null = null;
12
12
 
13
13
  export const getServerDB = async (): Promise<LobeChatDatabase> => {
14
- // 如果已经有缓存的实例,直接返回
14
+ // If there's already a cached instance, return it directly
15
15
  if (cachedDB) return cachedDB;
16
16
 
17
17
  try {
18
- // 根据环境选择合适的数据库实例
18
+ // Select the appropriate database instance based on the environment
19
19
  cachedDB = isDesktop ? await getPgliteInstance() : getDBInstance();
20
20
  return cachedDB;
21
21
  } catch (error) {
@@ -101,7 +101,7 @@ export class FileModel {
101
101
 
102
102
  delete = async (id: string, removeGlobalFile: boolean = true, trx?: Transaction) => {
103
103
  const executeInTransaction = async (tx: Transaction) => {
104
- // pglite 环境下不能再 transaction 中使用非事务操作,会阻塞住
104
+ // In pglite environment, non-transactional operations cannot be used within a transaction as it will block
105
105
  const file = await this.findById(id, tx);
106
106
  if (!file) return;
107
107
 
@@ -151,26 +151,26 @@ export class FileModel {
151
151
  if (ids.length === 0) return [];
152
152
 
153
153
  return await this.db.transaction(async (trx) => {
154
- // 1. 先获取文件列表,以便返回删除的文件
154
+ // 1. First get the file list to return the deleted files
155
155
  const fileList = await trx.query.files.findMany({
156
156
  where: and(inArray(files.id, ids), eq(files.userId, this.userId)),
157
157
  });
158
158
 
159
159
  if (fileList.length === 0) return [];
160
160
 
161
- // 提取需要检查的文件哈希值
161
+ // Extract file hashes that need to be checked
162
162
  const hashList = fileList.map((file) => file.fileHash!).filter(Boolean);
163
163
 
164
- // 2. 删除相关的 chunks
164
+ // 2. Delete related chunks
165
165
  await this.deleteFileChunks(trx as any, ids);
166
166
 
167
- // 3. 删除文件记录
167
+ // 3. Delete file records
168
168
  await trx.delete(files).where(and(inArray(files.id, ids), eq(files.userId, this.userId)));
169
169
 
170
- // 如果不需要删除全局文件,直接返回
170
+ // If global files don't need to be deleted, return directly
171
171
  if (!removeGlobalFile || hashList.length === 0) return fileList;
172
172
 
173
- // 4. 找出不再被引用的哈希值
173
+ // 4. Find hashes that are no longer referenced
174
174
  const remainingFiles = await trx
175
175
  .select({
176
176
  fileHash: files.fileHash,
@@ -178,18 +178,18 @@ export class FileModel {
178
178
  .from(files)
179
179
  .where(inArray(files.fileHash, hashList));
180
180
 
181
- // 将仍在使用的哈希值放入Set中,便于快速查找
181
+ // Put still-in-use hashes into a Set for quick lookup
182
182
  const usedHashes = new Set(remainingFiles.map((file) => file.fileHash));
183
183
 
184
- // 找出需要删除的哈希值(不再被任何文件使用的)
184
+ // Find hashes to delete (those no longer used by any file)
185
185
  const hashesToDelete = hashList.filter((hash) => !usedHashes.has(hash));
186
186
 
187
187
  if (hashesToDelete.length === 0) return fileList;
188
188
 
189
- // 5. 删除不再被引用的全局文件
189
+ // 5. Delete global files that are no longer referenced
190
190
  await trx.delete(globalFiles).where(inArray(globalFiles.hashId, hashesToDelete));
191
191
 
192
- // 返回删除的文件列表
192
+ // Return the list of deleted files
193
193
  return fileList;
194
194
  });
195
195
  };
@@ -206,7 +206,7 @@ export class FileModel {
206
206
  knowledgeBaseId,
207
207
  showFilesInKnowledgeBase,
208
208
  }: QueryFileListParams = {}) => {
209
- // 1. query where
209
+ // 1. Build where clause
210
210
  let whereClause = and(
211
211
  q ? ilike(files.name, `%${q}%`) : undefined,
212
212
  eq(files.userId, this.userId),
@@ -224,7 +224,7 @@ export class FileModel {
224
224
  }
225
225
  }
226
226
 
227
- // 2. order part
227
+ // 2. Build order clause
228
228
 
229
229
  let orderByClause = desc(files.createdAt);
230
230
  // create a map for sortable fields
@@ -241,7 +241,7 @@ export class FileModel {
241
241
  orderByClause = sortFunction(sortableFields[sorter as SortableField]);
242
242
  }
243
243
 
244
- // 3. build query
244
+ // 3. Build base query
245
245
  let query = this.db
246
246
  .select({
247
247
  chunkTaskId: files.chunkTaskId,
@@ -256,7 +256,7 @@ export class FileModel {
256
256
  })
257
257
  .from(files);
258
258
 
259
- // 4. add knowledge base query
259
+ // 4. Add knowledge base query if needed
260
260
  if (knowledgeBaseId) {
261
261
  // if knowledgeBaseId is provided, it means we are querying files in a knowledge-base
262
262
 
@@ -269,7 +269,7 @@ export class FileModel {
269
269
  ),
270
270
  );
271
271
  }
272
- // 5.if we don't show files in knowledge base, we need exclude files in knowledge base
272
+ // 5. If we don't show files in knowledge base, exclude them
273
273
  else if (!showFilesInKnowledgeBase) {
274
274
  whereClause = and(
275
275
  whereClause,
@@ -279,7 +279,7 @@ export class FileModel {
279
279
  );
280
280
  }
281
281
 
282
- // or we are just filter in the global files
282
+ // Otherwise, we are just filtering in the global files
283
283
  return query.where(whereClause).orderBy(orderByClause);
284
284
  };
285
285
 
@@ -347,11 +347,11 @@ export class FileModel {
347
347
  ),
348
348
  });
349
349
 
350
- // 抽象出通用的删除 chunks 方法
350
+ // Abstract common method for deleting chunks
351
351
  private deleteFileChunks = async (trx: PgTransaction<any>, fileIds: string[]) => {
352
352
  if (fileIds.length === 0) return;
353
353
 
354
- // 获取要删除的文件相关的所有 chunk IDs(移除知识库保护逻辑)
354
+ // Get all chunk IDs related to the files to be deleted (knowledge base protection logic removed)
355
355
  const relatedChunks = await trx
356
356
  .select({ chunkId: fileChunks.chunkId })
357
357
  .from(fileChunks)
@@ -361,15 +361,15 @@ export class FileModel {
361
361
 
362
362
  if (chunkIds.length === 0) return;
363
363
 
364
- // 批量处理配置
364
+ // Batch processing configuration
365
365
  const BATCH_SIZE = 1000;
366
366
  const MAX_CONCURRENT_BATCHES = 3;
367
367
 
368
- // 分批并行处理
368
+ // Process in batches concurrently
369
369
  for (let i = 0; i < chunkIds.length; i += BATCH_SIZE * MAX_CONCURRENT_BATCHES) {
370
370
  const batchPromises = [];
371
371
 
372
- // 创建多个并行批次
372
+ // Create multiple parallel batches
373
373
  for (let j = 0; j < MAX_CONCURRENT_BATCHES; j++) {
374
374
  const startIdx = i + j * BATCH_SIZE;
375
375
  if (startIdx >= chunkIds.length) break;
@@ -377,29 +377,29 @@ export class FileModel {
377
377
  const batchChunkIds = chunkIds.slice(startIdx, startIdx + BATCH_SIZE);
378
378
  if (batchChunkIds.length === 0) continue;
379
379
 
380
- // 按正确的删除顺序处理每个批次,失败不阻止流程
380
+ // Process each batch in the correct deletion order, failures do not block the flow
381
381
  const batchPromise = (async () => {
382
- // 1. 删除 embeddings (最顶层,有外键依赖)
382
+ // 1. Delete embeddings (top-level, has foreign key dependencies)
383
383
  try {
384
384
  await trx.delete(embeddings).where(inArray(embeddings.chunkId, batchChunkIds));
385
385
  } catch (e) {
386
- // 静默处理,不阻止删除流程
386
+ // Silent handling, does not block deletion process
387
387
  console.warn('Failed to delete embeddings:', e);
388
388
  }
389
389
 
390
- // 2. 删除 documentChunks 关联 (如果存在)
390
+ // 2. Delete documentChunks association (if exists)
391
391
  try {
392
392
  await trx.delete(documentChunks).where(inArray(documentChunks.chunkId, batchChunkIds));
393
393
  } catch (e) {
394
- // 静默处理,不阻止删除流程
394
+ // Silent handling, does not block deletion process
395
395
  console.warn('Failed to delete documentChunks:', e);
396
396
  }
397
397
 
398
- // 3. 删除 chunks (核心数据)
398
+ // 3. Delete chunks (core data)
399
399
  try {
400
400
  await trx.delete(chunks).where(inArray(chunks.id, batchChunkIds));
401
401
  } catch (e) {
402
- // 静默处理,不阻止删除流程
402
+ // Silent handling, does not block deletion process
403
403
  console.warn('Failed to delete chunks:', e);
404
404
  }
405
405
  })();
@@ -407,15 +407,15 @@ export class FileModel {
407
407
  batchPromises.push(batchPromise);
408
408
  }
409
409
 
410
- // 等待当前批次的所有任务完成
410
+ // Wait for all tasks in the current batch to complete
411
411
  await Promise.all(batchPromises);
412
412
  }
413
413
 
414
- // 4. 最后删除 fileChunks 关联表记录
414
+ // 4. Finally delete fileChunks association table records
415
415
  try {
416
416
  await trx.delete(fileChunks).where(inArray(fileChunks.fileId, fileIds));
417
417
  } catch (e) {
418
- // 静默处理,不阻止删除流程
418
+ // Silent handling, does not block deletion process
419
419
  console.warn('Failed to delete fileChunks:', e);
420
420
  }
421
421
 
@@ -155,7 +155,7 @@ export class MessageModel {
155
155
  })),
156
156
  );
157
157
 
158
- // 获取关联的文档内容
158
+ // Get associated document content
159
159
  const fileIds = relatedFileList.map((file) => file.id).filter(Boolean);
160
160
 
161
161
  let documentsMap: Record<string, string> = {};
@@ -662,17 +662,17 @@ export class MessageModel {
662
662
 
663
663
  deleteMessage = async (id: string) => {
664
664
  return this.db.transaction(async (tx) => {
665
- // 1. 查询要删除的 message 的完整信息
665
+ // 1. Query the complete information of the message to be deleted
666
666
  const message = await tx
667
667
  .select()
668
668
  .from(messages)
669
669
  .where(and(eq(messages.id, id), eq(messages.userId, this.userId)))
670
670
  .limit(1);
671
671
 
672
- // 如果找不到要删除的 message,直接返回
672
+ // If the message to be deleted is not found, return directly
673
673
  if (message.length === 0) return;
674
674
 
675
- // 2. 检查 message 是否包含 tools
675
+ // 2. Check if the message contains tools
676
676
  const toolCallIds = (message[0].tools as ChatToolPayload[])
677
677
  ?.map((tool) => tool.id)
678
678
  .filter(Boolean);
@@ -680,7 +680,7 @@ export class MessageModel {
680
680
  let relatedMessageIds: string[] = [];
681
681
 
682
682
  if (toolCallIds?.length > 0) {
683
- // 3. 如果 message 包含 tools,查询出所有相关联的 message id
683
+ // 3. If the message contains tools, query all associated message ids
684
684
  const res = await tx
685
685
  .select({ id: messagePlugins.id })
686
686
  .from(messagePlugins)
@@ -689,10 +689,10 @@ export class MessageModel {
689
689
  relatedMessageIds = res.map((row) => row.id);
690
690
  }
691
691
 
692
- // 4. 合并要删除的 message id 列表
692
+ // 4. Merge the list of message ids to be deleted
693
693
  const messageIdsToDelete = [id, ...relatedMessageIds];
694
694
 
695
- // 5. 删除所有相关的 message
695
+ // 5. Delete all related messages
696
696
  await tx.delete(messages).where(inArray(messages.id, messageIdsToDelete));
697
697
  });
698
698
  };
@@ -38,7 +38,7 @@ export type NewGlobalFile = typeof globalFiles.$inferInsert;
38
38
  export type GlobalFileItem = typeof globalFiles.$inferSelect;
39
39
 
40
40
  /**
41
- * 文档表 - 存储文件内容或网页搜索结果
41
+ * Documents table - Stores file content or web search results
42
42
  */
43
43
  // @ts-ignore
44
44
  export const documents = pgTable(
@@ -48,7 +48,7 @@ export const documents = pgTable(
48
48
  .$defaultFn(() => idGenerator('documents', 16))
49
49
  .primaryKey(),
50
50
 
51
- // 基本信息
51
+ // Basic information
52
52
  title: text('title'),
53
53
  content: text('content'),
54
54
 
@@ -56,34 +56,34 @@ export const documents = pgTable(
56
56
  fileType: varchar('file_type', { length: 255 }).notNull(),
57
57
  filename: text('filename'),
58
58
 
59
- // 统计信息
59
+ // Statistics
60
60
  totalCharCount: integer('total_char_count').notNull(),
61
61
  totalLineCount: integer('total_line_count').notNull(),
62
62
 
63
- // 元数据
63
+ // Metadata
64
64
  metadata: jsonb('metadata').$type<Record<string, any>>(),
65
65
 
66
- // 页面/块数据
66
+ // Page/chunk data
67
67
  pages: jsonb('pages').$type<LobeDocumentPage[]>(),
68
68
 
69
- // 来源类型
69
+ // Source type
70
70
  sourceType: text('source_type', { enum: ['file', 'web', 'api'] }).notNull(),
71
- source: text('source').notNull(), // 文件路径或网页URL
71
+ source: text('source').notNull(), // File path or web URL
72
72
 
73
- // 关联文件(可选)
73
+ // Associated file (optional)
74
74
  // Forward reference to files table defined below
75
75
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
76
76
  // @ts-expect-error - files is defined later in this file, forward reference is valid at runtime
77
77
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
78
78
  fileId: text('file_id').references(() => files.id, { onDelete: 'set null' }),
79
79
 
80
- // 父文档(用于文件夹层级结构)
80
+ // Parent document (for folder hierarchy structure)
81
81
  // @ts-ignore
82
82
  parentId: varchar('parent_id', { length: 255 }).references(() => documents.id, {
83
83
  onDelete: 'set null',
84
84
  }),
85
85
 
86
- // 用户关联
86
+ // User association
87
87
  userId: text('user_id')
88
88
  .references(() => users.id, { onDelete: 'cascade' })
89
89
  .notNull(),
@@ -91,7 +91,7 @@ export const documents = pgTable(
91
91
 
92
92
  editorData: jsonb('editor_data').$type<Record<string, any>>(),
93
93
 
94
- // 时间戳
94
+ // Timestamps
95
95
  ...timestamps,
96
96
  },
97
97
  (table) => [
@@ -133,7 +133,7 @@ export const files = pgTable(
133
133
  url: text('url').notNull(),
134
134
  source: text('source').$type<FileSource>(),
135
135
 
136
- // 父文档(用于文件夹层级结构)
136
+ // Parent document (for folder hierarchy structure)
137
137
  // @ts-ignore
138
138
  parentId: varchar('parent_id', { length: 255 }).references(() => documents.id, {
139
139
  onDelete: 'set null',
@@ -36,25 +36,25 @@ export const messageGroups = pgTable(
36
36
  .$defaultFn(() => idGenerator('messageGroups'))
37
37
  .notNull(),
38
38
 
39
- // 关联关系 - 只需要 topic 层级
39
+ // Association - only needs topic level
40
40
  topicId: text('topic_id').references(() => topics.id, { onDelete: 'cascade' }),
41
41
  userId: text('user_id')
42
42
  .references(() => users.id, { onDelete: 'cascade' })
43
43
  .notNull(),
44
44
 
45
- // 支持嵌套结构
45
+ // Support nested structure
46
46
  // @ts-ignore
47
47
  parentGroupId: varchar255('parent_group_id').references(() => messageGroups.id, {
48
48
  onDelete: 'cascade',
49
49
  }),
50
50
 
51
- // 关联的用户消息
51
+ // Associated user message
52
52
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
53
53
  parentMessageId: text('parent_message_id').references(() => messages.id, {
54
54
  onDelete: 'cascade',
55
55
  }),
56
56
 
57
- // 元数据
57
+ // Metadata
58
58
  title: varchar255('title'),
59
59
  description: text('description'),
60
60
 
@@ -89,8 +89,8 @@ export type NewEmbeddingsItem = typeof embeddings.$inferInsert;
89
89
  export type EmbeddingsSelectItem = typeof embeddings.$inferSelect;
90
90
 
91
91
  /**
92
- * 文档块表 - 将文档内容分割成块并关联到 chunks 表,用于向量检索
93
- * 注意:此表可选,如果已经使用 pages 字段存储了文档块,可以不需要此表
92
+ * Document chunks table - Splits document content into chunks and associates them with the chunks table for vector retrieval
93
+ * Note: This table is optional, if the pages field is already being used to store document chunks, this table may not be needed
94
94
  */
95
95
  export const documentChunks = pgTable(
96
96
  'document_chunks',
@@ -6,13 +6,94 @@ const xaiChatModels: AIChatModelCard[] = [
6
6
  abilities: {
7
7
  functionCall: true,
8
8
  search: true,
9
+ structuredOutput: true,
9
10
  vision: true,
10
11
  },
11
12
  contextWindowTokens: 2_000_000,
12
- description:
13
- '我们很高兴发布 Grok 4 Fast,这是我们在成本效益推理模型方面的最新进展。',
14
- displayName: 'Grok 4 Fast (Non-Reasoning)',
13
+ description: '前沿多模态模型,专门针对高性能代理工具调用进行优化。',
14
+ displayName: 'Grok 4.1 Fast (Non-Reasoning)',
15
+ enabled: true,
16
+ id: 'grok-4-1-fast-non-reasoning',
17
+ pricing: {
18
+ units: [
19
+ { name: 'textInput_cacheRead', rate: 0.05, strategy: 'fixed', unit: 'millionTokens' },
20
+ {
21
+ name: 'textInput',
22
+ strategy: 'tiered',
23
+ tiers: [
24
+ { rate: 0.2, upTo: 0.128 },
25
+ { rate: 0.4, upTo: 'infinity' },
26
+ ],
27
+ unit: 'millionTokens',
28
+ },
29
+ {
30
+ name: 'textOutput',
31
+ strategy: 'tiered',
32
+ tiers: [
33
+ { rate: 0.5, upTo: 0.128 },
34
+ { rate: 1, upTo: 'infinity' },
35
+ ],
36
+ unit: 'millionTokens',
37
+ },
38
+ ],
39
+ },
40
+ releasedAt: '2025-11-20',
41
+ settings: {
42
+ searchImpl: 'params',
43
+ },
44
+ type: 'chat',
45
+ },
46
+ {
47
+ abilities: {
48
+ functionCall: true,
49
+ reasoning: true,
50
+ search: true,
51
+ structuredOutput: true,
52
+ vision: true,
53
+ },
54
+ contextWindowTokens: 2_000_000,
55
+ description: '前沿多模态模型,专门针对高性能代理工具调用进行优化。',
56
+ displayName: 'Grok 4.1 Fast',
15
57
  enabled: true,
58
+ id: 'grok-4-1-fast-reasoning',
59
+ pricing: {
60
+ units: [
61
+ { name: 'textInput_cacheRead', rate: 0.05, strategy: 'fixed', unit: 'millionTokens' },
62
+ {
63
+ name: 'textInput',
64
+ strategy: 'tiered',
65
+ tiers: [
66
+ { rate: 0.2, upTo: 0.128 },
67
+ { rate: 0.4, upTo: 'infinity' },
68
+ ],
69
+ unit: 'millionTokens',
70
+ },
71
+ {
72
+ name: 'textOutput',
73
+ strategy: 'tiered',
74
+ tiers: [
75
+ { rate: 0.5, upTo: 0.128 },
76
+ { rate: 1, upTo: 'infinity' },
77
+ ],
78
+ unit: 'millionTokens',
79
+ },
80
+ ],
81
+ },
82
+ releasedAt: '2025-11-20',
83
+ settings: {
84
+ searchImpl: 'params',
85
+ },
86
+ type: 'chat',
87
+ },
88
+ {
89
+ abilities: {
90
+ functionCall: true,
91
+ search: true,
92
+ vision: true,
93
+ },
94
+ contextWindowTokens: 2_000_000,
95
+ description: '我们很高兴发布 Grok 4 Fast,这是我们在成本效益推理模型方面的最新进展。',
96
+ displayName: 'Grok 4 Fast (Non-Reasoning)',
16
97
  id: 'grok-4-fast-non-reasoning',
17
98
  pricing: {
18
99
  units: [
@@ -51,10 +132,8 @@ const xaiChatModels: AIChatModelCard[] = [
51
132
  vision: true,
52
133
  },
53
134
  contextWindowTokens: 2_000_000,
54
- description:
55
- '我们很高兴发布 Grok 4 Fast,这是我们在成本效益推理模型方面的最新进展。',
135
+ description: '我们很高兴发布 Grok 4 Fast,这是我们在成本效益推理模型方面的最新进展。',
56
136
  displayName: 'Grok 4 Fast',
57
- enabled: true,
58
137
  id: 'grok-4-fast-reasoning',
59
138
  pricing: {
60
139
  units: [
@@ -2,11 +2,10 @@ import { ActionIcon, Icon } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import type { ItemType } from 'antd/es/menu/interface';
4
4
  import { LucideArrowRight, LucideBolt } from 'lucide-react';
5
- import Link from 'next/link';
6
- import { useRouter } from 'next/navigation';
7
5
  import { type ReactNode, memo, useMemo } from 'react';
8
6
  import { useTranslation } from 'react-i18next';
9
7
  import { Flexbox } from 'react-layout-kit';
8
+ import { useNavigate } from 'react-router-dom';
10
9
 
11
10
  import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
12
11
  import ActionDropdown from '@/features/ChatInput/ActionBar/components/ActionDropdown';
@@ -53,7 +52,7 @@ const ModelSwitchPanel = memo<IProps>(({ children, onOpenChange, open }) => {
53
52
  agentSelectors.currentAgentModelProvider(s),
54
53
  s.updateAgentConfig,
55
54
  ]);
56
- const router = useRouter();
55
+ const navigate = useNavigate();
57
56
  const enabledList = useEnabledChatModels();
58
57
 
59
58
  const items = useMemo<ItemType[]>(() => {
@@ -78,7 +77,7 @@ const ModelSwitchPanel = memo<IProps>(({ children, onOpenChange, open }) => {
78
77
  </Flexbox>
79
78
  ),
80
79
  onClick: () => {
81
- router.push(`/settings?active=provider&provider=${provider.id}`);
80
+ navigate(`/settings?active=provider&provider=${provider.id}`);
82
81
  },
83
82
  },
84
83
  ];
@@ -97,7 +96,7 @@ const ModelSwitchPanel = memo<IProps>(({ children, onOpenChange, open }) => {
97
96
  </Flexbox>
98
97
  ),
99
98
  onClick: () => {
100
- router.push('/settings?active=provider');
99
+ navigate('/settings?active=provider');
101
100
  },
102
101
  },
103
102
  ];
@@ -114,18 +113,21 @@ const ModelSwitchPanel = memo<IProps>(({ children, onOpenChange, open }) => {
114
113
  provider={provider.id}
115
114
  source={provider.source}
116
115
  />
117
- <Link href={`/settings?active=provider&provider=${provider.id}`}>
118
- <ActionIcon
119
- icon={LucideBolt}
120
- size={'small'}
121
- title={t('ModelSwitchPanel.goToSettings')}
122
- />
123
- </Link>
116
+ <ActionIcon
117
+ icon={LucideBolt}
118
+ onClick={(e) => {
119
+ e.preventDefault();
120
+ e.stopPropagation();
121
+ navigate(`/settings?active=provider&provider=${provider.id}`);
122
+ }}
123
+ size={'small'}
124
+ title={t('ModelSwitchPanel.goToSettings')}
125
+ />
124
126
  </Flexbox>
125
127
  ),
126
128
  type: 'group',
127
129
  }));
128
- }, [enabledList]);
130
+ }, [enabledList, navigate, t, theme.colorTextTertiary]);
129
131
 
130
132
  const icon = <div className={styles.tag}>{children}</div>;
131
133
 
@@ -1,40 +0,0 @@
1
- import { Skeleton } from 'antd';
2
- import dynamic from 'next/dynamic';
3
-
4
- import { enableClerk } from '@/const/auth';
5
- import { metadataModule } from '@/server/metadata';
6
- import { translation } from '@/server/translation';
7
- import { DynamicLayoutProps } from '@/types/next';
8
- import { RouteVariants } from '@/utils/server/routeVariants';
9
-
10
- import Client from '../Client';
11
-
12
- // 为了兼容 ClerkProfile, 需要使用 [[...slug]]
13
-
14
- const ClerkProfile = dynamic(() => import('../../features/ClerkProfile'), {
15
- loading: () => (
16
- <div style={{ flex: 1 }}>
17
- <Skeleton paragraph={{ rows: 8 }} title={false} />
18
- </div>
19
- ),
20
- });
21
-
22
- export const generateMetadata = async (props: DynamicLayoutProps) => {
23
- const locale = await RouteVariants.getLocale(props);
24
- const { t } = await translation('auth', locale);
25
- return metadataModule.generate({
26
- description: t('header.desc'),
27
- title: t('tab.profile'),
28
- url: '/profile',
29
- });
30
- };
31
-
32
- const Page = async (props: DynamicLayoutProps) => {
33
- const mobile = await RouteVariants.getIsMobile(props);
34
-
35
- if (enableClerk) return <ClerkProfile mobile={mobile} />;
36
-
37
- return <Client mobile={mobile} />;
38
- };
39
-
40
- export default Page;
@@ -1,3 +0,0 @@
1
- 'use client';
2
-
3
- export { default } from '@/components/Error';
@@ -1,3 +0,0 @@
1
- import Loading from '@/components/Loading/BrandTextLoading';
2
-
3
- export default () => <Loading />;
@@ -1 +0,0 @@
1
- export { default } from '@/components/404';
@@ -1,41 +0,0 @@
1
- import ServerLayout from '@/components/server/ServerLayout';
2
- import { serverFeatureFlags } from '@/config/featureFlags';
3
- import { metadataModule } from '@/server/metadata';
4
- import { translation } from '@/server/translation';
5
- import { DynamicLayoutProps } from '@/types/next';
6
- import { RouteVariants } from '@/utils/server/routeVariants';
7
-
8
- import SettingsContextProvider from './_layout/ContextProvider';
9
- import Desktop from './_layout/Desktop';
10
- import Mobile from './_layout/Mobile';
11
- import { LayoutProps } from './_layout/type';
12
-
13
- export const generateMetadata = async (props: DynamicLayoutProps) => {
14
- const locale = await RouteVariants.getLocale(props);
15
- const { t } = await translation('setting', locale);
16
- return metadataModule.generate({
17
- description: t('header.desc'),
18
- title: t('header.title'),
19
- url: '/settings',
20
- });
21
- };
22
-
23
- const SettingsLayout = ServerLayout<LayoutProps>({ Desktop, Mobile });
24
-
25
- const SettingsPage = async (props: DynamicLayoutProps) => {
26
- const { showOpenAIProxyUrl, showOpenAIApiKey } = serverFeatureFlags();
27
-
28
- return (
29
- <SettingsContextProvider
30
- value={{
31
- showOpenAIApiKey: showOpenAIApiKey,
32
- showOpenAIProxyUrl: showOpenAIProxyUrl,
33
- }}
34
- >
35
- {/* @ts-ignore */}
36
- <SettingsLayout {...props} />
37
- </SettingsContextProvider>
38
- );
39
- };
40
-
41
- export default SettingsPage;