@customize-agent/knowledge 4.0.2 → 4.0.4
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/bge-tokenizer.d.ts +2 -0
- package/dist/chunking/bge-tokenizer.js +2 -0
- package/dist/chunking/text-chunker.d.ts +10 -0
- package/dist/chunking/text-chunker.js +8 -0
- package/dist/classification/classifier.d.ts +1 -0
- package/dist/classification/classifier.js +1 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -0
- package/dist/core/change-tracker.d.ts +13 -0
- package/dist/core/change-tracker.js +13 -0
- package/dist/core/file-scanner.d.ts +13 -0
- package/dist/core/file-scanner.js +12 -0
- package/dist/core/index-state-store.d.ts +20 -0
- package/dist/core/index-state-store.js +20 -0
- package/dist/core/knowledge-base-manager.d.ts +4 -1
- package/dist/core/knowledge-base-manager.js +12 -4
- package/dist/core/multi-project-manager.d.ts +8 -0
- package/dist/core/multi-project-manager.js +8 -0
- package/dist/core/project-config.d.ts +4 -0
- package/dist/core/project-config.js +4 -0
- package/dist/core/project-id.d.ts +5 -0
- package/dist/core/project-id.js +5 -0
- package/dist/core/project-registry.d.ts +1 -0
- package/dist/core/project-registry.js +1 -0
- package/dist/dedup/dedup-engine.d.ts +3 -0
- package/dist/dedup/dedup-engine.js +1 -0
- package/dist/dedup/relationship-detector.d.ts +7 -0
- package/dist/dedup/relationship-detector.js +7 -0
- package/dist/embedding/embedding-provider.d.ts +10 -0
- package/dist/embedding/embedding-provider.js +23 -3
- package/dist/extraction/content-extractor.d.ts +2 -0
- package/dist/extraction/content-extractor.js +10 -9
- package/dist/extraction/external-extractor.d.ts +10 -0
- package/dist/extraction/external-extractor.js +6 -0
- package/dist/extraction/module-resolver.js +2 -2
- package/dist/index.js +2 -2
- package/dist/search/federation-search.d.ts +7 -0
- package/dist/search/federation-search.js +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/vector/collection-manager.d.ts +3 -0
- package/dist/vector/collection-manager.js +3 -0
- package/dist/vector/hnsw-vector-store.d.ts +1 -0
- package/dist/vector/hnsw-vector-store.js +2 -1
- package/dist/vector/types.d.ts +6 -0
- package/dist/vector/vector-indexer.d.ts +13 -1
- package/dist/vector/vector-indexer.js +23 -6
- package/package.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
/** BGE Tokenizer,使用 tokenizer.json 执行 BGE 模型的真实 Token 化 */
|
|
1
2
|
export declare class BgeTokenizer {
|
|
2
3
|
private readonly vocab;
|
|
3
4
|
private readonly unkToken;
|
|
4
5
|
constructor(tokenizerPath?: string | undefined);
|
|
6
|
+
/** 统计文本的 Token 数量 */
|
|
5
7
|
countTokens(text: string): number;
|
|
6
8
|
encode(text: string): string[];
|
|
7
9
|
private preTokenize;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/** BGE Tokenizer,使用 tokenizer.json 执行 BGE 模型的真实 Token 化 */
|
|
4
5
|
export class BgeTokenizer {
|
|
5
6
|
vocab;
|
|
6
7
|
unkToken;
|
|
@@ -13,6 +14,7 @@ export class BgeTokenizer {
|
|
|
13
14
|
if (this.vocab.size === 0)
|
|
14
15
|
throw new Error('BGE tokenizer vocab 为空,无法执行真实 Token 计数');
|
|
15
16
|
}
|
|
17
|
+
/** 统计文本的 Token 数量 */
|
|
16
18
|
countTokens(text) {
|
|
17
19
|
return this.encode(text).length;
|
|
18
20
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClassifiedFile } from '../types.js';
|
|
2
|
+
/** 文本切片结果 */
|
|
2
3
|
export interface TextChunk {
|
|
3
4
|
index: number;
|
|
4
5
|
text: string;
|
|
@@ -8,13 +9,22 @@ export interface TextChunk {
|
|
|
8
9
|
sectionTitle?: string;
|
|
9
10
|
metadata: Record<string, unknown>;
|
|
10
11
|
}
|
|
12
|
+
/** 切片配置参数 */
|
|
11
13
|
export interface ChunkConfig {
|
|
12
14
|
maxChunkSize: number;
|
|
13
15
|
overlap: number;
|
|
14
16
|
headerInjection: boolean;
|
|
15
17
|
}
|
|
18
|
+
/** 文本切片器,支持文档、表格、代码等多类型文件的递归式切片 */
|
|
16
19
|
export declare class TextChunker {
|
|
17
20
|
private readonly tokenizer;
|
|
21
|
+
/**
|
|
22
|
+
* 将文本内容按类型和配置分割为切片
|
|
23
|
+
* @param text 原始文本内容
|
|
24
|
+
* @param file 已分类的文件信息
|
|
25
|
+
* @param metadata 额外元数据
|
|
26
|
+
* @returns 切片列表
|
|
27
|
+
*/
|
|
18
28
|
chunk(text: string, file: ClassifiedFile, metadata?: Record<string, unknown>): TextChunk[];
|
|
19
29
|
private createCandidates;
|
|
20
30
|
private createTextCandidates;
|
|
@@ -29,8 +29,16 @@ const LANGUAGE_ROUTER = {
|
|
|
29
29
|
cpp: { delimiters: /\n(?=(?:class|struct|namespace|template)\s|[\w:*&<>]+\s+\w+\s*\()/u, blockStart: /\{\s*$/u, indentSensitive: false },
|
|
30
30
|
c: { delimiters: /\n(?=(?:struct|enum)\s|[\w*]+\s+\w+\s*\()/u, blockStart: /\{\s*$/u, indentSensitive: false },
|
|
31
31
|
};
|
|
32
|
+
/** 文本切片器,支持文档、表格、代码等多类型文件的递归式切片 */
|
|
32
33
|
export class TextChunker {
|
|
33
34
|
tokenizer = new BgeTokenizer();
|
|
35
|
+
/**
|
|
36
|
+
* 将文本内容按类型和配置分割为切片
|
|
37
|
+
* @param text 原始文本内容
|
|
38
|
+
* @param file 已分类的文件信息
|
|
39
|
+
* @param metadata 额外元数据
|
|
40
|
+
* @returns 切片列表
|
|
41
|
+
*/
|
|
34
42
|
chunk(text, file, metadata = {}) {
|
|
35
43
|
const source = text.trim();
|
|
36
44
|
if (source.length === 0)
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { FileCategory } from './types.js';
|
|
2
|
+
/** 知识库数据目录名 */
|
|
2
3
|
export declare const KNOWLEDGE_BASE_DIR = "knowledgeBase";
|
|
4
|
+
/** 项目配置文件路径(相对于项目根目录) */
|
|
3
5
|
export declare const PROJECT_CONFIG_PATH: readonly [".customize-agent", "kb", "project.json"];
|
|
6
|
+
/** 自定义 Agent 用户数据目录 */
|
|
4
7
|
export declare const USER_DATA_DIR = ".customize-agent";
|
|
8
|
+
/** 全局知识库目录名 */
|
|
5
9
|
export declare const GLOBAL_KNOWLEDGE_DIR = "global-knowledge";
|
|
10
|
+
/** 所有支持的文件分类 */
|
|
6
11
|
export declare const ALL_CATEGORIES: readonly FileCategory[];
|
|
12
|
+
/** 各文件分类的默认目录(中文名称) */
|
|
7
13
|
export declare const DEFAULT_CATEGORY_DIRS: Record<FileCategory, string>;
|
|
14
|
+
/** 各文件分类对应的 Vector Collection 名称(英文,供存储使用) */
|
|
8
15
|
export declare const COLLECTION_CATEGORY_NAMES: Record<FileCategory, string>;
|
package/dist/constants.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
/** 知识库数据目录名 */
|
|
1
2
|
export const KNOWLEDGE_BASE_DIR = 'knowledgeBase';
|
|
3
|
+
/** 项目配置文件路径(相对于项目根目录) */
|
|
2
4
|
export const PROJECT_CONFIG_PATH = ['.customize-agent', 'kb', 'project.json'];
|
|
5
|
+
/** 自定义 Agent 用户数据目录 */
|
|
3
6
|
export const USER_DATA_DIR = '.customize-agent';
|
|
7
|
+
/** 全局知识库目录名 */
|
|
4
8
|
export const GLOBAL_KNOWLEDGE_DIR = 'global-knowledge';
|
|
9
|
+
/** 所有支持的文件分类 */
|
|
5
10
|
export const ALL_CATEGORIES = [
|
|
6
11
|
'document',
|
|
7
12
|
'spreadsheet',
|
|
@@ -14,6 +19,7 @@ export const ALL_CATEGORIES = [
|
|
|
14
19
|
'archive',
|
|
15
20
|
'other',
|
|
16
21
|
];
|
|
22
|
+
/** 各文件分类的默认目录(中文名称) */
|
|
17
23
|
export const DEFAULT_CATEGORY_DIRS = {
|
|
18
24
|
document: '文档资料',
|
|
19
25
|
spreadsheet: '表格数据',
|
|
@@ -26,6 +32,7 @@ export const DEFAULT_CATEGORY_DIRS = {
|
|
|
26
32
|
archive: '压缩包',
|
|
27
33
|
other: '其他文件',
|
|
28
34
|
};
|
|
35
|
+
/** 各文件分类对应的 Vector Collection 名称(英文,供存储使用) */
|
|
29
36
|
export const COLLECTION_CATEGORY_NAMES = {
|
|
30
37
|
document: 'documents',
|
|
31
38
|
spreadsheet: 'spreadsheets',
|
|
@@ -2,10 +2,23 @@ import type { FileClassifier } from '../classification/classifier.js';
|
|
|
2
2
|
import type { DiffResult } from '../types.js';
|
|
3
3
|
import type { DiskFileStat } from './file-scanner.js';
|
|
4
4
|
import type { IndexStateStore } from './index-state-store.js';
|
|
5
|
+
/** 文件变更追踪器,用于比对磁盘文件与索引状态之间的差异 */
|
|
5
6
|
export declare class ChangeTracker {
|
|
6
7
|
private readonly store;
|
|
7
8
|
constructor(store: IndexStateStore);
|
|
9
|
+
/**
|
|
10
|
+
* 计算磁盘文件与索引状态之间的差异
|
|
11
|
+
* @param diskFiles 磁盘上的文件列表
|
|
12
|
+
* @param classifier 文件分类器
|
|
13
|
+
* @param kbPath 知识库路径
|
|
14
|
+
* @returns 文件差异对比结果
|
|
15
|
+
*/
|
|
8
16
|
computeDiff(diskFiles: Map<string, DiskFileStat>, classifier: FileClassifier, kbPath: string): Promise<DiffResult>;
|
|
17
|
+
/**
|
|
18
|
+
* 计算文件的 SHA-256 哈希值
|
|
19
|
+
* @param filePath 文件路径
|
|
20
|
+
* @returns SHA-256 哈希字符串
|
|
21
|
+
*/
|
|
9
22
|
hashFile(filePath: string): string;
|
|
10
23
|
private parseMetadata;
|
|
11
24
|
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import * as crypto from 'node:crypto';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
/** 文件变更追踪器,用于比对磁盘文件与索引状态之间的差异 */
|
|
4
5
|
export class ChangeTracker {
|
|
5
6
|
store;
|
|
6
7
|
constructor(store) {
|
|
7
8
|
this.store = store;
|
|
8
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* 计算磁盘文件与索引状态之间的差异
|
|
12
|
+
* @param diskFiles 磁盘上的文件列表
|
|
13
|
+
* @param classifier 文件分类器
|
|
14
|
+
* @param kbPath 知识库路径
|
|
15
|
+
* @returns 文件差异对比结果
|
|
16
|
+
*/
|
|
9
17
|
async computeDiff(diskFiles, classifier, kbPath) {
|
|
10
18
|
const startTime = Date.now();
|
|
11
19
|
const indexedFiles = this.store.loadActiveRecords();
|
|
@@ -73,6 +81,11 @@ export class ChangeTracker {
|
|
|
73
81
|
diffTimeMs: Date.now() - startTime,
|
|
74
82
|
};
|
|
75
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* 计算文件的 SHA-256 哈希值
|
|
86
|
+
* @param filePath 文件路径
|
|
87
|
+
* @returns SHA-256 哈希字符串
|
|
88
|
+
*/
|
|
76
89
|
hashFile(filePath) {
|
|
77
90
|
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
78
91
|
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
/** 磁盘文件信息 */
|
|
1
2
|
export interface DiskFileStat {
|
|
2
3
|
size: number;
|
|
3
4
|
mtime: number;
|
|
4
5
|
}
|
|
6
|
+
/** 知识库文件扫描器,用于扫描和加载知识库目录中的文件 */
|
|
5
7
|
export declare class KnowledgeFileScanner {
|
|
8
|
+
/**
|
|
9
|
+
* 扫描知识库目录中的所有文件
|
|
10
|
+
* @param kbPath 知识库路径
|
|
11
|
+
* @param ignorePatterns 忽略模式列表
|
|
12
|
+
* @returns 文件相对路径到文件信息的映射
|
|
13
|
+
*/
|
|
6
14
|
scan(kbPath: string, ignorePatterns?: string[]): Promise<Map<string, DiskFileStat>>;
|
|
15
|
+
/**
|
|
16
|
+
* 加载 .kbignore 忽略规则文件
|
|
17
|
+
* @param kbPath 知识库路径
|
|
18
|
+
* @returns 忽略规则列表
|
|
19
|
+
*/
|
|
7
20
|
loadKbIgnore(kbPath: string): string[];
|
|
8
21
|
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import fg from 'fast-glob';
|
|
4
|
+
/** 知识库文件扫描器,用于扫描和加载知识库目录中的文件 */
|
|
4
5
|
export class KnowledgeFileScanner {
|
|
6
|
+
/**
|
|
7
|
+
* 扫描知识库目录中的所有文件
|
|
8
|
+
* @param kbPath 知识库路径
|
|
9
|
+
* @param ignorePatterns 忽略模式列表
|
|
10
|
+
* @returns 文件相对路径到文件信息的映射
|
|
11
|
+
*/
|
|
5
12
|
async scan(kbPath, ignorePatterns = []) {
|
|
6
13
|
if (!fs.existsSync(kbPath))
|
|
7
14
|
return new Map();
|
|
@@ -20,6 +27,11 @@ export class KnowledgeFileScanner {
|
|
|
20
27
|
}
|
|
21
28
|
return files;
|
|
22
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* 加载 .kbignore 忽略规则文件
|
|
32
|
+
* @param kbPath 知识库路径
|
|
33
|
+
* @returns 忽略规则列表
|
|
34
|
+
*/
|
|
23
35
|
loadKbIgnore(kbPath) {
|
|
24
36
|
const ignorePath = path.join(kbPath, '.kbignore');
|
|
25
37
|
if (!fs.existsSync(ignorePath))
|
|
@@ -84,12 +84,16 @@ export interface FileRelationship {
|
|
|
84
84
|
userConfirmed: number;
|
|
85
85
|
createdAt: number;
|
|
86
86
|
}
|
|
87
|
+
/** 索引状态存储器,使用 SQLite 管理知识库索引的持久化状态 */
|
|
87
88
|
export declare class IndexStateStore {
|
|
88
89
|
private readonly db;
|
|
89
90
|
private ftsEnabled;
|
|
90
91
|
constructor(dbPath: string);
|
|
92
|
+
/** 加载所有活跃的索引记录 */
|
|
91
93
|
loadActiveRecords(): Map<string, IndexStateRecord>;
|
|
94
|
+
/** 插入或更新索引记录 */
|
|
92
95
|
upsertRecord(record: IndexStateRecord): void;
|
|
96
|
+
/** 更新已验证文件的时间戳和状态 */
|
|
93
97
|
updateVerified(relativePath: string, mtime: number): void;
|
|
94
98
|
listRecords(): IndexStateRecord[];
|
|
95
99
|
enqueueIndexJob(job: {
|
|
@@ -108,6 +112,12 @@ export declare class IndexStateStore {
|
|
|
108
112
|
countPendingIndexJobs(): number;
|
|
109
113
|
listActiveIndexJobsByPath(relativePath: string): KnowledgeIndexJob[];
|
|
110
114
|
listIndexJobsByPrefix(prefix: string): KnowledgeIndexJob[];
|
|
115
|
+
/**
|
|
116
|
+
* 替换指定文件的切片数据(使用事务批量更新)
|
|
117
|
+
* @param relativePath 文件相对路径
|
|
118
|
+
* @param chunks 文本切片列表
|
|
119
|
+
* @param file 文件分类信息
|
|
120
|
+
*/
|
|
111
121
|
replaceChunks(relativePath: string, chunks: TextChunk[], file: {
|
|
112
122
|
category: FileCategory;
|
|
113
123
|
format: string;
|
|
@@ -125,6 +135,12 @@ export declare class IndexStateStore {
|
|
|
125
135
|
getParentChunk(relativePath: string, parentId: string): StoredParentChunk | undefined;
|
|
126
136
|
getDocumentChunk(relativePath: string): StoredDocumentChunk | undefined;
|
|
127
137
|
getChunksByParent(relativePath: string, parentId: string, limit?: number): StoredChunk[];
|
|
138
|
+
/**
|
|
139
|
+
* 使用关键词搜索切片(支持 FTS5 全文搜索和 LIKE 模糊匹配)
|
|
140
|
+
* @param query 搜索查询
|
|
141
|
+
* @param limit 返回结果数量上限
|
|
142
|
+
* @returns 搜索结果列表(按相关性得分排序)
|
|
143
|
+
*/
|
|
128
144
|
searchChunks(query: string, limit?: number): ChunkSearchResult[];
|
|
129
145
|
private searchChunksFts;
|
|
130
146
|
private searchChunksLike;
|
|
@@ -149,6 +165,10 @@ export declare class IndexStateStore {
|
|
|
149
165
|
enabled: boolean;
|
|
150
166
|
createdAt: number;
|
|
151
167
|
}>;
|
|
168
|
+
/**
|
|
169
|
+
* 删除指定文件的全部索引数据(含切片、哈希、MinHash、标签、关系等)
|
|
170
|
+
* @param relativePath 文件相对路径
|
|
171
|
+
*/
|
|
152
172
|
deleteRecord(relativePath: string): void;
|
|
153
173
|
setMetadata(key: string, value: string): void;
|
|
154
174
|
getMetadata(key: string): string | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
/** 索引状态存储器,使用 SQLite 管理知识库索引的持久化状态 */
|
|
4
5
|
export class IndexStateStore {
|
|
5
6
|
db;
|
|
6
7
|
ftsEnabled = false;
|
|
@@ -10,6 +11,7 @@ export class IndexStateStore {
|
|
|
10
11
|
this.db.pragma('journal_mode = WAL');
|
|
11
12
|
this.initTables();
|
|
12
13
|
}
|
|
14
|
+
/** 加载所有活跃的索引记录 */
|
|
13
15
|
loadActiveRecords() {
|
|
14
16
|
const rows = this.db.prepare(`
|
|
15
17
|
SELECT * FROM kb_index_state
|
|
@@ -20,6 +22,7 @@ export class IndexStateStore {
|
|
|
20
22
|
return [record.relativePath, record];
|
|
21
23
|
}));
|
|
22
24
|
}
|
|
25
|
+
/** 插入或更新索引记录 */
|
|
23
26
|
upsertRecord(record) {
|
|
24
27
|
this.db.prepare(`
|
|
25
28
|
INSERT INTO kb_index_state (
|
|
@@ -41,6 +44,7 @@ export class IndexStateStore {
|
|
|
41
44
|
metadata_json = excluded.metadata_json
|
|
42
45
|
`).run(record.relativePath, record.category, record.format, record.contentHash, record.fileSize, Math.round(record.mtime), record.chunkCount, record.collectionName, record.indexedAt, record.lastVerifiedAt, record.status, record.errorMessage ?? null, record.metadataJson ?? null);
|
|
43
46
|
}
|
|
47
|
+
/** 更新已验证文件的时间戳和状态 */
|
|
44
48
|
updateVerified(relativePath, mtime) {
|
|
45
49
|
this.db.prepare(`
|
|
46
50
|
UPDATE kb_index_state
|
|
@@ -108,6 +112,12 @@ export class IndexStateStore {
|
|
|
108
112
|
`).all(`${prefix}%`);
|
|
109
113
|
return rows.map(row => this.rowToJob(row));
|
|
110
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* 替换指定文件的切片数据(使用事务批量更新)
|
|
117
|
+
* @param relativePath 文件相对路径
|
|
118
|
+
* @param chunks 文本切片列表
|
|
119
|
+
* @param file 文件分类信息
|
|
120
|
+
*/
|
|
111
121
|
replaceChunks(relativePath, chunks, file) {
|
|
112
122
|
const now = Date.now();
|
|
113
123
|
const transaction = this.db.transaction(() => {
|
|
@@ -237,6 +247,12 @@ export class IndexStateStore {
|
|
|
237
247
|
`).all(relativePath, `%"parentId":"${parentId.replace(/[%_]/gu, '')}"%`, limit);
|
|
238
248
|
return rows.map(row => this.rowToChunk(row, 0));
|
|
239
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* 使用关键词搜索切片(支持 FTS5 全文搜索和 LIKE 模糊匹配)
|
|
252
|
+
* @param query 搜索查询
|
|
253
|
+
* @param limit 返回结果数量上限
|
|
254
|
+
* @returns 搜索结果列表(按相关性得分排序)
|
|
255
|
+
*/
|
|
240
256
|
searchChunks(query, limit = 10) {
|
|
241
257
|
const terms = this.expandSearchTerms(query);
|
|
242
258
|
if (terms.length === 0)
|
|
@@ -410,6 +426,10 @@ export class IndexStateStore {
|
|
|
410
426
|
createdAt: Number(row.created_at),
|
|
411
427
|
}));
|
|
412
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* 删除指定文件的全部索引数据(含切片、哈希、MinHash、标签、关系等)
|
|
431
|
+
* @param relativePath 文件相对路径
|
|
432
|
+
*/
|
|
413
433
|
deleteRecord(relativePath) {
|
|
414
434
|
this.db.prepare('DELETE FROM kb_chunks WHERE relative_path = ?').run(relativePath);
|
|
415
435
|
this.db.prepare('DELETE FROM kb_parent_chunks WHERE relative_path = ?').run(relativePath);
|
|
@@ -94,7 +94,10 @@ export declare class KnowledgeBaseManager {
|
|
|
94
94
|
createdAt: number;
|
|
95
95
|
}[];
|
|
96
96
|
} | undefined;
|
|
97
|
-
reindexFile(relativePath: string
|
|
97
|
+
reindexFile(relativePath: string, options?: {
|
|
98
|
+
onProgress?: (progress: KnowledgeIndexProgress) => void;
|
|
99
|
+
vectorMode?: 'sync' | 'defer';
|
|
100
|
+
}): Promise<DiffResult>;
|
|
98
101
|
addFile(sourcePath: string, targetRelativePath?: string): Promise<DiffResult>;
|
|
99
102
|
getUploadRelativePath(fileName: string, targetRelativePath?: string): string;
|
|
100
103
|
uploadFile(fileName: string, content: Buffer, targetRelativePath?: string, onProgress?: (progress: KnowledgeIndexProgress) => void, options?: {
|
|
@@ -331,7 +331,7 @@ export class KnowledgeBaseManager {
|
|
|
331
331
|
try {
|
|
332
332
|
rankedLists.push({ source: 'vector', items: (await this.semanticSearch(rewritten, { ...options, limit: limit * 3 })).results, queryIndex });
|
|
333
333
|
}
|
|
334
|
-
catch { /*
|
|
334
|
+
catch { /* 向量搜索在混合搜索中是可选的 */ }
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
const keywordItems = rankedLists.filter(list => list.source === 'keyword').flatMap(list => list.items);
|
|
@@ -396,7 +396,7 @@ export class KnowledgeBaseManager {
|
|
|
396
396
|
tags: this.store.listTags(normalized),
|
|
397
397
|
};
|
|
398
398
|
}
|
|
399
|
-
async reindexFile(relativePath) {
|
|
399
|
+
async reindexFile(relativePath, options = {}) {
|
|
400
400
|
const normalized = this.normalizeRelativePath(relativePath);
|
|
401
401
|
const record = this.store.listRecords().find(item => item.relativePath === normalized);
|
|
402
402
|
const targetPath = this.resolveKbRelativePath(normalized);
|
|
@@ -405,7 +405,7 @@ export class KnowledgeBaseManager {
|
|
|
405
405
|
if (record)
|
|
406
406
|
await this.deleteVectorFile(record.collectionName, normalized);
|
|
407
407
|
this.store.deleteRecord(normalized);
|
|
408
|
-
return this.incrementalIndex();
|
|
408
|
+
return this.incrementalIndex({ ...options, onlyRelativePaths: [normalized] });
|
|
409
409
|
}
|
|
410
410
|
async addFile(sourcePath, targetRelativePath) {
|
|
411
411
|
this.initialize();
|
|
@@ -497,7 +497,15 @@ export class KnowledgeBaseManager {
|
|
|
497
497
|
this.reportProgress({ stage: 'vectorizing', percent: 85, message: `正在写入 HNSWLib 向量库,共 ${chunks.length} 个切片`, chunkCount: chunks.length });
|
|
498
498
|
const indexer = new VectorIndexer(this.embeddingProvider, this.vectorStores);
|
|
499
499
|
try {
|
|
500
|
-
const results = await indexer.indexChunks(chunks
|
|
500
|
+
const results = await indexer.indexChunks(chunks, {
|
|
501
|
+
onProgress: progress => {
|
|
502
|
+
const percent = 85 + Math.round((progress.processedChunks / Math.max(1, progress.totalChunks)) * 14);
|
|
503
|
+
const message = `正在分批向量化并写入:${progress.processedChunks}/${progress.totalChunks} 个切片`;
|
|
504
|
+
this.reportProgress({ stage: 'vectorizing', percent, message, chunkCount: progress.totalChunks });
|
|
505
|
+
if (options.relativePath)
|
|
506
|
+
this.updateJobsForFile(options.relativePath, 'INDEXING', percent, message);
|
|
507
|
+
},
|
|
508
|
+
});
|
|
501
509
|
const actualModel = results[0]?.embeddingModel ?? this.embeddingProvider.model;
|
|
502
510
|
const actualDimension = results[0]?.embeddingDimension ?? this.embeddingProvider.dimensions;
|
|
503
511
|
this.store.setMetadata('embedding_model', actualModel);
|
|
@@ -2,6 +2,7 @@ import type { FederatedResult, RetrievalWeights, SearchFilters, SearchScope } fr
|
|
|
2
2
|
import type { CrossProjectDuplicate, ProjectInfo } from '../types.js';
|
|
3
3
|
import { KnowledgeBaseManager } from './knowledge-base-manager.js';
|
|
4
4
|
import type { LLMSearchProvider } from '../llm/llm-search-provider.js';
|
|
5
|
+
/** 多项目管理器,管理多个项目的知识库并支持跨项目搜索 */
|
|
5
6
|
export declare class MultiProjectManager {
|
|
6
7
|
private readonly storageRoot;
|
|
7
8
|
private readonly llmProvider?;
|
|
@@ -11,7 +12,14 @@ export declare class MultiProjectManager {
|
|
|
11
12
|
private readonly lastSearchIndexCheck;
|
|
12
13
|
private globalKB?;
|
|
13
14
|
constructor(storageRoot?: string, llmProvider?: LLMSearchProvider);
|
|
15
|
+
/**
|
|
16
|
+
* 获取或创建指定项目的知识库管理器
|
|
17
|
+
* @param projectRoot 项目根目录
|
|
18
|
+
*/
|
|
14
19
|
getProject(projectRoot: string): Promise<KnowledgeBaseManager>;
|
|
20
|
+
/**
|
|
21
|
+
* 获取或初始化全局知识库
|
|
22
|
+
*/
|
|
15
23
|
getGlobalKB(): Promise<KnowledgeBaseManager>;
|
|
16
24
|
listProjects(): Promise<ProjectInfo[]>;
|
|
17
25
|
search(projectRoot: string, query: string, options?: {
|
|
@@ -6,6 +6,7 @@ import { KnowledgeBaseManager } from './knowledge-base-manager.js';
|
|
|
6
6
|
import { computeProjectId } from './project-id.js';
|
|
7
7
|
import { getProjectKbPath, ProjectConfigManager } from './project-config.js';
|
|
8
8
|
import { ProjectRegistry } from './project-registry.js';
|
|
9
|
+
/** 多项目管理器,管理多个项目的知识库并支持跨项目搜索 */
|
|
9
10
|
export class MultiProjectManager {
|
|
10
11
|
storageRoot;
|
|
11
12
|
llmProvider;
|
|
@@ -20,6 +21,10 @@ export class MultiProjectManager {
|
|
|
20
21
|
this.registry = new ProjectRegistry(path.join(storageRoot, 'projects', 'registry.db'));
|
|
21
22
|
this.configManager = new ProjectConfigManager(storageRoot);
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* 获取或创建指定项目的知识库管理器
|
|
26
|
+
* @param projectRoot 项目根目录
|
|
27
|
+
*/
|
|
23
28
|
async getProject(projectRoot) {
|
|
24
29
|
const resolvedRoot = path.resolve(projectRoot);
|
|
25
30
|
const projectId = computeProjectId(resolvedRoot);
|
|
@@ -40,6 +45,9 @@ export class MultiProjectManager {
|
|
|
40
45
|
this.updateRegistry(manager, resolvedRoot, config.projectName, config.lastOpenedAt);
|
|
41
46
|
return manager;
|
|
42
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* 获取或初始化全局知识库
|
|
50
|
+
*/
|
|
43
51
|
async getGlobalKB() {
|
|
44
52
|
if (this.globalKB)
|
|
45
53
|
return this.globalKB;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { ProjectConfig } from '../types.js';
|
|
2
|
+
/** 获取项目配置文件路径 */
|
|
2
3
|
export declare function getProjectConfigPath(projectRoot: string, storageRoot?: string): string;
|
|
4
|
+
/** 获取项目知识库目录路径 */
|
|
3
5
|
export declare function getProjectKbPath(projectRoot: string): string;
|
|
6
|
+
/** 确保项目存在 CUSTOMIZE.md 文件(如不存在则创建默认模板) */
|
|
4
7
|
export declare function ensureProjectCustomizeFile(projectRoot: string): void;
|
|
8
|
+
/** 项目配置管理器,负责加载和保存项目配置 */
|
|
5
9
|
export declare class ProjectConfigManager {
|
|
6
10
|
private readonly storageRoot;
|
|
7
11
|
constructor(storageRoot?: string);
|
|
@@ -3,10 +3,12 @@ import * as path from 'node:path';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import { DEFAULT_CATEGORY_DIRS, KNOWLEDGE_BASE_DIR, USER_DATA_DIR } from '../constants.js';
|
|
5
5
|
import { computeProjectId } from './project-id.js';
|
|
6
|
+
/** 获取项目配置文件路径 */
|
|
6
7
|
export function getProjectConfigPath(projectRoot, storageRoot = path.join(os.homedir(), USER_DATA_DIR)) {
|
|
7
8
|
const projectId = computeProjectId(projectRoot);
|
|
8
9
|
return path.join(storageRoot, 'projects', projectId, 'project.json');
|
|
9
10
|
}
|
|
11
|
+
/** 获取项目知识库目录路径 */
|
|
10
12
|
export function getProjectKbPath(projectRoot) {
|
|
11
13
|
return path.join(projectRoot, KNOWLEDGE_BASE_DIR);
|
|
12
14
|
}
|
|
@@ -27,6 +29,7 @@ const DEFAULT_CUSTOMIZE_MD = `# Customize Agent 配置示例
|
|
|
27
29
|
- 使用中文回复。
|
|
28
30
|
- 重要改动完成后运行必要的类型检查或构建检查。
|
|
29
31
|
`;
|
|
32
|
+
/** 确保项目存在 CUSTOMIZE.md 文件(如不存在则创建默认模板) */
|
|
30
33
|
export function ensureProjectCustomizeFile(projectRoot) {
|
|
31
34
|
const filePath = path.join(projectRoot, 'CUSTOMIZE.md');
|
|
32
35
|
if (!fs.existsSync(filePath)) {
|
|
@@ -34,6 +37,7 @@ export function ensureProjectCustomizeFile(projectRoot) {
|
|
|
34
37
|
fs.writeFileSync(filePath, DEFAULT_CUSTOMIZE_MD, 'utf8');
|
|
35
38
|
}
|
|
36
39
|
}
|
|
40
|
+
/** 项目配置管理器,负责加载和保存项目配置 */
|
|
37
41
|
export class ProjectConfigManager {
|
|
38
42
|
storageRoot;
|
|
39
43
|
constructor(storageRoot = path.join(os.homedir(), USER_DATA_DIR)) {
|
package/dist/core/project-id.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import * as crypto from 'node:crypto';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* 使用项目根目录计算项目唯一标识
|
|
5
|
+
* @param projectRoot 项目根目录路径
|
|
6
|
+
* @returns 项目 ID(SHA-256 前 12 位)
|
|
7
|
+
*/
|
|
3
8
|
export function computeProjectId(projectRoot) {
|
|
4
9
|
return crypto.createHash('sha256')
|
|
5
10
|
.update(path.resolve(projectRoot))
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
/** MinHash 签名结果 */
|
|
1
2
|
export interface MinHashSignature {
|
|
2
3
|
signature: number[];
|
|
3
4
|
shingleCount: number;
|
|
4
5
|
buckets: string[];
|
|
5
6
|
}
|
|
7
|
+
/** 相似度匹配结果 */
|
|
6
8
|
export interface SimilarityMatch {
|
|
7
9
|
filePath: string;
|
|
8
10
|
similarity: number;
|
|
9
11
|
}
|
|
12
|
+
/** 去重引擎,支持归一化哈希、MinHash 相似度计算和 LSH 分桶 */
|
|
10
13
|
export declare class DedupEngine {
|
|
11
14
|
private readonly hashCount;
|
|
12
15
|
private readonly bandSize;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { ClassifiedFile, IndexStateRecord } from '../types.js';
|
|
2
2
|
import type { FileRelationship } from '../core/index-state-store.js';
|
|
3
|
+
/** 文件关系检测器,自动识别版本链、翻译关系和互补关系 */
|
|
3
4
|
export declare class RelationshipDetector {
|
|
5
|
+
/**
|
|
6
|
+
* 检测文件与已有索引记录之间的关系
|
|
7
|
+
* @param file 当前处理的文件
|
|
8
|
+
* @param indexedRecords 已有索引记录列表
|
|
9
|
+
* @returns 检测到的关系列表
|
|
10
|
+
*/
|
|
4
11
|
detect(file: ClassifiedFile, indexedRecords: IndexStateRecord[]): Array<Omit<FileRelationship, 'id' | 'createdAt'>>;
|
|
5
12
|
private detectVersionChain;
|
|
6
13
|
private detectTranslation;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
|
+
/** 文件关系检测器,自动识别版本链、翻译关系和互补关系 */
|
|
2
3
|
export class RelationshipDetector {
|
|
4
|
+
/**
|
|
5
|
+
* 检测文件与已有索引记录之间的关系
|
|
6
|
+
* @param file 当前处理的文件
|
|
7
|
+
* @param indexedRecords 已有索引记录列表
|
|
8
|
+
* @returns 检测到的关系列表
|
|
9
|
+
*/
|
|
3
10
|
detect(file, indexedRecords) {
|
|
4
11
|
const relationships = [];
|
|
5
12
|
for (const record of indexedRecords) {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
/** Embedding Provider 接口,负责将文本转换为向量 */
|
|
1
2
|
export interface EmbeddingProvider {
|
|
2
3
|
readonly model: string;
|
|
3
4
|
readonly dimensions: number;
|
|
4
5
|
embedDocuments(texts: string[]): Promise<number[][]>;
|
|
5
6
|
embedQuery(text: string): Promise<number[]>;
|
|
6
7
|
}
|
|
8
|
+
/** 基于哈希的本地 Embedding Provider(无需模型,使用哈希算法生成特征向量) */
|
|
7
9
|
export declare class HashEmbeddingProvider implements EmbeddingProvider {
|
|
8
10
|
readonly dimensions: number;
|
|
9
11
|
readonly model = "hash-embedding-local";
|
|
@@ -20,6 +22,7 @@ export interface OpenAICompatibleEmbeddingOptions {
|
|
|
20
22
|
model: string;
|
|
21
23
|
dimensions?: number;
|
|
22
24
|
}
|
|
25
|
+
/** OpenAI 兼容的 Embedding Provider,支持任何兼容 OpenAI API 的嵌入服务 */
|
|
23
26
|
export declare class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {
|
|
24
27
|
readonly model: string;
|
|
25
28
|
readonly dimensions: number;
|
|
@@ -34,11 +37,14 @@ export interface LocalTransformersEmbeddingOptions {
|
|
|
34
37
|
model?: string;
|
|
35
38
|
dimensions?: number;
|
|
36
39
|
modelPath?: string;
|
|
40
|
+
batchSize?: number;
|
|
37
41
|
}
|
|
42
|
+
/** 本地 Transformers.js 模型 Embedding Provider,使用 BGE 小模型生成向量 */
|
|
38
43
|
export declare class LocalTransformersEmbeddingProvider implements EmbeddingProvider {
|
|
39
44
|
readonly model: string;
|
|
40
45
|
readonly dimensions: number;
|
|
41
46
|
private readonly modelPath?;
|
|
47
|
+
private readonly batchSize;
|
|
42
48
|
private static pipelines;
|
|
43
49
|
constructor(options?: LocalTransformersEmbeddingOptions);
|
|
44
50
|
embedDocuments(texts: string[]): Promise<number[][]>;
|
|
@@ -52,4 +58,8 @@ export declare class LocalTransformersEmbeddingProvider implements EmbeddingProv
|
|
|
52
58
|
private resizeVector;
|
|
53
59
|
private normalize;
|
|
54
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* 根据环境变量或配置文件自动创建 Embedding Provider
|
|
63
|
+
* 优先级:环境变量 > 配置文件 > 本地 Transformers.js 默认
|
|
64
|
+
*/
|
|
55
65
|
export declare function createEmbeddingProviderFromEnvironment(): EmbeddingProvider;
|
|
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
/** 基于哈希的本地 Embedding Provider(无需模型,使用哈希算法生成特征向量) */
|
|
6
7
|
export class HashEmbeddingProvider {
|
|
7
8
|
dimensions;
|
|
8
9
|
model = 'hash-embedding-local';
|
|
@@ -48,6 +49,7 @@ export class HashEmbeddingProvider {
|
|
|
48
49
|
return vector.map(value => value / norm);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
52
|
+
/** OpenAI 兼容的 Embedding Provider,支持任何兼容 OpenAI API 的嵌入服务 */
|
|
51
53
|
export class OpenAICompatibleEmbeddingProvider {
|
|
52
54
|
model;
|
|
53
55
|
dimensions;
|
|
@@ -80,6 +82,13 @@ export class OpenAICompatibleEmbeddingProvider {
|
|
|
80
82
|
return (payload.data ?? []).map(item => item.embedding ?? []);
|
|
81
83
|
}
|
|
82
84
|
}
|
|
85
|
+
function resolveLocalEmbeddingBatchSize(configured) {
|
|
86
|
+
const raw = configured ?? Number(process.env.CUSTOMIZE_EMBEDDING_BATCH_SIZE ?? process.env.KB_EMBEDDING_BATCH_SIZE);
|
|
87
|
+
const fallback = process.platform === 'win32' ? 8 : 16;
|
|
88
|
+
if (!Number.isFinite(raw) || raw <= 0)
|
|
89
|
+
return fallback;
|
|
90
|
+
return Math.max(1, Math.min(128, Math.floor(raw)));
|
|
91
|
+
}
|
|
83
92
|
function resolveBundledBgeModelPath() {
|
|
84
93
|
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
85
94
|
const candidates = [
|
|
@@ -91,15 +100,18 @@ function resolveBundledBgeModelPath() {
|
|
|
91
100
|
].filter(Boolean);
|
|
92
101
|
return candidates.find(candidate => fs.existsSync(path.join(candidate, 'config.json')) && fs.existsSync(path.join(candidate, 'tokenizer.json')));
|
|
93
102
|
}
|
|
103
|
+
/** 本地 Transformers.js 模型 Embedding Provider,使用 BGE 小模型生成向量 */
|
|
94
104
|
export class LocalTransformersEmbeddingProvider {
|
|
95
105
|
model;
|
|
96
106
|
dimensions;
|
|
97
107
|
modelPath;
|
|
108
|
+
batchSize;
|
|
98
109
|
static pipelines = new Map();
|
|
99
110
|
constructor(options = {}) {
|
|
100
111
|
this.model = options.model?.trim() || 'BAAI/bge-small-zh-v1.5';
|
|
101
112
|
this.dimensions = options.dimensions ?? 512;
|
|
102
113
|
this.modelPath = options.modelPath || resolveBundledBgeModelPath();
|
|
114
|
+
this.batchSize = resolveLocalEmbeddingBatchSize(options.batchSize);
|
|
103
115
|
}
|
|
104
116
|
async embedDocuments(texts) {
|
|
105
117
|
return this.embed(texts);
|
|
@@ -109,9 +121,13 @@ export class LocalTransformersEmbeddingProvider {
|
|
|
109
121
|
}
|
|
110
122
|
async embed(input) {
|
|
111
123
|
const extractor = await this.getPipeline();
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
const vectors = [];
|
|
125
|
+
for (let offset = 0; offset < input.length; offset += this.batchSize) {
|
|
126
|
+
const batch = input.slice(offset, offset + this.batchSize);
|
|
127
|
+
const output = await extractor(batch, { pooling: 'mean', normalize: true });
|
|
128
|
+
vectors.push(...this.parseVectors(output, batch.length).map(vector => this.resizeVector(vector)));
|
|
129
|
+
}
|
|
130
|
+
return vectors;
|
|
115
131
|
}
|
|
116
132
|
getPipeline() {
|
|
117
133
|
const existing = LocalTransformersEmbeddingProvider.pipelines.get(this.model);
|
|
@@ -200,6 +216,10 @@ function readStoredEmbeddingConfig() {
|
|
|
200
216
|
return undefined;
|
|
201
217
|
}
|
|
202
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* 根据环境变量或配置文件自动创建 Embedding Provider
|
|
221
|
+
* 优先级:环境变量 > 配置文件 > 本地 Transformers.js 默认
|
|
222
|
+
*/
|
|
203
223
|
export function createEmbeddingProviderFromEnvironment() {
|
|
204
224
|
const stored = readStoredEmbeddingConfig();
|
|
205
225
|
const provider = process.env.CUSTOMIZE_EMBEDDING_PROVIDER ?? process.env.KB_EMBEDDING_PROVIDER ?? stored?.provider ?? 'transformers-local';
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ExternalExtractorRegistry } from './external-extractor.js';
|
|
2
2
|
import type { ClassifiedFile } from '../types.js';
|
|
3
|
+
/** 文件内容提取结果 */
|
|
3
4
|
export interface ExtractionResult {
|
|
4
5
|
text: string;
|
|
5
6
|
metadata: Record<string, unknown>;
|
|
6
7
|
warnings: string[];
|
|
7
8
|
extractionTimeMs: number;
|
|
8
9
|
}
|
|
10
|
+
/** 文件内容提取器,支持文档、表格、图片、CAD 等多种文件格式的内容抽取 */
|
|
9
11
|
export declare class ContentExtractor {
|
|
10
12
|
private readonly externalExtractors;
|
|
11
13
|
constructor(externalExtractors?: ExternalExtractorRegistry);
|
|
@@ -5,6 +5,7 @@ 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
|
+
/** 文件内容提取器,支持文档、表格、图片、CAD 等多种文件格式的内容抽取 */
|
|
8
9
|
export class ContentExtractor {
|
|
9
10
|
externalExtractors;
|
|
10
11
|
constructor(externalExtractors = ExternalExtractorRegistry.fromEnvironment()) {
|
|
@@ -694,7 +695,7 @@ export class ContentExtractor {
|
|
|
694
695
|
}
|
|
695
696
|
}
|
|
696
697
|
catch {
|
|
697
|
-
//
|
|
698
|
+
// 降级到下方 Office ZIP 解析
|
|
698
699
|
}
|
|
699
700
|
}
|
|
700
701
|
return this.extractOfficeZip(file);
|
|
@@ -805,7 +806,7 @@ export class ContentExtractor {
|
|
|
805
806
|
catch {
|
|
806
807
|
if (ext === '.xls')
|
|
807
808
|
return this.extractLegacyOfficeBinary(file);
|
|
808
|
-
//
|
|
809
|
+
// 降级到下方 ZIP 文件内容提取
|
|
809
810
|
}
|
|
810
811
|
if (ext === '.xls')
|
|
811
812
|
return this.extractLegacyOfficeBinary(file);
|
|
@@ -1026,7 +1027,7 @@ try {
|
|
|
1026
1027
|
async extractPdf(file) {
|
|
1027
1028
|
const metadata = { extractionMode: 'pdf_text', vectorizable: true };
|
|
1028
1029
|
const warnings = [];
|
|
1029
|
-
//
|
|
1030
|
+
// 第一层:pdfjs-dist 文本提取(处理常规 PDF、压缩内容流、CJK 字体等)
|
|
1030
1031
|
try {
|
|
1031
1032
|
const raw = fs.readFileSync(file.absolutePath);
|
|
1032
1033
|
const text = await this.extractPdfText(raw);
|
|
@@ -1040,11 +1041,11 @@ try {
|
|
|
1040
1041
|
warnings.push(`PDF 文本提取失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
1041
1042
|
metadata.parseError = error instanceof Error ? error.message : String(error);
|
|
1042
1043
|
}
|
|
1043
|
-
//
|
|
1044
|
+
// 第二层:OCR(扫描件/图片型 PDF)—— 必须保留并确保可用
|
|
1044
1045
|
const ocr = await this.extractScannedPdfOcr(file);
|
|
1045
1046
|
if (ocr.text.trim())
|
|
1046
1047
|
return ocr;
|
|
1047
|
-
//
|
|
1048
|
+
// 第三层:仅索引元数据(兜底)
|
|
1048
1049
|
metadata.extractionMode = 'pdf_metadata_only';
|
|
1049
1050
|
metadata.contentCoverage = 'metadata_filename';
|
|
1050
1051
|
metadata.ocrRecommended = true;
|
|
@@ -1143,7 +1144,7 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
|
|
|
1143
1144
|
}
|
|
1144
1145
|
}
|
|
1145
1146
|
async extractPdfText(buffer) {
|
|
1146
|
-
//
|
|
1147
|
+
// 第一层:pdfjs-dist 文本提取(处理压缩内容流、CJK 字体、现代 PDF)
|
|
1147
1148
|
try {
|
|
1148
1149
|
const mod = await resolveAndImport('pdfjs-dist/legacy/build/pdf.mjs');
|
|
1149
1150
|
const loadingTask = mod.getDocument({ data: new Uint8Array(buffer), verbosity: 0 });
|
|
@@ -1171,7 +1172,7 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
|
|
|
1171
1172
|
if (process.env.KB_DEBUG === '1')
|
|
1172
1173
|
console.warn('[kb] pdfjs-dist extraction failed:', e.message);
|
|
1173
1174
|
}
|
|
1174
|
-
//
|
|
1175
|
+
// 第二层:pdf-parse(兼容旧版 PDF)
|
|
1175
1176
|
try {
|
|
1176
1177
|
const mod = await resolveAndImport('pdf-parse');
|
|
1177
1178
|
const pdfParse = mod.default;
|
|
@@ -1182,9 +1183,9 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
|
|
|
1182
1183
|
}
|
|
1183
1184
|
}
|
|
1184
1185
|
catch {
|
|
1185
|
-
//
|
|
1186
|
+
// 降级到下方纯正则提取
|
|
1186
1187
|
}
|
|
1187
|
-
//
|
|
1188
|
+
// 第三层:raw regex 回退(未压缩的古老 PDF)
|
|
1188
1189
|
const raw = buffer.toString('latin1');
|
|
1189
1190
|
const matches = Array.from(raw.matchAll(/\(([^()]{2,500})\)\s*T[jJ]/gu), match => match[1] ?? '')
|
|
1190
1191
|
.concat(Array.from(raw.matchAll(/\[([^\]]{2,2000})\]\s*TJ/gu), match => match[1] ?? ''));
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ClassifiedFile, FileCategory } from '../types.js';
|
|
2
|
+
/** 外部解析器提取结果 */
|
|
2
3
|
export interface ExternalExtractionResult {
|
|
3
4
|
text: string;
|
|
4
5
|
metadata?: Record<string, unknown>;
|
|
5
6
|
warnings?: string[];
|
|
6
7
|
}
|
|
8
|
+
/** 外部解析器接口 */
|
|
7
9
|
export interface ExternalExtractor {
|
|
8
10
|
readonly id: string;
|
|
9
11
|
readonly name: string;
|
|
@@ -15,6 +17,7 @@ export interface ExternalExtractor {
|
|
|
15
17
|
extract(file: ClassifiedFile): ExternalExtractionResult;
|
|
16
18
|
describe(): ExternalExtractorCapability;
|
|
17
19
|
}
|
|
20
|
+
/** 外部解析器能力描述 */
|
|
18
21
|
export interface ExternalExtractorCapability {
|
|
19
22
|
id: string;
|
|
20
23
|
name: string;
|
|
@@ -24,6 +27,7 @@ export interface ExternalExtractorCapability {
|
|
|
24
27
|
available: boolean;
|
|
25
28
|
kind: 'command';
|
|
26
29
|
}
|
|
30
|
+
/** 命令行外部解析器配置选项 */
|
|
27
31
|
export interface CommandExternalExtractorOptions {
|
|
28
32
|
id: string;
|
|
29
33
|
name: string;
|
|
@@ -34,6 +38,7 @@ export interface CommandExternalExtractorOptions {
|
|
|
34
38
|
extensions?: string[];
|
|
35
39
|
timeoutMs?: number;
|
|
36
40
|
}
|
|
41
|
+
/** 命令行外部解析器,通过调用外部命令提取文件内容 */
|
|
37
42
|
export declare class CommandExternalExtractor implements ExternalExtractor {
|
|
38
43
|
readonly id: string;
|
|
39
44
|
readonly name: string;
|
|
@@ -51,8 +56,13 @@ export declare class CommandExternalExtractor implements ExternalExtractor {
|
|
|
51
56
|
private interpolate;
|
|
52
57
|
private checkAvailable;
|
|
53
58
|
}
|
|
59
|
+
/** 外部解析器注册表,管理所有已注册的外部解析器 */
|
|
54
60
|
export declare class ExternalExtractorRegistry {
|
|
55
61
|
private readonly extractors;
|
|
62
|
+
/**
|
|
63
|
+
* 根据环境变量自动注册可用解析器
|
|
64
|
+
* @param env 环境变量(默认 process.env)
|
|
65
|
+
*/
|
|
56
66
|
static fromEnvironment(env?: NodeJS.ProcessEnv): ExternalExtractorRegistry;
|
|
57
67
|
register(extractor: ExternalExtractor): void;
|
|
58
68
|
findAll(file: ClassifiedFile): ExternalExtractor[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
/** 命令行外部解析器,通过调用外部命令提取文件内容 */
|
|
3
4
|
export class CommandExternalExtractor {
|
|
4
5
|
id;
|
|
5
6
|
name;
|
|
@@ -87,8 +88,13 @@ export class CommandExternalExtractor {
|
|
|
87
88
|
return !result.error;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
91
|
+
/** 外部解析器注册表,管理所有已注册的外部解析器 */
|
|
90
92
|
export class ExternalExtractorRegistry {
|
|
91
93
|
extractors = [];
|
|
94
|
+
/**
|
|
95
|
+
* 根据环境变量自动注册可用解析器
|
|
96
|
+
* @param env 环境变量(默认 process.env)
|
|
97
|
+
*/
|
|
92
98
|
static fromEnvironment(env = process.env) {
|
|
93
99
|
const registry = new ExternalExtractorRegistry();
|
|
94
100
|
registry.registerConfiguredOrAuto('cad-dwg', 'DWG Advanced Parser', env.CUSTOMIZE_AGENT_DWG_PARSER, [{ command: 'dwg-parser' }, { command: 'oda-dwg-parser' }], { category: 'cad', formats: ['autocad'], extensions: ['.dwg', '.dwt'] });
|
|
@@ -37,7 +37,7 @@ function findInPnpm(packageName) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
catch { /*
|
|
40
|
+
catch { /* 继续向上遍历 */ }
|
|
41
41
|
}
|
|
42
42
|
const parent = path.dirname(dir);
|
|
43
43
|
if (parent === dir)
|
|
@@ -54,7 +54,7 @@ export function resolvePackage(specifier) {
|
|
|
54
54
|
try {
|
|
55
55
|
return localRequire.resolve(specifier);
|
|
56
56
|
}
|
|
57
|
-
catch { /*
|
|
57
|
+
catch { /* 继续尝试后续回退方式 */ }
|
|
58
58
|
const parts = specifier.split('/');
|
|
59
59
|
let packageName;
|
|
60
60
|
if (parts[0]?.startsWith('@')) {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// types.ts 和 constants.ts 中的类型和常量不对外导出
|
|
2
|
+
// 它们仅在 knowledge 包内部使用。
|
|
3
3
|
export { TextChunker } from './chunking/text-chunker.js';
|
|
4
4
|
export { FileClassifier } from './classification/classifier.js';
|
|
5
5
|
export { DedupEngine } from './dedup/dedup-engine.js';
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { VectorStoreInterface } from '../vector/types.js';
|
|
2
|
+
/** 搜索范围:项目级、全局级或全部 */
|
|
2
3
|
export type SearchScope = 'project' | 'global' | 'all';
|
|
4
|
+
/** 联合搜索结果项 */
|
|
3
5
|
export interface FederatedSearchItem {
|
|
4
6
|
id: string;
|
|
5
7
|
rowid?: number;
|
|
@@ -26,6 +28,7 @@ export interface FederatedSearchItem {
|
|
|
26
28
|
};
|
|
27
29
|
facets?: Record<string, string | number | string[]>;
|
|
28
30
|
}
|
|
31
|
+
/** 联合搜索结果 */
|
|
29
32
|
export interface FederatedResult {
|
|
30
33
|
results: FederatedSearchItem[];
|
|
31
34
|
scopesSearched: Array<'project' | 'global'>;
|
|
@@ -38,16 +41,19 @@ export interface FederatedResult {
|
|
|
38
41
|
reranker?: string;
|
|
39
42
|
};
|
|
40
43
|
}
|
|
44
|
+
/** 搜索过滤器 */
|
|
41
45
|
export interface SearchFilters {
|
|
42
46
|
category?: string;
|
|
43
47
|
filePath?: string;
|
|
44
48
|
}
|
|
49
|
+
/** 各检索方式权重配置 */
|
|
45
50
|
export interface RetrievalWeights {
|
|
46
51
|
keyword?: number;
|
|
47
52
|
vector?: number;
|
|
48
53
|
rewrite?: number;
|
|
49
54
|
hybridBonus?: number;
|
|
50
55
|
}
|
|
56
|
+
/** 联合搜索查询参数 */
|
|
51
57
|
export interface FederatedQuery {
|
|
52
58
|
query: string;
|
|
53
59
|
queryEmbedding: number[];
|
|
@@ -57,6 +63,7 @@ export interface FederatedQuery {
|
|
|
57
63
|
collections?: string[];
|
|
58
64
|
filters?: SearchFilters;
|
|
59
65
|
}
|
|
66
|
+
/** 联合搜索器,支持跨向量存储的分布式搜索和结果合并 */
|
|
60
67
|
export declare class FederationSearch {
|
|
61
68
|
private readonly vectorStores;
|
|
62
69
|
static readonly SCOPE_WEIGHTS: Record<'project' | 'global', number>;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
/** 知识库作用域:项目级、全局级或会话级 */
|
|
1
2
|
export type KBScope = 'project' | 'global' | 'session';
|
|
3
|
+
/** 文件分类枚举 */
|
|
2
4
|
export type FileCategory = 'document' | 'spreadsheet' | 'image' | 'cad' | 'code' | 'data' | 'web' | 'diagram' | 'archive' | 'other';
|
|
5
|
+
/** 项目状态:活跃、空闲或错误 */
|
|
3
6
|
export type ProjectStatus = 'active' | 'idle' | 'error';
|
|
7
|
+
/** 项目信息 */
|
|
4
8
|
export interface ProjectInfo {
|
|
5
9
|
projectId: string;
|
|
6
10
|
projectRoot: string;
|
|
@@ -13,6 +17,7 @@ export interface ProjectInfo {
|
|
|
13
17
|
lastOpenedAt: number;
|
|
14
18
|
status: ProjectStatus;
|
|
15
19
|
}
|
|
20
|
+
/** 项目配置 */
|
|
16
21
|
export interface ProjectConfig {
|
|
17
22
|
projectId: string;
|
|
18
23
|
projectName?: string;
|
|
@@ -27,6 +32,7 @@ export interface ProjectConfig {
|
|
|
27
32
|
createdAt: number;
|
|
28
33
|
lastOpenedAt: number;
|
|
29
34
|
}
|
|
35
|
+
/** 已分类的文件信息 */
|
|
30
36
|
export interface ClassifiedFile {
|
|
31
37
|
absolutePath: string;
|
|
32
38
|
relativePath: string;
|
|
@@ -36,6 +42,7 @@ export interface ClassifiedFile {
|
|
|
36
42
|
mtime: number;
|
|
37
43
|
mimeType: string;
|
|
38
44
|
}
|
|
45
|
+
/** 索引状态记录 */
|
|
39
46
|
export interface IndexStateRecord {
|
|
40
47
|
relativePath: string;
|
|
41
48
|
category: FileCategory;
|
|
@@ -51,6 +58,7 @@ export interface IndexStateRecord {
|
|
|
51
58
|
errorMessage?: string;
|
|
52
59
|
metadataJson?: string;
|
|
53
60
|
}
|
|
61
|
+
/** 文件差异对比结果 */
|
|
54
62
|
export interface DiffResult {
|
|
55
63
|
newFiles: ClassifiedFile[];
|
|
56
64
|
modifiedFiles: ClassifiedFile[];
|
|
@@ -64,6 +72,7 @@ export interface DiffResult {
|
|
|
64
72
|
hasChanges: boolean;
|
|
65
73
|
diffTimeMs: number;
|
|
66
74
|
}
|
|
75
|
+
/** 知识库统计信息 */
|
|
67
76
|
export interface KnowledgeBaseStats {
|
|
68
77
|
scope: Exclude<KBScope, 'session'>;
|
|
69
78
|
projectId?: string;
|
|
@@ -72,6 +81,7 @@ export interface KnowledgeBaseStats {
|
|
|
72
81
|
totalSizeBytes: number;
|
|
73
82
|
lastIndexedAt: number;
|
|
74
83
|
}
|
|
84
|
+
/** 跨项目重复文件检测结果 */
|
|
75
85
|
export interface CrossProjectDuplicate {
|
|
76
86
|
contentHash: string;
|
|
77
87
|
files: Array<{
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { FileCategory } from '../types.js';
|
|
2
2
|
import type { CollectionClient } from './types.js';
|
|
3
|
+
/** 生成项目级 Collection 名称 */
|
|
3
4
|
export declare function projectCollectionName(projectId: string, category: FileCategory): string;
|
|
5
|
+
/** 生成全局 Collection 名称 */
|
|
4
6
|
export declare function globalCollectionName(category: FileCategory): string;
|
|
7
|
+
/** Collection 管理器,负责 Vector Collection 的创建和管理 */
|
|
5
8
|
export declare class CollectionManager {
|
|
6
9
|
private readonly client?;
|
|
7
10
|
constructor(client?: CollectionClient | undefined);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { ALL_CATEGORIES, COLLECTION_CATEGORY_NAMES } from '../constants.js';
|
|
2
|
+
/** 生成项目级 Collection 名称 */
|
|
2
3
|
export function projectCollectionName(projectId, category) {
|
|
3
4
|
return `proj_${projectId}_kb_${COLLECTION_CATEGORY_NAMES[category]}`;
|
|
4
5
|
}
|
|
6
|
+
/** 生成全局 Collection 名称 */
|
|
5
7
|
export function globalCollectionName(category) {
|
|
6
8
|
return `global_kb_${COLLECTION_CATEGORY_NAMES[category]}`;
|
|
7
9
|
}
|
|
10
|
+
/** Collection 管理器,负责 Vector Collection 的创建和管理 */
|
|
8
11
|
export class CollectionManager {
|
|
9
12
|
client;
|
|
10
13
|
constructor(client) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './types.js';
|
|
2
|
+
/** HNSW(分层可导航小世界图)向量存储,基于 hnswlib-node 实现的高效近似最近邻搜索 */
|
|
2
3
|
export declare class HNSWVectorStore implements VectorStoreInterface {
|
|
3
4
|
readonly collectionName: string;
|
|
4
5
|
private readonly indexPath;
|
|
@@ -2,6 +2,7 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
|
+
/** HNSW(分层可导航小世界图)向量存储,基于 hnswlib-node 实现的高效近似最近邻搜索 */
|
|
5
6
|
export class HNSWVectorStore {
|
|
6
7
|
collectionName;
|
|
7
8
|
indexPath;
|
|
@@ -57,7 +58,7 @@ export class HNSWVectorStore {
|
|
|
57
58
|
this.index.markDelete(rowid);
|
|
58
59
|
this.deletedSinceRebuild += 1;
|
|
59
60
|
}
|
|
60
|
-
catch { /*
|
|
61
|
+
catch { /* 忽略缺失的标签 */ }
|
|
61
62
|
this.documents.delete(rowid);
|
|
62
63
|
}
|
|
63
64
|
}
|
package/dist/vector/types.d.ts
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
|
+
/** 向量文档,包含文本内容、嵌入向量和元数据 */
|
|
1
2
|
export interface VectorDocument {
|
|
2
3
|
id: string;
|
|
3
4
|
content: string;
|
|
4
5
|
embedding: number[];
|
|
5
6
|
metadata: Record<string, string | number | boolean | null>;
|
|
6
7
|
}
|
|
8
|
+
/** 向量搜索查询参数 */
|
|
7
9
|
export interface VectorSearchQuery {
|
|
8
10
|
queryEmbedding: number[];
|
|
9
11
|
topK: number;
|
|
10
12
|
where?: Record<string, string | number | boolean>;
|
|
11
13
|
}
|
|
14
|
+
/** 向量搜索结果 */
|
|
12
15
|
export interface VectorSearchResult {
|
|
13
16
|
document: Omit<VectorDocument, 'embedding'>;
|
|
14
17
|
score: number;
|
|
15
18
|
collection: string;
|
|
16
19
|
}
|
|
20
|
+
/** Vector Collection 信息 */
|
|
17
21
|
export interface VectorCollectionInfo {
|
|
18
22
|
id?: string;
|
|
19
23
|
name: string;
|
|
20
24
|
metadata?: Record<string, unknown>;
|
|
21
25
|
}
|
|
26
|
+
/** 向量存储接口,定义所有向量存储实现必须支持的方法 */
|
|
22
27
|
export interface VectorStoreInterface {
|
|
23
28
|
readonly collectionName: string;
|
|
24
29
|
ensureCollection(metadata?: Record<string, unknown>): Promise<void>;
|
|
@@ -28,6 +33,7 @@ export interface VectorStoreInterface {
|
|
|
28
33
|
needsRebuild?(): boolean;
|
|
29
34
|
search(query: VectorSearchQuery): Promise<VectorSearchResult[]>;
|
|
30
35
|
}
|
|
36
|
+
/** Collection Client 接口,用于管理远程 Vector Collection */
|
|
31
37
|
export interface CollectionClient {
|
|
32
38
|
getOrCreateCollection(name: string, metadata?: Record<string, unknown>): Promise<VectorCollectionInfo>;
|
|
33
39
|
listCollections(): Promise<VectorCollectionInfo[]>;
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import type { EmbeddingProvider } from '../embedding/embedding-provider.js';
|
|
2
2
|
import type { StoredChunk } from '../core/index-state-store.js';
|
|
3
3
|
import type { VectorStoreInterface } from './types.js';
|
|
4
|
+
/** 向量索引结果 */
|
|
4
5
|
export interface VectorIndexResult {
|
|
5
6
|
collectionName: string;
|
|
6
7
|
chunkCount: number;
|
|
7
8
|
embeddingModel: string;
|
|
8
9
|
embeddingDimension: number;
|
|
9
10
|
}
|
|
11
|
+
export interface VectorIndexProgress {
|
|
12
|
+
collectionName: string;
|
|
13
|
+
processedChunks: number;
|
|
14
|
+
totalChunks: number;
|
|
15
|
+
batchSize: number;
|
|
16
|
+
}
|
|
17
|
+
export interface VectorIndexOptions {
|
|
18
|
+
batchSize?: number;
|
|
19
|
+
onProgress?: (progress: VectorIndexProgress) => void;
|
|
20
|
+
}
|
|
21
|
+
/** 向量索引器,负责将文本切片生成 Embedding 并写入向量存储 */
|
|
10
22
|
export declare class VectorIndexer {
|
|
11
23
|
private readonly embeddingProvider;
|
|
12
24
|
private readonly vectorStores;
|
|
13
25
|
constructor(embeddingProvider: EmbeddingProvider, vectorStores: Map<string, VectorStoreInterface>);
|
|
14
|
-
indexChunks(chunks: StoredChunk[]): Promise<VectorIndexResult[]>;
|
|
26
|
+
indexChunks(chunks: StoredChunk[], options?: VectorIndexOptions): Promise<VectorIndexResult[]>;
|
|
15
27
|
deleteFile(collectionName: string, filePath: string): Promise<void>;
|
|
16
28
|
private embedDocuments;
|
|
17
29
|
private isValidEmbeddings;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
function resolveVectorIndexBatchSize(configured) {
|
|
2
|
+
const raw = configured ?? Number(process.env.CUSTOMIZE_VECTOR_INDEX_BATCH_SIZE ?? process.env.KB_VECTOR_INDEX_BATCH_SIZE);
|
|
3
|
+
if (!Number.isFinite(raw) || raw <= 0)
|
|
4
|
+
return 32;
|
|
5
|
+
return Math.max(1, Math.min(256, Math.floor(raw)));
|
|
6
|
+
}
|
|
7
|
+
/** 向量索引器,负责将文本切片生成 Embedding 并写入向量存储 */
|
|
1
8
|
export class VectorIndexer {
|
|
2
9
|
embeddingProvider;
|
|
3
10
|
vectorStores;
|
|
@@ -5,24 +12,34 @@ export class VectorIndexer {
|
|
|
5
12
|
this.embeddingProvider = embeddingProvider;
|
|
6
13
|
this.vectorStores = vectorStores;
|
|
7
14
|
}
|
|
8
|
-
async indexChunks(chunks) {
|
|
15
|
+
async indexChunks(chunks, options = {}) {
|
|
9
16
|
const byCollection = this.groupByCollection(chunks);
|
|
10
17
|
const results = [];
|
|
18
|
+
const totalChunks = chunks.length;
|
|
19
|
+
let processedTotalChunks = 0;
|
|
11
20
|
for (const [collectionName, collectionChunks] of byCollection) {
|
|
12
21
|
const store = this.vectorStores.get(collectionName);
|
|
13
22
|
if (!store)
|
|
14
23
|
continue;
|
|
15
|
-
const texts = collectionChunks.map(chunk => chunk.content);
|
|
16
|
-
const embeddings = await this.embedDocuments(texts);
|
|
17
24
|
await store.ensureCollection({
|
|
18
25
|
embedding_model: this.embeddingProvider.model,
|
|
19
26
|
embedding_dimension: this.embeddingProvider.dimensions,
|
|
20
27
|
});
|
|
21
|
-
const
|
|
22
|
-
|
|
28
|
+
const batchSize = resolveVectorIndexBatchSize(options.batchSize);
|
|
29
|
+
let processedChunks = 0;
|
|
30
|
+
for (let offset = 0; offset < collectionChunks.length; offset += batchSize) {
|
|
31
|
+
const batchChunks = collectionChunks.slice(offset, offset + batchSize);
|
|
32
|
+
const texts = batchChunks.map(chunk => chunk.content);
|
|
33
|
+
const embeddings = await this.embedDocuments(texts);
|
|
34
|
+
const documents = batchChunks.map((chunk, index) => this.toVectorDocument(chunk, embeddings[index] ?? []));
|
|
35
|
+
await store.upsert(documents);
|
|
36
|
+
processedChunks += documents.length;
|
|
37
|
+
processedTotalChunks += documents.length;
|
|
38
|
+
options.onProgress?.({ collectionName, processedChunks: processedTotalChunks, totalChunks, batchSize: documents.length });
|
|
39
|
+
}
|
|
23
40
|
results.push({
|
|
24
41
|
collectionName,
|
|
25
|
-
chunkCount:
|
|
42
|
+
chunkCount: processedChunks,
|
|
26
43
|
embeddingModel: this.embeddingProvider.model,
|
|
27
44
|
embeddingDimension: this.embeddingProvider.dimensions,
|
|
28
45
|
});
|