@lobehub/chat 1.77.4 → 1.77.5
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/package.json +1 -1
- package/src/app/[variants]/(main)/repos/[id]/@menu/default.tsx +3 -2
- package/src/components/Loading/BrandTextLoading/index.tsx +2 -2
- package/src/database/models/file.ts +70 -38
- package/src/libs/agent-runtime/openrouter/__snapshots__/index.test.ts.snap +1 -1
- package/src/server/routers/lambda/file.ts +1 -1
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,23 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.77.5](https://github.com/lobehub/lobe-chat/compare/v1.77.4...v1.77.5)
|
6
|
+
|
7
|
+
<sup>Released on **2025-04-01**</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 1.77.4](https://github.com/lobehub/lobe-chat/compare/v1.77.3...v1.77.4)
|
6
23
|
|
7
24
|
<sup>Released on **2025-03-31**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.77.
|
3
|
+
"version": "1.77.5",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -11,9 +11,10 @@ interface Params {
|
|
11
11
|
id: string;
|
12
12
|
}
|
13
13
|
|
14
|
-
type Props = { params: Params };
|
14
|
+
type Props = { params: Promise<Params> };
|
15
15
|
|
16
|
-
const MenuPage = async (
|
16
|
+
const MenuPage = async (props: Props) => {
|
17
|
+
const params = await props.params;
|
17
18
|
const id = params.id;
|
18
19
|
const item = await KnowledgeBaseModel.findById(serverDB, params.id);
|
19
20
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { BrandLoading,
|
1
|
+
import { BrandLoading, LobeHubText } from '@lobehub/ui/brand';
|
2
2
|
import { Center } from 'react-layout-kit';
|
3
3
|
|
4
4
|
import { isCustomBranding } from '@/const/version';
|
@@ -10,7 +10,7 @@ export default () => {
|
|
10
10
|
|
11
11
|
return (
|
12
12
|
<Center height={'100%'} width={'100%'}>
|
13
|
-
<BrandLoading size={40} style={{ opacity: 0.6 }} text={
|
13
|
+
<BrandLoading size={40} style={{ opacity: 0.6 }} text={LobeHubText} />
|
14
14
|
</Center>
|
15
15
|
);
|
16
16
|
};
|
@@ -128,50 +128,49 @@ export class FileModel {
|
|
128
128
|
};
|
129
129
|
|
130
130
|
deleteMany = async (ids: string[], removeGlobalFile: boolean = true) => {
|
131
|
-
|
132
|
-
const hashList = fileList.map((file) => file.fileHash!);
|
131
|
+
if (ids.length === 0) return [];
|
133
132
|
|
134
133
|
return await this.db.transaction(async (trx) => {
|
135
|
-
// 1.
|
134
|
+
// 1. 先获取文件列表,以便返回删除的文件
|
135
|
+
const fileList = await trx.query.files.findMany({
|
136
|
+
where: and(inArray(files.id, ids), eq(files.userId, this.userId)),
|
137
|
+
});
|
138
|
+
|
139
|
+
if (fileList.length === 0) return [];
|
140
|
+
|
141
|
+
// 提取需要检查的文件哈希值
|
142
|
+
const hashList = fileList.map((file) => file.fileHash!).filter(Boolean);
|
143
|
+
|
144
|
+
// 2. 删除相关的 chunks
|
136
145
|
await this.deleteFileChunks(trx as any, ids);
|
137
146
|
|
138
|
-
//
|
147
|
+
// 3. 删除文件记录
|
139
148
|
await trx.delete(files).where(and(inArray(files.id, ids), eq(files.userId, this.userId)));
|
140
149
|
|
141
|
-
//
|
142
|
-
|
150
|
+
// 如果不需要删除全局文件,直接返回
|
151
|
+
if (!removeGlobalFile || hashList.length === 0) return fileList;
|
152
|
+
|
153
|
+
// 4. 找出不再被引用的哈希值
|
154
|
+
const remainingFiles = await trx
|
143
155
|
.select({
|
144
|
-
|
145
|
-
hashId: files.fileHash,
|
156
|
+
fileHash: files.fileHash,
|
146
157
|
})
|
147
158
|
.from(files)
|
148
|
-
.where(inArray(files.fileHash, hashList))
|
149
|
-
.groupBy(files.fileHash);
|
150
|
-
|
151
|
-
// Create a Map to store the query result
|
152
|
-
const countMap = new Map(result.map((item) => [item.hashId, item.count]));
|
159
|
+
.where(inArray(files.fileHash, hashList));
|
153
160
|
|
154
|
-
//
|
155
|
-
const
|
156
|
-
count: countMap.get(hashId) || 0,
|
157
|
-
hashId: hashId,
|
158
|
-
}));
|
161
|
+
// 将仍在使用的哈希值放入Set中,便于快速查找
|
162
|
+
const usedHashes = new Set(remainingFiles.map((file) => file.fileHash));
|
159
163
|
|
160
|
-
|
164
|
+
// 找出需要删除的哈希值(不再被任何文件使用的)
|
165
|
+
const hashesToDelete = hashList.filter((hash) => !usedHashes.has(hash));
|
161
166
|
|
162
|
-
if (
|
167
|
+
if (hashesToDelete.length === 0) return fileList;
|
163
168
|
|
164
|
-
//
|
165
|
-
await trx.delete(globalFiles).where(
|
166
|
-
inArray(
|
167
|
-
globalFiles.hashId,
|
168
|
-
needToDeleteList.map((item) => item.hashId!),
|
169
|
-
),
|
170
|
-
);
|
169
|
+
// 5. 删除不再被引用的全局文件
|
170
|
+
await trx.delete(globalFiles).where(inArray(globalFiles.hashId, hashesToDelete));
|
171
171
|
|
172
|
-
|
173
|
-
|
174
|
-
);
|
172
|
+
// 返回删除的文件列表
|
173
|
+
return fileList;
|
175
174
|
});
|
176
175
|
};
|
177
176
|
|
@@ -318,25 +317,58 @@ export class FileModel {
|
|
318
317
|
|
319
318
|
// 抽象出通用的删除 chunks 方法
|
320
319
|
private deleteFileChunks = async (trx: PgTransaction<any>, fileIds: string[]) => {
|
321
|
-
|
320
|
+
if (fileIds.length === 0) return;
|
322
321
|
|
323
|
-
//
|
322
|
+
// 直接使用 JOIN 优化查询,减少数据传输量
|
324
323
|
const relatedChunks = await trx
|
325
324
|
.select({ chunkId: fileChunks.chunkId })
|
326
325
|
.from(fileChunks)
|
327
|
-
.where(
|
326
|
+
.where(
|
327
|
+
and(
|
328
|
+
inArray(fileChunks.fileId, fileIds),
|
329
|
+
// 确保只查询有效的 chunkId
|
330
|
+
notExists(
|
331
|
+
trx
|
332
|
+
.select()
|
333
|
+
.from(knowledgeBaseFiles)
|
334
|
+
.where(eq(knowledgeBaseFiles.fileId, fileChunks.fileId)),
|
335
|
+
),
|
336
|
+
),
|
337
|
+
);
|
328
338
|
|
329
339
|
const chunkIds = relatedChunks.map((c) => c.chunkId).filter(Boolean) as string[];
|
330
340
|
|
331
341
|
if (chunkIds.length === 0) return;
|
332
342
|
|
333
|
-
//
|
334
|
-
|
335
|
-
|
343
|
+
// 批量处理配置
|
344
|
+
const BATCH_SIZE = 1000; // 增加批处理量
|
345
|
+
const MAX_CONCURRENT_BATCHES = 3; // 最大并行批次数
|
346
|
+
|
347
|
+
// 分批并行处理
|
348
|
+
for (let i = 0; i < chunkIds.length; i += BATCH_SIZE * MAX_CONCURRENT_BATCHES) {
|
349
|
+
const batchPromises = [];
|
336
350
|
|
337
|
-
|
351
|
+
// 创建多个并行批次
|
352
|
+
for (let j = 0; j < MAX_CONCURRENT_BATCHES; j++) {
|
353
|
+
const startIdx = i + j * BATCH_SIZE;
|
354
|
+
if (startIdx >= chunkIds.length) break;
|
355
|
+
|
356
|
+
const batchChunkIds = chunkIds.slice(startIdx, startIdx + BATCH_SIZE);
|
357
|
+
if (batchChunkIds.length === 0) continue;
|
358
|
+
|
359
|
+
// 为每个批次创建一个删除任务
|
360
|
+
const batchPromise = (async () => {
|
361
|
+
// 先删除嵌入向量
|
362
|
+
await trx.delete(embeddings).where(inArray(embeddings.chunkId, batchChunkIds));
|
363
|
+
// 再删除块
|
364
|
+
await trx.delete(chunks).where(inArray(chunks.id, batchChunkIds));
|
365
|
+
})();
|
366
|
+
|
367
|
+
batchPromises.push(batchPromise);
|
368
|
+
}
|
338
369
|
|
339
|
-
|
370
|
+
// 等待当前批次的所有任务完成
|
371
|
+
await Promise.all(batchPromises);
|
340
372
|
}
|
341
373
|
|
342
374
|
return chunkIds;
|
@@ -1724,7 +1724,7 @@ See benchmarks on the launch announcement [here](https://mistral.ai/news/mixtral
|
|
1724
1724
|
#moe",
|
1725
1725
|
"displayName": "Mistral: Mixtral 8x22B Instruct",
|
1726
1726
|
"enabled": false,
|
1727
|
-
"functionCall":
|
1727
|
+
"functionCall": true,
|
1728
1728
|
"id": "mistralai/mixtral-8x22b-instruct",
|
1729
1729
|
"maxTokens": undefined,
|
1730
1730
|
"pricing": {
|
@@ -74,7 +74,7 @@ export const fileRouter = router({
|
|
74
74
|
.query(async ({ ctx, input }): Promise<FileListItem | undefined> => {
|
75
75
|
const item = await ctx.fileModel.findById(input.id);
|
76
76
|
|
77
|
-
if (!item) throw new TRPCError({ code: '
|
77
|
+
if (!item) throw new TRPCError({ code: 'NOT_FOUND', message: 'File not found' });
|
78
78
|
|
79
79
|
let embeddingTask = null;
|
80
80
|
if (item.embeddingTaskId) {
|