mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-05-08 08:00:10 +00:00
Optimize the insight view component, add workspace insight initialization functionality, update internationalization support, improve user interaction prompts, enhance log output, and ensure better user experience and code readability.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Result, err, ok } from "neverthrow";
|
||||
import { App, TFolder, getLanguage } from 'obsidian';
|
||||
import { App, TFolder, getLanguage, normalizePath } from 'obsidian';
|
||||
|
||||
import { DBManager } from '../../database/database-manager';
|
||||
import { InsightManager } from '../../database/modules/insight/insight-manager';
|
||||
@@ -24,7 +24,7 @@ import { getEmbeddingModel } from '../rag/embedding';
|
||||
type EmbeddingManager = {
|
||||
modelLoaded: boolean
|
||||
currentModel: string | null
|
||||
loadModel(modelId: string, useGpu: boolean): Promise<any>
|
||||
loadModel(modelId: string, useGpu: boolean): Promise<void>
|
||||
embed(text: string): Promise<{ vec: number[] }>
|
||||
embedBatch(texts: string[]): Promise<{ vec: number[] }[]>
|
||||
}
|
||||
@@ -141,19 +141,12 @@ export const TRANSFORMATIONS: Record<TransformationType, TransformationConfig> =
|
||||
|
||||
// 转换参数接口
|
||||
export interface TransformationParams {
|
||||
filePath: string; // 文件路径、文件夹路径或工作区标识
|
||||
contentType?: 'document' | 'tag' | 'folder' | 'workspace';
|
||||
filePath: string; // 文件路径、文件夹路径
|
||||
contentType?: 'document' | 'tag' | 'folder';
|
||||
transformationType: TransformationType;
|
||||
model?: LLMModel;
|
||||
maxContentTokens?: number;
|
||||
saveToDatabase?: boolean;
|
||||
// 对于 workspace 类型,可以传入额外的元数据
|
||||
workspaceMetadata?: {
|
||||
name: string;
|
||||
description?: string;
|
||||
// 完整的 workspace 对象,用于获取配置信息
|
||||
workspace?: import('../../database/json/workspace/types').Workspace;
|
||||
};
|
||||
}
|
||||
|
||||
// 转换结果接口
|
||||
@@ -166,6 +159,33 @@ export interface TransformationResult {
|
||||
processedTokens?: number;
|
||||
}
|
||||
|
||||
// 工作区洞察初始化进度接口
|
||||
export interface WorkspaceInsightProgress {
|
||||
stage: string;
|
||||
current: number;
|
||||
total: number;
|
||||
currentItem: string;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
// 工作区洞察初始化参数接口
|
||||
export interface InitWorkspaceInsightParams {
|
||||
workspace: import('../../database/json/workspace/types').Workspace;
|
||||
model?: LLMModel;
|
||||
onProgress?: (progress: WorkspaceInsightProgress) => void;
|
||||
}
|
||||
|
||||
// 工作区洞察初始化结果接口
|
||||
export interface InitWorkspaceInsightResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
processedFiles: number;
|
||||
processedFolders: number;
|
||||
totalItems: number;
|
||||
skippedItems: number;
|
||||
insightId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* LLM 客户端类,用于与语言模型交互
|
||||
*/
|
||||
@@ -371,7 +391,7 @@ export class TransEngine {
|
||||
error: string;
|
||||
}
|
||||
> {
|
||||
const targetFile = this.app.vault.getFileByPath(filePath);
|
||||
const targetFile = this.app.vault.getFileByPath(normalizePath(filePath));
|
||||
if (!targetFile) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -472,7 +492,7 @@ export class TransEngine {
|
||||
error: string;
|
||||
}
|
||||
> {
|
||||
const targetFile = this.app.vault.getFileByPath(filePath);
|
||||
const targetFile = this.app.vault.getFileByPath(normalizePath(filePath));
|
||||
if (!targetFile) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -548,8 +568,7 @@ export class TransEngine {
|
||||
transformationType,
|
||||
model,
|
||||
maxContentTokens,
|
||||
saveToDatabase = false,
|
||||
workspaceMetadata
|
||||
saveToDatabase = false
|
||||
} = params;
|
||||
|
||||
try {
|
||||
@@ -596,7 +615,16 @@ export class TransEngine {
|
||||
|
||||
case 'folder': {
|
||||
sourcePath = filePath;
|
||||
sourceMtime = Date.now();
|
||||
|
||||
// 计算文件夹的真实 mtime(基于所有子项目的最大 mtime)
|
||||
const folderItems = await this.collectFolderItems(filePath);
|
||||
let maxMtime = 0;
|
||||
for (const item of folderItems) {
|
||||
if (item.mtime > maxMtime) {
|
||||
maxMtime = item.mtime;
|
||||
}
|
||||
}
|
||||
sourceMtime = maxMtime > 0 ? maxMtime : 0;
|
||||
|
||||
// 检查数据库缓存
|
||||
const cacheCheckResult = await this.checkDatabaseCache(
|
||||
@@ -620,44 +648,6 @@ export class TransEngine {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'workspace': {
|
||||
if (!workspaceMetadata?.workspace) {
|
||||
return {
|
||||
success: false,
|
||||
error: '工作区对象未提供'
|
||||
};
|
||||
}
|
||||
|
||||
sourcePath = `workspace:${workspaceMetadata.workspace.name}`;
|
||||
sourceMtime = Date.now();
|
||||
|
||||
// 检查数据库缓存
|
||||
const cacheCheckResult = await this.checkDatabaseCache(
|
||||
sourcePath,
|
||||
sourceMtime,
|
||||
transformationType
|
||||
);
|
||||
if (cacheCheckResult.foundCache) {
|
||||
return cacheCheckResult.result;
|
||||
}
|
||||
|
||||
// 处理工作区内容
|
||||
const workspaceContentResult = await this.processWorkspaceContent(
|
||||
workspaceMetadata.workspace,
|
||||
transformationType,
|
||||
model
|
||||
);
|
||||
|
||||
if (!workspaceContentResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: workspaceContentResult.error
|
||||
};
|
||||
}
|
||||
content = workspaceContentResult.content;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
success: false,
|
||||
@@ -733,7 +723,7 @@ export class TransEngine {
|
||||
transformationType,
|
||||
sourcePath,
|
||||
sourceMtime,
|
||||
contentType === 'workspace' ? 'folder' : contentType // workspace 在数据库中存储为 folder 类型
|
||||
contentType
|
||||
);
|
||||
})(); // 立即执行异步函数,但不等待其完成
|
||||
}
|
||||
@@ -763,7 +753,7 @@ export class TransEngine {
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const folder = this.app.vault.getAbstractFileByPath(folderPath);
|
||||
const folder = this.app.vault.getAbstractFileByPath(normalizePath(folderPath));
|
||||
if (!folder || !(folder instanceof TFolder)) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -852,119 +842,6 @@ export class TransEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理工作区内容 - 根据workspace配置递归处理文件和文件夹
|
||||
*/
|
||||
private async processWorkspaceContent(
|
||||
workspace: import('../../database/json/workspace/types').Workspace,
|
||||
transformationType: TransformationType,
|
||||
model?: LLMModel
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
content?: string;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
// 根据 workspace 配置获取相应的文件和文件夹
|
||||
const workspaceFiles: string[] = []
|
||||
const workspaceFolders: string[] = []
|
||||
|
||||
// 解析 workspace 的 content 配置
|
||||
for (const contentItem of workspace.content) {
|
||||
if (contentItem.type === 'folder') {
|
||||
// 添加文件夹到列表
|
||||
workspaceFolders.push(contentItem.content)
|
||||
} else if (contentItem.type === 'tag') {
|
||||
// 对于标签类型,搜索包含该标签的文件
|
||||
const taggedFiles = this.getFilesByTag(contentItem.content)
|
||||
workspaceFiles.push(...taggedFiles.map(f => f.path))
|
||||
}
|
||||
}
|
||||
|
||||
if (workspaceFiles.length === 0 && workspaceFolders.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `工作区 "${workspace.name}" 没有找到任何内容`
|
||||
}
|
||||
}
|
||||
|
||||
// 构建工作区内容描述
|
||||
let content = `# Workspace Summary: ${workspace.name}\n\n`
|
||||
const description = typeof workspace.metadata?.description === 'string' ? workspace.metadata.description : undefined
|
||||
if (description) {
|
||||
content += `Workspace Description: ${description}\n\n`
|
||||
}
|
||||
|
||||
const childSummaries: string[] = []
|
||||
|
||||
// 处理工作区配置的文件
|
||||
if (workspaceFiles.length > 0) {
|
||||
content += `## File Summaries (${workspaceFiles.length} files)\n\n`
|
||||
|
||||
for (const filePath of workspaceFiles) {
|
||||
const fileName = filePath.split('/').pop() || filePath
|
||||
|
||||
const fileResult = await this.runTransformation({
|
||||
filePath: filePath,
|
||||
contentType: 'document',
|
||||
transformationType: TransformationType.DENSE_SUMMARY,
|
||||
model: model,
|
||||
saveToDatabase: true
|
||||
})
|
||||
|
||||
if (fileResult.success && fileResult.result) {
|
||||
childSummaries.push(`### ${fileName}\n${fileResult.result}`)
|
||||
} else {
|
||||
console.warn(`处理文件失败: ${filePath}`, fileResult.error)
|
||||
childSummaries.push(`### ${fileName}\n*处理失败: ${fileResult.error}*`)
|
||||
}
|
||||
}
|
||||
|
||||
if (workspaceFolders.length > 0) {
|
||||
content += '\n\n'
|
||||
}
|
||||
}
|
||||
|
||||
// 处理工作区配置的文件夹
|
||||
if (workspaceFolders.length > 0) {
|
||||
content += `## Folder Summaries (${workspaceFolders.length} folders)\n\n`
|
||||
|
||||
for (const folderPath of workspaceFolders) {
|
||||
const folderName = folderPath.split('/').pop() || folderPath
|
||||
|
||||
const folderResult = await this.runTransformation({
|
||||
filePath: folderPath,
|
||||
contentType: 'folder',
|
||||
transformationType: TransformationType.HIERARCHICAL_SUMMARY,
|
||||
model: model,
|
||||
saveToDatabase: true
|
||||
})
|
||||
|
||||
if (folderResult.success && folderResult.result) {
|
||||
childSummaries.push(`### ${folderName}/\n${folderResult.result}`)
|
||||
} else {
|
||||
console.warn(`处理文件夹失败: ${folderPath}`, folderResult.error)
|
||||
childSummaries.push(`### ${folderName}/\n*处理失败: ${folderResult.error}*`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 合并所有子摘要
|
||||
content += childSummaries.join('\n\n')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `处理工作区内容失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后处理转换结果
|
||||
*/
|
||||
@@ -1072,7 +949,7 @@ export class TransEngine {
|
||||
// 添加文件夹下的所有文件
|
||||
if (scope.folders.length > 0) {
|
||||
for (const folderPath of scope.folders) {
|
||||
const folder = this.app.vault.getAbstractFileByPath(folderPath)
|
||||
const folder = this.app.vault.getAbstractFileByPath(normalizePath(folderPath))
|
||||
if (folder && folder instanceof TFolder) {
|
||||
// 获取文件夹下的所有 Markdown 文件
|
||||
const folderFiles = this.app.vault.getMarkdownFiles().filter(file =>
|
||||
@@ -1167,7 +1044,7 @@ export class TransEngine {
|
||||
}): Promise<string | null> {
|
||||
const { folderPath, llmModel, concurrencyLimiter, signal, onFileProcessed, onFolderProcessed } = params
|
||||
|
||||
const folder = this.app.vault.getAbstractFileByPath(folderPath)
|
||||
const folder = this.app.vault.getAbstractFileByPath(normalizePath(folderPath))
|
||||
if (!folder || !(folder instanceof TFolder)) {
|
||||
return null
|
||||
}
|
||||
@@ -1381,6 +1258,16 @@ export class TransEngine {
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取文件夹的真实 mtime(基于所有子项目的最大 mtime)
|
||||
const folderItems = await this.collectFolderItems(folderPath)
|
||||
let maxMtime = 0
|
||||
for (const item of folderItems) {
|
||||
if (item.mtime > maxMtime) {
|
||||
maxMtime = item.mtime
|
||||
}
|
||||
}
|
||||
const sourceMtime = maxMtime > 0 ? maxMtime : 0
|
||||
|
||||
const embedding = await this.embeddingModel.getEmbedding(summary)
|
||||
await this.insightManager.storeInsight(
|
||||
{
|
||||
@@ -1388,7 +1275,7 @@ export class TransEngine {
|
||||
insight: summary,
|
||||
sourceType: 'folder',
|
||||
sourcePath: folderPath,
|
||||
sourceMtime: Date.now(),
|
||||
sourceMtime: sourceMtime,
|
||||
embedding: embedding,
|
||||
},
|
||||
this.embeddingModel
|
||||
@@ -1621,4 +1508,480 @@ export class TransEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化工作区洞察 - 专门用于工作区洞察的初始化流程
|
||||
*/
|
||||
async initWorkspaceInsight(params: InitWorkspaceInsightParams): Promise<InitWorkspaceInsightResult> {
|
||||
const { workspace, model, onProgress } = params;
|
||||
|
||||
// 统计信息
|
||||
let processedFiles = 0;
|
||||
let processedFolders = 0;
|
||||
let skippedItems = 0;
|
||||
|
||||
try {
|
||||
// 1. 深度分析工作区内容,统计所有需要处理的项目
|
||||
onProgress?.({
|
||||
stage: '分析工作区内容',
|
||||
current: 0,
|
||||
total: 1,
|
||||
currentItem: '深度扫描文件和文件夹...',
|
||||
percentage: 0
|
||||
});
|
||||
|
||||
// 收集所有需要处理的项目(深度递归)
|
||||
const allItems: Array<{
|
||||
type: 'file' | 'folder';
|
||||
path: string;
|
||||
name: string;
|
||||
mtime: number;
|
||||
}> = [];
|
||||
|
||||
// 收集工作区顶层配置的项目(仅用于最终摘要)
|
||||
const topLevelFiles: Array<{
|
||||
path: string;
|
||||
name: string;
|
||||
}> = [];
|
||||
|
||||
const topLevelFolders: Array<{
|
||||
path: string;
|
||||
name: string;
|
||||
}> = [];
|
||||
|
||||
// 解析 workspace 的 content 配置
|
||||
const seenPaths = new Set<string>();
|
||||
|
||||
for (const contentItem of workspace.content) {
|
||||
if (contentItem.type === 'folder') {
|
||||
const folderPath = contentItem.content;
|
||||
const folderName = folderPath.split('/').pop() || folderPath;
|
||||
|
||||
// 收集顶层文件夹(用于最终摘要)
|
||||
topLevelFolders.push({
|
||||
path: folderPath,
|
||||
name: folderName
|
||||
});
|
||||
|
||||
// 深度遍历收集所有项目(用于进度统计和处理)
|
||||
const items = await this.collectFolderItems(folderPath);
|
||||
for (const item of items) {
|
||||
if (!seenPaths.has(item.path)) {
|
||||
seenPaths.add(item.path);
|
||||
allItems.push(item);
|
||||
}
|
||||
}
|
||||
} else if (contentItem.type === 'tag') {
|
||||
// 收集标签对应的文件
|
||||
const taggedFiles = this.getFilesByTag(contentItem.content);
|
||||
for (const file of taggedFiles) {
|
||||
if (!seenPaths.has(file.path)) {
|
||||
seenPaths.add(file.path);
|
||||
// 添加到顶层文件(用于最终摘要)
|
||||
topLevelFiles.push({
|
||||
path: file.path,
|
||||
name: file.name
|
||||
});
|
||||
// 添加到所有项目(用于处理)
|
||||
allItems.push({
|
||||
type: 'file',
|
||||
path: file.path,
|
||||
name: file.name,
|
||||
mtime: file.stat.mtime
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('allItems', allItems);
|
||||
if (allItems.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `工作区 "${workspace.name}" 没有找到任何内容`,
|
||||
processedFiles: 0,
|
||||
processedFolders: 0,
|
||||
totalItems: 0,
|
||||
skippedItems: 0
|
||||
};
|
||||
}
|
||||
|
||||
// 分离文件和文件夹
|
||||
const files = allItems.filter(item => item.type === 'file');
|
||||
const folders = allItems.filter(item => item.type === 'folder');
|
||||
const totalItems = allItems.length;
|
||||
|
||||
onProgress?.({
|
||||
stage: '分析完成',
|
||||
current: 1,
|
||||
total: 1,
|
||||
currentItem: `深度扫描完成:${files.length} 个文件,${folders.length} 个文件夹`,
|
||||
percentage: 5
|
||||
});
|
||||
|
||||
// 用于收集顶层摘要(仅用于工作区摘要)
|
||||
const topLevelSummaries: string[] = [];
|
||||
let currentProgress = 0;
|
||||
|
||||
// 2. 处理所有文件(深度递归的结果)
|
||||
for (const file of files) {
|
||||
currentProgress++;
|
||||
|
||||
onProgress?.({
|
||||
stage: '处理文件',
|
||||
current: currentProgress,
|
||||
total: totalItems,
|
||||
currentItem: `📄 ${file.name}`,
|
||||
percentage: Math.round((currentProgress / totalItems) * 90) + 5 // 5-95%
|
||||
});
|
||||
|
||||
try {
|
||||
const fileResult = await this.runTransformation({
|
||||
filePath: file.path,
|
||||
contentType: 'document',
|
||||
transformationType: TransformationType.DENSE_SUMMARY,
|
||||
model: model,
|
||||
saveToDatabase: true
|
||||
});
|
||||
|
||||
if (fileResult.success && fileResult.result) {
|
||||
// 检查是否是顶层文件(标签文件),如果是则添加到顶层摘要
|
||||
const isTopLevelFile = topLevelFiles.some(f => f.path === file.path);
|
||||
if (isTopLevelFile) {
|
||||
topLevelSummaries.push(`### 📄 ${file.name}\n${fileResult.result}`);
|
||||
}
|
||||
processedFiles++;
|
||||
} else {
|
||||
console.warn(`处理文件失败: ${file.path}`, fileResult.error);
|
||||
const isTopLevelFile = topLevelFiles.some(f => f.path === file.path);
|
||||
if (isTopLevelFile) {
|
||||
topLevelSummaries.push(`### 📄 ${file.name}\n*处理失败: ${fileResult.error}*`);
|
||||
}
|
||||
skippedItems++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`文件处理异常: ${file.path}`, error);
|
||||
const isTopLevelFile = topLevelFiles.some(f => f.path === file.path);
|
||||
if (isTopLevelFile) {
|
||||
topLevelSummaries.push(`### 📄 ${file.name}\n*处理异常: ${error instanceof Error ? error.message : String(error)}*`);
|
||||
}
|
||||
skippedItems++;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 处理所有文件夹(深度递归的结果,从最深层开始)
|
||||
const sortedFolders = folders.sort((a, b) => {
|
||||
const depthA = a.path.split('/').length;
|
||||
const depthB = b.path.split('/').length;
|
||||
return depthB - depthA; // 深度大的先处理
|
||||
});
|
||||
|
||||
for (const folder of sortedFolders) {
|
||||
currentProgress++;
|
||||
|
||||
onProgress?.({
|
||||
stage: '处理文件夹',
|
||||
current: currentProgress,
|
||||
total: totalItems,
|
||||
currentItem: `📂 ${folder.name}`,
|
||||
percentage: Math.round((currentProgress / totalItems) * 90) + 5 // 5-95%
|
||||
});
|
||||
|
||||
try {
|
||||
const folderResult = await this.runTransformation({
|
||||
filePath: folder.path,
|
||||
contentType: 'folder',
|
||||
transformationType: TransformationType.HIERARCHICAL_SUMMARY,
|
||||
model: model,
|
||||
saveToDatabase: true
|
||||
});
|
||||
|
||||
if (folderResult.success && folderResult.result) {
|
||||
// 检查是否是顶层文件夹,如果是则添加到顶层摘要
|
||||
const isTopLevelFolder = topLevelFolders.some(f => f.path === folder.path);
|
||||
if (isTopLevelFolder) {
|
||||
topLevelSummaries.push(`### 📂 ${folder.name}/\n${folderResult.result}`);
|
||||
}
|
||||
processedFolders++;
|
||||
} else {
|
||||
console.warn(`处理文件夹失败: ${folder.path}`, folderResult.error);
|
||||
const isTopLevelFolder = topLevelFolders.some(f => f.path === folder.path);
|
||||
if (isTopLevelFolder) {
|
||||
topLevelSummaries.push(`### 📂 ${folder.name}/\n*处理失败: ${folderResult.error}*`);
|
||||
}
|
||||
skippedItems++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`文件夹处理异常: ${folder.path}`, error);
|
||||
const isTopLevelFolder = topLevelFolders.some(f => f.path === folder.path);
|
||||
if (isTopLevelFolder) {
|
||||
topLevelSummaries.push(`### 📂 ${folder.name}/\n*处理异常: ${error instanceof Error ? error.message : String(error)}*`);
|
||||
}
|
||||
skippedItems++;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 生成工作区整体洞察
|
||||
onProgress?.({
|
||||
stage: '生成工作区洞察',
|
||||
current: 1,
|
||||
total: 1,
|
||||
currentItem: '汇总分析工作区内容...',
|
||||
percentage: 95
|
||||
});
|
||||
|
||||
// 构建工作区内容描述
|
||||
let workspaceContent = `# Workspace: ${workspace.name}\n\n`;
|
||||
|
||||
// 只添加顶层摘要(避免重叠)
|
||||
if (topLevelSummaries.length > 0) {
|
||||
workspaceContent += topLevelSummaries.join('\n\n');
|
||||
} else {
|
||||
workspaceContent += '*No top-level content summaries available.*';
|
||||
}
|
||||
|
||||
// 5. 生成工作区的整体洞察
|
||||
const sourcePath = `workspace:${workspace.name}`;
|
||||
|
||||
// 计算所有项目的最大 mtime
|
||||
let maxMtime = 0;
|
||||
for (const item of allItems) {
|
||||
if (item.mtime > maxMtime) {
|
||||
maxMtime = item.mtime;
|
||||
}
|
||||
}
|
||||
console.log('maxMtime', maxMtime);
|
||||
|
||||
// 如果没有找到任何有效的 mtime,使用当前时间
|
||||
const sourceMtime = maxMtime > 0 ? maxMtime : 0;
|
||||
|
||||
// 验证内容
|
||||
const contentValidation = DocumentProcessor.validateContent(workspaceContent);
|
||||
if (contentValidation.isErr()) {
|
||||
return {
|
||||
success: false,
|
||||
error: `工作区内容验证失败: ${contentValidation.error.message}`,
|
||||
processedFiles,
|
||||
processedFolders,
|
||||
totalItems,
|
||||
skippedItems
|
||||
};
|
||||
}
|
||||
|
||||
// 处理文档内容(检查 token 数量并截断)
|
||||
const processedDocument = await DocumentProcessor.processContent(workspaceContent);
|
||||
|
||||
// 查询数据库中是否存在工作区洞察
|
||||
const cacheCheckResult = await this.checkDatabaseCache(
|
||||
sourcePath,
|
||||
sourceMtime,
|
||||
TransformationType.HIERARCHICAL_SUMMARY
|
||||
);
|
||||
|
||||
if (cacheCheckResult.foundCache && cacheCheckResult.result.success) {
|
||||
// 找到缓存的工作区洞察,直接返回
|
||||
console.log(`使用缓存的工作区洞察: ${workspace.name}`);
|
||||
|
||||
onProgress?.({
|
||||
stage: '使用缓存洞察',
|
||||
current: 1,
|
||||
total: 1,
|
||||
currentItem: '已找到缓存的工作区洞察',
|
||||
percentage: 100
|
||||
});
|
||||
|
||||
// 尝试获取洞察ID
|
||||
let insightId: number | undefined;
|
||||
if (this.insightManager) {
|
||||
const recentInsights = await this.insightManager.getInsightsBySourcePath(sourcePath, this.embeddingModel);
|
||||
const latestInsight = recentInsights.find(insight =>
|
||||
insight.insight_type === TransformationType.HIERARCHICAL_SUMMARY.toString() &&
|
||||
insight.source_mtime === sourceMtime
|
||||
);
|
||||
insightId = latestInsight?.id;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
processedFiles,
|
||||
processedFolders,
|
||||
totalItems,
|
||||
skippedItems,
|
||||
insightId
|
||||
};
|
||||
}
|
||||
|
||||
// 使用默认模型或传入的模型
|
||||
const llmModel: LLMModel = model || {
|
||||
provider: this.settings.applyModelProvider,
|
||||
modelId: this.settings.applyModelId,
|
||||
};
|
||||
|
||||
// 创建 LLM 客户端
|
||||
const client = new TransformationLLMClient(this.llmManager, llmModel);
|
||||
|
||||
// 构建请求消息
|
||||
const transformationConfig = TRANSFORMATIONS[TransformationType.HIERARCHICAL_SUMMARY];
|
||||
const messages: RequestMessage[] = [
|
||||
{
|
||||
role: 'system',
|
||||
content: transformationConfig.prompt.replace('{userLanguage}', getFullLanguageName(getLanguage()))
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: processedDocument.processedContent
|
||||
}
|
||||
];
|
||||
|
||||
// 调用 LLM 执行转换
|
||||
const result = await client.queryChatModel(messages);
|
||||
|
||||
if (result.isErr()) {
|
||||
return {
|
||||
success: false,
|
||||
error: `LLM 调用失败: ${result.error.message}`,
|
||||
processedFiles,
|
||||
processedFolders,
|
||||
totalItems,
|
||||
skippedItems
|
||||
};
|
||||
}
|
||||
|
||||
// 后处理结果
|
||||
const processedResult = this.postProcessResult(result.value, TransformationType.HIERARCHICAL_SUMMARY);
|
||||
|
||||
// 6. 保存工作区洞察到数据库
|
||||
onProgress?.({
|
||||
stage: '保存洞察结果',
|
||||
current: 1,
|
||||
total: 1,
|
||||
currentItem: '保存到数据库...',
|
||||
percentage: 98
|
||||
});
|
||||
|
||||
let insightId: number | undefined;
|
||||
|
||||
try {
|
||||
await this.saveResultToDatabase(
|
||||
processedResult,
|
||||
TransformationType.HIERARCHICAL_SUMMARY,
|
||||
sourcePath,
|
||||
sourceMtime,
|
||||
'folder' // workspace 在数据库中存储为 folder 类型
|
||||
);
|
||||
|
||||
// 尝试获取刚保存的洞察ID(可选)
|
||||
if (this.insightManager) {
|
||||
const recentInsights = await this.insightManager.getInsightsBySourcePath(sourcePath, this.embeddingModel);
|
||||
const latestInsight = recentInsights.find(insight =>
|
||||
insight.insight_type === TransformationType.HIERARCHICAL_SUMMARY.toString() &&
|
||||
insight.source_mtime === sourceMtime
|
||||
);
|
||||
insightId = latestInsight?.id;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('保存洞察到数据库失败:', error);
|
||||
// 不影响主流程,仅记录警告
|
||||
}
|
||||
|
||||
// 7. 完成
|
||||
onProgress?.({
|
||||
stage: '完成',
|
||||
current: 1,
|
||||
total: 1,
|
||||
currentItem: '工作区洞察初始化完成',
|
||||
percentage: 100
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
processedFiles,
|
||||
processedFolders,
|
||||
totalItems,
|
||||
skippedItems,
|
||||
insightId
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `初始化工作区洞察失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
processedFiles,
|
||||
processedFolders,
|
||||
totalItems: processedFiles + processedFolders + skippedItems,
|
||||
skippedItems
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度收集文件夹中的所有项目(文件和子文件夹)
|
||||
*/
|
||||
private async collectFolderItems(folderPath: string): Promise<Array<{
|
||||
type: 'file' | 'folder';
|
||||
path: string;
|
||||
name: string;
|
||||
mtime: number;
|
||||
}>> {
|
||||
const items: Array<{
|
||||
type: 'file' | 'folder';
|
||||
path: string;
|
||||
name: string;
|
||||
mtime: number;
|
||||
}> = [];
|
||||
|
||||
try {
|
||||
const folder = this.app.vault.getAbstractFileByPath(normalizePath(folderPath));
|
||||
if (!folder || !(folder instanceof TFolder)) {
|
||||
console.warn(`文件夹不存在或无法访问: ${folderPath}`);
|
||||
return items;
|
||||
}
|
||||
|
||||
// 收集当前文件夹中的所有文件
|
||||
const allFiles = this.app.vault.getMarkdownFiles();
|
||||
const filesInFolder = allFiles.filter(file => {
|
||||
const fileDirPath = file.path.substring(0, file.path.lastIndexOf('/'));
|
||||
return fileDirPath === folderPath;
|
||||
});
|
||||
|
||||
// 添加文件
|
||||
for (const file of filesInFolder) {
|
||||
items.push({
|
||||
type: 'file',
|
||||
path: file.path,
|
||||
name: file.name,
|
||||
mtime: file.stat.mtime
|
||||
});
|
||||
}
|
||||
|
||||
// 收集直接子文件夹
|
||||
const subfolders = folder.children.filter((child): child is TFolder => child instanceof TFolder);
|
||||
|
||||
// 递归处理子文件夹
|
||||
for (const subfolder of subfolders) {
|
||||
// 递归收集子文件夹中的内容(包含子文件夹本身)
|
||||
const subItems = await this.collectFolderItems(subfolder.path);
|
||||
items.push(...subItems);
|
||||
}
|
||||
|
||||
// 添加当前文件夹本身,其 mtime 为所有子项目的最大 mtime
|
||||
let maxMtime = 0;
|
||||
for (const item of items) {
|
||||
if (item.mtime > maxMtime) {
|
||||
maxMtime = item.mtime;
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
type: 'folder',
|
||||
path: folderPath,
|
||||
name: folder.name,
|
||||
mtime: maxMtime > 0 ? maxMtime : 0
|
||||
});
|
||||
|
||||
return items;
|
||||
} catch (error) {
|
||||
console.error(`收集文件夹项目时出错: ${folderPath}`, error);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user