@huyooo/ai-search 0.2.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/search.ts","../src/types.ts","../src/storage/vector.ts","../src/storage/fulltext.ts","../src/storage/meta.ts","../src/parsers/index.ts","../src/embeddings/index.ts","../src/core/scanner.ts","../src/core/rules.ts","../src/core/utils.ts","../src/core/progress.ts","../src/core/watch.ts","../src/core/maintenance.ts","../src/core/backup.ts","../src/core/pipeline.ts","../src/core/chunker.ts","../src/tools/index.ts"],"sourcesContent":["/**\n * 文档搜索引擎核心\n */\nimport * as fs from 'fs/promises'\nimport * as path from 'path'\nimport type {\n SearchConfig,\n SearchOptions,\n SearchResult,\n IndexedDocument,\n IndexProgress,\n IndexStats,\n FileType,\n WatchOptions,\n BatchOperationResult,\n ExportInfo,\n BackupInfo,\n IndexError,\n HealthCheckResult,\n} from '../types'\nimport { DEFAULT_CONFIG } from '../types'\nimport { VectorStore, FullTextIndex, MetaStore } from '../storage'\nimport { parseDocument } from '../parsers'\nimport { embedQuery, embedDocument, initEmbedder, getEmbeddingDimension, disposeEmbedder } from '../embeddings'\nimport { scanDirectories, getDefaultDirectories } from './scanner'\nimport {\n hashFile,\n generateId,\n formatSize,\n formatDate,\n getFileType,\n extractSnippet,\n reciprocalRankFusion,\n sleep,\n} from './utils'\nimport { notifyGlobalProgress } from './progress'\nimport { DirectoryWatcher } from './watch'\nimport * as maintenance from './maintenance'\nimport * as backup from './backup'\nimport { createIndexingPipeline, type PipelineConfig } from './pipeline'\n\n// 重新导出全局进度监听器(保持向后兼容)\nexport {\n addGlobalProgressListener,\n removeGlobalProgressListener,\n type GlobalProgressListener,\n} from './progress'\n\nexport class DocumentSearch {\n private config: SearchConfig\n private vectorStore: VectorStore\n private fullTextIndex: FullTextIndex\n private metaStore: MetaStore\n private initialized = false\n private directoryWatcher: DirectoryWatcher\n private indexErrors: Map<string, IndexError> = new Map()\n private maxRetries = 3\n /** 索引锁:防止同一文件被并发索引 */\n private indexingLocks: Set<string> = new Set()\n /** 当前运行的流水线(用于取消) */\n private currentPipeline: ReturnType<typeof createIndexingPipeline> | null = null\n\n constructor(config: SearchConfig) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n } as SearchConfig\n\n const dataDir = this.config.dataDir\n\n this.vectorStore = new VectorStore(\n path.join(dataDir, 'vectors'),\n 'documents',\n getEmbeddingDimension()\n )\n this.fullTextIndex = new FullTextIndex(dataDir)\n this.metaStore = new MetaStore(dataDir)\n\n // 初始化目录监听器\n this.directoryWatcher = new DirectoryWatcher({\n config: this.config,\n fullTextIndex: this.fullTextIndex,\n indexFile: (filePath) => this.indexFile(filePath),\n removeFile: (filePath) => this.removeFile(filePath),\n })\n }\n\n /**\n * 初始化搜索引擎\n */\n async init(): Promise<void> {\n if (this.initialized) return\n\n // 初始化 Embedding 模型(豆包 API)\n await initEmbedder(\n this.config.arkApiKey,\n this.config.embeddingModel,\n this.config.embeddingDimension\n )\n\n // 初始化存储\n await this.vectorStore.init()\n await this.fullTextIndex.init()\n\n this.initialized = true\n console.log('DocumentSearch 初始化完成')\n }\n\n /**\n * 索引单个文件(带重试机制和并发安全)\n */\n async indexFile(filePath: string, retryCount = 0): Promise<void> {\n await this.ensureInitialized()\n\n // 并发安全:检查是否正在索引\n const normalizedPath = path.normalize(filePath)\n if (this.indexingLocks.has(normalizedPath)) {\n // 文件正在被其他进程索引,跳过(不输出日志,避免日志过多)\n return\n }\n\n // 获取锁\n this.indexingLocks.add(normalizedPath)\n\n try {\n // 获取文件信息\n const stat = await fs.stat(filePath)\n\n // 检查文件大小\n if (stat.size > (this.config.maxFileSize || DEFAULT_CONFIG.maxFileSize!)) {\n // 文件过大,跳过(不输出日志,避免日志过多)\n return // finally 块会释放锁\n }\n\n // 检查是否已索引且未变化(使用快速维度:路径 + 修改时间 + 文件大小)\n // 优化策略:\n // 1. 如果大小和时间都匹配 → 跳过(最快,无需计算哈希)\n // 2. 如果大小匹配但时间不匹配 → 计算哈希验证(避免不必要的重新索引)\n // 3. 如果大小不匹配 → 直接重新索引(内容一定变化)\n const existing = this.metaStore.getByPath(filePath)\n if (existing) {\n const timeMatch = existing.modifiedAt.getTime() === stat.mtime.getTime()\n const sizeMatch = existing.fileSize === stat.size\n \n // 如果路径、修改时间、文件大小都匹配,认为文件未变化\n if (timeMatch && sizeMatch) {\n // 完全匹配,无需重新索引和更新\n return // finally 块会释放锁\n }\n \n // 如果大小匹配但时间不匹配,需要验证内容是否变化\n // 可能的情况:\n // 1. 文件被 touch(时间变了,内容没变)- 只需要更新元数据\n // 2. 文件被覆盖但内容相同(时间变了,内容没变)- 只需要更新元数据\n // 3. 文件被修改但大小相同(时间变了,内容也变了)- 需要重新索引\n // 为了准确判断,需要计算哈希验证\n if (sizeMatch) {\n const contentHash = await hashFile(filePath)\n if (existing.contentHash === contentHash) {\n // 内容未变,只更新元数据(修改时间、文件大小)\n existing.modifiedAt = stat.mtime\n existing.fileSize = stat.size\n this.metaStore.upsert(existing)\n return // finally 块会释放锁\n }\n // 内容变化,需要重新索引,继续执行下面的重新索引逻辑\n }\n \n // 如果大小不匹配,文件内容一定变化,需要重新索引\n // 继续执行下面的重新索引逻辑\n }\n\n // 计算哈希(仅在需要重新索引时计算,用于去重检查)\n const contentHash = await hashFile(filePath)\n\n // 检查是否有相同内容的文件已索引(基于内容哈希去重)\n const duplicateByHash = this.metaStore.getByHash(contentHash)\n if (duplicateByHash && duplicateByHash.path !== filePath) {\n // 发现重复内容,复用已有索引(不输出日志,避免日志过多)\n \n // 创建新记录,但复用相同的向量和全文索引\n const docId = generateId()\n const doc: IndexedDocument = {\n id: docId,\n path: filePath,\n name: path.basename(filePath),\n fileType: getFileType(filePath),\n extension: path.extname(filePath).toLowerCase(),\n title: duplicateByHash.title, // 复用标题\n content: duplicateByHash.content, // 复用内容\n fileSize: stat.size,\n createdAt: stat.birthtime,\n modifiedAt: stat.mtime,\n indexedAt: new Date(),\n contentHash,\n }\n\n // 存储元数据\n this.metaStore.upsert(doc)\n \n // 复用向量(从已有文档复制)\n const existingVector = await this.vectorStore.getById(duplicateByHash.id)\n if (existingVector) {\n await this.vectorStore.update({ id: docId, vector: existingVector })\n }\n \n // 复用全文索引\n this.fullTextIndex.update({\n id: docId,\n title: doc.title || doc.name,\n content: doc.content,\n })\n \n return // finally 块会释放锁\n }\n\n // 解析文档\n const parsed = await parseDocument(filePath)\n \n // 检查解析结果\n if (!parsed.content.trim()) {\n // 如果解析时发生了错误,打印警告\n if (parsed.metadata?.error) {\n const reason = parsed.metadata.reason as string\n if (reason?.includes('bad XRef') || reason?.includes('FormatError') || reason?.includes('XRef')) {\n console.warn(`跳过损坏的 PDF 文件: ${filePath} - ${reason}`)\n } else {\n console.warn(`跳过无法解析的文件: ${filePath} - ${reason}`)\n }\n }\n // 空内容文件跳过(可能是损坏的文件、图标文件等)\n return // finally 块会释放锁\n }\n\n // 生成向量(使用文档指令)\n const vector = await embedDocument(parsed.content)\n\n // 创建文档记录\n const docId = existing?.id || generateId()\n const doc: IndexedDocument = {\n id: docId,\n path: filePath,\n name: path.basename(filePath),\n fileType: getFileType(filePath),\n extension: path.extname(filePath).toLowerCase(),\n title: parsed.title,\n content: parsed.content,\n fileSize: stat.size,\n createdAt: stat.birthtime,\n modifiedAt: stat.mtime,\n indexedAt: new Date(),\n contentHash,\n }\n\n // 存储\n this.metaStore.upsert(doc)\n await this.vectorStore.update({ id: docId, vector })\n this.fullTextIndex.update({\n id: docId,\n title: doc.title || doc.name,\n content: doc.content,\n })\n } catch (err) {\n // 所有错误都打印,方便调试和排查问题\n const errorMsg = err instanceof Error ? err.message : String(err)\n const errorStack = err instanceof Error ? err.stack : String(err)\n \n // 检查是否是常见的非关键错误(这些错误通常是文件损坏、格式不支持等)\n const isCommonError = \n errorMsg.includes('bad XRef') || \n errorMsg.includes('FormatError') ||\n errorMsg.includes('Invalid PDF') ||\n errorMsg.includes('XRef') ||\n errorStack?.includes('pdf-parse') ||\n errorStack?.includes('pdf.js')\n \n // 记录错误\n const indexError: IndexError = {\n filePath,\n error: errorMsg,\n retryCount,\n timestamp: new Date(),\n }\n this.indexErrors.set(filePath, indexError)\n\n // 如果是可重试的错误且未达到最大重试次数,则重试\n if (!isCommonError && retryCount < this.maxRetries) {\n console.warn(`索引文件失败,将重试 (${retryCount + 1}/${this.maxRetries}): ${filePath}`)\n await sleep(1000 * (retryCount + 1)) // 指数退避\n return this.indexFile(filePath, retryCount + 1)\n }\n\n if (isCommonError) {\n // 常见错误打印简短警告(不打印完整堆栈,避免日志污染)\n console.warn(`跳过损坏的 PDF 文件: ${filePath} - ${errorMsg}`)\n } else {\n // 其他错误打印完整警告\n console.warn(`索引文件失败: ${filePath} - ${errorMsg}`)\n }\n \n return\n } finally {\n // 释放锁(无论成功还是失败都要释放)\n this.indexingLocks.delete(normalizedPath)\n }\n }\n\n /**\n * 索引目录(流水线架构)\n * 三阶段并行处理:解析 → 嵌入 → 存储\n * 智能增量索引:只处理新增、修改的文件,跳过未变化的文件\n */\n async indexDirectory(\n directory: string,\n onProgress?: (progress: IndexProgress) => void,\n pipelineConfig?: Partial<PipelineConfig>\n ): Promise<void> {\n await this.ensureInitialized()\n\n const startTime = Date.now()\n console.log(`\\n📁 开始索引目录: ${directory}`)\n console.log(` ⏱️ 开始时间: ${new Date().toLocaleTimeString()}`)\n\n // 封装进度通知\n const notifyProgress = (progress: IndexProgress) => {\n onProgress?.(progress)\n notifyGlobalProgress(progress)\n }\n\n notifyProgress({ indexed: 0, total: 0, stage: 'scanning' })\n\n // 1. 加载已索引文件列表\n const phase1Start = Date.now()\n const indexedDocs = this.metaStore.getAll()\n const indexedPathsInDir = new Set(\n indexedDocs\n .filter(doc => doc.path.startsWith(directory))\n .map(doc => doc.path)\n )\n console.log(` ⏱️ 加载已索引列表: ${Date.now() - phase1Start}ms`)\n console.log(` 📊 已索引: ${indexedPathsInDir.size} 个文件`)\n\n // 2. 扫描文件系统\n const scanStart = Date.now()\n console.log(` 🔍 正在扫描文件系统...`)\n let scannedCount = 0\n const files = await scanDirectories([directory], {\n excludeDirs: this.config.excludeDirs,\n extensions: this.config.extensions,\n maxFileSize: this.config.maxFileSize!,\n directories: [directory],\n onProgress: (progress) => {\n scannedCount = progress.scanned\n notifyProgress({\n indexed: progress.scanned,\n total: 0,\n currentFile: progress.currentDir,\n stage: 'scanning',\n })\n },\n })\n console.log(` ⏱️ 扫描耗时: ${Date.now() - scanStart}ms`)\n console.log(` 📄 找到 ${files.length} 个文件 (扫描了 ${scannedCount.toLocaleString()} 个)`)\n\n // 3. 分析文件变更\n const analyzeStart = Date.now()\n notifyProgress({ indexed: 0, total: 0, stage: 'parsing', currentFile: '正在分析文件变更...' })\n \n const filesSet = new Set(files)\n const normalizedFilesSet = new Set(files.map(f => path.normalize(f)))\n const normalizedIndexedPaths = new Set(\n Array.from(indexedPathsInDir).map(p => path.normalize(p))\n )\n \n // 统计需要处理的文件\n const filesToProcess: string[] = []\n for (const file of files) {\n const existing = this.metaStore.getByPath(file)\n if (!existing) {\n filesToProcess.push(file) // 新文件\n } else {\n filesToProcess.push(file) // 需要检查是否更新\n }\n }\n \n // 找出已删除的文件\n const deletedFiles: string[] = []\n for (const indexedPath of indexedPathsInDir) {\n const normalizedIndexedPath = path.normalize(indexedPath)\n if (!normalizedFilesSet.has(normalizedIndexedPath) && !filesSet.has(indexedPath)) {\n deletedFiles.push(indexedPath)\n }\n }\n console.log(` ⏱️ 分析变更: ${Date.now() - analyzeStart}ms`)\n\n // 4. 删除已删除文件的索引\n let deletedCount = 0\n if (deletedFiles.length > 0) {\n console.log(` 🗑️ 清理 ${deletedFiles.length} 个已删除文件的索引...`)\n const deleteResult = await this.removeFiles(deletedFiles)\n deletedCount = deleteResult.success\n console.log(` ✅ 已清理 ${deletedCount} 个索引`)\n }\n\n // 5. 打印统计信息\n console.log(`\\n 📈 索引统计:`)\n console.log(` - 扫描文件: ${files.length}`)\n console.log(` - 需要处理: ${filesToProcess.length}`)\n console.log(` - 已删除: ${deletedFiles.length}`)\n\n // 6. 创建已索引文件的 mtime 映射(用于快速跳过检查)\n const indexedMtimeMap = new Map<string, number>()\n for (const doc of indexedDocs) {\n if (doc.path.startsWith(directory) && doc.modifiedAt) {\n indexedMtimeMap.set(doc.path, doc.modifiedAt.getTime())\n }\n }\n \n // 7. 运行流水线处理文件\n this.currentPipeline = createIndexingPipeline(pipelineConfig)\n \n // 缓存:文档 ID 映射(避免重复查询)\n const docIdCache = new Map<string, string>()\n \n const stats = await this.currentPipeline.run(\n filesToProcess,\n // 存储回调(每个 chunk 调用一次)\n async (doc) => {\n try {\n const chunkIndex = doc.chunkIndex ?? 0\n const totalChunks = doc.totalChunks ?? 1\n \n // 第一个 chunk 时检查是否需要跳过\n if (chunkIndex === 0) {\n const existing = this.metaStore.getByPath(doc.filePath)\n if (existing && existing.contentHash === doc.contentHash) {\n return { stored: false, skipped: true }\n }\n \n // 删除旧的 chunk 数据(如果存在)\n if (existing && existing.chunkCount) {\n // 删除所有旧 chunk 的向量和全文索引\n for (let i = 0; i < existing.chunkCount; i++) {\n const oldChunkId = `${existing.id}_${i}`\n await this.vectorStore.delete(oldChunkId)\n this.fullTextIndex.remove(oldChunkId)\n }\n }\n \n // 获取文件信息并存储文档元数据\n const stat = await fs.stat(doc.filePath)\n const docId = existing?.id || generateId()\n docIdCache.set(doc.filePath, docId)\n \n const indexedDoc: IndexedDocument = {\n id: docId,\n path: doc.filePath,\n name: path.basename(doc.filePath),\n fileType: getFileType(doc.filePath),\n extension: path.extname(doc.filePath).toLowerCase(),\n title: doc.title,\n content: doc.content, // 第一个 chunk 的内容\n fileSize: stat.size,\n createdAt: stat.birthtime,\n modifiedAt: stat.mtime,\n indexedAt: new Date(),\n contentHash: doc.contentHash,\n chunkCount: totalChunks, // 记录 chunk 数量\n }\n this.metaStore.upsert(indexedDoc)\n }\n \n // 获取文档 ID\n const docId = docIdCache.get(doc.filePath) || this.metaStore.getByPath(doc.filePath)?.id\n if (!docId) {\n return { stored: false, skipped: false }\n }\n \n // 存储 chunk 向量(ID 格式:docId_chunkIndex)\n const chunkId = `${docId}_${chunkIndex}`\n await this.vectorStore.update({ id: chunkId, vector: doc.vector })\n \n // 存储 chunk 全文索引\n this.fullTextIndex.update({\n id: chunkId,\n title: doc.title,\n content: doc.content,\n })\n\n return { stored: true, skipped: false }\n } catch {\n return { stored: false, skipped: false }\n }\n },\n notifyProgress,\n // 快速跳过检查(基于 mtime)\n (filePath: string, mtime: Date) => {\n const indexedMtime = indexedMtimeMap.get(filePath)\n if (!indexedMtime) return false // 新文件,不跳过\n // 如果 mtime 相同或更早,跳过(文件未修改)\n return mtime.getTime() <= indexedMtime\n }\n )\n\n // 8. 检查是否被取消\n const wasCancelled = this.currentPipeline?.isCancelled() ?? false\n this.currentPipeline = null // 清除 pipeline 引用\n\n // 9. 保存全文索引\n await this.fullTextIndex.save()\n\n // 10. 打印最终统计\n const totalTime = Date.now() - startTime\n if (wasCancelled) {\n console.log(`\\n ⚠️ 索引已取消:`)\n } else {\n console.log(`\\n ✅ 索引完成:`)\n }\n console.log(` ⏱️ 总耗时: ${(totalTime / 1000).toFixed(1)}s`)\n console.log(` 📥 新增/更新: ${stats.stored} 个文件`)\n console.log(` ⏭️ 跳过: ${stats.skipped} 个文件`)\n console.log(` ❌ 失败: ${stats.failed} 个文件`)\n console.log(` 🗑️ 删除: ${deletedCount} 个文件`)\n console.log(` 📊 总计: ${this.metaStore.getAll().filter(doc => doc.path.startsWith(directory)).length} 个已索引文件\\n`)\n\n // 11. 通知完成或取消\n if (wasCancelled) {\n notifyProgress({ indexed: stats.completed, total: filesToProcess.length, stage: 'cancelled' })\n } else {\n notifyProgress({ indexed: filesToProcess.length, total: filesToProcess.length, stage: 'done' })\n }\n }\n\n /**\n * 取消正在进行的索引任务\n * @returns 是否成功取消(如果没有运行中的任务则返回 false)\n */\n cancelIndexing(): boolean {\n if (this.currentPipeline) {\n this.currentPipeline.cancel()\n console.log('[AI-Search] 索引任务已发出取消信号')\n return true\n }\n return false\n }\n\n /**\n * 检查是否正在索引\n */\n isIndexing(): boolean {\n return this.currentPipeline !== null && !this.currentPipeline.isCancelled()\n }\n\n /**\n * 索引默认目录\n */\n async indexDefaultDirectories(\n onProgress?: (progress: IndexProgress) => void\n ): Promise<void> {\n const directories = this.config.indexDirectories || getDefaultDirectories()\n\n for (const dir of directories) {\n await this.indexDirectory(dir, onProgress)\n }\n }\n\n /**\n * 搜索文档\n */\n async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {\n await this.ensureInitialized()\n\n const limit = options?.limit || 10\n const mode = options?.mode || 'hybrid'\n\n let vectorResults: Array<{ id: string; distance: number }> = []\n let textResults: Array<{ id: string; score: number }> = []\n\n // 向量搜索(使用查询指令)\n if (mode === 'semantic' || mode === 'hybrid') {\n const queryVector = await embedQuery(query)\n vectorResults = await this.vectorStore.search(queryVector, limit * 2)\n }\n\n // 全文搜索\n if (mode === 'keyword' || mode === 'hybrid') {\n textResults = this.fullTextIndex.search(query, limit * 2)\n }\n\n // 融合结果\n let fusedResults: Array<{ id: string; score: number }>\n\n if (mode === 'hybrid') {\n // 混合模式:RRF 融合向量搜索和全文搜索结果\n const hasVectorResults = vectorResults.length > 0\n const hasTextResults = textResults.length > 0\n\n if (hasVectorResults && hasTextResults) {\n fusedResults = reciprocalRankFusion([\n {\n results: vectorResults.map((r) => ({\n id: r.id,\n score: Math.max(0, 1 - r.distance), // 距离转相似度\n })),\n weight: 0.6, // 语义搜索权重更高\n },\n {\n results: textResults,\n weight: 0.4,\n },\n ])\n } else if (hasVectorResults) {\n fusedResults = this.normalizeVectorScores(vectorResults)\n } else if (hasTextResults) {\n fusedResults = textResults\n } else {\n fusedResults = []\n }\n } else if (mode === 'semantic') {\n fusedResults = this.normalizeVectorScores(vectorResults)\n } else {\n fusedResults = textResults\n }\n\n // 从 chunk ID 提取 doc ID(格式:docId_chunkIndex)\n const parseChunkId = (chunkId: string): { docId: string; chunkIndex: number } => {\n const lastUnderscore = chunkId.lastIndexOf('_')\n const chunkIndex = parseInt(chunkId.slice(lastUnderscore + 1), 10)\n return { \n docId: chunkId.slice(0, lastUnderscore), \n chunkIndex \n }\n }\n\n // 获取所有唯一的 doc ID\n const topChunkIds = fusedResults.slice(0, limit * 2).map((r) => r.id)\n const docIds = [...new Set(topChunkIds.map((id) => parseChunkId(id).docId))]\n const documents = this.metaStore.getByIds(docIds)\n\n // 创建 ID 到文档的映射\n const docMap = new Map(documents.map((d) => [d.id, d]))\n const scoreMap = new Map(fusedResults.map((r) => [r.id, r.score]))\n\n // 构建搜索结果(去重:同一文档只取最高分的 chunk)\n const results: SearchResult[] = []\n const seenDocs = new Set<string>()\n\n for (const chunkId of topChunkIds) {\n if (results.length >= limit) break\n \n const { docId, chunkIndex } = parseChunkId(chunkId)\n \n // 同一文档只返回一次(最高分的 chunk)\n if (seenDocs.has(docId)) continue\n \n const doc = docMap.get(docId)\n if (!doc) continue\n\n const score = scoreMap.get(chunkId) || 0\n\n // 应用过滤\n if (!this.matchesFilters(doc, options)) {\n continue\n }\n \n seenDocs.add(docId)\n\n // 获取匹配的 chunk 内容用于摘要\n const chunkContent = this.fullTextIndex.getContent(chunkId)\n\n results.push({\n // FileItem 兼容字段\n id: doc.path, // 使用路径作为 ID,与 file-explorer 兼容\n name: doc.name,\n type: doc.fileType,\n size: formatSize(doc.fileSize),\n dateModified: formatDate(doc.modifiedAt),\n url: `file://${doc.path}`,\n thumbnailUrl: undefined, // 文档类型通常没有缩略图\n\n // 搜索特有字段\n score,\n snippet: extractSnippet(chunkContent || doc.content, query),\n matchType: mode === 'hybrid' ? 'hybrid' : mode === 'semantic' ? 'semantic' : 'keyword',\n \n // chunk 信息(可选)\n chunkIndex,\n })\n }\n\n return results\n }\n\n /**\n * 检查文档是否匹配所有过滤条件\n */\n private matchesFilters(doc: IndexedDocument, options?: SearchOptions): boolean {\n if (!options) return true\n\n const combineMode = options.combineMode || 'AND'\n const filters: boolean[] = []\n\n // 文件类型过滤\n if (options.fileTypes) {\n filters.push(options.fileTypes.includes(doc.fileType))\n }\n\n // 时间范围过滤\n if (options.dateRange) {\n let dateMatch = true\n if (options.dateRange.start && doc.modifiedAt < options.dateRange.start) {\n dateMatch = false\n }\n if (options.dateRange.end && doc.modifiedAt > options.dateRange.end) {\n dateMatch = false\n }\n filters.push(dateMatch)\n }\n\n // 目录过滤\n if (options.directories && options.directories.length > 0) {\n const dirMatch = options.directories.some((dir) => doc.path.startsWith(dir))\n filters.push(dirMatch)\n }\n\n // 文件大小过滤\n if (options.sizeRange) {\n let sizeMatch = true\n if (options.sizeRange.min !== undefined && doc.fileSize < options.sizeRange.min) {\n sizeMatch = false\n }\n if (options.sizeRange.max !== undefined && doc.fileSize > options.sizeRange.max) {\n sizeMatch = false\n }\n filters.push(sizeMatch)\n }\n\n // 文件名模式过滤(支持通配符)\n if (options.fileNamePattern) {\n const pattern = this.wildcardToRegex(options.fileNamePattern)\n filters.push(pattern.test(doc.name))\n }\n\n // 文档标题包含\n if (options.titleContains) {\n const title = doc.title || doc.name\n filters.push(title.toLowerCase().includes(options.titleContains.toLowerCase()))\n }\n\n // 根据组合模式判断\n if (filters.length === 0) return true\n\n if (combineMode === 'AND') {\n return filters.every((f) => f)\n } else {\n return filters.some((f) => f)\n }\n }\n\n /**\n * 将通配符模式转换为正则表达式\n * * 匹配任意字符\n * ? 匹配单个字符\n */\n private wildcardToRegex(pattern: string): RegExp {\n // 转义特殊字符,但保留 * 和 ?\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.')\n return new RegExp(`^${escaped}$`, 'i')\n }\n\n /**\n * 标准化向量搜索分数\n * 将距离转换为 0-1 的相似度分数\n */\n private normalizeVectorScores(\n vectorResults: Array<{ id: string; distance: number }>\n ): Array<{ id: string; score: number }> {\n if (vectorResults.length === 0) {\n return []\n }\n\n // 距离越小越相似,转换为相似度分数\n // 使用 1 / (1 + distance) 转换,确保分数在 0-1 之间\n const scores = vectorResults.map((r) => ({\n id: r.id,\n rawScore: 1 / (1 + r.distance),\n }))\n\n // 标准化到 0-1\n const maxScore = Math.max(...scores.map((s) => s.rawScore))\n \n return scores.map((s) => ({\n id: s.id,\n score: maxScore > 0 ? s.rawScore / maxScore : 0,\n }))\n }\n\n /**\n * 删除文件索引\n */\n async removeFile(filePath: string): Promise<void> {\n const doc = this.metaStore.getByPath(filePath)\n if (!doc) return\n\n await this.vectorStore.delete(doc.id)\n this.fullTextIndex.remove(doc.id)\n this.metaStore.deleteByPath(filePath)\n }\n\n /**\n * 获取统计信息\n */\n getStats(): IndexStats {\n const stats = this.metaStore.getStats()\n stats.directories = this.config.indexDirectories || getDefaultDirectories()\n return stats\n }\n\n /**\n * 清空所有索引\n */\n async clear(): Promise<void> {\n this.metaStore.clear()\n this.fullTextIndex.clear()\n // VectorStore 需要重新初始化来清空\n }\n\n /**\n * 保存索引\n */\n async save(): Promise<void> {\n await this.fullTextIndex.save()\n }\n\n /**\n * 批量索引文件(根据配置自动选择串行或并行)\n */\n async indexFiles(\n filePaths: string[],\n onProgress?: (progress: IndexProgress) => void\n ): Promise<BatchOperationResult> {\n const enableParallel = this.config.enableParallelIndexing !== false\n if (enableParallel && filePaths.length > 1) {\n return this.indexFilesParallel(filePaths, onProgress)\n }\n return this.indexFilesSerial(filePaths, onProgress)\n }\n\n /**\n * 串行索引文件(原实现)\n */\n private async indexFilesSerial(\n filePaths: string[],\n onProgress?: (progress: IndexProgress) => void\n ): Promise<BatchOperationResult> {\n await this.ensureInitialized()\n\n const result: BatchOperationResult = {\n success: 0,\n failed: 0,\n errors: [],\n }\n\n const total = filePaths.length\n onProgress?.({ indexed: 0, total, stage: 'parsing' })\n\n for (let i = 0; i < filePaths.length; i++) {\n const filePath = filePaths[i]\n try {\n await this.indexFile(filePath)\n result.success++\n } catch (error) {\n result.failed++\n result.errors.push({\n path: filePath,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n onProgress?.({\n indexed: i + 1,\n total,\n currentFile: filePath,\n stage: i === filePaths.length - 1 ? 'done' : 'parsing',\n })\n\n // 每处理 10 个文件休息一下\n if (i % 10 === 0) {\n await sleep(10)\n }\n }\n\n await this.fullTextIndex.save()\n return result\n }\n\n /**\n * 并行索引文件(控制并发数)\n */\n async indexFilesParallel(\n filePaths: string[],\n onProgress?: (progress: IndexProgress) => void,\n concurrency?: number\n ): Promise<BatchOperationResult> {\n await this.ensureInitialized()\n\n const result: BatchOperationResult = {\n success: 0,\n failed: 0,\n errors: [],\n }\n\n const total = filePaths.length\n const concurrencyLimit = concurrency || this.config.indexConcurrency || 5\n onProgress?.({ indexed: 0, total, stage: 'parsing' })\n\n let processed = 0\n let currentIndex = 0\n const running = new Set<Promise<void>>()\n\n // 并发控制:使用 Promise 池\n const processNext = async (): Promise<void> => {\n while (currentIndex < total) {\n const fileIndex = currentIndex++\n const filePath = filePaths[fileIndex]\n\n const task = (async () => {\n try {\n await this.indexFile(filePath)\n result.success++\n } catch (error) {\n result.failed++\n result.errors.push({\n path: filePath,\n error: error instanceof Error ? error.message : String(error),\n })\n } finally {\n processed++\n onProgress?.({\n indexed: processed,\n total,\n currentFile: filePath,\n stage: processed === total ? 'done' : 'parsing',\n })\n }\n })()\n\n running.add(task)\n task.finally(() => {\n running.delete(task)\n })\n\n // 如果达到并发限制,等待一个任务完成\n if (running.size >= concurrencyLimit) {\n await Promise.race(running)\n }\n }\n }\n\n // 启动并发任务\n const workers = Array.from({ length: Math.min(concurrencyLimit, total) }, () => processNext())\n await Promise.all(workers)\n\n // 等待所有任务完成\n await Promise.all(running)\n\n await this.fullTextIndex.save()\n return result\n }\n\n /**\n * 批量删除文件索引\n */\n async removeFiles(filePaths: string[]): Promise<BatchOperationResult> {\n const result: BatchOperationResult = {\n success: 0,\n failed: 0,\n errors: [],\n }\n\n let notFound = 0 // 文件不存在于索引中\n\n for (const filePath of filePaths) {\n try {\n // 检查文件是否存在于索引中\n const doc = this.metaStore.getByPath(filePath)\n if (!doc) {\n // 文件不存在于索引中,可能已经被删除或路径不匹配\n notFound++\n continue\n }\n \n await this.removeFile(filePath)\n result.success++\n } catch (error) {\n result.failed++\n result.errors.push({\n path: filePath,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n // 如果有文件不存在,记录到 errors 中(用于调试)\n if (notFound > 0 && result.errors.length < 10) {\n // 只在错误较少时记录,避免日志过多\n result.errors.push({\n path: `[${notFound} 个文件不存在于索引中]`,\n error: '文件可能已被删除或路径不匹配',\n })\n }\n\n return result\n }\n\n /**\n * 批量更新文件索引(重新索引)\n */\n async updateFiles(\n filePaths: string[],\n onProgress?: (progress: IndexProgress) => void\n ): Promise<BatchOperationResult> {\n // 先删除旧索引,再重新索引\n await this.removeFiles(filePaths)\n return this.indexFiles(filePaths, onProgress)\n }\n\n // ==================== 目录监听(委托给 DirectoryWatcher)====================\n\n /** 监听目录变化并自动更新索引 */\n watchDirectory(directory: string, options?: WatchOptions): void {\n this.directoryWatcher.watch(directory, options)\n }\n\n /** 停止监听目录 */\n unwatchDirectory(directory: string): void {\n this.directoryWatcher.unwatch(directory)\n }\n\n /** 停止所有监听 */\n unwatchAll(): void {\n this.directoryWatcher.unwatchAll()\n }\n\n /** 获取正在监听的目录列表 */\n getWatchedDirectories(): string[] {\n return this.directoryWatcher.getWatchedDirectories()\n }\n\n // ==================== 维护功能(委托给 maintenance 模块)====================\n\n /** 获取维护依赖 */\n private getMaintenanceDeps(): maintenance.MaintenanceDependencies {\n return {\n metaStore: this.metaStore,\n vectorStore: this.vectorStore,\n fullTextIndex: this.fullTextIndex,\n indexErrors: this.indexErrors,\n indexFile: (filePath) => this.indexFile(filePath),\n removeFile: (filePath) => this.removeFile(filePath),\n indexFiles: (filePaths) => this.indexFiles(filePaths),\n }\n }\n\n /** 清理无效索引(文件已删除但索引还在) */\n async cleanup(): Promise<{ removed: number; updated: number }> {\n await this.ensureInitialized()\n return maintenance.cleanup(this.getMaintenanceDeps())\n }\n\n /** 优化索引(压缩、碎片整理) */\n async optimize(): Promise<void> {\n await this.ensureInitialized()\n return maintenance.optimize(this.getMaintenanceDeps())\n }\n\n /** 健康检查 */\n async healthCheck(): Promise<HealthCheckResult> {\n await this.ensureInitialized()\n return maintenance.healthCheck(this.getMaintenanceDeps())\n }\n\n /** 获取索引错误列表 */\n getIndexErrors(): IndexError[] {\n return maintenance.getIndexErrors(this.getMaintenanceDeps())\n }\n\n /** 重试失败的索引 */\n async retryFailedIndexes(): Promise<BatchOperationResult> {\n return maintenance.retryFailedIndexes(this.getMaintenanceDeps())\n }\n\n /** 确保已初始化 */\n private async ensureInitialized(): Promise<void> {\n if (!this.initialized) {\n await this.init()\n }\n }\n\n // ==================== 备份功能(委托给 backup 模块)====================\n\n /** 获取备份依赖 */\n private getBackupDeps(): backup.BackupDependencies {\n return {\n config: this.config,\n metaStore: this.metaStore,\n vectorStore: this.vectorStore,\n fullTextIndex: this.fullTextIndex,\n getStats: () => this.getStats(),\n reinitializeStores: (newMetaStore, newVectorStore, newFullTextIndex) => {\n this.metaStore = newMetaStore\n this.vectorStore = newVectorStore\n this.fullTextIndex = newFullTextIndex\n },\n }\n }\n\n /** 导出索引数据 */\n async exportIndex(outputPath: string): Promise<ExportInfo> {\n await this.ensureInitialized()\n return backup.exportIndex(this.getBackupDeps(), outputPath)\n }\n\n /** 导入索引数据 */\n async importIndex(inputPath: string): Promise<void> {\n await this.ensureInitialized()\n return backup.importIndex(this.getBackupDeps(), inputPath, {\n MetaStore,\n VectorStore,\n FullTextIndex,\n })\n }\n\n /** 列出备份 */\n async listBackups(backupDir: string): Promise<BackupInfo[]> {\n return backup.listBackups(backupDir)\n }\n\n // ==================== 资源清理 ====================\n\n /** 销毁资源,释放所有占用的资源 */\n async destroy(): Promise<void> {\n // 1. 停止所有文件监听\n this.unwatchAll()\n\n // 2. 清理目录监听器的定时器\n this.directoryWatcher.clearTimers()\n\n // 3. 清理索引锁\n this.indexingLocks.clear()\n\n // 3. 关闭数据库连接\n try {\n this.metaStore.close()\n } catch (error) {\n console.warn('关闭 MetaStore 失败:', error)\n }\n\n // 4. 清理向量存储\n try {\n this.vectorStore.close()\n } catch (error) {\n console.warn('关闭 VectorStore 失败:', error)\n }\n\n // 5. 清理 Embedding 模型(释放内存)\n try {\n disposeEmbedder()\n } catch (error) {\n console.warn('清理 Embedding 模型失败:', error)\n }\n\n // 6. 清理全文索引(FlexSearch 不需要显式清理,但可以重置)\n // 注意:FlexSearch 是内存索引,会在对象销毁时自动清理\n\n // 7. 清理错误记录\n this.indexErrors.clear()\n\n // 8. 重置初始化状态\n this.initialized = false\n\n console.log('DocumentSearch 资源已清理')\n }\n}\n\n","/**\n * 文件类型(与 file-explorer 兼容)\n */\nexport const FileType = {\n FOLDER: 'folder',\n FILE: 'file',\n IMAGE: 'image',\n VIDEO: 'video',\n MUSIC: 'music',\n DOCUMENT: 'document',\n CODE: 'code',\n TEXT: 'text',\n PDF: 'pdf',\n ARCHIVE: 'archive',\n APPLICATION: 'application',\n UNKNOWN: 'unknown',\n} as const\n\nexport type FileType = (typeof FileType)[keyof typeof FileType]\n\n/**\n * 搜索结果(兼容 file-explorer 的 FileItem)\n */\nexport interface SearchResult {\n // === 与 FileItem 兼容的字段 ===\n /** 唯一标识(文件路径) */\n id: string\n /** 文件名 */\n name: string\n /** 文件类型 */\n type: FileType\n /** 格式化后的文件大小 */\n size?: string\n /** 格式化后的修改日期 */\n dateModified?: string\n /** 文件 URL */\n url?: string\n /** 缩略图 URL(文档类型通常没有) */\n thumbnailUrl?: string\n\n // === 搜索特有字段 ===\n /** 相关度分数 0-1 */\n score: number\n /** 匹配文本摘要 */\n snippet?: string\n /** 匹配类型 */\n matchType: 'semantic' | 'keyword' | 'hybrid'\n /** 匹配的块索引(分块后可用) */\n chunkIndex?: number\n}\n\n/**\n * 索引项(内部使用)\n */\nexport interface IndexedDocument {\n /** 唯一 ID */\n id: string\n /** 文件路径 */\n path: string\n /** 文件名 */\n name: string\n /** 文件类型 */\n fileType: FileType\n /** 文件扩展名 */\n extension: string\n /** 文档标题(从内容提取) */\n title?: string\n /** 文档内容(用于全文搜索) */\n content: string\n /** 文件大小(字节) */\n fileSize: number\n /** 创建时间 */\n createdAt: Date\n /** 修改时间 */\n modifiedAt: Date\n /** 索引时间 */\n indexedAt: Date\n /** 内容哈希(用于检测变更) */\n contentHash: string\n /** 分块数量(用于清理旧数据) */\n chunkCount?: number\n}\n\n/**\n * 搜索配置\n */\nexport interface SearchConfig {\n /** 数据存储目录 */\n dataDir: string\n /** 索引目录列表 */\n indexDirectories?: string[]\n /** 排除目录 */\n excludeDirs?: string[]\n /** 支持的扩展名 */\n extensions?: string[]\n /** 最大文件大小(字节) */\n maxFileSize?: number\n /** Embedding 模型(默认 doubao-embedding-vision-250615) */\n embeddingModel?: string\n /** 豆包 API Key(ARK_API_KEY),也可通过环境变量设置 */\n arkApiKey?: string\n /** 向量维度(默认 1024) */\n embeddingDimension?: number\n /** 并行索引并发数(默认 5) */\n indexConcurrency?: number\n /** 是否启用并行索引(默认 true) */\n enableParallelIndexing?: boolean\n}\n\n/**\n * 搜索选项\n */\nexport interface SearchOptions {\n /** 返回数量限制 */\n limit?: number\n /** 文件类型过滤 */\n fileTypes?: FileType[]\n /** 时间范围过滤 */\n dateRange?: {\n start?: Date\n end?: Date\n }\n /** 目录过滤 */\n directories?: string[]\n /** 搜索模式 */\n mode?: 'semantic' | 'keyword' | 'hybrid'\n /** 文件大小过滤 */\n sizeRange?: {\n min?: number // 字节\n max?: number // 字节\n }\n /** 文件名模式过滤(支持通配符 * 和 ?) */\n fileNamePattern?: string\n /** 文档标题包含 */\n titleContains?: string\n /** 组合条件模式 */\n combineMode?: 'AND' | 'OR'\n}\n\n/**\n * 索引进度回调\n */\nexport interface IndexProgress {\n /** 当前已索引数量 */\n indexed: number\n /** 总文件数量 */\n total: number\n /** 当前正在处理的文件 */\n currentFile?: string\n /** 阶段 */\n stage: 'scanning' | 'parsing' | 'embedding' | 'storing' | 'done' | 'cancelled'\n}\n\n/**\n * 索引统计\n */\nexport interface IndexStats {\n /** 总文档数 */\n totalDocuments: number\n /** 按类型统计 */\n byType: Record<FileType, number>\n /** 索引目录 */\n directories: string[]\n /** 最后更新时间 */\n lastUpdated?: Date\n /** 索引大小(字节) */\n indexSize: number\n}\n\n/**\n * 文件监听选项\n */\nexport interface WatchOptions {\n /** 是否忽略初始扫描(只监听后续变化) */\n ignoreInitial?: boolean\n /** 防抖延迟(毫秒),避免频繁触发 */\n debounce?: number\n /** 监听事件回调 */\n onEvent?: (event: WatchEvent) => void\n}\n\n/**\n * 文件监听事件\n */\nexport interface WatchEvent {\n /** 事件类型 */\n type: 'add' | 'change' | 'unlink' | 'unlinkDir'\n /** 文件路径 */\n path: string\n /** 时间戳 */\n timestamp: Date\n}\n\n/**\n * 批量操作结果\n */\nexport interface BatchOperationResult {\n /** 成功数量 */\n success: number\n /** 失败数量 */\n failed: number\n /** 失败的文件路径和错误信息 */\n errors: Array<{ path: string; error: string }>\n}\n\n/**\n * 索引导出信息\n */\nexport interface ExportInfo {\n /** 导出路径 */\n exportPath: string\n /** 导出时间 */\n timestamp: Date\n /** 包含的组件 */\n components: {\n meta: boolean\n vectors: boolean\n fulltext: boolean\n }\n /** 统计信息 */\n stats: IndexStats\n}\n\n/**\n * 备份信息\n */\nexport interface BackupInfo {\n /** 备份路径 */\n path: string\n /** 备份时间 */\n timestamp: Date\n /** 备份大小(字节) */\n size: number\n}\n\n/**\n * 索引错误信息\n */\nexport interface IndexError {\n /** 文件路径 */\n filePath: string\n /** 错误信息 */\n error: string\n /** 重试次数 */\n retryCount: number\n /** 时间戳 */\n timestamp: Date\n}\n\n/**\n * 健康检查结果\n */\nexport interface HealthCheckResult {\n /** 是否健康 */\n healthy: boolean\n /** 总文档数 */\n totalDocuments: number\n /** 无效索引数(文件不存在) */\n invalidIndexes: number\n /** 过期索引数(文件已修改) */\n staleIndexes: number\n /** 错误数 */\n errorCount: number\n /** 索引完整性 */\n integrity: {\n meta: boolean\n vectors: boolean\n fulltext: boolean\n }\n}\n\n/**\n * 默认配置\n * 注意:详细的排除规则和文件类型规则现在由 rules.ts 中的规则管理器统一管理\n * 这里的配置主要用于向后兼容和简单配置场景\n */\nexport const DEFAULT_CONFIG: Partial<SearchConfig> = {\n // 排除目录(这些会被合并到规则管理器的 excludedPaths 中)\n excludeDirs: [\n 'node_modules',\n '.git',\n '.svn',\n '__pycache__',\n '.cache',\n '.Trash',\n 'Library',\n 'AppData',\n '$RECYCLE.BIN',\n ],\n // 支持的扩展名(这些会被合并到规则管理器的 allowed extensions 中)\n extensions: [\n '.docx',\n '.doc',\n '.pdf',\n '.xlsx',\n '.xls',\n '.pptx',\n '.ppt',\n '.txt',\n '.md',\n '.rtf',\n ],\n maxFileSize: 50 * 1024 * 1024, // 50MB\n embeddingModel: 'doubao-embedding-vision-250615',\n embeddingDimension: 1024, // 向量维度\n indexConcurrency: 5, // 并行索引并发数\n enableParallelIndexing: true, // 默认启用并行索引\n}\n\n","/**\n * 向量存储模块\n * 使用 LanceDB 存储和检索向量\n */\nimport * as lancedb from '@lancedb/lancedb'\n\nexport interface VectorRecord {\n id: string\n vector: number[]\n}\n\nexport interface VectorSearchResult {\n id: string\n distance: number\n}\n\nexport class VectorStore {\n private db: lancedb.Connection | null = null\n private table: lancedb.Table | null = null\n private dbPath: string\n private tableName: string\n private dimension: number\n\n constructor(dbPath: string, tableName: string = 'documents', dimension: number = 384) {\n this.dbPath = dbPath\n this.tableName = tableName\n this.dimension = dimension\n }\n\n /**\n * 初始化连接\n */\n async init(): Promise<void> {\n this.db = await lancedb.connect(this.dbPath)\n \n // 尝试打开已有表,如果不存在则创建\n try {\n this.table = await this.db.openTable(this.tableName)\n } catch {\n // 表不存在,创建一个空表\n const initData = [{ id: '__init__', vector: new Array(this.dimension).fill(0) }]\n this.table = await this.db.createTable(this.tableName, initData)\n // 删除初始化记录\n await this.table.delete('id = \"__init__\"')\n }\n }\n\n /**\n * 添加向量\n */\n async add(records: VectorRecord[]): Promise<void> {\n if (!this.table) {\n throw new Error('VectorStore not initialized')\n }\n\n if (records.length === 0) return\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await this.table.add(records as any)\n }\n\n /**\n * 更新向量(删除后重新添加)\n */\n async update(record: VectorRecord): Promise<void> {\n if (!this.table) {\n throw new Error('VectorStore not initialized')\n }\n\n await this.delete(record.id)\n await this.add([record])\n }\n\n /**\n * 根据 ID 获取向量\n */\n async getById(id: string): Promise<number[] | null> {\n if (!this.table) {\n throw new Error('VectorStore not initialized')\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const results = await (this.table as any).search().where(`id = \"${id}\"`).limit(1).toArray()\n if (results.length === 0) {\n return null\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (results[0] as any).vector as number[]\n } catch {\n return null\n }\n }\n\n /**\n * 删除向量\n */\n async delete(id: string): Promise<void> {\n if (!this.table) {\n throw new Error('VectorStore not initialized')\n }\n\n await this.table.delete(`id = \"${id}\"`)\n }\n\n /**\n * 搜索最相似的向量\n */\n async search(queryVector: number[], limit: number = 10): Promise<VectorSearchResult[]> {\n if (!this.table) {\n throw new Error('VectorStore not initialized')\n }\n\n // 使用 vectorSearch 方法进行向量搜索\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const results = await (this.table as any).vectorSearch(queryVector).limit(limit).toArray()\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return results.map((r: any) => ({\n id: r.id as string,\n distance: r._distance as number,\n }))\n }\n\n /**\n * 获取记录数量\n */\n async count(): Promise<number> {\n if (!this.table) {\n throw new Error('VectorStore not initialized')\n }\n\n return await this.table.countRows()\n }\n\n /**\n * 检查 ID 是否存在\n */\n async exists(id: string): Promise<boolean> {\n if (!this.table) {\n throw new Error('VectorStore not initialized')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const results = await (this.table as any).search([]).where(`id = \"${id}\"`).limit(1).toArray()\n return results.length > 0\n }\n\n /**\n * 关闭连接(清理资源)\n * 注意:LanceDB 连接会在对象销毁时自动清理,但可以显式重置引用\n */\n close(): void {\n // LanceDB 没有显式的 close 方法\n // 重置引用,让垃圾回收器处理\n this.table = null\n this.db = null\n }\n}\n\n","/**\n * 全文搜索模块\n * 使用 FlexSearch 进行中文全文检索\n */\nimport FlexSearch from 'flexsearch'\nimport nodejieba from 'nodejieba'\nimport * as path from 'path'\n\nexport interface FullTextRecord {\n id: string\n title: string\n content: string\n}\n\nexport interface FullTextSearchResult {\n id: string\n score: number\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype FlexSearchIndex = any\n\nexport class FullTextIndex {\n private index: FlexSearchIndex\n private indexPath: string\n private docCount = 0\n\n constructor(dataDir: string) {\n this.indexPath = path.join(dataDir, 'fulltext-index.json')\n this.index = this.createIndex()\n }\n\n private createIndex(): FlexSearchIndex {\n // 使用 encode 选项进行中文分词\n return new FlexSearch.Document({\n document: {\n id: 'id',\n index: ['title', 'content'],\n store: true,\n },\n encode: (str: string) => {\n // 使用结巴分词\n const tokens = nodejieba.cut(str)\n // 过滤空白和标点\n return tokens.filter((t) => t.trim().length > 0 && !/^[\\s\\p{P}]+$/u.test(t))\n },\n cache: 100,\n })\n }\n\n /**\n * 初始化\n */\n async init(): Promise<void> {\n // 暂不支持持久化\n }\n\n /**\n * 添加文档\n */\n add(record: FullTextRecord): void {\n this.index.add(record)\n this.docCount++\n }\n\n /**\n * 更新文档\n */\n update(record: FullTextRecord): void {\n this.remove(record.id)\n this.add(record)\n }\n\n /**\n * 删除文档\n */\n remove(id: string): void {\n this.index.remove(id)\n this.docCount = Math.max(0, this.docCount - 1)\n }\n\n /**\n * 搜索\n */\n search(query: string, limit: number = 10): FullTextSearchResult[] {\n if (!query.trim()) {\n return []\n }\n\n // 直接搜索,encode 会自动分词\n const results = this.index.search(query, {\n limit: limit * 2,\n index: ['title', 'content'],\n enrich: true,\n })\n\n // FlexSearch Document 返回格式: [{ field: 'title', result: [...] }, { field: 'content', result: [...] }]\n const idScores = new Map<string, number>()\n\n for (const fieldResult of results) {\n const field = fieldResult.field as string\n const weight = field === 'title' ? 2 : 1 // 标题匹配权重更高\n\n if (Array.isArray(fieldResult.result)) {\n for (let rank = 0; rank < fieldResult.result.length; rank++) {\n const item = fieldResult.result[rank]\n // item 可能是 { id, doc } 或者直接是 id\n const id = typeof item === 'object' && item !== null \n ? (item.id ?? item) \n : item\n \n if (typeof id === 'string') {\n // 基于排名的分数 + 字段权重\n const score = weight / (rank + 1)\n idScores.set(id, (idScores.get(id) || 0) + score)\n }\n }\n }\n }\n\n // 按分数排序并标准化\n const maxScore = Math.max(...idScores.values(), 1)\n \n return Array.from(idScores.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, limit)\n .map(([id, score]) => ({\n id,\n score: score / maxScore, // 标准化到 0-1\n }))\n }\n\n /**\n * 获取文档数量\n */\n getDocCount(): number {\n return this.docCount\n }\n\n /**\n * 根据 ID 获取文档内容\n */\n getContent(id: string): string | null {\n try {\n // FlexSearch Document 模式支持通过 ID 获取存储的文档\n const doc = this.index.get(id)\n if (doc && typeof doc === 'object') {\n return doc.content || null\n }\n return null\n } catch {\n return null\n }\n }\n\n /**\n * 保存索引\n */\n async save(): Promise<void> {\n // FlexSearch Document 模式的持久化比较复杂,暂时跳过\n }\n\n /**\n * 加载索引\n */\n async load(): Promise<void> {\n // 暂时跳过\n }\n\n /**\n * 清空索引\n */\n clear(): void {\n this.index = this.createIndex()\n this.docCount = 0\n }\n}\n\n","/**\n * 元数据存储模块\n * 使用 SQLite 存储文档元信息\n */\nimport Database from 'better-sqlite3'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport type { IndexedDocument, FileType, IndexStats } from '../types'\n\nexport class MetaStore {\n private db: Database.Database\n private dbPath: string\n\n constructor(dataDir: string) {\n this.dbPath = path.join(dataDir, 'meta.db')\n fs.mkdirSync(dataDir, { recursive: true })\n this.db = new Database(this.dbPath)\n this.init()\n }\n\n /**\n * 初始化数据库表\n */\n private init(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n path TEXT UNIQUE NOT NULL,\n name TEXT NOT NULL,\n file_type TEXT NOT NULL,\n extension TEXT NOT NULL,\n title TEXT,\n content TEXT,\n file_size INTEGER NOT NULL,\n created_at TEXT NOT NULL,\n modified_at TEXT NOT NULL,\n indexed_at TEXT NOT NULL,\n content_hash TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_documents_path ON documents(path);\n CREATE INDEX IF NOT EXISTS idx_documents_file_type ON documents(file_type);\n CREATE INDEX IF NOT EXISTS idx_documents_modified_at ON documents(modified_at);\n CREATE INDEX IF NOT EXISTS idx_documents_content_hash ON documents(content_hash);\n `)\n }\n\n /**\n * 添加或更新文档\n */\n upsert(doc: IndexedDocument): void {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO documents \n (id, path, name, file_type, extension, title, content, file_size, created_at, modified_at, indexed_at, content_hash)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `)\n\n stmt.run(\n doc.id,\n doc.path,\n doc.name,\n doc.fileType,\n doc.extension,\n doc.title || null,\n doc.content,\n doc.fileSize,\n doc.createdAt.toISOString(),\n doc.modifiedAt.toISOString(),\n doc.indexedAt.toISOString(),\n doc.contentHash\n )\n }\n\n /**\n * 根据 ID 获取文档\n */\n getById(id: string): IndexedDocument | null {\n const stmt = this.db.prepare('SELECT * FROM documents WHERE id = ?')\n const row = stmt.get(id) as Record<string, unknown> | undefined\n\n if (!row) return null\n\n return this.rowToDocument(row)\n }\n\n /**\n * 根据路径获取文档\n */\n getByPath(filePath: string): IndexedDocument | null {\n const stmt = this.db.prepare('SELECT * FROM documents WHERE path = ?')\n const row = stmt.get(filePath) as Record<string, unknown> | undefined\n\n if (!row) return null\n\n return this.rowToDocument(row)\n }\n\n /**\n * 根据路径和修改时间获取文档(更精确的唯一性检查)\n */\n getByPathAndTime(filePath: string, modifiedTime: Date): IndexedDocument | null {\n const stmt = this.db.prepare(\n 'SELECT * FROM documents WHERE path = ? AND modified_at = ?'\n )\n const row = stmt.get(filePath, modifiedTime.toISOString()) as Record<string, unknown> | undefined\n\n if (!row) return null\n\n return this.rowToDocument(row)\n }\n\n /**\n * 根据内容哈希获取文档(用于检测重复内容)\n */\n getByHash(contentHash: string): IndexedDocument | null {\n const stmt = this.db.prepare('SELECT * FROM documents WHERE content_hash = ? LIMIT 1')\n const row = stmt.get(contentHash) as Record<string, unknown> | undefined\n\n if (!row) return null\n\n return this.rowToDocument(row)\n }\n\n /**\n * 根据内容哈希获取所有相同内容的文档路径\n */\n getPathsByHash(contentHash: string): string[] {\n const stmt = this.db.prepare('SELECT path FROM documents WHERE content_hash = ?')\n const rows = stmt.all(contentHash) as Array<{ path: string }>\n return rows.map((row) => row.path)\n }\n\n /**\n * 根据 ID 列表获取文档\n */\n getByIds(ids: string[]): IndexedDocument[] {\n if (ids.length === 0) return []\n\n const placeholders = ids.map(() => '?').join(',')\n const stmt = this.db.prepare(`SELECT * FROM documents WHERE id IN (${placeholders})`)\n const rows = stmt.all(...ids) as Record<string, unknown>[]\n\n return rows.map((row) => this.rowToDocument(row))\n }\n\n /**\n * 删除文档\n */\n delete(id: string): void {\n const stmt = this.db.prepare('DELETE FROM documents WHERE id = ?')\n stmt.run(id)\n }\n\n /**\n * 根据路径删除文档\n */\n deleteByPath(filePath: string): void {\n const stmt = this.db.prepare('DELETE FROM documents WHERE path = ?')\n stmt.run(filePath)\n }\n\n /**\n * 获取所有文档\n */\n getAll(): IndexedDocument[] {\n const stmt = this.db.prepare('SELECT * FROM documents')\n const rows = stmt.all() as Record<string, unknown>[]\n return rows.map((row) => this.rowToDocument(row))\n }\n\n /**\n * 获取所有文档路径和哈希(用于增量更新)\n */\n getAllPathsAndHashes(): Map<string, { id: string; hash: string; modifiedAt: Date }> {\n const stmt = this.db.prepare('SELECT id, path, content_hash, modified_at FROM documents')\n const rows = stmt.all() as Array<{ id: string; path: string; content_hash: string; modified_at: string }>\n\n const result = new Map<string, { id: string; hash: string; modifiedAt: Date }>()\n for (const row of rows) {\n result.set(row.path, {\n id: row.id,\n hash: row.content_hash,\n modifiedAt: new Date(row.modified_at),\n })\n }\n\n return result\n }\n\n /**\n * 获取统计信息\n */\n getStats(): IndexStats {\n const totalStmt = this.db.prepare('SELECT COUNT(*) as count FROM documents')\n const total = (totalStmt.get() as { count: number }).count\n\n const byTypeStmt = this.db.prepare(\n 'SELECT file_type, COUNT(*) as count FROM documents GROUP BY file_type'\n )\n const byTypeRows = byTypeStmt.all() as Array<{ file_type: string; count: number }>\n const byType: Record<string, number> = {}\n for (const row of byTypeRows) {\n byType[row.file_type] = row.count\n }\n\n const lastUpdatedStmt = this.db.prepare(\n 'SELECT MAX(indexed_at) as last FROM documents'\n )\n const lastRow = lastUpdatedStmt.get() as { last: string | null }\n const lastUpdated = lastRow.last ? new Date(lastRow.last) : undefined\n\n // 获取索引文件大小\n let indexSize = 0\n try {\n const stat = fs.statSync(this.dbPath)\n indexSize = stat.size\n } catch {\n // ignore\n }\n\n return {\n totalDocuments: total,\n byType: byType as Record<FileType, number>,\n directories: [], // 这个需要从配置获取\n lastUpdated,\n indexSize,\n }\n }\n\n /**\n * 清空所有数据\n */\n clear(): void {\n this.db.exec('DELETE FROM documents')\n }\n\n /**\n * 关闭数据库连接\n */\n close(): void {\n this.db.close()\n }\n\n /**\n * 数据行转换为文档对象\n */\n private rowToDocument(row: Record<string, unknown>): IndexedDocument {\n return {\n id: row.id as string,\n path: row.path as string,\n name: row.name as string,\n fileType: row.file_type as FileType,\n extension: row.extension as string,\n title: row.title as string | undefined,\n content: row.content as string,\n fileSize: row.file_size as number,\n createdAt: new Date(row.created_at as string),\n modifiedAt: new Date(row.modified_at as string),\n indexedAt: new Date(row.indexed_at as string),\n contentHash: row.content_hash as string,\n }\n }\n}\n\n","/**\n * 文档解析器\n * 支持 Word、PDF、Excel、TXT、MD 等格式\n */\nimport mammoth from 'mammoth'\nimport pdfParse from 'pdf-parse'\nimport xlsx from 'xlsx'\nimport JSZip from 'jszip'\nimport { parseString } from 'xml2js'\nimport * as fs from 'fs/promises'\nimport * as path from 'path'\n\nexport interface ParsedDocument {\n /** 提取的文本内容 */\n content: string\n /** 文档标题(如果能提取) */\n title?: string\n /** 元数据 */\n metadata?: Record<string, unknown>\n}\n\n/**\n * 解析 Word 文档 (.docx)\n * 注意:mammoth 只支持 .docx 格式,不支持旧版 .doc\n */\nasync function parseWord(filePath: string): Promise<ParsedDocument> {\n const ext = path.extname(filePath).toLowerCase()\n \n // .doc 格式不支持(旧版二进制格式)\n if (ext === '.doc') {\n const title = path.basename(filePath, ext)\n return {\n content: '',\n title,\n metadata: { unsupported: true, reason: '旧版 .doc 格式暂不支持,请转换为 .docx' }\n }\n }\n\n try {\n const buffer = await fs.readFile(filePath)\n const result = await mammoth.extractRawText({ buffer })\n const content = result.value.trim()\n\n // 尝试从第一行提取标题\n const lines = content.split('\\n').filter((l) => l.trim())\n const title = lines[0]?.slice(0, 100)\n\n return { content, title }\n } catch (error) {\n // Word 解析失败,静默返回空内容\n const title = path.basename(filePath, ext)\n return {\n content: '',\n title,\n metadata: { \n error: true, \n reason: error instanceof Error ? error.message : String(error) \n },\n }\n }\n}\n\n/**\n * 解析 PDF 文档\n */\nasync function parsePDF(filePath: string): Promise<ParsedDocument> {\n // 跳过资源目录中的 PDF(这些是图标文件,不是文档)\n const pathLower = filePath.toLowerCase()\n if (pathLower.includes('.xcassets') || \n pathLower.includes('.imageset') ||\n pathLower.includes('.appiconset')) {\n return {\n content: '',\n title: path.basename(filePath, '.pdf'),\n metadata: { skipped: true, reason: '资源文件,跳过解析' },\n }\n }\n \n try {\n // 临时抑制 pdf-parse 的内部警告(这些警告来自库内部,我们无法控制)\n const originalWarn = console.warn\n const warnings: string[] = []\n console.warn = (...args: unknown[]) => {\n const msg = args.join(' ')\n // 过滤掉 pdf-parse 的格式警告(这些是库内部的警告,不影响功能)\n if (msg.includes('Ignoring invalid character') || \n msg.includes('Warning:') && msg.includes('hex string')) {\n // 静默忽略这些警告\n return\n }\n // 其他警告正常输出\n originalWarn.apply(console, args)\n }\n \n try {\n const buffer = await fs.readFile(filePath)\n const data = await pdfParse(buffer)\n const content = data.text.trim()\n \n // 恢复 console.warn\n console.warn = originalWarn\n \n // 如果内容为空,可能是损坏的文件\n if (!content) {\n return {\n content: '',\n title: path.basename(filePath, '.pdf'),\n metadata: { empty: true },\n }\n }\n\n // 尝试从 PDF 元数据或第一行提取标题\n const title = data.info?.Title || content.split('\\n')[0]?.slice(0, 100)\n\n return {\n content,\n title,\n metadata: {\n pages: data.numpages,\n info: data.info,\n },\n }\n } finally {\n // 确保恢复 console.warn\n console.warn = originalWarn\n }\n } catch (error) {\n // PDF 解析失败(损坏的文件、格式错误等)\n // 返回空内容,由调用方决定是否跳过\n const title = path.basename(filePath, '.pdf')\n return {\n content: '',\n title,\n metadata: { \n error: true, \n reason: error instanceof Error ? error.message : String(error) \n },\n }\n }\n}\n\n/**\n * 解析 Excel 文档 (.xlsx, .xls)\n */\nasync function parseExcel(filePath: string): Promise<ParsedDocument> {\n try {\n const workbook = xlsx.readFile(filePath)\n const sheets: string[] = []\n\n for (const sheetName of workbook.SheetNames) {\n const sheet = workbook.Sheets[sheetName]\n const text = xlsx.utils.sheet_to_txt(sheet)\n if (text.trim()) {\n sheets.push(`[${sheetName}]\\n${text}`)\n }\n }\n\n const content = sheets.join('\\n\\n')\n const title = workbook.SheetNames[0]\n\n return {\n content,\n title,\n metadata: {\n sheetCount: workbook.SheetNames.length,\n sheetNames: workbook.SheetNames,\n },\n }\n } catch (error) {\n // Excel 解析失败,静默返回空内容\n const title = path.basename(filePath, path.extname(filePath))\n return {\n content: '',\n title,\n metadata: { \n error: true, \n reason: error instanceof Error ? error.message : String(error) \n },\n }\n }\n}\n\n/**\n * 解析纯文本文档 (.txt, .md, .rtf)\n */\nasync function parseText(filePath: string): Promise<ParsedDocument> {\n const content = await fs.readFile(filePath, 'utf-8')\n\n // 尝试从第一行提取标题\n const lines = content.split('\\n').filter((l) => l.trim())\n let title = lines[0]?.slice(0, 100)\n\n // Markdown 标题处理\n if (title?.startsWith('#')) {\n title = title.replace(/^#+\\s*/, '')\n }\n\n return { content: content.trim(), title }\n}\n\n/**\n * 解析 PPT 文档 (.pptx)\n * 直接使用 jszip + xml2js 解析 PPTX 文件(PPTX 本质上是 ZIP + XML)\n * 这样可以避免第三方库的 XML 解析问题\n */\nasync function parsePPT(filePath: string): Promise<ParsedDocument> {\n const title = path.basename(filePath, path.extname(filePath))\n \n try {\n // 确保使用绝对路径\n const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath)\n \n // 读取文件\n const fileBuffer = await fs.readFile(absolutePath)\n \n // PPTX 是 ZIP 文件,使用 jszip 解压\n const zip = await JSZip.loadAsync(fileBuffer)\n \n // 提取所有幻灯片的文本,保留幻灯片结构\n const slides: Array<{ number: number; content: string }> = []\n \n // 查找所有幻灯片文件 (ppt/slides/slide*.xml)\n const slideFiles: string[] = []\n zip.forEach((relativePath) => {\n if (relativePath.startsWith('ppt/slides/slide') && relativePath.endsWith('.xml')) {\n slideFiles.push(relativePath)\n }\n })\n \n // 按幻灯片编号排序\n slideFiles.sort((a, b) => {\n const matchA = a.match(/slide(\\d+)\\.xml/)\n const matchB = b.match(/slide(\\d+)\\.xml/)\n const numA = matchA ? parseInt(matchA[1]) : 0\n const numB = matchB ? parseInt(matchB[1]) : 0\n return numA - numB\n })\n \n // 解析每个幻灯片\n for (const slidePath of slideFiles) {\n const slideMatch = slidePath.match(/slide(\\d+)\\.xml/)\n const slideNumber = slideMatch ? parseInt(slideMatch[1]) : 0\n const slideFile = zip.file(slidePath)\n if (!slideFile) continue\n \n const slideXml = await slideFile.async('string')\n if (!slideXml) continue\n \n // 解析 XML\n const slideJson = await new Promise<any>((resolve, reject) => {\n parseString(slideXml, { \n explicitArray: false, \n mergeAttrs: true,\n trim: true,\n normalize: true,\n }, (err, result) => {\n if (err) reject(err)\n else resolve(result)\n })\n })\n \n // 提取文本内容(只从 <a:t> 标签中提取,这是 PowerPoint 中文本节点的名称)\n // 严格只提取 a:t 标签中的文本,忽略所有其他内容\n const extractTextFromXml = (obj: any, inTextNode = false): string[] => {\n const texts: string[] = []\n \n if (typeof obj === 'string') {\n // 只有在文本节点中的字符串才提取\n if (inTextNode) {\n const trimmed = obj.trim()\n if (trimmed && trimmed.length > 0) {\n texts.push(trimmed)\n }\n }\n } else if (Array.isArray(obj)) {\n for (const item of obj) {\n texts.push(...extractTextFromXml(item, inTextNode))\n }\n } else if (obj && typeof obj === 'object') {\n // 只查找 'a:t' 字段(这是 PowerPoint 中文本节点的名称)\n // 这是唯一包含实际文本内容的字段\n if (obj['a:t']) {\n // 标记进入文本节点\n const fieldTexts = extractTextFromXml(obj['a:t'], true)\n texts.push(...fieldTexts)\n }\n \n // 递归处理所有属性,继续查找 a:t(但不提取其他内容)\n for (const value of Object.values(obj)) {\n texts.push(...extractTextFromXml(value, inTextNode))\n }\n }\n \n return texts\n }\n \n const slideTexts = extractTextFromXml(slideJson)\n \n // 严格过滤:只保留有意义的文本\n const filteredTexts = slideTexts.filter(t => {\n const trimmed = t.trim()\n if (!trimmed || trimmed.length === 0) return false\n \n // 过滤掉纯数字(除非是百分比或带单位的数字)\n if (/^-?\\d+$/.test(trimmed)) return false\n \n // 过滤掉 GUID\n if (/^\\{[0-9A-F-]+\\}$/i.test(trimmed)) return false\n \n // 过滤掉 URL\n if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) return false\n \n // 过滤掉 XML 命名空间\n if (trimmed.includes('xmlns') || trimmed.includes('schemas.openxmlformats')) return false\n \n // 过滤掉格式标识符\n const formatKeywords = [\n 'ctrTitle', 'zh-CN', 'en-US', 'body', 'title', 'subtitle',\n 'connsite', 'islide', '组合', '矩形', '任意多边形', '文本框',\n '文本占位符', '图文框', '直接连接符', '椭圆', 'rId', 'accent',\n 'minor', 'lt', 'ctr', 'rect', 'square', 'roundRect', 'ellipse',\n 'none', 'solid', 'horz', 'Bullet', 'noStrike', 'Arial', '方正',\n 'Component', 'Icon', 'Group', 'Presenter', 'subTitle', 'quarter',\n 'Shape', 'Number', 'Text', 'TextBox', 'Picture', 'QR', 'adj',\n 'Thank you', 'IMPORTANT NOTE', 'Template file', '20XX.XX.XX'\n ]\n if (formatKeywords.some(keyword => trimmed.includes(keyword))) return false\n \n // 过滤掉坐标和尺寸(如 \"*/ 0 w 12192000\")\n if (/^\\*\\/.*[wh]$/.test(trimmed)) return false\n if (/^connsite[XY]\\d+$/.test(trimmed)) return false\n \n // 过滤掉颜色代码(如 \"2F2F2F\", \"FFFFFF\")\n if (/^[0-9A-F]{6}$/i.test(trimmed)) return false\n \n // 过滤掉单个字符(除非是中文且有意义)\n if (trimmed.length === 1) {\n // 保留有意义的单个中文字符(如\"个\"、\"项\"、\"分\"等量词)\n // 但这里我们统一过滤掉,因为它们在上下文中才有意义\n return false\n }\n \n // 过滤掉短英文单词(除非是常见的有意义单词)\n if (/^[a-zA-Z-]+$/.test(trimmed) && trimmed.length < 3) return false\n \n // 保留包含中文的文本(至少2个字符),或长度大于等于3的英文文本\n const hasChinese = /[\\u4e00-\\u9fa5]/.test(trimmed)\n const chineseCount = (trimmed.match(/[\\u4e00-\\u9fa5]/g) || []).length\n \n // 如果包含中文,至少要有2个中文字符\n if (hasChinese && chineseCount < 2) return false\n \n // 如果是纯英文,至少3个字符\n if (!hasChinese && trimmed.length < 3) return false\n \n return true\n })\n \n if (filteredTexts.length > 0) {\n // 去重\n const uniqueTexts = [...new Set(filteredTexts)]\n \n // 智能合并:将短文本片段合并到前一个长文本中\n const mergedTexts: string[] = []\n for (let i = 0; i < uniqueTexts.length; i++) {\n const current = uniqueTexts[i].trim()\n if (!current) continue\n \n const hasChinese = /[\\u4e00-\\u9fa5]/.test(current)\n const isShort = current.length <= 3 && hasChinese\n \n // 如果是短文本且包含中文,尝试合并到前一个文本\n if (isShort && mergedTexts.length > 0) {\n const lastIndex = mergedTexts.length - 1\n const lastText = mergedTexts[lastIndex]\n if (/[\\u4e00-\\u9fa5]/.test(lastText)) {\n mergedTexts[lastIndex] = lastText + current\n continue\n }\n }\n \n // 如果是数字或百分比,也尝试合并到前一个文本\n if ((/^\\d+%?$/.test(current) || /^\\d+\\.\\d+$/.test(current)) && mergedTexts.length > 0) {\n const lastIndex = mergedTexts.length - 1\n const lastText = mergedTexts[lastIndex]\n if (/[\\u4e00-\\u9fa5]/.test(lastText)) {\n mergedTexts[lastIndex] = lastText + current\n continue\n }\n }\n \n mergedTexts.push(current)\n }\n \n // 合并幻灯片内容\n const slideContent = mergedTexts.join('\\n\\n').trim()\n if (slideContent) {\n slides.push({\n number: slideNumber,\n content: slideContent,\n })\n }\n }\n }\n \n // 如果没有提取到任何内容\n if (slides.length === 0) {\n return {\n content: `[PPT文档] ${title}`,\n title,\n metadata: {\n error: false,\n slides: [],\n },\n }\n }\n \n // 生成 Markdown 格式的内容(用 --- 分隔幻灯片)\n const markdownContent = slides\n .map((slide, index) => {\n const slideHeader = `## 幻灯片 ${slide.number}${index === 0 ? '(封面)' : ''}\\n\\n`\n return slideHeader + slide.content\n })\n .join('\\n\\n---\\n\\n')\n \n // 提取标题(从第一张幻灯片或文件名)\n const extractedTitle = slides[0]?.content.split('\\n')[0]?.slice(0, 100) || title\n \n return {\n content: markdownContent,\n title: extractedTitle,\n metadata: {\n slides: slides.map(s => ({\n number: s.number,\n content: s.content,\n })),\n totalSlides: slides.length,\n },\n }\n } catch (error) {\n // PPT 解析失败,返回基本信息\n const errorMsg = error instanceof Error ? error.message : String(error)\n console.warn(`PPT 解析失败: ${filePath} - ${errorMsg}`)\n \n return {\n content: `[PPT文档] ${title}`,\n title,\n metadata: {\n error: true,\n reason: errorMsg,\n },\n }\n }\n}\n\n/**\n * 根据文件扩展名选择解析器\n */\nexport async function parseDocument(filePath: string): Promise<ParsedDocument> {\n const ext = path.extname(filePath).toLowerCase()\n\n switch (ext) {\n case '.docx':\n case '.doc':\n return parseWord(filePath)\n\n case '.pdf':\n return parsePDF(filePath)\n\n case '.xlsx':\n case '.xls':\n return parseExcel(filePath)\n\n case '.pptx':\n case '.ppt':\n return parsePPT(filePath)\n\n case '.txt':\n case '.md':\n case '.rtf':\n case '.text':\n return parseText(filePath)\n\n default:\n // 尝试作为纯文本读取\n try {\n return await parseText(filePath)\n } catch {\n return { content: '', title: path.basename(filePath) }\n }\n }\n}\n\n/**\n * 检查文件是否支持解析\n */\nexport function isSupportedDocument(filePath: string): boolean {\n const ext = path.extname(filePath).toLowerCase()\n return ['.docx', '.doc', '.pdf', '.xlsx', '.xls', '.pptx', '.ppt', '.txt', '.md', '.rtf'].includes(ext)\n}\n\n/**\n * 获取文件类型\n */\nexport function getDocumentType(filePath: string): string {\n const ext = path.extname(filePath).toLowerCase()\n\n if (['.docx', '.doc'].includes(ext)) return 'word'\n if (ext === '.pdf') return 'pdf'\n if (['.xlsx', '.xls'].includes(ext)) return 'excel'\n if (['.pptx', '.ppt'].includes(ext)) return 'powerpoint'\n if (['.txt', '.md', '.rtf'].includes(ext)) return 'text'\n\n return 'unknown'\n}\n\n","/**\n * 文本向量化模块\n * 使用豆包 doubao-embedding-vision-250615 多模态向量化模型\n * \n * 支持:\n * - 文本向量化(支持 instructions 指令)\n * - 图片向量化(URL 或 Base64)\n * - 视频向量化\n * - 多模态混合输入\n */\n\n/** 豆包 Embedding API 响应格式 */\ninterface DoubaoEmbeddingResponse {\n data: {\n embedding: number[]\n object: string\n }\n id: string\n model: string\n object: string\n usage: {\n prompt_tokens: number\n total_tokens: number\n }\n}\n\n/** Embedding 选项 */\nexport interface EmbedOptions {\n /** 最大文本长度(超出截断) */\n maxLength?: number\n /** 指令(用于指导模型理解输入意图) */\n instructions?: string\n}\n\n/** 豆包 API 配置 */\nlet arkApiKey: string | null = null\nlet currentModel: string = 'doubao-embedding-vision-250615'\nlet initialized = false\n\n/** API 基础 URL */\nconst ARK_EMBEDDING_URL = 'https://ark.cn-beijing.volces.com/api/v3/embeddings/multimodal'\n\n/** \n * 向量维度配置\n * doubao-embedding-vision-250615 支持通过 dimensions 参数指定维度\n * 默认使用 1024 维度(平衡效果和存储)\n */\nlet embeddingDimension = 1024\n\n/**\n * 默认指令配置\n * 用于区分文档索引和查询检索\n */\nexport const DEFAULT_INSTRUCTIONS = {\n /** 文档索引指令 */\n document: '将以下文档内容转换为语义向量,用于后续的相似度检索',\n /** 查询检索指令 */\n query: '将以下搜索查询转换为语义向量,用于检索相关文档',\n}\n\n/**\n * 初始化 Embedding 模型\n * @param apiKey 豆包 API Key(ARK_API_KEY)\n * @param modelName 模型名称(可选)\n * @param dimensions 向量维度(可选,默认 1024)\n */\nexport async function initEmbedder(\n apiKey?: string,\n modelName: string = 'doubao-embedding-vision-250615',\n dimensions: number = 1024\n): Promise<void> {\n // 支持环境变量\n const key = apiKey || process.env.ARK_API_KEY\n \n if (!key) {\n throw new Error('缺少 ARK_API_KEY,请通过参数或环境变量提供')\n }\n \n arkApiKey = key\n currentModel = modelName\n embeddingDimension = dimensions\n initialized = true\n console.log(`豆包 Embedding 模型已配置: ${modelName}, 维度: ${dimensions}`)\n}\n\n/**\n * 生成文本向量(底层函数)\n * @param text 输入文本\n * @param options 选项(maxLength, instructions)\n * @returns 向量数组\n */\n/** 延迟函数 */\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/** 重试配置 */\nconst RETRY_CONFIG = {\n maxRetries: 3, // 最大重试次数\n baseDelay: 1000, // 基础延迟 1 秒\n maxDelay: 10000, // 最大延迟 10 秒\n}\n\nexport async function embed(\n text: string,\n options?: EmbedOptions\n): Promise<number[]> {\n if (!initialized || !arkApiKey) {\n await initEmbedder()\n }\n\n const maxLength = options?.maxLength ?? 8192\n const instructions = options?.instructions\n\n // 截断文本\n const truncated = text.slice(0, maxLength)\n\n // 构建请求体\n const requestBody: Record<string, unknown> = {\n model: currentModel,\n encoding_format: 'float',\n dimensions: embeddingDimension,\n input: [\n {\n type: 'text',\n text: truncated,\n },\n ],\n }\n\n // 添加 instructions\n if (instructions) {\n requestBody.instructions = instructions\n }\n\n // 带重试的请求\n let lastError: Error | null = null\n for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {\n try {\n const response = await fetch(ARK_EMBEDDING_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${arkApiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (response.ok) {\n const result = await response.json() as DoubaoEmbeddingResponse\n \n if (!result.data?.embedding) {\n throw new Error(`豆包 Embedding API 返回格式错误: ${JSON.stringify(result)}`)\n }\n\n return result.data.embedding\n }\n\n // 429 错误:限流,需要重试\n if (response.status === 429 && attempt < RETRY_CONFIG.maxRetries) {\n const retryDelay = Math.min(\n RETRY_CONFIG.baseDelay * Math.pow(2, attempt),\n RETRY_CONFIG.maxDelay\n )\n await delay(retryDelay)\n continue\n }\n\n const errorText = await response.text()\n throw new Error(`豆包 Embedding API 错误 (${response.status}): ${errorText}`)\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error))\n \n // 网络错误也重试\n if (attempt < RETRY_CONFIG.maxRetries) {\n const retryDelay = Math.min(\n RETRY_CONFIG.baseDelay * Math.pow(2, attempt),\n RETRY_CONFIG.maxDelay\n )\n await delay(retryDelay)\n continue\n }\n }\n }\n\n throw lastError || new Error('Embedding 请求失败')\n}\n\n/**\n * 生成文档向量(用于索引)\n * 自动使用文档指令\n */\nexport async function embedDocument(\n text: string,\n maxLength: number = 8192\n): Promise<number[]> {\n return embed(text, { \n maxLength, \n instructions: DEFAULT_INSTRUCTIONS.document \n })\n}\n\n/**\n * 生成查询向量(用于搜索)\n * 自动使用查询指令\n */\nexport async function embedQuery(\n text: string,\n maxLength: number = 8192\n): Promise<number[]> {\n return embed(text, { \n maxLength, \n instructions: DEFAULT_INSTRUCTIONS.query \n })\n}\n\n/**\n * 批量生成文档向量(串行处理,避免并发限制)\n * @param texts 文本数组\n * @param maxLength 最大文本长度\n * @returns 向量数组的数组\n */\nexport async function embedBatch(\n texts: string[],\n maxLength: number = 8192\n): Promise<number[][]> {\n if (!initialized || !arkApiKey) {\n await initEmbedder()\n }\n\n const results: number[][] = []\n\n for (const text of texts) {\n const vector = await embedDocument(text, maxLength)\n results.push(vector)\n }\n\n return results\n}\n\n/**\n * 并发批量生成文档向量(适合大量数据)\n * @param texts 文本数组\n * @param concurrency 并发数(默认 5)\n * @param maxLength 最大文本长度\n * @returns 向量数组的数组\n */\nexport async function embedBatchConcurrent(\n texts: string[],\n concurrency: number = 5,\n maxLength: number = 8192\n): Promise<number[][]> {\n if (!initialized || !arkApiKey) {\n await initEmbedder()\n }\n\n const results: number[][] = new Array(texts.length)\n const executing: Promise<void>[] = []\n\n for (let i = 0; i < texts.length; i++) {\n const promise = (async (index: number) => {\n results[index] = await embedDocument(texts[index], maxLength)\n })(i)\n\n executing.push(promise)\n\n // 控制并发数\n if (executing.length >= concurrency) {\n await Promise.race(executing)\n // 移除已完成的\n const completed = executing.filter(p => {\n const state = (p as Promise<void> & { _state?: string })\n return state._state === 'fulfilled' || state._state === 'rejected'\n })\n for (const c of completed) {\n const idx = executing.indexOf(c)\n if (idx > -1) executing.splice(idx, 1)\n }\n }\n }\n\n // 等待剩余任务完成\n await Promise.all(executing)\n\n return results\n}\n\n/**\n * 生成图片向量\n * @param imageUrl 图片 URL 或 Base64 编码(格式: data:image/jpeg;base64,xxx)\n * @returns 向量数组\n */\nexport async function embedImage(imageUrl: string): Promise<number[]> {\n if (!initialized || !arkApiKey) {\n await initEmbedder()\n }\n\n const response = await fetch(ARK_EMBEDDING_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${arkApiKey}`,\n },\n body: JSON.stringify({\n model: currentModel,\n encoding_format: 'float',\n dimensions: embeddingDimension,\n input: [\n {\n type: 'image_url',\n image_url: {\n url: imageUrl,\n },\n },\n ],\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`豆包 Embedding API 错误 (${response.status}): ${errorText}`)\n }\n\n const result = await response.json() as DoubaoEmbeddingResponse\n \n if (!result.data?.embedding) {\n throw new Error(`豆包 Embedding API 返回格式错误: ${JSON.stringify(result)}`)\n }\n\n return result.data.embedding\n}\n\n/**\n * 生成视频向量\n * @param videoUrl 视频 URL 或 Base64 编码\n * @returns 向量数组\n */\nexport async function embedVideo(videoUrl: string): Promise<number[]> {\n if (!initialized || !arkApiKey) {\n await initEmbedder()\n }\n\n const response = await fetch(ARK_EMBEDDING_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${arkApiKey}`,\n },\n body: JSON.stringify({\n model: currentModel,\n encoding_format: 'float',\n dimensions: embeddingDimension,\n input: [\n {\n type: 'video_url',\n video_url: {\n url: videoUrl,\n },\n },\n ],\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`豆包 Embedding API 错误 (${response.status}): ${errorText}`)\n }\n\n const result = await response.json() as DoubaoEmbeddingResponse\n \n if (!result.data?.embedding) {\n throw new Error(`豆包 Embedding API 返回格式错误: ${JSON.stringify(result)}`)\n }\n\n return result.data.embedding\n}\n\n/**\n * 多模态混合向量化\n * @param inputs 混合输入数组\n * @returns 向量数组\n */\nexport async function embedMultimodal(\n inputs: Array<\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n | { type: 'video_url'; video_url: { url: string } }\n >\n): Promise<number[]> {\n if (!initialized || !arkApiKey) {\n await initEmbedder()\n }\n\n const response = await fetch(ARK_EMBEDDING_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${arkApiKey}`,\n },\n body: JSON.stringify({\n model: currentModel,\n encoding_format: 'float',\n dimensions: embeddingDimension,\n input: inputs,\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`豆包 Embedding API 错误 (${response.status}): ${errorText}`)\n }\n\n const result = await response.json() as DoubaoEmbeddingResponse\n \n if (!result.data?.embedding) {\n throw new Error(`豆包 Embedding API 返回格式错误: ${JSON.stringify(result)}`)\n }\n\n return result.data.embedding\n}\n\n/**\n * 获取向量维度\n */\nexport function getEmbeddingDimension(): number {\n return embeddingDimension\n}\n\n/**\n * 设置向量维度\n * @param dimension 新的维度值\n */\nexport function setEmbeddingDimension(dimension: number): void {\n embeddingDimension = dimension\n}\n\n/**\n * 释放资源(保持 API 兼容)\n */\nexport function disposeEmbedder(): void {\n arkApiKey = null\n initialized = false\n}\n\n/**\n * 检查是否已初始化\n */\nexport function isEmbedderInitialized(): boolean {\n return initialized && arkApiKey !== null\n}\n","/**\n * 文件扫描器\n * 使用 fdir 快速扫描目录\n */\nimport { fdir } from 'fdir'\nimport * as path from 'path'\nimport * as os from 'os'\nimport { createRulesManager, type ScanRulesManager } from './rules'\n\nexport interface ScanConfig {\n /** 扫描目录 */\n directories: string[]\n /** 排除目录(用户自定义,会合并到规则管理器中) */\n excludeDirs?: string[]\n /** 支持的扩展名(用户自定义,会合并到规则管理器中) */\n extensions?: string[]\n /** 最大文件大小 */\n maxFileSize: number\n /** 扫描进度回调 */\n onProgress?: (progress: { scanned: number; currentDir?: string }) => void\n /** 自定义规则(可选,用于覆盖默认规则) */\n customRules?: Partial<import('./rules').ScanRules>\n}\n\n/**\n * 展开 ~ 为用户目录\n */\nfunction expandPath(p: string): string {\n if (p.startsWith('~')) {\n return path.join(os.homedir(), p.slice(1))\n }\n return p\n}\n\n/**\n * 扫描目录获取所有支持的文档文件\n */\nexport async function scanDirectories(\n directories: string[],\n config?: Partial<ScanConfig>\n): Promise<string[]> {\n const onProgress = config?.onProgress\n\n // 创建规则管理器,合并用户自定义规则\n const customRules: Partial<import('./rules').ScanRules> = config?.customRules || {}\n \n // 如果用户提供了 excludeDirs 或 extensions,合并到规则中\n if (config?.excludeDirs && config.excludeDirs.length > 0) {\n const existingPaths = customRules.directories?.excludedPaths || []\n customRules.directories = {\n ...customRules.directories,\n excludedNames: customRules.directories?.excludedNames || [],\n excludedPaths: [...existingPaths, ...config.excludeDirs],\n windowsSystemDirs: customRules.directories?.windowsSystemDirs || [],\n buildDirs: customRules.directories?.buildDirs || [],\n resourceDirs: customRules.directories?.resourceDirs || [],\n }\n }\n \n if (config?.extensions && config.extensions.length > 0) {\n const existingAllowed = customRules.extensions?.allowed || []\n customRules.extensions = {\n ...customRules.extensions,\n allowed: [...existingAllowed, ...config.extensions],\n excluded: customRules.extensions?.excluded || [],\n }\n }\n\n const rulesManager = createRulesManager(customRules)\n\n const allFiles: string[] = []\n let totalScanned = 0\n\n for (const dir of directories) {\n const expandedDir = expandPath(dir)\n \n // 通知开始扫描目录\n onProgress?.({ scanned: totalScanned, currentDir: expandedDir })\n\n try {\n let fileCount = 0\n let lastLogTime = Date.now()\n const logInterval = 2000 // 每2秒输出一次日志\n \n // 使用规则管理器的 exclude() 方法排除目录\n const crawler = new fdir()\n .withFullPaths()\n .exclude((dirName) => {\n return rulesManager.shouldExcludeDirectory(dirName)\n })\n .filter((filePath, isDirectory) => {\n // 只处理文件,不处理目录\n if (isDirectory) {\n return false\n }\n \n fileCount++\n totalScanned++\n \n // 每扫描一定数量的文件或每2秒输出一次进度\n const now = Date.now()\n if (fileCount % 5000 === 0 || now - lastLogTime >= logInterval) {\n onProgress?.({ scanned: totalScanned, currentDir: expandedDir })\n console.log(` 🔍 已扫描 ${totalScanned.toLocaleString()} 个文件...`)\n lastLogTime = now\n }\n \n const fileName = path.basename(filePath)\n \n // 使用规则管理器检查文件是否应该被排除\n if (rulesManager.shouldExcludeFile(filePath, fileName)) {\n return false\n }\n\n // 检查扩展名\n const ext = path.extname(filePath)\n return rulesManager.isExtensionAllowed(ext)\n })\n\n const files = await crawler.crawl(expandedDir).withPromise()\n allFiles.push(...(files as string[]))\n \n // 最终进度更新\n onProgress?.({ scanned: totalScanned, currentDir: expandedDir })\n } catch (err) {\n console.warn(` ⚠️ 扫描目录失败: ${dir}`, err)\n }\n }\n\n console.log(` 📊 扫描完成,共找到 ${allFiles.length} 个文件`)\n return allFiles\n}\n\n/**\n * 获取默认扫描目录(跨平台,只扫描用户目录,不扫描系统盘)\n */\nexport function getDefaultDirectories(): string[] {\n const home = os.homedir()\n const platform = os.platform()\n \n // 只扫描用户目录,避免扫描系统文件\n const userDirs = [\n path.join(home, 'Documents'),\n path.join(home, 'Desktop'),\n path.join(home, 'Downloads'),\n path.join(home, 'Pictures'),\n path.join(home, 'Music'),\n ]\n \n // 根据平台添加视频目录\n if (platform === 'darwin') {\n // macOS 使用 Movies\n userDirs.push(path.join(home, 'Movies'))\n } else {\n // Windows/Linux 使用 Videos\n userDirs.push(path.join(home, 'Videos'))\n }\n \n return userDirs\n}\n\n","/**\n * 文件扫描和索引规则管理器\n * 统一管理所有文件过滤、排除规则,便于维护和扩展\n * \n * 隐藏文件/目录处理策略:\n * - 所有以 . 开头的文件和目录默认都会被排除(excludeHidden: true)\n * - 隐藏目录在 fdir 的 exclude() 阶段就被排除,避免进入目录扫描(性能优化)\n * - 隐藏文件在 filter() 阶段被排除(fdir 不支持在 exclude 阶段排除文件)\n * - 这样可以避免扫描 .git、.vscode、.idea、.env 等常见的隐藏文件/目录\n */\nimport * as path from 'path'\nimport * as os from 'os'\n\n/**\n * 文件扩展名规则\n */\nexport interface ExtensionRules {\n /** 支持的文档扩展名列表 */\n allowed: string[]\n /** 排除的扩展名列表(即使在其他地方允许) */\n excluded: string[]\n}\n\n/**\n * 目录排除规则\n */\nexport interface DirectoryRules {\n /** 排除的目录名(不区分大小写) */\n excludedNames: string[]\n /** 排除的目录路径(完整路径或相对路径) */\n excludedPaths: string[]\n /** Windows 系统目录 */\n windowsSystemDirs: string[]\n /** 构建和缓存目录 */\n buildDirs: string[]\n /** 资源目录(通常包含图标等,不是文档) */\n resourceDirs: string[]\n}\n\n/**\n * 文件排除规则\n */\nexport interface FileRules {\n /** 是否排除隐藏文件(以 . 开头) */\n excludeHidden: boolean\n /** 排除的文件名模式(支持通配符) */\n excludedPatterns: string[]\n /** 排除的路径包含模式(路径中包含这些字符串的文件将被排除) */\n excludedPathContains: string[]\n}\n\n/**\n * 路径匹配规则\n */\nexport interface PathRules {\n /** Windows 平台特殊规则 */\n windows: {\n /** 是否只扫描用户目录 */\n onlyUserDirs: boolean\n /** 排除的系统盘根目录下的目录 */\n excludedRootDirs: string[]\n }\n}\n\n/**\n * 扫描规则配置\n */\nexport interface ScanRules {\n extensions: ExtensionRules\n directories: DirectoryRules\n files: FileRules\n paths: PathRules\n}\n\n/**\n * 默认文件扩展名规则\n */\nexport const DEFAULT_EXTENSION_RULES: ExtensionRules = {\n allowed: [\n '.docx',\n '.doc',\n '.pdf',\n '.xlsx',\n '.xls',\n '.pptx',\n '.ppt',\n '.txt',\n '.md',\n '.rtf',\n ],\n excluded: [\n // .doc 格式明确不支持(解析困难)\n '.doc',\n ],\n}\n\n/**\n * 默认目录排除规则\n * 注意:所有以 . 开头的隐藏目录会自动被排除(在 shouldExcludeDirectory 中处理)\n * 这里只列出非隐藏的目录名\n */\nexport const DEFAULT_DIRECTORY_RULES: DirectoryRules = {\n // 版本控制目录(非隐藏的)\n excludedNames: [\n 'node_modules',\n ],\n // 缓存和临时目录(非隐藏的)\n buildDirs: [\n '__pycache__',\n 'build',\n 'dist',\n 'out',\n 'target',\n ],\n // 资源目录(非隐藏的)\n resourceDirs: [\n 'Assets.xcassets',\n ],\n // 系统目录(平台特定)\n windowsSystemDirs: [\n 'Windows',\n 'Program Files',\n 'Program Files (x86)',\n 'ProgramData',\n 'System Volume Information',\n 'AppData',\n 'Library',\n '$RECYCLE.BIN',\n ],\n // 用户配置的排除路径(默认空,由用户配置)\n excludedPaths: [],\n}\n\n/**\n * 默认文件排除规则\n * 注意:所有以 . 开头的隐藏文件会自动被排除(在 shouldExcludeFile 中处理)\n */\nexport const DEFAULT_FILE_RULES: FileRules = {\n // 默认排除所有隐藏文件(以 . 开头)\n excludeHidden: true,\n excludedPatterns: [],\n excludedPathContains: [\n // 资源文件路径模式(即使文件名不是隐藏的,但路径中包含这些模式也要排除)\n '.imageset',\n '.xcassets',\n '.appiconset',\n ],\n}\n\n/**\n * 默认路径规则\n */\nexport const DEFAULT_PATH_RULES: PathRules = {\n windows: {\n onlyUserDirs: true,\n excludedRootDirs: [\n 'Windows',\n 'Program Files',\n 'Program Files (x86)',\n 'ProgramData',\n 'System Volume Information',\n ],\n },\n}\n\n/**\n * 默认扫描规则\n */\nexport const DEFAULT_SCAN_RULES: ScanRules = {\n extensions: DEFAULT_EXTENSION_RULES,\n directories: DEFAULT_DIRECTORY_RULES,\n files: DEFAULT_FILE_RULES,\n paths: DEFAULT_PATH_RULES,\n}\n\n/**\n * 规则管理器类\n * 提供统一的规则检查和匹配接口\n */\nexport class ScanRulesManager {\n private rules: ScanRules\n private excludedDirNamesSet: Set<string>\n private allowedExtensionsSet: Set<string>\n private excludedExtensionsSet: Set<string>\n\n constructor(rules: ScanRules = DEFAULT_SCAN_RULES) {\n this.rules = rules\n this.excludedDirNamesSet = new Set(\n [\n ...rules.directories.excludedNames,\n ...rules.directories.buildDirs,\n ...rules.directories.resourceDirs,\n ].map(name => name.toLowerCase())\n )\n this.allowedExtensionsSet = new Set(\n rules.extensions.allowed.map(ext => ext.toLowerCase())\n )\n this.excludedExtensionsSet = new Set(\n rules.extensions.excluded.map(ext => ext.toLowerCase())\n )\n }\n\n /**\n * 检查文件扩展名是否允许\n */\n isExtensionAllowed(ext: string): boolean {\n const extLower = ext.toLowerCase()\n // 如果扩展名在排除列表中,直接返回 false\n if (this.excludedExtensionsSet.has(extLower)) {\n return false\n }\n // 检查是否在允许列表中\n return this.allowedExtensionsSet.has(extLower)\n }\n\n /**\n * 检查目录是否应该被排除(用于 fdir 的 exclude 方法)\n * 这是性能优化的关键:在进入目录之前就排除,避免扫描目录内的文件\n */\n shouldExcludeDirectory(dirName: string): boolean {\n // 优先排除隐藏目录(以 . 开头),这是最常见的排除情况\n // 这样可以避免进入 .git、.vscode、.idea 等隐藏目录\n if (this.rules.files.excludeHidden && dirName.startsWith('.')) {\n return true\n }\n // 检查目录名是否在排除列表中(非隐藏目录)\n return this.excludedDirNamesSet.has(dirName.toLowerCase())\n }\n\n /**\n * 检查文件是否应该被排除\n * 注意:隐藏目录已经在 shouldExcludeDirectory 中被排除,不会进入这里\n * 这里主要处理隐藏文件和路径模式匹配\n */\n shouldExcludeFile(filePath: string, fileName: string): boolean {\n // 优先排除隐藏文件(以 . 开头),这是最快的检查\n // 例如:.env、.gitignore、.DS_Store 等\n if (this.rules.files.excludeHidden && fileName.startsWith('.')) {\n return true\n }\n\n // 检查路径中是否包含排除模式\n const normalizedPath = filePath.replace(/[/\\\\]/g, path.sep)\n const pathLower = normalizedPath.toLowerCase()\n\n // 检查路径包含模式(例如:路径中包含 .imageset 的资源文件)\n for (const pattern of this.rules.files.excludedPathContains) {\n if (pathLower.includes(pattern.toLowerCase())) {\n return true\n }\n }\n\n // 检查是否在用户配置的排除路径中\n const parts = normalizedPath.split(path.sep).filter(p => p.length > 0)\n for (const excludedPath of this.rules.directories.excludedPaths) {\n const normalizedExclude = excludedPath.replace(/[/\\\\]/g, path.sep)\n if (parts.includes(normalizedExclude) || \n parts.some(p => p === path.basename(normalizedExclude))) {\n return true\n }\n }\n\n // Windows 平台特殊处理\n if (os.platform() === 'win32') {\n return this.shouldExcludeWindowsPath(normalizedPath)\n }\n\n return false\n }\n\n /**\n * Windows 平台路径排除检查\n */\n private shouldExcludeWindowsPath(normalizedPath: string): boolean {\n const winRules = this.rules.paths.windows\n\n // 检查系统目录\n const winRootMatch = normalizedPath.match(/^([A-Z]:)\\\\([^\\\\]+)/)\n if (winRootMatch) {\n const drive = winRootMatch[1]\n const firstDir = winRootMatch[2]\n\n // 检查是否在系统目录列表中\n if (this.rules.directories.windowsSystemDirs.includes(firstDir)) {\n return true\n }\n\n // 如果只扫描用户目录,排除 C 盘根目录下的非用户目录\n if (winRules.onlyUserDirs && drive === 'C:' && firstDir !== 'Users') {\n return true\n }\n }\n\n return false\n }\n\n /**\n * 获取所有排除的目录名(用于 fdir exclude)\n */\n getExcludedDirectoryNames(): Set<string> {\n return new Set([\n ...this.rules.directories.excludedNames,\n ...this.rules.directories.buildDirs,\n ...this.rules.directories.resourceDirs,\n ])\n }\n\n /**\n * 更新规则(允许运行时修改)\n */\n updateRules(newRules: Partial<ScanRules>): void {\n this.rules = {\n ...this.rules,\n ...newRules,\n extensions: { ...this.rules.extensions, ...newRules.extensions },\n directories: { ...this.rules.directories, ...newRules.directories },\n files: { ...this.rules.files, ...newRules.files },\n paths: { ...this.rules.paths, ...newRules.paths },\n }\n // 重新构建 Set\n this.excludedDirNamesSet = new Set(\n [\n ...this.rules.directories.excludedNames,\n ...this.rules.directories.buildDirs,\n ...this.rules.directories.resourceDirs,\n ].map(name => name.toLowerCase())\n )\n this.allowedExtensionsSet = new Set(\n this.rules.extensions.allowed.map(ext => ext.toLowerCase())\n )\n this.excludedExtensionsSet = new Set(\n this.rules.extensions.excluded.map(ext => ext.toLowerCase())\n )\n }\n\n /**\n * 获取当前规则配置\n */\n getRules(): Readonly<ScanRules> {\n return this.rules\n }\n}\n\n/**\n * 创建规则管理器实例\n */\nexport function createRulesManager(\n customRules?: Partial<ScanRules>\n): ScanRulesManager {\n const rules: ScanRules = customRules\n ? {\n extensions: { ...DEFAULT_EXTENSION_RULES, ...customRules.extensions },\n directories: { ...DEFAULT_DIRECTORY_RULES, ...customRules.directories },\n files: { ...DEFAULT_FILE_RULES, ...customRules.files },\n paths: { ...DEFAULT_PATH_RULES, ...customRules.paths },\n }\n : DEFAULT_SCAN_RULES\n\n return new ScanRulesManager(rules)\n}\n\n","/**\n * 工具函数\n */\nimport * as crypto from 'crypto'\nimport * as fs from 'fs/promises'\nimport * as path from 'path'\nimport { FileType } from '../types'\n\n/**\n * 生成文件快速哈希(基于元数据,不读取文件内容)\n * 使用 文件路径 + 大小 + 修改时间 计算,比内容 hash 快 100x+\n */\nexport async function hashFile(filePath: string): Promise<string> {\n const stats = await fs.stat(filePath)\n const hashInput = `${filePath}:${stats.size}:${stats.mtime.getTime()}`\n return crypto.createHash('md5').update(hashInput).digest('hex').slice(0, 16)\n}\n\n/**\n * 生成文件内容哈希(精确但慢,需要读取整个文件)\n * @deprecated 使用 hashFile 代替,除非需要精确检测内容变化\n */\nexport async function hashFileContent(filePath: string): Promise<string> {\n const content = await fs.readFile(filePath)\n return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16)\n}\n\n/**\n * 生成唯一 ID\n */\nexport function generateId(): string {\n return crypto.randomUUID()\n}\n\n/**\n * 格式化文件大小\n */\nexport function formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n}\n\n/**\n * 格式化日期\n */\nexport function formatDate(date: Date): string {\n return date.toISOString().split('T')[0]\n}\n\n/**\n * 根据扩展名获取文件类型\n */\nexport function getFileType(filePath: string): FileType {\n const ext = path.extname(filePath).toLowerCase()\n\n if (['.docx', '.doc', '.rtf'].includes(ext)) return FileType.DOCUMENT\n if (ext === '.pdf') return FileType.PDF\n if (['.xlsx', '.xls'].includes(ext)) return FileType.DOCUMENT\n if (['.pptx', '.ppt'].includes(ext)) return FileType.DOCUMENT\n if (['.txt', '.md'].includes(ext)) return FileType.TEXT\n\n return FileType.FILE\n}\n\n/**\n * 提取文本摘要(用于搜索结果预览)\n * 优先显示匹配查询词附近的内容\n */\nexport function extractSnippet(\n content: string,\n query: string,\n maxLength: number = 200\n): string {\n // 清理内容中的多余空白\n const cleanContent = content.replace(/\\s+/g, ' ').trim()\n \n // 尝试找到查询词(分词后的任意一个)\n const queryTokens = query.toLowerCase().split(/\\s+/).filter(t => t.length > 1)\n const lowerContent = cleanContent.toLowerCase()\n \n let bestIndex = -1\n for (const token of queryTokens) {\n const idx = lowerContent.indexOf(token)\n if (idx !== -1 && (bestIndex === -1 || idx < bestIndex)) {\n bestIndex = idx\n }\n }\n\n if (bestIndex === -1) {\n // 没找到匹配词,返回开头\n const snippet = cleanContent.slice(0, maxLength)\n return snippet + (cleanContent.length > maxLength ? '...' : '')\n }\n\n // 找到匹配词,返回其周围的内容\n const contextBefore = Math.floor(maxLength * 0.3)\n const contextAfter = Math.floor(maxLength * 0.7)\n \n const start = Math.max(0, bestIndex - contextBefore)\n const end = Math.min(cleanContent.length, bestIndex + contextAfter)\n\n let snippet = cleanContent.slice(start, end)\n\n if (start > 0) snippet = '...' + snippet\n if (end < cleanContent.length) snippet = snippet + '...'\n\n return snippet\n}\n\n/**\n * RRF 融合算法\n * 将多个排序列表融合为一个,返回标准化分数\n */\nexport function reciprocalRankFusion<T extends { id: string }>(\n lists: Array<{ results: T[]; weight?: number }>,\n k: number = 60\n): Array<{ id: string; score: number }> {\n const scores = new Map<string, number>()\n\n for (const { results, weight = 1 } of lists) {\n for (let rank = 0; rank < results.length; rank++) {\n const id = results[rank].id\n const rrf = weight / (k + rank + 1)\n scores.set(id, (scores.get(id) || 0) + rrf)\n }\n }\n\n // 标准化分数到 0-1 范围\n const sortedScores = Array.from(scores.entries()).sort((a, b) => b[1] - a[1])\n \n if (sortedScores.length === 0) {\n return []\n }\n\n const maxScore = sortedScores[0][1]\n \n return sortedScores.map(([id, score]) => ({\n id,\n score: maxScore > 0 ? score / maxScore : 0,\n }))\n}\n\n/**\n * 延迟函数\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n","/**\n * 全局进度监听器\n * \n * 所有 DocumentSearch.indexDirectory 调用都会触发这些监听器\n * 用于 Electron 桥接等场景,无需包装方法,解决时序问题\n * \n * 使用 globalThis 确保在 monorepo 多包场景下只有一个实例\n */\n\nimport type { IndexProgress } from '../types'\n\n/** 全局进度监听器类型 */\nexport type GlobalProgressListener = (progress: IndexProgress) => void\n\n// 使用 Symbol 作为 key,避免命名冲突\nconst LISTENERS_KEY = Symbol.for('ai-search-progress-listeners')\n\n/** 获取全局进度监听器列表(确保单例) */\nfunction getGlobalProgressListeners(): Set<GlobalProgressListener> {\n const g = globalThis as typeof globalThis & { [LISTENERS_KEY]?: Set<GlobalProgressListener> }\n if (!g[LISTENERS_KEY]) {\n g[LISTENERS_KEY] = new Set<GlobalProgressListener>()\n }\n return g[LISTENERS_KEY]\n}\n\n/** 添加全局进度监听器 */\nexport function addGlobalProgressListener(listener: GlobalProgressListener): void {\n getGlobalProgressListeners().add(listener)\n}\n\n/** 移除全局进度监听器 */\nexport function removeGlobalProgressListener(listener: GlobalProgressListener): void {\n getGlobalProgressListeners().delete(listener)\n}\n\n/** 通知所有全局进度监听器 */\nexport function notifyGlobalProgress(progress: IndexProgress): void {\n const listeners = getGlobalProgressListeners()\n \n listeners.forEach((listener) => {\n try {\n listener(progress)\n } catch (error) {\n console.error('[DocumentSearch] 全局进度监听器错误:', error)\n }\n })\n}\n\n","/**\n * 目录监听功能\n */\nimport * as path from 'path'\nimport chokidar from 'chokidar'\nimport type { SearchConfig, WatchOptions, WatchEvent } from '../types'\nimport { DEFAULT_CONFIG } from '../types'\nimport type { FullTextIndex } from '../storage'\n\n/** 监听管理器需要的依赖 */\nexport interface WatchDependencies {\n config: SearchConfig\n fullTextIndex: FullTextIndex\n indexFile: (filePath: string) => Promise<void>\n removeFile: (filePath: string) => Promise<void>\n}\n\n/** 目录监听管理器 */\nexport class DirectoryWatcher {\n private watchers: Map<string, chokidar.FSWatcher> = new Map()\n private debounceTimers: Map<string, NodeJS.Timeout> = new Map()\n\n constructor(private deps: WatchDependencies) {}\n\n /** 检查文件扩展名是否支持 */\n private isSupportedFile(filePath: string): boolean {\n const ext = path.extname(filePath).toLowerCase()\n const extensions = this.deps.config.extensions || DEFAULT_CONFIG.extensions || []\n return extensions.includes(ext)\n }\n\n /** 检查是否应该排除 */\n private shouldExclude(filePath: string): boolean {\n const excludeDirs = this.deps.config.excludeDirs || DEFAULT_CONFIG.excludeDirs || []\n return excludeDirs.some((excludeDir) => filePath.includes(excludeDir))\n }\n\n /**\n * 监听目录变化并自动更新索引\n */\n watch(directory: string, options?: WatchOptions): void {\n const {\n ignoreInitial = true,\n debounce = 1000,\n onEvent,\n } = options || {}\n\n // 如果已经在监听,先关闭旧的监听器\n if (this.watchers.has(directory)) {\n this.unwatch(directory)\n }\n\n // 防抖处理函数\n const debouncedHandler = (eventType: 'add' | 'change' | 'unlink', filePath: string) => {\n const timerKey = `${eventType}:${filePath}`\n const existingTimer = this.debounceTimers.get(timerKey)\n if (existingTimer) {\n clearTimeout(existingTimer)\n }\n\n const timer = setTimeout(async () => {\n this.debounceTimers.delete(timerKey)\n\n const event: WatchEvent = {\n type: eventType,\n path: filePath,\n timestamp: new Date(),\n }\n\n onEvent?.(event)\n\n try {\n if (eventType === 'add' || eventType === 'change') {\n if (this.isSupportedFile(filePath) && !this.shouldExclude(filePath)) {\n await this.deps.indexFile(filePath)\n await this.deps.fullTextIndex.save()\n }\n } else if (eventType === 'unlink') {\n await this.deps.removeFile(filePath)\n }\n } catch (error) {\n console.warn(`文件监听处理失败: ${filePath}`, error)\n }\n }, debounce)\n\n this.debounceTimers.set(timerKey, timer)\n }\n\n // 创建监听器\n const watcher = chokidar.watch(directory, {\n ignored: (filePath) => {\n if (this.shouldExclude(filePath)) {\n return true\n }\n if (path.extname(filePath)) {\n return !this.isSupportedFile(filePath)\n }\n return false\n },\n ignoreInitial,\n persistent: true,\n awaitWriteFinish: {\n stabilityThreshold: 500,\n pollInterval: 100,\n },\n })\n\n watcher.on('add', (filePath) => debouncedHandler('add', filePath))\n watcher.on('change', (filePath) => debouncedHandler('change', filePath))\n watcher.on('unlink', (filePath) => debouncedHandler('unlink', filePath))\n watcher.on('unlinkDir', (dirPath) => {\n const event: WatchEvent = {\n type: 'unlinkDir',\n path: dirPath,\n timestamp: new Date(),\n }\n onEvent?.(event)\n })\n\n this.watchers.set(directory, watcher)\n console.log(`📁 开始监听目录: ${directory}`)\n }\n\n /**\n * 停止监听目录\n */\n unwatch(directory: string): void {\n const watcher = this.watchers.get(directory)\n if (watcher) {\n watcher.close()\n this.watchers.delete(directory)\n console.log(`📁 停止监听目录: ${directory}`)\n }\n\n // 清理相关的防抖定时器\n for (const [key, timer] of this.debounceTimers.entries()) {\n if (key.includes(directory)) {\n clearTimeout(timer)\n this.debounceTimers.delete(key)\n }\n }\n }\n\n /**\n * 停止所有监听\n */\n unwatchAll(): void {\n for (const directory of this.watchers.keys()) {\n this.unwatch(directory)\n }\n }\n\n /**\n * 获取正在监听的目录列表\n */\n getWatchedDirectories(): string[] {\n return Array.from(this.watchers.keys())\n }\n\n /**\n * 清理所有定时器\n */\n clearTimers(): void {\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer)\n }\n this.debounceTimers.clear()\n }\n}\n\n","/**\n * 索引维护功能(清理、优化、健康检查等)\n */\nimport * as fs from 'fs/promises'\nimport type { HealthCheckResult, IndexError, BatchOperationResult, IndexedDocument } from '../types'\nimport type { MetaStore, VectorStore, FullTextIndex } from '../storage'\nimport { getEmbeddingDimension } from '../embeddings'\nimport { hashFile } from './utils'\n\n/** 维护管理器需要的依赖 */\nexport interface MaintenanceDependencies {\n metaStore: MetaStore\n vectorStore: VectorStore\n fullTextIndex: FullTextIndex\n indexErrors: Map<string, IndexError>\n indexFile: (filePath: string) => Promise<void>\n removeFile: (filePath: string) => Promise<void>\n indexFiles: (filePaths: string[]) => Promise<BatchOperationResult>\n}\n\n/**\n * 清理无效索引(文件已删除但索引还在)\n */\nexport async function cleanup(deps: MaintenanceDependencies): Promise<{ removed: number; updated: number }> {\n const allDocs = deps.metaStore.getAll() as IndexedDocument[]\n let removed = 0\n let updated = 0\n\n for (const doc of allDocs) {\n try {\n await fs.access(doc.path)\n // 文件存在,检查是否需要更新\n const stat = await fs.stat(doc.path)\n const contentHash = await hashFile(doc.path)\n if (doc.contentHash !== contentHash || doc.modifiedAt.getTime() !== stat.mtime.getTime()) {\n await deps.indexFile(doc.path)\n updated++\n }\n } catch {\n // 文件不存在,删除索引\n await deps.removeFile(doc.path)\n removed++\n }\n }\n\n await deps.fullTextIndex.save()\n return { removed, updated }\n}\n\n/**\n * 优化索引(压缩、碎片整理)\n */\nexport async function optimize(deps: MaintenanceDependencies): Promise<void> {\n // 清理无效索引\n await cleanup(deps)\n\n // 保存全文索引(会触发压缩)\n await deps.fullTextIndex.save()\n\n console.log('索引优化完成')\n}\n\n/**\n * 健康检查\n */\nexport async function healthCheck(deps: MaintenanceDependencies): Promise<HealthCheckResult> {\n const allDocs = deps.metaStore.getAll() as IndexedDocument[]\n let invalidIndexes = 0\n let staleIndexes = 0\n\n // 检查索引完整性\n const integrity = {\n meta: true,\n vectors: true,\n fulltext: true,\n }\n\n try {\n const stats = deps.metaStore.getStats()\n integrity.meta = stats.totalDocuments >= 0\n } catch {\n integrity.meta = false\n }\n\n try {\n await deps.vectorStore.search(new Array(getEmbeddingDimension()).fill(0), 1)\n integrity.vectors = true\n } catch {\n integrity.vectors = false\n }\n\n try {\n deps.fullTextIndex.search('test', 1)\n integrity.fulltext = true\n } catch {\n integrity.fulltext = false\n }\n\n // 采样检查无效和过期索引\n const sampleSize = Math.min(100, allDocs.length)\n const sampleDocs = allDocs.slice(0, sampleSize)\n\n for (const doc of sampleDocs) {\n try {\n await fs.access(doc.path)\n const stat = await fs.stat(doc.path)\n const contentHash = await hashFile(doc.path)\n if (doc.contentHash !== contentHash || doc.modifiedAt.getTime() !== stat.mtime.getTime()) {\n staleIndexes++\n }\n } catch {\n invalidIndexes++\n }\n }\n\n // 按比例估算\n if (allDocs.length > sampleSize) {\n const ratio = invalidIndexes / sampleSize\n invalidIndexes = Math.floor(ratio * allDocs.length)\n const staleRatio = staleIndexes / sampleSize\n staleIndexes = Math.floor(staleRatio * allDocs.length)\n }\n\n const healthy = \n integrity.meta && \n integrity.vectors && \n integrity.fulltext && \n invalidIndexes === 0 && \n staleIndexes === 0 &&\n deps.indexErrors.size === 0\n\n return {\n healthy,\n totalDocuments: allDocs.length,\n invalidIndexes,\n staleIndexes,\n errorCount: deps.indexErrors.size,\n integrity,\n }\n}\n\n/**\n * 获取索引错误列表\n */\nexport function getIndexErrors(deps: MaintenanceDependencies): IndexError[] {\n return Array.from(deps.indexErrors.values())\n}\n\n/**\n * 重试失败的索引\n */\nexport async function retryFailedIndexes(deps: MaintenanceDependencies): Promise<BatchOperationResult> {\n const errors = getIndexErrors(deps)\n const filePaths = errors.map((e) => e.filePath)\n\n // 清除错误记录\n deps.indexErrors.clear()\n\n // 重新索引\n return deps.indexFiles(filePaths)\n}\n\n","/**\n * 索引备份和导入导出功能\n */\nimport * as fs from 'fs/promises'\nimport * as path from 'path'\nimport type { ExportInfo, BackupInfo, IndexStats } from '../types'\nimport type { MetaStore, VectorStore, FullTextIndex } from '../storage'\nimport { getEmbeddingDimension } from '../embeddings'\n\n/** 备份管理器需要的依赖 */\nexport interface BackupDependencies {\n config: { dataDir: string }\n metaStore: MetaStore\n vectorStore: VectorStore\n fullTextIndex: FullTextIndex\n getStats: () => IndexStats\n reinitializeStores: (metaStore: MetaStore, vectorStore: VectorStore, fullTextIndex: FullTextIndex) => void\n}\n\n/** 复制目录(递归) */\nasync function copyDirectory(source: string, target: string): Promise<void> {\n await fs.mkdir(target, { recursive: true })\n const entries = await fs.readdir(source, { withFileTypes: true })\n\n for (const entry of entries) {\n const sourcePath = path.join(source, entry.name)\n const targetPath = path.join(target, entry.name)\n\n if (entry.isDirectory()) {\n await copyDirectory(sourcePath, targetPath)\n } else {\n await fs.copyFile(sourcePath, targetPath)\n }\n }\n}\n\n/** 计算目录大小 */\nasync function calculateDirSize(dir: string): Promise<number> {\n const entries = await fs.readdir(dir, { withFileTypes: true })\n let size = 0\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n size += await calculateDirSize(fullPath)\n } else {\n const stat = await fs.stat(fullPath)\n size += stat.size\n }\n }\n return size\n}\n\n/**\n * 导出索引数据\n */\nexport async function exportIndex(\n deps: BackupDependencies,\n outputPath: string\n): Promise<ExportInfo> {\n const outputDir = path.dirname(outputPath)\n await fs.mkdir(outputDir, { recursive: true })\n\n const exportDir = path.join(outputDir, path.basename(outputPath, path.extname(outputPath)))\n await fs.mkdir(exportDir, { recursive: true })\n\n const components = {\n meta: false,\n vectors: false,\n fulltext: false,\n }\n\n // 导出元数据(SQLite 数据库)\n try {\n const metaDbPath = path.join(deps.config.dataDir, 'meta.db')\n const targetMetaPath = path.join(exportDir, 'meta.db')\n await fs.copyFile(metaDbPath, targetMetaPath)\n components.meta = true\n } catch (error) {\n console.warn('导出元数据失败:', error)\n }\n\n // 导出向量数据(LanceDB 目录)\n try {\n const vectorsDir = path.join(deps.config.dataDir, 'vectors')\n const targetVectorsDir = path.join(exportDir, 'vectors')\n await copyDirectory(vectorsDir, targetVectorsDir)\n components.vectors = true\n } catch (error) {\n console.warn('导出向量数据失败:', error)\n }\n\n // 导出全文索引(FlexSearch JSON)\n try {\n const fulltextPath = path.join(deps.config.dataDir, 'fulltext.json')\n const targetFulltextPath = path.join(exportDir, 'fulltext.json')\n try {\n await fs.access(fulltextPath)\n await fs.copyFile(fulltextPath, targetFulltextPath)\n components.fulltext = true\n } catch {\n // 文件不存在,跳过\n }\n } catch (error) {\n console.warn('导出全文索引失败:', error)\n }\n\n // 保存导出信息\n const stats = deps.getStats()\n const exportInfo: ExportInfo = {\n exportPath: exportDir,\n timestamp: new Date(),\n components,\n stats,\n }\n\n const infoPath = path.join(exportDir, 'export-info.json')\n await fs.writeFile(infoPath, JSON.stringify(exportInfo, null, 2), 'utf-8')\n\n return exportInfo\n}\n\n/**\n * 导入索引数据\n */\nexport async function importIndex(\n deps: BackupDependencies,\n inputPath: string,\n createStores: {\n MetaStore: new (dataDir: string) => MetaStore\n VectorStore: new (dir: string, tableName: string, dimension: number) => VectorStore\n FullTextIndex: new (dataDir: string) => FullTextIndex\n }\n): Promise<void> {\n // 检查是否是目录(导出目录)还是文件(压缩包)\n const stat = await fs.stat(inputPath)\n let importDir: string\n\n if (stat.isDirectory()) {\n importDir = inputPath\n } else {\n throw new Error('目前只支持从目录导入,请先解压备份文件')\n }\n\n // 读取导出信息\n const infoPath = path.join(importDir, 'export-info.json')\n try {\n await fs.readFile(infoPath, 'utf-8')\n } catch {\n // 没有导出信息,继续导入\n }\n\n let newMetaStore = deps.metaStore\n let newVectorStore = deps.vectorStore\n let newFullTextIndex = deps.fullTextIndex\n\n // 导入元数据\n const sourceMetaPath = path.join(importDir, 'meta.db')\n const targetMetaPath = path.join(deps.config.dataDir, 'meta.db')\n try {\n await fs.access(sourceMetaPath)\n await fs.copyFile(sourceMetaPath, targetMetaPath)\n newMetaStore = new createStores.MetaStore(deps.config.dataDir)\n } catch (error) {\n console.warn('导入元数据失败:', error)\n }\n\n // 导入向量数据\n const sourceVectorsDir = path.join(importDir, 'vectors')\n const targetVectorsDir = path.join(deps.config.dataDir, 'vectors')\n try {\n await fs.access(sourceVectorsDir)\n await copyDirectory(sourceVectorsDir, targetVectorsDir)\n newVectorStore = new createStores.VectorStore(\n targetVectorsDir,\n 'documents',\n getEmbeddingDimension()\n )\n await newVectorStore.init()\n } catch (error) {\n console.warn('导入向量数据失败:', error)\n }\n\n // 导入全文索引\n const sourceFulltextPath = path.join(importDir, 'fulltext.json')\n const targetFulltextPath = path.join(deps.config.dataDir, 'fulltext.json')\n try {\n await fs.access(sourceFulltextPath)\n await fs.copyFile(sourceFulltextPath, targetFulltextPath)\n newFullTextIndex = new createStores.FullTextIndex(deps.config.dataDir)\n await newFullTextIndex.init()\n } catch (error) {\n console.warn('导入全文索引失败:', error)\n }\n\n // 通知调用者更新 store 引用\n deps.reinitializeStores(newMetaStore, newVectorStore, newFullTextIndex)\n\n console.log('索引导入完成')\n}\n\n/**\n * 列出备份(从导出目录)\n */\nexport async function listBackups(backupDir: string): Promise<BackupInfo[]> {\n try {\n const entries = await fs.readdir(backupDir, { withFileTypes: true })\n const backups: BackupInfo[] = []\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n\n const backupPath = path.join(backupDir, entry.name)\n const infoPath = path.join(backupPath, 'export-info.json')\n\n try {\n const infoContent = await fs.readFile(infoPath, 'utf-8')\n const exportInfo = JSON.parse(infoContent) as ExportInfo\n const totalSize = await calculateDirSize(backupPath)\n\n backups.push({\n path: backupPath,\n timestamp: new Date(exportInfo.timestamp),\n size: totalSize,\n })\n } catch {\n // 跳过无效的备份目录\n }\n }\n\n // 按时间倒序排序\n backups.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n\n return backups\n } catch {\n return []\n }\n}\n\n","/**\n * 索引流水线 - 支持文本分块\n * \n * 设计原则:\n * 1. 一个文件 → 多个 chunk → 多个向量\n * 2. 并发执行文件处理,每完成一个文件进度 +1\n * 3. 分块后每个 chunk 单独向量化,提高检索精度\n */\n\nimport * as path from 'node:path'\nimport * as fs from 'node:fs/promises'\nimport { parseDocument } from '../parsers/index.js'\nimport { embedDocument } from '../embeddings/index.js'\nimport { IndexProgress } from '../types.js'\nimport { createHash } from 'node:crypto'\nimport { splitText, type TextChunk, type ChunkOptions } from './chunker.js'\n\n/** 计算文件哈希 */\nasync function hashFile(filePath: string): Promise<string> {\n const content = await fs.readFile(filePath)\n return createHash('md5').update(content).digest('hex')\n}\n\n/** 流水线配置 */\nexport interface PipelineConfig {\n /** 并发数(同时处理的文件数) */\n concurrency: number\n /** 分块配置(小于 chunkSize 的文档自然只有 1 个 chunk) */\n chunk?: ChunkOptions\n}\n\n/** 默认配置 */\nconst DEFAULT_CONFIG: PipelineConfig = {\n concurrency: 50, // 高并发\n chunk: {\n chunkSize: 8000, // 8k 字符/块 ≈ 多个段落,小文档自然 1 个 chunk\n overlap: 500, // 500 字符重叠\n minChunkSize: 500, // 最小块 500 字符\n },\n}\n\n/** 嵌入后的文档块 */\nexport interface EmbeddedChunk {\n /** 文件路径 */\n filePath: string\n /** 块内容 */\n content: string\n /** 文档标题 */\n title: string\n /** 文件内容哈希(用于判断文件是否变化) */\n contentHash: string\n /** 块索引(从 0 开始) */\n chunkIndex: number\n /** 块总数 */\n totalChunks: number\n /** 向量 */\n vector: number[]\n}\n\n/** 兼容旧接口 */\nexport interface EmbeddedDoc {\n filePath: string\n content: string\n title: string\n contentHash: string\n vector: number[]\n /** 新增:块信息 */\n chunkIndex?: number\n totalChunks?: number\n}\n\n/** 流水线统计 */\nexport interface PipelineStats {\n totalFiles: number\n completed: number\n stored: number\n skipped: number\n failed: number\n totalTime: number\n}\n\n/** 存储回调 */\nexport type StoreCallback = (doc: EmbeddedDoc) => Promise<{ stored: boolean; skipped: boolean }>\n\n/** 跳过检查回调(基于 mtime) */\nexport type SkipCheckCallback = (filePath: string, mtime: Date) => boolean\n\n/**\n * 简化版索引流水线\n */\nexport class IndexingPipeline {\n private config: PipelineConfig\n private onProgress?: (progress: IndexProgress) => void\n private storeCallback?: StoreCallback\n private skipCheck?: SkipCheckCallback\n private cancelled = false // 取消标志\n \n private stats: PipelineStats = {\n totalFiles: 0,\n completed: 0,\n stored: 0,\n skipped: 0,\n failed: 0,\n totalTime: 0,\n }\n\n constructor(config?: Partial<PipelineConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config }\n }\n\n /** 取消正在执行的任务 */\n cancel(): void {\n this.cancelled = true\n console.log(' ⚠️ 索引任务已取消')\n }\n\n /** 检查是否已取消 */\n isCancelled(): boolean {\n return this.cancelled\n }\n\n /**\n * 处理单个文件(完整流程:解析 → 分块 → 嵌入 → 存储)\n */\n private async processFile(filePath: string): Promise<void> {\n try {\n // 1. 检查是否需要跳过(mtime 未变)\n if (this.skipCheck) {\n const stat = await fs.stat(filePath)\n if (this.skipCheck(filePath, stat.mtime)) {\n this.stats.skipped++\n return\n }\n }\n\n // 2. 解析文档\n const parsed = await parseDocument(filePath)\n if (!parsed.content.trim()) {\n this.stats.skipped++\n return\n }\n\n // 检查取消(解析后)\n if (this.cancelled) return\n\n // 3. 计算哈希\n const contentHash = await hashFile(filePath)\n\n // 检查取消(哈希后,分块前)\n if (this.cancelled) return\n\n // 4. 分块处理(小于 chunkSize 的文档自然只有 1 个 chunk)\n const chunks = splitText(parsed.content, this.config.chunk)\n const totalChunks = chunks.length\n\n // 5. 并发生成所有 chunk 的向量\n const embedPromises = chunks.map(chunk => embedDocument(chunk.content))\n const vectors = await Promise.all(embedPromises)\n\n if (this.cancelled) return\n\n // 6. 并发存储所有 chunk\n const storePromises = chunks.map(async (chunk, i) => {\n if (this.cancelled) return false\n\n const doc: EmbeddedDoc = {\n filePath,\n content: chunk.content,\n title: parsed.title || path.basename(filePath),\n contentHash,\n vector: vectors[i],\n chunkIndex: i,\n totalChunks,\n }\n \n const result = await this.storeCallback!(doc)\n return result.stored\n })\n \n const results = await Promise.all(storePromises)\n const storedCount = results.filter(Boolean).length\n\n // 统计:只要有一个 chunk 存储成功就算 stored\n if (storedCount > 0) {\n this.stats.stored++\n } else {\n this.stats.skipped++\n }\n } catch (error) {\n this.stats.failed++\n console.error(` ❌ 处理失败 [${filePath}]:`, error instanceof Error ? error.message : error)\n }\n }\n\n /**\n * 执行流水线\n */\n async run(\n files: string[],\n storeCallback: StoreCallback,\n onProgress?: (progress: IndexProgress) => void,\n skipCheck?: SkipCheckCallback\n ): Promise<PipelineStats> {\n const startTime = Date.now()\n \n // 重置状态\n this.cancelled = false\n this.stats = {\n totalFiles: files.length,\n completed: 0,\n stored: 0,\n skipped: 0,\n failed: 0,\n totalTime: 0,\n }\n this.onProgress = onProgress\n this.storeCallback = storeCallback\n this.skipCheck = skipCheck\n\n console.log(` 🚀 开始处理 ${files.length} 个文件 (并发: ${this.config.concurrency})`)\n\n // 并发执行\n const executing: Promise<void>[] = []\n let index = 0\n\n while ((index < files.length || executing.length > 0) && !this.cancelled) {\n // 填充执行队列到 concurrency 个\n while (index < files.length && executing.length < this.config.concurrency && !this.cancelled) {\n const filePath = files[index++]\n const fileIndex = index // 捕获当前索引\n \n const promise = this.processFile(filePath).then(() => {\n if (this.cancelled) return\n \n this.stats.completed++\n this.reportProgress(filePath)\n \n // 终端显示进度(显示并发数)\n console.log(` 📄 [${this.stats.completed}/${this.stats.totalFiles}] (并发:${executing.length}) ${filePath}`)\n }).finally(() => {\n const idx = executing.indexOf(promise)\n if (idx > -1) executing.splice(idx, 1)\n })\n executing.push(promise)\n }\n\n // 等待任一完成\n if (executing.length > 0) {\n await Promise.race(executing)\n }\n }\n\n // 等待正在执行的任务完成\n if (executing.length > 0) {\n await Promise.all(executing)\n }\n\n this.stats.totalTime = Date.now() - startTime\n\n if (this.cancelled) {\n console.log(`\\n ⚠️ 索引已取消:`)\n console.log(` - 已处理: ${this.stats.completed}/${this.stats.totalFiles}`)\n } else {\n console.log(`\\n ✅ 处理完成:`)\n }\n console.log(` - 总耗时: ${(this.stats.totalTime / 1000).toFixed(1)}s`)\n console.log(` - 新增/更新: ${this.stats.stored} 个`)\n console.log(` - 跳过: ${this.stats.skipped} 个`)\n console.log(` - 失败: ${this.stats.failed} 个`)\n\n return this.stats\n }\n\n /** 报告进度 */\n private reportProgress(currentFile?: string): void {\n const { completed, totalFiles } = this.stats\n \n this.onProgress?.({\n indexed: completed,\n total: totalFiles,\n currentFile,\n stage: completed === totalFiles ? 'done' : 'storing',\n })\n }\n}\n\n/**\n * 创建索引流水线\n */\nexport function createIndexingPipeline(config?: Partial<PipelineConfig>): IndexingPipeline {\n return new IndexingPipeline(config)\n}\n","/**\n * 文本分块模块\n * 递归语义分割,保持上下文连贯性\n */\n\nexport interface ChunkOptions {\n /** 目标块大小(字符数) */\n chunkSize?: number\n /** 重叠大小(字符数) */\n overlap?: number\n /** 最小块大小(小于此值会合并到上一块) */\n minChunkSize?: number\n}\n\nexport interface TextChunk {\n /** 块内容 */\n content: string\n /** 块索引(从 0 开始) */\n index: number\n /** 在原文中的起始位置 */\n startOffset: number\n /** 在原文中的结束位置 */\n endOffset: number\n}\n\nconst DEFAULT_OPTIONS: Required<ChunkOptions> = {\n chunkSize: 800, // 目标块大小\n overlap: 100, // 重叠大小\n minChunkSize: 100, // 最小块大小\n}\n\n/** 分隔符优先级(从高到低) */\nconst SEPARATORS = [\n '\\n\\n\\n', // 多空行(章节分隔)\n '\\n\\n', // 段落分隔\n '\\n', // 换行\n '。', // 中文句号\n '!', // 中文感叹号\n '?', // 中文问号\n ';', // 中文分号\n '. ', // 英文句号\n '! ', // 英文感叹号\n '? ', // 英文问号\n '; ', // 英文分号\n ',', // 中文逗号\n ', ', // 英文逗号\n ' ', // 空格\n]\n\n/**\n * 递归文本分块\n * \n * 算法:\n * 1. 尝试用当前分隔符切分文本\n * 2. 如果块太大,用下一个分隔符继续切分\n * 3. 合并小块,添加重叠\n */\nexport function splitText(text: string, options?: ChunkOptions): TextChunk[] {\n const opts = { ...DEFAULT_OPTIONS, ...options }\n \n // 空文本直接返回\n if (!text.trim()) return []\n \n // 文本小于目标大小,直接返回\n if (text.length <= opts.chunkSize) {\n return [{\n content: text,\n index: 0,\n startOffset: 0,\n endOffset: text.length,\n }]\n }\n \n // 递归分割\n const rawChunks = recursiveSplit(text, opts.chunkSize, 0)\n \n // 合并小块并添加重叠\n return mergeAndOverlap(rawChunks, text, opts)\n}\n\n/** 递归分割(核心算法) */\nfunction recursiveSplit(\n text: string,\n targetSize: number,\n separatorIndex: number\n): string[] {\n // 文本已足够小\n if (text.length <= targetSize) {\n return [text]\n }\n \n // 尝试所有分隔符\n while (separatorIndex < SEPARATORS.length) {\n const separator = SEPARATORS[separatorIndex]\n const parts = text.split(separator)\n \n // 如果能分割成多个部分\n if (parts.length > 1) {\n const chunks: string[] = []\n let current = ''\n \n for (const part of parts) {\n const withSep = current ? current + separator + part : part\n \n if (withSep.length <= targetSize) {\n // 累积\n current = withSep\n } else if (current) {\n // 保存当前块,开始新块\n chunks.push(current)\n \n // 如果单个 part 就超过目标大小,递归处理\n if (part.length > targetSize) {\n chunks.push(...recursiveSplit(part, targetSize, separatorIndex + 1))\n current = ''\n } else {\n current = part\n }\n } else {\n // current 为空但 part 太大,递归处理\n chunks.push(...recursiveSplit(part, targetSize, separatorIndex + 1))\n }\n }\n \n // 保存最后一块\n if (current) {\n chunks.push(current)\n }\n \n return chunks\n }\n \n separatorIndex++\n }\n \n // 没有分隔符能用,强制按字符切分\n return forceChunk(text, targetSize)\n}\n\n/** 强制按字符切分(最后手段) */\nfunction forceChunk(text: string, targetSize: number): string[] {\n const chunks: string[] = []\n let start = 0\n \n while (start < text.length) {\n chunks.push(text.slice(start, start + targetSize))\n start += targetSize\n }\n \n return chunks\n}\n\n/** 合并小块并添加重叠 */\nfunction mergeAndOverlap(\n rawChunks: string[],\n originalText: string,\n opts: Required<ChunkOptions>\n): TextChunk[] {\n const result: TextChunk[] = []\n let currentOffset = 0\n \n for (let i = 0; i < rawChunks.length; i++) {\n let content = rawChunks[i].trim()\n \n // 跳过空块\n if (!content) {\n // 更新 offset(查找原文中的位置)\n const idx = originalText.indexOf(rawChunks[i], currentOffset)\n if (idx >= 0) {\n currentOffset = idx + rawChunks[i].length\n }\n continue\n }\n \n // 合并过小的块到上一块\n if (content.length < opts.minChunkSize && result.length > 0) {\n const lastChunk = result[result.length - 1]\n // 只有合并后不超过 1.5 倍目标大小才合并\n if (lastChunk.content.length + content.length < opts.chunkSize * 1.5) {\n lastChunk.content += '\\n' + content\n lastChunk.endOffset = currentOffset + rawChunks[i].length\n continue\n }\n }\n \n // 计算位置\n const startOffset = originalText.indexOf(content.slice(0, 50), Math.max(0, currentOffset - 10))\n const actualStart = startOffset >= 0 ? startOffset : currentOffset\n \n // 添加重叠(从上一块末尾取一部分)\n if (opts.overlap > 0 && result.length > 0) {\n const lastChunk = result[result.length - 1]\n const overlapText = lastChunk.content.slice(-opts.overlap)\n \n // 找到合适的重叠起点(尽量从句子开始)\n const sentenceStart = findSentenceStart(overlapText)\n if (sentenceStart > 0) {\n content = overlapText.slice(sentenceStart) + '\\n' + content\n }\n }\n \n result.push({\n content,\n index: result.length,\n startOffset: actualStart,\n endOffset: actualStart + content.length,\n })\n \n currentOffset = actualStart + rawChunks[i].length\n }\n \n return result\n}\n\n/** 找到句子开始位置 */\nfunction findSentenceStart(text: string): number {\n const sentenceEnders = ['。', '!', '?', '. ', '! ', '? ', '\\n']\n \n for (const ender of sentenceEnders) {\n const idx = text.lastIndexOf(ender)\n if (idx >= 0) {\n return idx + ender.length\n }\n }\n \n return 0\n}\n\n/**\n * 获取分块统计信息\n */\nexport function getChunkStats(chunks: TextChunk[]): {\n count: number\n avgSize: number\n minSize: number\n maxSize: number\n totalSize: number\n} {\n if (chunks.length === 0) {\n return { count: 0, avgSize: 0, minSize: 0, maxSize: 0, totalSize: 0 }\n }\n \n const sizes = chunks.map(c => c.content.length)\n const totalSize = sizes.reduce((a, b) => a + b, 0)\n \n return {\n count: chunks.length,\n avgSize: Math.round(totalSize / chunks.length),\n minSize: Math.min(...sizes),\n maxSize: Math.max(...sizes),\n totalSize,\n }\n}\n\n","/**\n * AI Agent 工具定义\n *\n * Vite 插件风格 API\n */\nimport { DocumentSearch } from '../core/search'\nimport type { SearchOptions, FileType } from '../types'\nimport type { Tool, ToolContext, ToolResult, SideEffect, ToolPlugin } from '@huyooo/ai-chat-core'\n\n// 导出类型(供外部使用)\nexport type { Tool, ToolContext, ToolResult, SideEffect, ToolPlugin }\n\n/** 工作空间状态 */\ninterface WorkspaceState {\n /** 当前工作空间目录 */\n directory: string | null\n /** 是否已索引 */\n indexed: boolean\n /** 索引文件数量 */\n filesIndexed: number\n}\n\n// ==================== 搜索插件(Vite 风格) ====================\n\n/** 搜索插件配置 */\nexport interface SearchPluginOptions {\n /** 数据存储目录 */\n dataDir: string\n /** 初始工作空间(可选,设置后自动索引) */\n workspace?: string\n /** 豆包 API Key(用于向量化,也可通过环境变量 ARK_API_KEY 设置) */\n arkApiKey?: string\n /** 向量维度(默认 1024) */\n embeddingDimension?: number\n}\n\n/** 搜索插件实例(扩展 ToolPlugin,兼容 Vite 插件风格) */\nexport interface SearchPluginInstance extends ToolPlugin {\n /** AI Agent 工具数组 */\n tools: Tool[]\n /** 设置/切换工作空间 */\n setWorkspace: (directory: string) => Promise<void>\n /** 获取工作空间状态 */\n getWorkspaceState: () => WorkspaceState\n /** 底层 DocumentSearch 实例(高级用法) */\n search: DocumentSearch\n}\n\n// 使用 Symbol.for() 确保全局唯一的插件实例\nconst SEARCH_PLUGIN_KEY = Symbol.for('ai-search-plugin-instance')\n\n/** 获取或设置全局搜索插件实例 */\nfunction getGlobalSearchPlugin(): SearchPluginInstance | null {\n const g = globalThis as typeof globalThis & { [SEARCH_PLUGIN_KEY]?: SearchPluginInstance | null }\n return g[SEARCH_PLUGIN_KEY] ?? null\n}\n\nfunction setGlobalSearchPlugin(instance: SearchPluginInstance): void {\n const g = globalThis as typeof globalThis & { [SEARCH_PLUGIN_KEY]?: SearchPluginInstance | null }\n g[SEARCH_PLUGIN_KEY] = instance\n}\n\n/**\n * 搜索插件(Vite 风格)\n * \n * @example\n * ```typescript\n * import { searchPlugin } from '@huyooo/ai-search';\n * \n * // 像 Vite 插件一样使用\n * await createElectronBridge({\n * tools: [\n * getCwdTool,\n * searchPlugin({ dataDir, workspace }), // 直接传入\n * ],\n * });\n * \n * // 运行时控制(通过 getSearchPlugin 获取实例)\n * import { getSearchPlugin } from '@huyooo/ai-search';\n * await getSearchPlugin()?.setWorkspace('/new/path');\n * ```\n */\nexport function searchPlugin(options: SearchPluginOptions): Promise<SearchPluginInstance> {\n return createSearchPluginInstance(options)\n}\n\n/** 获取搜索插件实例(用于运行时控制) */\nexport function getSearchPlugin(): SearchPluginInstance | null {\n return getGlobalSearchPlugin()\n}\n\n/** 创建搜索插件实例 */\nasync function createSearchPluginInstance(options: SearchPluginOptions): Promise<SearchPluginInstance> {\n const { dataDir, workspace, arkApiKey, embeddingDimension } = options\n\n try {\n // 1. 创建并初始化 DocumentSearch(使用豆包向量化 API)\n console.log('📂 正在初始化文档搜索引擎...', dataDir)\n const search = new DocumentSearch({ \n dataDir,\n arkApiKey,\n embeddingDimension,\n })\n await search.init()\n console.log('✅ 文档搜索引擎初始化完成:', dataDir)\n\n // 2. 工作空间状态\n const workspaceState: WorkspaceState = {\n directory: null,\n indexed: false,\n filesIndexed: 0,\n }\n\n // 3. 设置工作空间方法\n const setWorkspace = async (directory: string) => {\n workspaceState.directory = directory\n workspaceState.indexed = false\n workspaceState.filesIndexed = 0\n\n console.log(`📁 开始索引工作空间: ${directory}`)\n try {\n await search.indexDirectory(directory, (progress) => {\n // 只在索引阶段(非扫描阶段)更新文件数和输出进度\n if (progress.stage !== 'scanning' && progress.total > 0) {\n workspaceState.filesIndexed = progress.indexed\n // 每 100 个文件输出一次进度\n if (progress.indexed % 100 === 0) {\n console.log(` 📄 已索引 ${progress.indexed}/${progress.total} 个文件...`)\n }\n }\n })\n workspaceState.indexed = true\n console.log(`✅ 工作空间索引完成,共 ${workspaceState.filesIndexed} 个文件`)\n } catch (error) {\n console.error('❌ 工作空间索引失败:', error)\n throw error\n }\n }\n\n // 4. 如果配置了初始工作空间,后台异步索引(不阻塞启动)\n if (workspace) {\n console.log(`📁 检测到工作空间,将在后台开始索引: ${workspace}`)\n // 不 await,让应用先启动,索引在后台进行\n setWorkspace(workspace).catch((error) => {\n console.error('❌ 后台索引失败:', error)\n })\n }\n\n // 5. 创建工具数组\n const tools: Tool[] = [\n createSearchDocumentsTool(search, workspaceState),\n createIndexFileTool(search),\n createIndexDirectoryTool(search),\n createIndexFilesTool(search),\n createRemoveFilesTool(search),\n createUpdateFilesTool(search),\n createGetStatsTool(search, workspaceState),\n createClearIndexTool(search),\n createSetWorkspaceTool(search, workspaceState),\n createGetWorkspaceTool(workspaceState),\n createWatchDirectoryTool(search),\n createUnwatchDirectoryTool(search),\n createGetWatchedDirectoriesTool(search),\n createCleanupIndexTool(search),\n createExportIndexTool(search),\n createImportIndexTool(search),\n createListBackupsTool(search),\n createOptimizeIndexTool(search),\n createHealthCheckTool(search),\n createGetIndexErrorsTool(search),\n createRetryFailedIndexesTool(search),\n ]\n\n // 6. 创建插件实例\n const instance: SearchPluginInstance = {\n tools,\n setWorkspace,\n getWorkspaceState: () => ({ ...workspaceState }),\n search,\n }\n\n // 存储实例到全局(供 getSearchPlugin 使用,确保跨模块共享)\n setGlobalSearchPlugin(instance)\n\n return instance\n } catch (error) {\n console.error('❌ 搜索插件初始化失败:', error)\n throw error\n }\n}\n\n// ==================== 内部工具创建函数 ====================\n\n/**\n * 搜索文档工具\n */\nfunction createSearchDocumentsTool(search: DocumentSearch, workspaceState: WorkspaceState): Tool {\n return {\n name: 'search_local_documents',\n description: `搜索用户电脑上的本地文档,支持语义搜索(理解意图)和关键词搜索。\n\n⚠️ 重要:查找文档时,优先使用此工具而不是 execute_command 执行 find 命令!\n- 此工具支持语义理解,可以根据内容查找文档,不仅仅是文件名匹配\n- 已索引的文档可以直接搜索,无需遍历文件系统\n- 支持按文件类型过滤(如只搜索 PDF:file_types=\"pdf\")\n\n支持的文件类型:\n- Word 文档 (.docx, .doc)\n- PDF 文件 (.pdf)\n- Excel 表格 (.xlsx, .xls)\n- PPT 演示文稿 (.pptx, .ppt)\n- 文本文件 (.txt, .md)\n\n使用场景:\n- 用户想查找某个主题的文档,如\"找一下去年的采购合同\"、\"搜索关于糖尿病的PDF文档\"\n- 用户需要特定内容的文件,如\"关于用户隐私政策的文档\"\n- 用户记不清文件名但记得内容,如\"有个文档提到了季度销售目标\"\n- 用户想找特定类型的文件,如\"找所有PDF文件\"(使用 file_types=\"pdf\")\n\n搜索范围:\n- 如果设置了工作空间,默认只搜索工作空间内的文档\n- 可以通过 scope 参数选择搜索范围:workspace(工作空间)、all(全部已索引)\n- 使用 set_search_workspace 工具可以设置工作空间`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n query: {\n type: 'string',\n description: '搜索内容,可以是关键词或自然语言描述,如\"采购合同\"或\"关于项目预算的文档\"',\n },\n limit: {\n type: 'number',\n description: '返回结果数量,默认 10,最大 50',\n },\n mode: {\n type: 'string',\n enum: ['semantic', 'keyword', 'hybrid'],\n description:\n '搜索模式:semantic(语义,理解意图)、keyword(精确关键词)、hybrid(混合,推荐)',\n },\n file_types: {\n type: 'string',\n description:\n '限定文件类型,逗号分隔,可选:document,pdf,text。如 \"pdf,document\"',\n },\n scope: {\n type: 'string',\n enum: ['workspace', 'all'],\n description: '搜索范围:workspace(仅工作空间,默认)、all(全部已索引文档)',\n },\n },\n required: ['query'],\n },\n\n execute: async (args, _context) => {\n const query = args.query as string\n const limit = Math.min((args.limit as number) || 10, 50)\n const mode =\n (args.mode as 'semantic' | 'keyword' | 'hybrid') || 'hybrid'\n const scope = (args.scope as 'workspace' | 'all') || 'workspace'\n\n const options: SearchOptions = { limit, mode }\n\n // 解析文件类型\n if (args.file_types) {\n const types = (args.file_types as string)\n .split(',')\n .map((t) => t.trim()) as FileType[]\n options.fileTypes = types\n }\n\n let results = await search.search(query, options)\n\n // 如果设置了工作空间且 scope 为 workspace,过滤结果\n if (scope === 'workspace' && workspaceState.directory) {\n results = results.filter(r => r.id.startsWith(workspaceState.directory!))\n }\n\n if (results.length === 0) {\n const suggestion = workspaceState.directory && scope === 'workspace'\n ? '可以尝试换个关键词,或使用 scope=\"all\" 搜索全部文档,或用 set_search_workspace 设置新的工作空间'\n : '可以尝试换个关键词,或者先用 set_search_workspace 设置工作空间'\n return JSON.stringify({\n message: '没有找到匹配的文档',\n suggestion,\n workspace: workspaceState.directory,\n scope,\n })\n }\n\n // 格式化结果\n const formattedResults = results.map((r, idx) => ({\n rank: idx + 1,\n path: r.id,\n name: r.name,\n type: r.type,\n size: r.size,\n modified: r.dateModified,\n relevance: `${Math.round(r.score * 100)}%`,\n matchType: r.matchType,\n snippet: r.snippet,\n }))\n\n return JSON.stringify({\n query,\n mode,\n scope,\n workspace: workspaceState.directory,\n total: results.length,\n results: formattedResults,\n })\n },\n }\n}\n\n/**\n * 索引单个文件工具\n */\nfunction createIndexFileTool(search: DocumentSearch): Tool {\n return {\n name: 'index_document_file',\n description: `将指定的文档文件添加到搜索索引中。\n索引后,该文件可以通过 search_local_documents 搜索到。\n支持的文件类型:.docx, .doc, .pdf, .xlsx, .xls, .pptx, .ppt, .txt, .md`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n file_path: {\n type: 'string',\n description: '要索引的文件绝对路径,如 /Users/xxx/Documents/report.pdf',\n },\n },\n required: ['file_path'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }], // 声明会修改索引数据\n\n execute: async (args, _context) => {\n const filePath = args.file_path as string\n\n try {\n await search.indexFile(filePath)\n return JSON.stringify({\n success: true,\n message: `文件已成功索引: ${filePath}`,\n path: filePath,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n path: filePath,\n })\n }\n },\n }\n}\n\n/**\n * 索引目录工具\n */\nfunction createIndexDirectoryTool(search: DocumentSearch): Tool {\n return {\n name: 'index_document_directory',\n description: `扫描并索引指定目录下的所有文档文件(包括子目录)。\n索引完成后,目录内的所有支持的文档都可以被搜索。\n首次索引可能需要一些时间,取决于文件数量。\n\n使用场景:\n- 用户想搜索某个目录但还没索引过\n- 用户的文档目录有更新,需要重新索引`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n directory: {\n type: 'string',\n description:\n '要索引的目录绝对路径,如 /Users/xxx/Documents 或 ~/Documents',\n },\n },\n required: ['directory'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (args, context) => {\n let directory = args.directory as string\n\n // 处理 ~ 路径(跨平台)\n if (directory.startsWith('~')) {\n const os = await import('os')\n directory = directory.replace('~', os.homedir())\n }\n\n // 判断是否为绝对路径(跨平台)\n const path = await import('path')\n const isAbsolute = path.isAbsolute(directory)\n \n // 如果是相对路径,基于当前工作目录\n if (!isAbsolute) {\n directory = path.resolve(context.cwd, directory)\n }\n\n try {\n let indexed = 0\n let total = 0\n\n await search.indexDirectory(directory, (progress) => {\n indexed = progress.indexed\n total = progress.total\n })\n\n return JSON.stringify({\n success: true,\n message: `目录索引完成`,\n directory,\n filesIndexed: indexed,\n filesFound: total,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n directory,\n })\n }\n },\n }\n}\n\n/**\n * 获取索引统计工具\n */\nfunction createGetStatsTool(search: DocumentSearch, workspaceState: WorkspaceState): Tool {\n return {\n name: 'get_document_index_stats',\n description: `获取文档搜索索引的统计信息。\n包括已索引文档数量、文件类型分布、索引目录、当前工作空间等。\n用于了解当前索引状态,帮助决定是否需要索引新目录。`,\n\n parameters: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n\n execute: async (_args, _context) => {\n const stats = search.getStats()\n\n return JSON.stringify({\n totalDocuments: stats.totalDocuments,\n byType: stats.byType,\n indexedDirectories: stats.directories,\n lastUpdated: stats.lastUpdated?.toISOString() || 'N/A',\n indexSize: stats.indexSize,\n // 工作空间信息\n workspace: {\n directory: workspaceState.directory,\n indexed: workspaceState.indexed,\n filesIndexed: workspaceState.filesIndexed,\n },\n })\n },\n }\n}\n\n/**\n * 清除索引工具(管理用)\n */\nfunction createClearIndexTool(search: DocumentSearch): Tool {\n return {\n name: 'clear_document_index',\n description: `清除所有文档索引数据。\n这是一个危险操作,会删除所有已索引的文档信息。\n清除后需要重新索引才能搜索。\n通常只在索引出问题或需要重建时使用。`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n confirm: {\n type: 'string',\n description: '确认清除,必须输入 \"YES\" 才会执行',\n },\n },\n required: ['confirm'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (args, _context) => {\n if (args.confirm !== 'YES') {\n return JSON.stringify({\n success: false,\n message: '操作取消:需要输入 \"YES\" 确认清除',\n })\n }\n\n try {\n await search.clear()\n return JSON.stringify({\n success: true,\n message: '索引已清除,需要重新索引文档',\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 设置工作空间工具\n */\nfunction createSetWorkspaceTool(search: DocumentSearch, workspaceState: WorkspaceState): Tool {\n return {\n name: 'set_search_workspace',\n description: `设置文档搜索的工作空间目录。\n\n设置后:\n- 工作空间内的文档会被自动索引\n- search_local_documents 默认只搜索工作空间内的文档\n- 可以随时更换工作空间\n\n使用场景:\n- 用户说\"在这个项目里找文档\"时,设置当前目录为工作空间\n- 用户切换项目时,更新工作空间\n- 用户想搜索特定文件夹的文档`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n directory: {\n type: 'string',\n description: '工作空间目录路径,可以是绝对路径或相对路径(相对于当前 cwd)',\n },\n },\n required: ['directory'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (args, context) => {\n let directory = args.directory as string\n\n // 处理 ~ 路径\n if (directory.startsWith('~')) {\n const os = await import('os')\n directory = directory.replace('~', os.homedir())\n }\n\n // 处理相对路径\n const pathModule = await import('path')\n if (!pathModule.isAbsolute(directory)) {\n directory = pathModule.resolve(context.cwd, directory)\n }\n\n // 检查目录是否存在\n const fs = await import('fs/promises')\n try {\n const stat = await fs.stat(directory)\n if (!stat.isDirectory()) {\n return JSON.stringify({\n success: false,\n error: '指定路径不是一个目录',\n path: directory,\n })\n }\n } catch {\n return JSON.stringify({\n success: false,\n error: '目录不存在',\n path: directory,\n })\n }\n\n // 设置工作空间\n workspaceState.directory = directory\n workspaceState.indexed = false\n workspaceState.filesIndexed = 0\n\n // 索引工作空间\n try {\n await search.indexDirectory(directory, (progress) => {\n workspaceState.filesIndexed = progress.indexed\n })\n workspaceState.indexed = true\n\n return JSON.stringify({\n success: true,\n message: `工作空间已设置并索引完成`,\n workspace: directory,\n filesIndexed: workspaceState.filesIndexed,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n workspace: directory,\n })\n }\n },\n }\n}\n\n/**\n * 批量索引文件工具\n */\nfunction createIndexFilesTool(search: DocumentSearch): Tool {\n return {\n name: 'index_document_files',\n description: `批量索引多个文档文件。\n相比单个文件索引,批量索引效率更高,适合一次性索引多个文件。\n会返回成功和失败的数量,以及失败的文件列表。`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n file_paths: {\n type: 'array',\n items: {\n type: 'string',\n },\n description: '要索引的文件路径数组(绝对路径)',\n },\n },\n required: ['file_paths'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (args, _context) => {\n const filePaths = args.file_paths as string[]\n\n if (!Array.isArray(filePaths) || filePaths.length === 0) {\n return JSON.stringify({\n success: false,\n error: 'file_paths 必须是非空数组',\n })\n }\n\n try {\n const result = await search.indexFiles(filePaths)\n return JSON.stringify({\n success: true,\n message: `批量索引完成`,\n successCount: result.success,\n failedCount: result.failed,\n errors: result.errors,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 批量删除文件索引工具\n */\nfunction createRemoveFilesTool(search: DocumentSearch): Tool {\n return {\n name: 'remove_document_files',\n description: `批量删除多个文件的索引。\n用于清理不再需要的文档索引,比如文件已删除或移动到其他位置。`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n file_paths: {\n type: 'array',\n items: {\n type: 'string',\n },\n description: '要删除索引的文件路径数组(绝对路径)',\n },\n },\n required: ['file_paths'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (args, _context) => {\n const filePaths = args.file_paths as string[]\n\n if (!Array.isArray(filePaths) || filePaths.length === 0) {\n return JSON.stringify({\n success: false,\n error: 'file_paths 必须是非空数组',\n })\n }\n\n try {\n const result = await search.removeFiles(filePaths)\n return JSON.stringify({\n success: true,\n message: `批量删除完成`,\n successCount: result.success,\n failedCount: result.failed,\n errors: result.errors,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 批量更新文件索引工具\n */\nfunction createUpdateFilesTool(search: DocumentSearch): Tool {\n return {\n name: 'update_document_files',\n description: `批量更新多个文件的索引(重新索引)。\n用于文件内容已修改,需要更新索引的场景。\n会先删除旧索引,再重新索引文件。`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n file_paths: {\n type: 'array',\n items: {\n type: 'string',\n },\n description: '要更新索引的文件路径数组(绝对路径)',\n },\n },\n required: ['file_paths'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (args, _context) => {\n const filePaths = args.file_paths as string[]\n\n if (!Array.isArray(filePaths) || filePaths.length === 0) {\n return JSON.stringify({\n success: false,\n error: 'file_paths 必须是非空数组',\n })\n }\n\n try {\n const result = await search.updateFiles(filePaths)\n return JSON.stringify({\n success: true,\n message: `批量更新完成`,\n successCount: result.success,\n failedCount: result.failed,\n errors: result.errors,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 监听目录工具\n */\nfunction createWatchDirectoryTool(search: DocumentSearch): Tool {\n return {\n name: 'watch_document_directory',\n description: `开始监听目录变化,自动更新索引。\n监听后,目录内的文件新增、修改、删除都会自动更新索引,无需手动操作。\n适合需要实时保持索引同步的场景。\n\n注意:监听会持续运行,直到调用 unwatch_document_directory 停止。`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n directory: {\n type: 'string',\n description: '要监听的目录绝对路径',\n },\n ignore_initial: {\n type: 'boolean',\n description: '是否忽略初始扫描(只监听后续变化),默认 true',\n },\n debounce: {\n type: 'number',\n description: '防抖延迟(毫秒),避免频繁触发,默认 1000',\n },\n },\n required: ['directory'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (args, context) => {\n let directory = args.directory as string\n\n // 处理 ~ 路径\n if (directory.startsWith('~')) {\n const os = await import('os')\n directory = directory.replace('~', os.homedir())\n }\n\n // 处理相对路径\n const pathModule = await import('path')\n if (!pathModule.isAbsolute(directory)) {\n directory = pathModule.resolve(context.cwd, directory)\n }\n\n // 检查目录是否存在\n const fs = await import('fs/promises')\n try {\n const stat = await fs.stat(directory)\n if (!stat.isDirectory()) {\n return JSON.stringify({\n success: false,\n error: '指定路径不是一个目录',\n path: directory,\n })\n }\n } catch {\n return JSON.stringify({\n success: false,\n error: '目录不存在',\n path: directory,\n })\n }\n\n try {\n search.watchDirectory(directory, {\n ignoreInitial: args.ignore_initial !== false,\n debounce: typeof args.debounce === 'number' ? args.debounce : 1000,\n })\n\n return JSON.stringify({\n success: true,\n message: `开始监听目录`,\n directory,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n directory,\n })\n }\n },\n }\n}\n\n/**\n * 停止监听目录工具\n */\nfunction createUnwatchDirectoryTool(search: DocumentSearch): Tool {\n return {\n name: 'unwatch_document_directory',\n description: `停止监听指定目录的变化。\n停止后,该目录的文件变化将不再自动更新索引。`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n directory: {\n type: 'string',\n description: '要停止监听的目录绝对路径',\n },\n },\n required: ['directory'],\n },\n\n execute: async (args, context) => {\n let directory = args.directory as string\n\n // 处理 ~ 路径\n if (directory.startsWith('~')) {\n const os = await import('os')\n directory = directory.replace('~', os.homedir())\n }\n\n // 处理相对路径\n const pathModule = await import('path')\n if (!pathModule.isAbsolute(directory)) {\n directory = pathModule.resolve(context.cwd, directory)\n }\n\n try {\n search.unwatchDirectory(directory)\n return JSON.stringify({\n success: true,\n message: `已停止监听目录`,\n directory,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n directory,\n })\n }\n },\n }\n}\n\n/**\n * 获取正在监听的目录列表工具\n */\nfunction createGetWatchedDirectoriesTool(search: DocumentSearch): Tool {\n return {\n name: 'get_watched_directories',\n description: `获取当前正在监听的目录列表。\n用于查看哪些目录正在被自动监听和更新。`,\n\n parameters: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n\n execute: async (_args, _context) => {\n const directories = search.getWatchedDirectories()\n return JSON.stringify({\n success: true,\n directories,\n count: directories.length,\n })\n },\n }\n}\n\n/**\n * 清理无效索引工具\n */\nfunction createCleanupIndexTool(search: DocumentSearch): Tool {\n return {\n name: 'cleanup_document_index',\n description: `清理无效的索引(文件已删除但索引还在)。\n会检查所有已索引的文件是否还存在:\n- 如果文件不存在,删除索引\n- 如果文件已修改,重新索引\n\n用于维护索引的一致性,建议定期执行。`,\n\n parameters: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (_args, _context) => {\n try {\n const result = await search.cleanup()\n return JSON.stringify({\n success: true,\n message: `清理完成`,\n removedCount: result.removed,\n updatedCount: result.updated,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 导出索引工具\n */\nfunction createExportIndexTool(search: DocumentSearch): Tool {\n return {\n name: 'export_document_index',\n description: `导出文档索引数据到指定路径。\n导出内容包括:\n- 元数据(SQLite 数据库)\n- 向量数据(LanceDB)\n- 全文索引(FlexSearch)\n\n导出的数据可以用于备份、迁移或恢复索引。`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n output_path: {\n type: 'string',\n description: '导出路径(目录路径,会在该目录下创建导出文件夹)',\n },\n },\n required: ['output_path'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (args, context) => {\n let outputPath = args.output_path as string\n\n // 处理 ~ 路径\n if (outputPath.startsWith('~')) {\n const os = await import('os')\n outputPath = outputPath.replace('~', os.homedir())\n }\n\n // 处理相对路径\n const pathModule = await import('path')\n if (!pathModule.isAbsolute(outputPath)) {\n outputPath = pathModule.resolve(context.cwd, outputPath)\n }\n\n try {\n const exportInfo = await search.exportIndex(outputPath)\n return JSON.stringify({\n success: true,\n message: '索引导出完成',\n exportPath: exportInfo.exportPath,\n timestamp: exportInfo.timestamp.toISOString(),\n components: exportInfo.components,\n stats: {\n totalDocuments: exportInfo.stats.totalDocuments,\n byType: exportInfo.stats.byType,\n },\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 导入索引工具\n */\nfunction createImportIndexTool(search: DocumentSearch): Tool {\n return {\n name: 'import_document_index',\n description: `从指定路径导入文档索引数据。\n导入前会检查导出信息,确保数据完整性。\n导入后会替换当前索引数据,请谨慎使用。`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n input_path: {\n type: 'string',\n description: '导入路径(导出时创建的目录路径)',\n },\n },\n required: ['input_path'],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (args, context) => {\n let inputPath = args.input_path as string\n\n // 处理 ~ 路径\n if (inputPath.startsWith('~')) {\n const os = await import('os')\n inputPath = inputPath.replace('~', os.homedir())\n }\n\n // 处理相对路径\n const pathModule = await import('path')\n if (!pathModule.isAbsolute(inputPath)) {\n inputPath = pathModule.resolve(context.cwd, inputPath)\n }\n\n try {\n await search.importIndex(inputPath)\n return JSON.stringify({\n success: true,\n message: '索引导入完成',\n inputPath,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 列出备份工具\n */\nfunction createListBackupsTool(search: DocumentSearch): Tool {\n return {\n name: 'list_document_index_backups',\n description: `列出指定目录下的所有索引备份。\n用于查看可用的备份,以便选择恢复。`,\n\n parameters: {\n type: 'object' as const,\n properties: {\n backup_dir: {\n type: 'string',\n description: '备份目录路径',\n },\n },\n required: ['backup_dir'],\n },\n\n execute: async (args, context) => {\n let backupDir = args.backup_dir as string\n\n // 处理 ~ 路径\n if (backupDir.startsWith('~')) {\n const os = await import('os')\n backupDir = backupDir.replace('~', os.homedir())\n }\n\n // 处理相对路径\n const pathModule = await import('path')\n if (!pathModule.isAbsolute(backupDir)) {\n backupDir = pathModule.resolve(context.cwd, backupDir)\n }\n\n try {\n const backups = await search.listBackups(backupDir)\n return JSON.stringify({\n success: true,\n backups: backups.map((b) => ({\n path: b.path,\n timestamp: b.timestamp.toISOString(),\n size: b.size,\n sizeFormatted: formatBytes(b.size),\n })),\n count: backups.length,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 格式化字节大小\n */\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B'\n const k = 1024\n const sizes = ['B', 'KB', 'MB', 'GB']\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`\n}\n\n/**\n * 优化索引工具\n */\nfunction createOptimizeIndexTool(search: DocumentSearch): Tool {\n return {\n name: 'optimize_document_index',\n description: `优化文档索引,包括:\n- 清理无效索引(文件已删除)\n- 更新过期索引(文件已修改)\n- 压缩索引数据\n- 碎片整理\n\n建议定期执行以保持索引性能和一致性。`,\n\n parameters: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (_args, _context) => {\n try {\n await search.optimize()\n return JSON.stringify({\n success: true,\n message: '索引优化完成',\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 健康检查工具\n */\nfunction createHealthCheckTool(search: DocumentSearch): Tool {\n return {\n name: 'check_document_index_health',\n description: `检查文档索引的健康状态。\n包括:\n- 索引完整性检查(元数据、向量、全文索引)\n- 无效索引统计(文件已删除)\n- 过期索引统计(文件已修改)\n- 错误统计\n\n用于诊断索引问题,决定是否需要优化或修复。`,\n\n parameters: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n\n execute: async (_args, _context) => {\n try {\n const health = await search.healthCheck()\n return JSON.stringify({\n success: true,\n healthy: health.healthy,\n totalDocuments: health.totalDocuments,\n invalidIndexes: health.invalidIndexes,\n staleIndexes: health.staleIndexes,\n errorCount: health.errorCount,\n integrity: health.integrity,\n message: health.healthy\n ? '索引健康'\n : '索引存在问题,建议执行 optimize_document_index 或 cleanup_document_index',\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 获取索引错误工具\n */\nfunction createGetIndexErrorsTool(search: DocumentSearch): Tool {\n return {\n name: 'get_document_index_errors',\n description: `获取索引过程中发生的错误列表。\n包括文件路径、错误信息、重试次数和时间戳。\n用于排查索引问题,了解哪些文件索引失败。`,\n\n parameters: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n\n execute: async (_args, _context) => {\n try {\n const errors = search.getIndexErrors()\n return JSON.stringify({\n success: true,\n errors: errors.map((e) => ({\n filePath: e.filePath,\n error: e.error,\n retryCount: e.retryCount,\n timestamp: e.timestamp.toISOString(),\n })),\n count: errors.length,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 重试失败的索引工具\n */\nfunction createRetryFailedIndexesTool(search: DocumentSearch): Tool {\n return {\n name: 'retry_failed_document_indexes',\n description: `重试之前索引失败的文件。\n会清除错误记录,然后重新尝试索引所有失败的文件。\n用于修复索引错误,恢复完整的索引覆盖。`,\n\n parameters: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n\n sideEffects: [{ type: 'filesystem', success: true }],\n\n execute: async (_args, _context) => {\n try {\n const result = await search.retryFailedIndexes()\n return JSON.stringify({\n success: true,\n message: '重试完成',\n successCount: result.success,\n failedCount: result.failed,\n errors: result.errors,\n })\n } catch (error) {\n return JSON.stringify({\n success: false,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n },\n }\n}\n\n/**\n * 获取工作空间状态工具\n */\nfunction createGetWorkspaceTool(workspaceState: WorkspaceState): Tool {\n return {\n name: 'get_search_workspace',\n description: `获取当前文档搜索的工作空间状态。\n返回当前工作空间目录、是否已索引、索引文件数量等信息。`,\n\n parameters: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n\n execute: async (_args, _context) => {\n if (!workspaceState.directory) {\n return JSON.stringify({\n hasWorkspace: false,\n message: '尚未设置工作空间,使用 set_search_workspace 设置',\n })\n }\n\n return JSON.stringify({\n hasWorkspace: true,\n directory: workspaceState.directory,\n indexed: workspaceState.indexed,\n filesIndexed: workspaceState.filesIndexed,\n })\n },\n }\n}\n\n"],"mappings":"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","names":["fs","path","path","stat","fs","path","resolve","resolve","path","os","path","platform","fs","path","snippet","resolve","path","fs","stat","fs","path","stat","path","fs","createHash","hashFile","createHash","DEFAULT_CONFIG","stat","stat","contentHash","docId","doc","arkApiKey","embeddingDimension","os","path","isAbsolute","fs","stat"]}