@customize-agent/knowledge 4.0.7 → 4.0.9
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/dist/chunking/text-chunker.js +1 -1
- package/dist/core/change-tracker.d.ts +1 -1
- package/dist/core/change-tracker.js +11 -3
- package/dist/core/index-state-store.js +2 -2
- package/dist/core/knowledge-base-manager.d.ts +6 -1
- package/dist/core/knowledge-base-manager.js +45 -8
- package/dist/extraction/content-extractor.d.ts +3 -0
- package/dist/extraction/content-extractor.js +48 -26
- package/dist/vector/vector-indexer.js +2 -2
- package/package.json +1 -1
|
@@ -324,7 +324,7 @@ export class TextChunker {
|
|
|
324
324
|
withHeader(text, file, config) {
|
|
325
325
|
if (!config.headerInjection)
|
|
326
326
|
return text;
|
|
327
|
-
return
|
|
327
|
+
return `资料类型: ${file.category}/${file.format}\n\n${text}`;
|
|
328
328
|
}
|
|
329
329
|
createChunk(index, candidate, file, metadata) {
|
|
330
330
|
const text = candidate.text.trim();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as crypto from 'node:crypto';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
+
import { createReadStream } from 'node:fs';
|
|
3
4
|
import * as path from 'node:path';
|
|
4
5
|
/** 文件变更追踪器,用于比对磁盘文件与索引状态之间的差异 */
|
|
5
6
|
export class ChangeTracker {
|
|
@@ -51,7 +52,7 @@ export class ChangeTracker {
|
|
|
51
52
|
continue;
|
|
52
53
|
}
|
|
53
54
|
if (Math.round(diskStat.mtime) !== Math.round(indexed.mtime) || diskStat.size !== indexed.fileSize) {
|
|
54
|
-
const contentHash = this.hashFile(absolutePath);
|
|
55
|
+
const contentHash = await this.hashFile(absolutePath);
|
|
55
56
|
if (contentHash !== indexed.contentHash) {
|
|
56
57
|
modifiedFiles.push(classified);
|
|
57
58
|
}
|
|
@@ -86,8 +87,15 @@ export class ChangeTracker {
|
|
|
86
87
|
* @param filePath 文件路径
|
|
87
88
|
* @returns SHA-256 哈希字符串
|
|
88
89
|
*/
|
|
89
|
-
hashFile(filePath) {
|
|
90
|
-
|
|
90
|
+
async hashFile(filePath) {
|
|
91
|
+
const hash = crypto.createHash('sha256');
|
|
92
|
+
await new Promise((resolve, reject) => {
|
|
93
|
+
const stream = createReadStream(filePath);
|
|
94
|
+
stream.on('data', chunk => hash.update(chunk));
|
|
95
|
+
stream.on('error', reject);
|
|
96
|
+
stream.on('end', resolve);
|
|
97
|
+
});
|
|
98
|
+
return hash.digest('hex');
|
|
91
99
|
}
|
|
92
100
|
parseMetadata(metadataJson) {
|
|
93
101
|
if (!metadataJson)
|
|
@@ -278,7 +278,7 @@ export class IndexStateStore {
|
|
|
278
278
|
`).all(matchQuery, limit * 8);
|
|
279
279
|
return rows
|
|
280
280
|
.map(row => {
|
|
281
|
-
const keyword = this.scoreChunkDetailed(`${String(row.
|
|
281
|
+
const keyword = this.scoreChunkDetailed(`${String(row.section_title ?? '')}\n${String(row.category)}\n${String(row.format)}\n${String(row.content)}`, terms);
|
|
282
282
|
const bm25Score = this.bm25ToPositiveScore(Number(row.bm25_score));
|
|
283
283
|
return this.rowToChunk(row, keyword.keywordScore + bm25Score, { ...keyword, bm25Score });
|
|
284
284
|
})
|
|
@@ -299,7 +299,7 @@ export class IndexStateStore {
|
|
|
299
299
|
`).all(...terms.flatMap(term => [`%${term}%`, `%${term}%`, `%${term}%`, `%${term}%`]), limit * 6);
|
|
300
300
|
return rows
|
|
301
301
|
.map(row => {
|
|
302
|
-
const keyword = this.scoreChunkDetailed(`${String(row.
|
|
302
|
+
const keyword = this.scoreChunkDetailed(`${String(row.section_title ?? '')}\n${String(row.category)}\n${String(row.format)}\n${String(row.content)}`, terms);
|
|
303
303
|
return this.rowToChunk(row, keyword.keywordScore, keyword);
|
|
304
304
|
})
|
|
305
305
|
.filter(row => row.score > 0)
|
|
@@ -59,6 +59,7 @@ export declare class KnowledgeBaseManager {
|
|
|
59
59
|
onProgress?: (progress: KnowledgeIndexProgress) => void;
|
|
60
60
|
vectorMode?: 'sync' | 'defer';
|
|
61
61
|
limit?: number;
|
|
62
|
+
waitForUploadId?: string;
|
|
62
63
|
}): Promise<DiffResult>;
|
|
63
64
|
incrementalIndex(options?: {
|
|
64
65
|
onProgress?: (progress: KnowledgeIndexProgress) => void;
|
|
@@ -112,7 +113,7 @@ export declare class KnowledgeBaseManager {
|
|
|
112
113
|
fileName: string;
|
|
113
114
|
sourcePath: string;
|
|
114
115
|
targetRelativePath?: string;
|
|
115
|
-
}>, operationId?: string, offset?: number): Promise<import("./index-state-store.js").KnowledgeIndexJob[]>;
|
|
116
|
+
}>, operationId?: string, offset?: number, uploadComplete?: boolean): Promise<import("./index-state-store.js").KnowledgeIndexJob[]>;
|
|
116
117
|
uploadFiles(files: Array<{
|
|
117
118
|
fileName: string;
|
|
118
119
|
content: Buffer;
|
|
@@ -176,6 +177,10 @@ export declare class KnowledgeBaseManager {
|
|
|
176
177
|
private ensureVectorStore;
|
|
177
178
|
private deleteVectorFile;
|
|
178
179
|
private ensureVectorIndexFresh;
|
|
180
|
+
uploadSessionIsOpen(operationId: string): boolean;
|
|
181
|
+
private emptyDiff;
|
|
182
|
+
private statRelativePaths;
|
|
183
|
+
private moveUploadedFile;
|
|
179
184
|
private hasUsableContent;
|
|
180
185
|
private defaultUploadRelativePath;
|
|
181
186
|
private validateUploadRelativePath;
|
|
@@ -87,9 +87,12 @@ export class KnowledgeBaseManager {
|
|
|
87
87
|
}
|
|
88
88
|
async consumePendingIndexJobs(options = {}) {
|
|
89
89
|
this.initialize();
|
|
90
|
-
const jobs = this.store.listPendingIndexJobs(options.limit ??
|
|
91
|
-
if (jobs.length === 0)
|
|
90
|
+
const jobs = this.store.listPendingIndexJobs(options.limit ?? 500);
|
|
91
|
+
if (jobs.length === 0) {
|
|
92
|
+
if (options.waitForUploadId && this.uploadSessionIsOpen(options.waitForUploadId))
|
|
93
|
+
return this.emptyDiff();
|
|
92
94
|
return this.incrementalIndex(options);
|
|
95
|
+
}
|
|
93
96
|
return this.incrementalIndex({ ...options, onlyRelativePaths: jobs.map(job => job.relativePath) });
|
|
94
97
|
}
|
|
95
98
|
async incrementalIndex(options = {}) {
|
|
@@ -101,10 +104,10 @@ export class KnowledgeBaseManager {
|
|
|
101
104
|
this.reportProgress({ stage: 'scanning', percent: 10, message: '正在扫描知识库文件' });
|
|
102
105
|
const kbIgnore = this.scanner.loadKbIgnore(this.kbPath);
|
|
103
106
|
const configIgnore = this.projectConfig?.kbignore ?? [];
|
|
104
|
-
const
|
|
107
|
+
const onlyRelativePaths = options.onlyRelativePaths ? new Set(options.onlyRelativePaths) : undefined;
|
|
108
|
+
const diskFiles = onlyRelativePaths ? this.statRelativePaths([...onlyRelativePaths]) : await this.scanner.scan(this.kbPath, [...kbIgnore, ...configIgnore]);
|
|
105
109
|
const tracker = new ChangeTracker(this.store);
|
|
106
110
|
const diff = await tracker.computeDiff(diskFiles, this.classifier, this.kbPath);
|
|
107
|
-
const onlyRelativePaths = options.onlyRelativePaths ? new Set(options.onlyRelativePaths) : undefined;
|
|
108
111
|
let vectorDeletesApplied = 0;
|
|
109
112
|
if (onlyRelativePaths) {
|
|
110
113
|
diff.newFiles = diff.newFiles.filter(file => onlyRelativePaths.has(file.relativePath));
|
|
@@ -129,7 +132,7 @@ export class KnowledgeBaseManager {
|
|
|
129
132
|
const vectorRelativePaths = [];
|
|
130
133
|
const changedCollectionNames = new Set();
|
|
131
134
|
for (const [index, file] of filesToIndex.entries()) {
|
|
132
|
-
const hash = tracker.hashFile(file.absolutePath);
|
|
135
|
+
const hash = await tracker.hashFile(file.absolutePath);
|
|
133
136
|
const duplicate = this.store.findExactDuplicate(hash, file.relativePath);
|
|
134
137
|
const collectionName = this.scope === 'global'
|
|
135
138
|
? this.collections.getCollectionName('global', file.category)
|
|
@@ -448,15 +451,16 @@ export class KnowledgeBaseManager {
|
|
|
448
451
|
}
|
|
449
452
|
return jobs;
|
|
450
453
|
}
|
|
451
|
-
async stageUploadedFilePaths(files, operationId = `upload-${Date.now()}`, offset = 0) {
|
|
454
|
+
async stageUploadedFilePaths(files, operationId = `upload-${Date.now()}`, offset = 0, uploadComplete = true) {
|
|
452
455
|
this.initialize();
|
|
456
|
+
this.store.setMetadata(`upload_session:${operationId}`, uploadComplete ? 'complete' : 'open');
|
|
453
457
|
const jobs = [];
|
|
454
458
|
for (let index = 0; index < files.length; index++) {
|
|
455
459
|
const file = files[index];
|
|
456
460
|
const relativePath = this.getUploadRelativePath(file.fileName, file.targetRelativePath);
|
|
457
461
|
const targetPath = this.resolveKbRelativePath(relativePath);
|
|
458
462
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
459
|
-
|
|
463
|
+
this.moveUploadedFile(file.sourcePath, targetPath);
|
|
460
464
|
const record = this.store.listRecords().find(item => item.relativePath === relativePath);
|
|
461
465
|
if (record)
|
|
462
466
|
await this.deleteVectorFile(record.collectionName, relativePath);
|
|
@@ -927,6 +931,39 @@ ${resultsText}
|
|
|
927
931
|
return;
|
|
928
932
|
await this.indexVectors({ rebuild: true });
|
|
929
933
|
}
|
|
934
|
+
uploadSessionIsOpen(operationId) {
|
|
935
|
+
return this.store.getMetadata(`upload_session:${operationId}`) === 'open';
|
|
936
|
+
}
|
|
937
|
+
emptyDiff() {
|
|
938
|
+
return { newFiles: [], modifiedFiles: [], deletedFiles: [], unchangedCount: 0, mtimeOnlyCount: 0, skippedFiles: [], hasChanges: false, diffTimeMs: 0 };
|
|
939
|
+
}
|
|
940
|
+
statRelativePaths(relativePaths) {
|
|
941
|
+
const files = new Map();
|
|
942
|
+
for (const relativePath of relativePaths) {
|
|
943
|
+
const normalized = this.normalizeRelativePath(relativePath);
|
|
944
|
+
const absolutePath = this.resolveKbRelativePath(normalized);
|
|
945
|
+
if (!fs.existsSync(absolutePath))
|
|
946
|
+
continue;
|
|
947
|
+
const stat = fs.statSync(absolutePath);
|
|
948
|
+
if (stat.isFile())
|
|
949
|
+
files.set(normalized, { size: stat.size, mtime: stat.mtimeMs });
|
|
950
|
+
}
|
|
951
|
+
return files;
|
|
952
|
+
}
|
|
953
|
+
moveUploadedFile(sourcePath, targetPath) {
|
|
954
|
+
if (fs.existsSync(targetPath))
|
|
955
|
+
fs.rmSync(targetPath, { force: true });
|
|
956
|
+
try {
|
|
957
|
+
fs.renameSync(sourcePath, targetPath);
|
|
958
|
+
}
|
|
959
|
+
catch (error) {
|
|
960
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
961
|
+
if (code !== 'EXDEV')
|
|
962
|
+
throw error;
|
|
963
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
964
|
+
fs.rmSync(sourcePath, { force: true });
|
|
965
|
+
}
|
|
966
|
+
}
|
|
930
967
|
hasUsableContent(text, metadata) {
|
|
931
968
|
const coverage = String(metadata.contentCoverage ?? '');
|
|
932
969
|
if (['metadata', 'metadata_filename', 'pdf_metadata_only', 'office_zip_empty_text', 'office_zip_failed'].includes(coverage))
|
|
@@ -964,6 +1001,6 @@ ${resultsText}
|
|
|
964
1001
|
return targetPath;
|
|
965
1002
|
}
|
|
966
1003
|
normalizeRelativePath(relativePath) {
|
|
967
|
-
return relativePath.replace(/\\/gu, '/').split(path.sep).join('/').replace(/^\/+/, '');
|
|
1004
|
+
return relativePath.replace(/\\/gu, '/').split(path.sep).join('/').replace(/^\/+/, '').replace(/\/+/gu, '/');
|
|
968
1005
|
}
|
|
969
1006
|
}
|
|
@@ -16,6 +16,9 @@ export declare class ContentExtractor {
|
|
|
16
16
|
private extractTextClipping;
|
|
17
17
|
private swapUtf16Bytes;
|
|
18
18
|
private extractReadableFragments;
|
|
19
|
+
private cleanCadReadableText;
|
|
20
|
+
private isReadableCadValue;
|
|
21
|
+
private cleanExtractedText;
|
|
19
22
|
private textScore;
|
|
20
23
|
private extractCad;
|
|
21
24
|
private extractDxf;
|
|
@@ -5,6 +5,8 @@ import { tmpdir } from 'node:os';
|
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
import { ExternalExtractorRegistry } from './external-extractor.js';
|
|
7
7
|
import { resolveAndImport, resolvePackage, getNodeModulesRoot } from './module-resolver.js';
|
|
8
|
+
const CAD_INTERNAL_TOKEN_RE = /\b(?:TDbPipe|TDbPipeValve|TDbPipeFitting|TDbWellh|AcDb\w+|Dwg\w+|ObjectId|Handle|ByLayer|Continuous|Model|Layout\d*)\b/giu;
|
|
9
|
+
const CAD_INTERNAL_LINE_RE = /^(?:TDb\w+|AcDb\w+|[A-F0-9]{8,}|\d+|Model|Layout\d*|ByLayer|Continuous)$/iu;
|
|
8
10
|
/** 文件内容提取器,支持文档、表格、图片、CAD 等多种文件格式的内容抽取 */
|
|
9
11
|
export class ContentExtractor {
|
|
10
12
|
externalExtractors;
|
|
@@ -102,7 +104,7 @@ export class ContentExtractor {
|
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
return {
|
|
105
|
-
text: text.trim(),
|
|
107
|
+
text: this.cleanExtractedText(text, file).trim(),
|
|
106
108
|
metadata,
|
|
107
109
|
warnings,
|
|
108
110
|
extractionTimeMs: Date.now() - start,
|
|
@@ -177,9 +179,36 @@ export class ContentExtractor {
|
|
|
177
179
|
return value
|
|
178
180
|
.replace(/[^\p{L}\p{N}\p{P}\p{S}\s]/gu, '\n')
|
|
179
181
|
.split(/[\r\n]+/u)
|
|
180
|
-
.map(line =>
|
|
182
|
+
.map(line => this.cleanCadReadableText(line))
|
|
181
183
|
.filter(line => line.length >= 2 && /[\p{L}\p{N}]/u.test(line));
|
|
182
184
|
}
|
|
185
|
+
cleanCadReadableText(value) {
|
|
186
|
+
return value
|
|
187
|
+
.replace(CAD_INTERNAL_TOKEN_RE, '')
|
|
188
|
+
.replace(/\b(?:LINE|LWPOLYLINE|POLYLINE|INSERT|HATCH|CIRCLE|ARC|DIMENSION|TEXT|MTEXT)\b/giu, '')
|
|
189
|
+
.replace(/\s+/gu, ' ')
|
|
190
|
+
.trim();
|
|
191
|
+
}
|
|
192
|
+
isReadableCadValue(value) {
|
|
193
|
+
const cleaned = this.cleanCadReadableText(value);
|
|
194
|
+
return cleaned.length >= 2 && !CAD_INTERNAL_LINE_RE.test(cleaned) && /[\p{Script=Han}\p{Letter}\d]/u.test(cleaned);
|
|
195
|
+
}
|
|
196
|
+
cleanExtractedText(value, file) {
|
|
197
|
+
const normalized = [...value]
|
|
198
|
+
.filter(char => {
|
|
199
|
+
const code = char.charCodeAt(0);
|
|
200
|
+
return code === 9 || code === 10 || code === 13 || code >= 32;
|
|
201
|
+
})
|
|
202
|
+
.join('');
|
|
203
|
+
if (file.category !== 'cad')
|
|
204
|
+
return normalized.replace(/\n{3,}/gu, '\n\n');
|
|
205
|
+
return normalized
|
|
206
|
+
.split(/\r?\n/u)
|
|
207
|
+
.map(line => this.cleanCadReadableText(line))
|
|
208
|
+
.filter(line => line && !CAD_INTERNAL_LINE_RE.test(line))
|
|
209
|
+
.join('\n')
|
|
210
|
+
.replace(/\n{3,}/gu, '\n\n');
|
|
211
|
+
}
|
|
183
212
|
textScore(value) {
|
|
184
213
|
const cjk = (value.match(/[\p{Script=Han}]/gu) ?? []).length;
|
|
185
214
|
const alnum = (value.match(/[\p{L}\p{N}]/gu) ?? []).length;
|
|
@@ -202,9 +231,9 @@ export class ContentExtractor {
|
|
|
202
231
|
}
|
|
203
232
|
if (file.format === 'autocad' && ext === '.dxf') {
|
|
204
233
|
const raw = fs.readFileSync(file.absolutePath, 'utf8');
|
|
205
|
-
const layers = this.matchAll(raw, /\n\s*8\s*\n([^\n]+)/gu).slice(0, 300);
|
|
234
|
+
const layers = this.matchAll(raw, /\n\s*8\s*\n([^\n]+)/gu).filter(value => this.isReadableCadValue(value)).slice(0, 300);
|
|
206
235
|
const textEntities = this.extractDxfTextAnnotations(raw).slice(0, 500);
|
|
207
|
-
const blocks = this.matchAll(raw, /\n\s*2\s*\n([^\n]+)/gu).slice(0, 300);
|
|
236
|
+
const blocks = this.matchAll(raw, /\n\s*2\s*\n([^\n]+)/gu).filter(value => this.isReadableCadValue(value)).slice(0, 300);
|
|
208
237
|
const entityTypes = this.matchAll(raw, /\n\s*0\s*\n([A-Z][A-Z0-9_]+)/gu).slice(0, 1000);
|
|
209
238
|
const uniqueLayers = Array.from(new Set(layers));
|
|
210
239
|
const uniqueBlocks = Array.from(new Set(blocks));
|
|
@@ -278,7 +307,7 @@ export class ContentExtractor {
|
|
|
278
307
|
if (result.text.trim())
|
|
279
308
|
return result;
|
|
280
309
|
}
|
|
281
|
-
const readable = this.extractBinaryReadableFragments(file.absolutePath).slice(0,
|
|
310
|
+
const readable = this.extractBinaryReadableFragments(file.absolutePath).filter(value => this.isReadableCadValue(value)).slice(0, 5000);
|
|
282
311
|
metadata.extractionMode = 'builtin_cad_readable_fragments';
|
|
283
312
|
metadata.contentCoverage = readable.length > 0 ? 'cad_readable_text_fragments' : 'metadata';
|
|
284
313
|
metadata.stringCount = readable.length;
|
|
@@ -303,10 +332,10 @@ export class ContentExtractor {
|
|
|
303
332
|
catch {
|
|
304
333
|
warnings.push('dxf-parser 解析失败,已使用 DXF 文本结构抽取回退');
|
|
305
334
|
}
|
|
306
|
-
const layers = this.matchAll(raw,
|
|
307
|
-
const textEntities = this.extractDxfTextAnnotations(raw).slice(0,
|
|
308
|
-
const blocks = this.matchAll(raw,
|
|
309
|
-
const entityTypes = this.matchAll(raw,
|
|
335
|
+
const layers = this.matchAll(raw, /(?:^|\r?\n)\s*8\s*\r?\n([^\r\n]+)/gu).filter(value => this.isReadableCadValue(value)).slice(0, 300);
|
|
336
|
+
const textEntities = this.extractDxfTextAnnotations(raw).slice(0, 5000);
|
|
337
|
+
const blocks = this.matchAll(raw, /(?:^|\r?\n)\s*2\s*\r?\n([^\r\n]+)/gu).filter(value => this.isReadableCadValue(value)).slice(0, 300);
|
|
338
|
+
const entityTypes = this.matchAll(raw, /(?:^|\r?\n)\s*0\s*\r?\n([A-Z][A-Z0-9_]+)/gu).slice(0, 1200);
|
|
310
339
|
const uniqueLayers = Array.from(new Set(layers));
|
|
311
340
|
const uniqueBlocks = Array.from(new Set(blocks));
|
|
312
341
|
const uniqueEntityTypes = Array.from(new Set(entityTypes));
|
|
@@ -350,18 +379,18 @@ export class ContentExtractor {
|
|
|
350
379
|
});
|
|
351
380
|
}
|
|
352
381
|
extractDxfTextAnnotations(raw) {
|
|
353
|
-
const entities = raw.split(
|
|
382
|
+
const entities = raw.split(/(?:^|\r?\n)\s*0\s*\r?\n/u).filter(section => /^(?:TEXT|MTEXT|DIMENSION|LEADER)/u.test(section.trim()));
|
|
354
383
|
return entities.flatMap(section => {
|
|
355
|
-
const text =
|
|
356
|
-
if (!text)
|
|
384
|
+
const text = this.cleanCadReadableText(/(?:^|\r?\n)\s*(?:1|3)\s*\r?\n([^\r\n]+)/u.exec(section)?.[1] ?? '');
|
|
385
|
+
if (!text || !this.isReadableCadValue(text))
|
|
357
386
|
return [];
|
|
358
387
|
return [{
|
|
359
388
|
text,
|
|
360
|
-
layer:
|
|
361
|
-
block:
|
|
389
|
+
layer: /(?:^|\r?\n)\s*8\s*\r?\n([^\r\n]+)/u.exec(section)?.[1]?.trim(),
|
|
390
|
+
block: /(?:^|\r?\n)\s*2\s*\r?\n([^\r\n]+)/u.exec(section)?.[1]?.trim(),
|
|
362
391
|
entityType: section.trim().split(/\s+/u)[0],
|
|
363
|
-
x: Number(
|
|
364
|
-
y: Number(
|
|
392
|
+
x: Number(/(?:^|\r?\n)\s*10\s*\r?\n([^\r\n]+)/u.exec(section)?.[1]),
|
|
393
|
+
y: Number(/(?:^|\r?\n)\s*20\s*\r?\n([^\r\n]+)/u.exec(section)?.[1]),
|
|
365
394
|
}].map(item => ({ ...item, x: Number.isFinite(item.x) ? item.x : undefined, y: Number.isFinite(item.y) ? item.y : undefined }));
|
|
366
395
|
});
|
|
367
396
|
}
|
|
@@ -1165,7 +1194,7 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
|
|
|
1165
1194
|
if (pages.length > 0) {
|
|
1166
1195
|
const combined = pages.join('\n\n');
|
|
1167
1196
|
if (combined.trim())
|
|
1168
|
-
return combined
|
|
1197
|
+
return combined;
|
|
1169
1198
|
}
|
|
1170
1199
|
}
|
|
1171
1200
|
catch (e) {
|
|
@@ -1179,7 +1208,7 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
|
|
|
1179
1208
|
if (pdfParse) {
|
|
1180
1209
|
const result = await pdfParse(buffer);
|
|
1181
1210
|
if (result.text.trim())
|
|
1182
|
-
return result.text
|
|
1211
|
+
return result.text;
|
|
1183
1212
|
}
|
|
1184
1213
|
}
|
|
1185
1214
|
catch {
|
|
@@ -1450,15 +1479,8 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
|
|
|
1450
1479
|
return lines.filter(line => !line.endsWith(':'));
|
|
1451
1480
|
}
|
|
1452
1481
|
metadataOnlyText(file) {
|
|
1453
|
-
const fileName = path.basename(file.relativePath);
|
|
1454
|
-
const directory = path.dirname(file.relativePath);
|
|
1455
|
-
const searchableName = fileName.replace(/[_\-.]+/gu, ' ');
|
|
1456
1482
|
return [
|
|
1457
|
-
|
|
1458
|
-
`文件路径: ${file.relativePath}`,
|
|
1459
|
-
`所在目录: ${directory === '.' ? 'knowledgeBase' : directory}`,
|
|
1460
|
-
`可搜索名称: ${searchableName}`,
|
|
1461
|
-
`文件类型: ${file.category}/${file.format}`,
|
|
1483
|
+
`资料类型: ${file.category}/${file.format}`,
|
|
1462
1484
|
`MIME: ${file.mimeType}`,
|
|
1463
1485
|
`文件大小: ${file.fileSize} bytes`,
|
|
1464
1486
|
].join('\n');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
function resolveVectorIndexBatchSize(configured) {
|
|
2
2
|
const raw = configured ?? Number(process.env.CUSTOMIZE_VECTOR_INDEX_BATCH_SIZE ?? process.env.KB_VECTOR_INDEX_BATCH_SIZE);
|
|
3
3
|
if (!Number.isFinite(raw) || raw <= 0)
|
|
4
|
-
return
|
|
5
|
-
return Math.max(1, Math.min(
|
|
4
|
+
return 256;
|
|
5
|
+
return Math.max(1, Math.min(1024, Math.floor(raw)));
|
|
6
6
|
}
|
|
7
7
|
/** 向量索引器,负责将文本切片生成 Embedding 并写入向量存储 */
|
|
8
8
|
export class VectorIndexer {
|