更新 TransEngine 以支持嵌入管理器,优化日志输出,添加删除确认对话框功能,改进 ChatView 组件的文件管理和上下文信息处理,确保更好的用户体验和代码可读性。

This commit is contained in:
duanfuxiang 2025-07-05 16:24:49 +08:00
parent bbd89fbfa4
commit a2fcb7c20f
4 changed files with 242 additions and 45 deletions

View File

@ -630,17 +630,17 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
if (settings.workspace && settings.workspace !== 'vault') { if (settings.workspace && settings.workspace !== 'vault') {
currentWorkspace = await workspaceManager.findByName(String(settings.workspace)) currentWorkspace = await workspaceManager.findByName(String(settings.workspace))
} }
const files = await listFilesAndFolders( const files = await listFilesAndFolders(
app.vault, app.vault,
toolArgs.filepath, toolArgs.filepath,
toolArgs.recursive, toolArgs.recursive,
currentWorkspace || undefined, currentWorkspace || undefined,
app app
) )
const contextInfo = currentWorkspace const contextInfo = currentWorkspace
? `workspace '${currentWorkspace.name}'` ? `workspace '${currentWorkspace.name}'`
: toolArgs.filepath || 'vault root' : toolArgs.filepath || 'vault root'
const formattedContent = `[list_files for '${contextInfo}'] Result:\n${files.join('\n')}\n`; const formattedContent = `[list_files for '${contextInfo}'] Result:\n${files.join('\n')}\n`;
return { return {
@ -710,7 +710,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
if (settings.workspace && settings.workspace !== 'vault') { if (settings.workspace && settings.workspace !== 'vault') {
currentWorkspace = await workspaceManager.findByName(String(settings.workspace)) currentWorkspace = await workspaceManager.findByName(String(settings.workspace))
} }
const snippets = await semanticSearchFiles( const snippets = await semanticSearchFiles(
await getRAGEngine(), await getRAGEngine(),
toolArgs.query, toolArgs.query,
@ -719,9 +719,9 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
app, app,
await getTransEngine() await getTransEngine()
) )
const contextInfo = currentWorkspace const contextInfo = currentWorkspace
? `workspace '${currentWorkspace.name}'` ? `workspace '${currentWorkspace.name}'`
: toolArgs.filepath || 'vault' : toolArgs.filepath || 'vault'
const formattedContent = `[semantic_search_files for '${contextInfo}'] Result:\n${snippets}\n`; const formattedContent = `[semantic_search_files for '${contextInfo}'] Result:\n${snippets}\n`;
return { return {
@ -842,14 +842,14 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
// 执行 Dataview 查询 // 执行 Dataview 查询
const result = await dataviewManager.executeQuery(toolArgs.query) const result = await dataviewManager.executeQuery(toolArgs.query)
let formattedContent: string; let formattedContent: string;
if (result.success) { if (result.success) {
formattedContent = `[dataview_query] 查询成功:\n${result.data}`; formattedContent = `[dataview_query] 查询成功:\n${result.data}`;
} else { } else {
formattedContent = `[dataview_query] 查询失败:\n${result.error}`; formattedContent = `[dataview_query] 查询失败:\n${result.error}`;
} }
return { return {
type: 'dataview_query', type: 'dataview_query',
applyMsgId, applyMsgId,
@ -893,7 +893,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
// Build the result message // Build the result message
let formattedContent = `[${toolArgs.transformation}] transformation complete:\n\n${transformationResult.result}`; let formattedContent = `[${toolArgs.transformation}] transformation complete:\n\n${transformationResult.result}`;
if (transformationResult.truncated) { if (transformationResult.truncated) {
formattedContent += `\n\n*Note: The original content was too long (${transformationResult.originalTokens} tokens) and was truncated to ${transformationResult.processedTokens} tokens for processing.*`; formattedContent += `\n\n*Note: The original content was too long (${transformationResult.originalTokens} tokens) and was truncated to ${transformationResult.processedTokens} tokens for processing.*`;
} }
@ -930,7 +930,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
} else if (toolArgs.type === 'manage_files') { } else if (toolArgs.type === 'manage_files') {
try { try {
const results: string[] = []; const results: string[] = [];
// 处理每个文件操作 // 处理每个文件操作
for (const operation of toolArgs.operations) { for (const operation of toolArgs.operations) {
switch (operation.action) { switch (operation.action) {
@ -945,7 +945,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
} }
} }
break; break;
case 'move': case 'move':
if (operation.source_path && operation.destination_path) { if (operation.source_path && operation.destination_path) {
// 使用 getAbstractFileByPath 而不是 getFileByPath这样可以获取文件和文件夹 // 使用 getAbstractFileByPath 而不是 getFileByPath这样可以获取文件和文件夹
@ -967,7 +967,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
} }
} }
break; break;
case 'delete': case 'delete':
if (operation.path) { if (operation.path) {
// 使用 getAbstractFileByPath 而不是 getFileByPath // 使用 getAbstractFileByPath 而不是 getFileByPath
@ -989,7 +989,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
} }
} }
break; break;
case 'copy': case 'copy':
if (operation.source_path && operation.destination_path) { if (operation.source_path && operation.destination_path) {
// 文件夹复制比较复杂,需要递归处理 // 文件夹复制比较复杂,需要递归处理
@ -1016,7 +1016,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
} }
} }
break; break;
case 'rename': case 'rename':
if (operation.path && operation.new_name) { if (operation.path && operation.new_name) {
// 使用 getAbstractFileByPath 而不是 getFileByPath // 使用 getAbstractFileByPath 而不是 getFileByPath
@ -1031,14 +1031,14 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
} }
} }
break; break;
default: default:
results.push(`❌ 不支持的操作类型: ${String(operation.action)}`); results.push(`❌ 不支持的操作类型: ${String(operation.action)}`);
} }
} }
const formattedContent = `[manage_files] 文件管理操作结果:\n${results.join('\n')}`; const formattedContent = `[manage_files] 文件管理操作结果:\n${results.join('\n')}`;
return { return {
type: 'manage_files', type: 'manage_files',
applyMsgId, applyMsgId,
@ -1165,15 +1165,15 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
} }
const activeFile = app.workspace.getActiveFile() const activeFile = app.workspace.getActiveFile()
// 🎯 关键优化:只有当活动文件真正发生变化时才更新 // 🎯 关键优化:只有当活动文件真正发生变化时才更新
if (activeFile === currentActiveFileRef.current) { if (activeFile === currentActiveFileRef.current) {
return // 文件没有变化,不需要更新 return // 文件没有变化,不需要更新
} }
// 更新文件引用 // 更新文件引用
currentActiveFileRef.current = activeFile currentActiveFileRef.current = activeFile
if (!activeFile) return if (!activeFile) return
const mentionable: Omit<MentionableCurrentFile, 'id'> = { const mentionable: Omit<MentionableCurrentFile, 'id'> = {
@ -1312,15 +1312,15 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
</button> </button>
<button <button
onClick={() => { onClick={() => {
if (tab === 'workspace') { if (tab === 'insights') {
setTab('chat') setTab('chat')
} else { } else {
setTab('workspace') setTab('insights')
} }
}} }}
className="infio-chat-list-dropdown" className="infio-chat-list-dropdown"
> >
<Box size={18} color={tab === 'workspace' ? 'var(--text-accent)' : 'var(--text-color)'} /> <Brain size={18} color={tab === 'insights' ? 'var(--text-accent)' : 'var(--text-color)'} />
</button> </button>
<button <button
onClick={() => { onClick={() => {
@ -1334,6 +1334,18 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
> >
<Search size={18} color={tab === 'search' ? 'var(--text-accent)' : 'var(--text-color)'} /> <Search size={18} color={tab === 'search' ? 'var(--text-accent)' : 'var(--text-color)'} />
</button> </button>
<button
onClick={() => {
if (tab === 'workspace') {
setTab('chat')
} else {
setTab('workspace')
}
}}
className="infio-chat-list-dropdown"
>
<Box size={18} color={tab === 'workspace' ? 'var(--text-accent)' : 'var(--text-color)'} />
</button>
<button <button
onClick={() => { onClick={() => {
// switch between chat and prompts // switch between chat and prompts
@ -1372,18 +1384,6 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
> >
<Server size={18} color={tab === 'mcp' ? 'var(--text-accent)' : 'var(--text-color)'} /> <Server size={18} color={tab === 'mcp' ? 'var(--text-accent)' : 'var(--text-color)'} />
</button> </button>
<button
onClick={() => {
if (tab === 'insights') {
setTab('chat')
} else {
setTab('insights')
}
}}
className="infio-chat-list-dropdown"
>
<Brain size={18} color={tab === 'insights' ? 'var(--text-accent)' : 'var(--text-color)'} />
</button>
</div> </div>
</div> </div>
{/* main view */} {/* main view */}

View File

@ -50,6 +50,8 @@ const InsightView = () => {
// 删除洞察状态 // 删除洞察状态
const [isDeleting, setIsDeleting] = useState(false) const [isDeleting, setIsDeleting] = useState(false)
const [deletingInsightId, setDeletingInsightId] = useState<number | null>(null) const [deletingInsightId, setDeletingInsightId] = useState<number | null>(null)
// 确认对话框状态
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const loadInsights = useCallback(async () => { const loadInsights = useCallback(async () => {
setIsLoading(true) setIsLoading(true)
@ -243,6 +245,11 @@ const InsightView = () => {
} }
}, [getTransEngine, settings, workspaceManager, loadInsights]) }, [getTransEngine, settings, workspaceManager, loadInsights])
// 确认删除工作区洞察
const handleDeleteWorkspaceInsights = useCallback(() => {
setShowDeleteConfirm(true)
}, [])
// 删除工作区洞察 // 删除工作区洞察
const deleteWorkspaceInsights = useCallback(async () => { const deleteWorkspaceInsights = useCallback(async () => {
setIsDeleting(true) setIsDeleting(true)
@ -280,6 +287,17 @@ const InsightView = () => {
} }
}, [getTransEngine, settings, workspaceManager, loadInsights]) }, [getTransEngine, settings, workspaceManager, loadInsights])
// 确认删除工作区洞察
const confirmDeleteWorkspaceInsights = useCallback(async () => {
setShowDeleteConfirm(false)
await deleteWorkspaceInsights()
}, [deleteWorkspaceInsights])
// 取消删除确认
const cancelDeleteConfirm = useCallback(() => {
setShowDeleteConfirm(false)
}, [])
// 删除单个洞察 // 删除单个洞察
const deleteSingleInsight = useCallback(async (insightId: number) => { const deleteSingleInsight = useCallback(async (insightId: number) => {
setDeletingInsightId(insightId) setDeletingInsightId(insightId)
@ -482,7 +500,7 @@ const InsightView = () => {
{isInitializing ? '初始化中...' : '初始化洞察'} {isInitializing ? '初始化中...' : '初始化洞察'}
</button> </button>
<button <button
onClick={deleteWorkspaceInsights} onClick={handleDeleteWorkspaceInsights}
disabled={isDeleting || isLoading || isInitializing} disabled={isDeleting || isLoading || isInitializing}
className="obsidian-insight-delete-btn" className="obsidian-insight-delete-btn"
title="删除当前工作区的所有转换和洞察" title="删除当前工作区的所有转换和洞察"
@ -564,6 +582,42 @@ const InsightView = () => {
</div> </div>
)} )}
{/* 确认删除对话框 */}
{showDeleteConfirm && (
<div className="obsidian-confirm-dialog-overlay">
<div className="obsidian-confirm-dialog">
<div className="obsidian-confirm-dialog-header">
<h3></h3>
</div>
<div className="obsidian-confirm-dialog-body">
<p>
</p>
<p className="obsidian-confirm-dialog-warning">
</p>
<div className="obsidian-confirm-dialog-scope">
<strong>:</strong> {currentScope}
</div>
</div>
<div className="obsidian-confirm-dialog-footer">
<button
onClick={cancelDeleteConfirm}
className="obsidian-confirm-dialog-cancel-btn"
>
</button>
<button
onClick={confirmDeleteWorkspaceInsights}
className="obsidian-confirm-dialog-confirm-btn"
>
</button>
</div>
</div>
</div>
)}
{/* 洞察结果 */} {/* 洞察结果 */}
<div className="obsidian-insight-results"> <div className="obsidian-insight-results">
{!isLoading && insightGroupedResults.length > 0 && ( {!isLoading && insightGroupedResults.length > 0 && (
@ -1098,6 +1152,117 @@ const InsightView = () => {
color: var(--text-faint); color: var(--text-faint);
font-style: italic; font-style: italic;
} }
/* 确认对话框样式 */
.obsidian-confirm-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.obsidian-confirm-dialog {
background-color: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-l);
box-shadow: var(--shadow-l);
max-width: 400px;
width: 90%;
max-height: 80vh;
overflow: hidden;
}
.obsidian-confirm-dialog-header {
padding: 16px 20px;
border-bottom: 1px solid var(--background-modifier-border);
background-color: var(--background-secondary);
}
.obsidian-confirm-dialog-header h3 {
margin: 0;
color: var(--text-normal);
font-size: var(--font-ui-large);
font-weight: 600;
}
.obsidian-confirm-dialog-body {
padding: 20px;
color: var(--text-normal);
font-size: var(--font-ui-medium);
line-height: 1.5;
}
.obsidian-confirm-dialog-body p {
margin: 0 0 12px 0;
}
.obsidian-confirm-dialog-warning {
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-s);
padding: 12px;
margin: 12px 0;
color: var(--text-error);
font-size: var(--font-ui-small);
font-weight: 500;
}
.obsidian-confirm-dialog-scope {
background-color: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-s);
padding: 8px 12px;
margin: 12px 0 0 0;
font-size: var(--font-ui-small);
color: var(--text-muted);
}
.obsidian-confirm-dialog-footer {
padding: 16px 20px;
border-top: 1px solid var(--background-modifier-border);
background-color: var(--background-secondary);
display: flex;
justify-content: flex-end;
gap: 12px;
}
.obsidian-confirm-dialog-cancel-btn {
padding: 8px 16px;
background-color: var(--interactive-normal);
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-s);
color: var(--text-normal);
font-size: var(--font-ui-small);
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
}
.obsidian-confirm-dialog-cancel-btn:hover {
background-color: var(--interactive-hover);
}
.obsidian-confirm-dialog-confirm-btn {
padding: 8px 16px;
background-color: #dc3545;
border: 1px solid #dc3545;
border-radius: var(--radius-s);
color: white;
font-size: var(--font-ui-small);
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
}
.obsidian-confirm-dialog-confirm-btn:hover {
background-color: #c82333;
border-color: #c82333;
}
`} `}
</style> </style>
</div> </div>

View File

@ -20,6 +20,15 @@ import { SIMPLE_SUMMARY_DESCRIPTION, SIMPLE_SUMMARY_PROMPT } from '../prompts/tr
import { TABLE_OF_CONTENTS_DESCRIPTION, TABLE_OF_CONTENTS_PROMPT } from '../prompts/transformations/table-of-contents'; import { TABLE_OF_CONTENTS_DESCRIPTION, TABLE_OF_CONTENTS_PROMPT } from '../prompts/transformations/table-of-contents';
import { getEmbeddingModel } from '../rag/embedding'; import { getEmbeddingModel } from '../rag/embedding';
// EmbeddingManager 类型定义
type EmbeddingManager = {
modelLoaded: boolean
currentModel: string | null
loadModel(modelId: string, useGpu: boolean): Promise<any>
embed(text: string): Promise<{ vec: number[] }>
embedBatch(texts: string[]): Promise<{ vec: number[] }[]>
}
/** /**
* *
*/ */
@ -298,21 +307,24 @@ export class TransEngine {
private llmManager: LLMManager; private llmManager: LLMManager;
private insightManager: InsightManager | null = null; private insightManager: InsightManager | null = null;
private embeddingModel: EmbeddingModel | null = null; private embeddingModel: EmbeddingModel | null = null;
private embeddingManager?: EmbeddingManager;
constructor( constructor(
app: App, app: App,
settings: InfioSettings, settings: InfioSettings,
dbManager: DBManager, dbManager: DBManager,
embeddingManager?: EmbeddingManager,
) { ) {
this.app = app; this.app = app;
this.settings = settings; this.settings = settings;
this.llmManager = new LLMManager(settings); this.llmManager = new LLMManager(settings);
this.insightManager = dbManager.getInsightManager(); this.insightManager = dbManager.getInsightManager();
this.embeddingManager = embeddingManager;
// 初始化 embedding model // 初始化 embedding model
if (settings.embeddingModelId && settings.embeddingModelId.trim() !== '') { if (settings.embeddingModelId && settings.embeddingModelId.trim() !== '') {
try { try {
this.embeddingModel = getEmbeddingModel(settings); this.embeddingModel = getEmbeddingModel(settings, embeddingManager);
} catch (error) { } catch (error) {
console.warn('Failed to initialize embedding model:', error); console.warn('Failed to initialize embedding model:', error);
this.embeddingModel = null; this.embeddingModel = null;
@ -334,7 +346,7 @@ export class TransEngine {
// 重新初始化 embedding model // 重新初始化 embedding model
if (settings.embeddingModelId && settings.embeddingModelId.trim() !== '') { if (settings.embeddingModelId && settings.embeddingModelId.trim() !== '') {
try { try {
this.embeddingModel = getEmbeddingModel(settings); this.embeddingModel = getEmbeddingModel(settings, this.embeddingManager);
} catch (error) { } catch (error) {
console.warn('Failed to initialize embedding model:', error); console.warn('Failed to initialize embedding model:', error);
this.embeddingModel = null; this.embeddingModel = null;
@ -395,7 +407,12 @@ export class TransEngine {
> { > {
// 如果没有必要的参数,跳过缓存检查 // 如果没有必要的参数,跳过缓存检查
if (!this.embeddingModel || !this.insightManager) { if (!this.embeddingModel || !this.insightManager) {
console.log("no embeddingModel or insightManager"); console.log("TransEngine: 跳过缓存检查");
console.log("embeddingModel:", this.embeddingModel ? "已初始化" : "未初始化");
console.log("insightManager:", this.insightManager ? "已初始化" : "未初始化");
console.log("embeddingModelId:", this.settings.embeddingModelId);
console.log("embeddingModelProvider:", this.settings.embeddingModelProvider);
console.log("提示:请在插件设置中配置嵌入模型,或点击'一键配置'按钮");
return { return {
success: true, success: true,
foundCache: false foundCache: false
@ -488,6 +505,11 @@ export class TransEngine {
contentType: 'document' | 'tag' | 'folder' contentType: 'document' | 'tag' | 'folder'
): Promise<void> { ): Promise<void> {
if (!this.embeddingModel || !this.insightManager) { if (!this.embeddingModel || !this.insightManager) {
console.log("TransEngine: 无法保存到数据库");
console.log("embeddingModel:", this.embeddingModel ? "已初始化" : "未初始化");
console.log("insightManager:", this.insightManager ? "已初始化" : "未初始化");
console.log("embeddingModelId:", this.settings.embeddingModelId);
console.log("embeddingModelProvider:", this.settings.embeddingModelProvider);
return; return;
} }
@ -1027,6 +1049,11 @@ export class TransEngine {
> { > {
if (!this.embeddingModel || !this.insightManager) { if (!this.embeddingModel || !this.insightManager) {
console.warn('TransEngine: embedding model or insight manager not available') console.warn('TransEngine: embedding model or insight manager not available')
console.log("embeddingModel:", this.embeddingModel ? "已初始化" : "未初始化");
console.log("insightManager:", this.insightManager ? "已初始化" : "未初始化");
console.log("embeddingModelId:", this.settings.embeddingModelId);
console.log("embeddingModelProvider:", this.settings.embeddingModelProvider);
console.log("提示:请在插件设置中配置嵌入模型,或点击'一键配置'按钮");
return [] return []
} }
@ -1082,6 +1109,11 @@ export class TransEngine {
async getAllInsights(): Promise<Omit<import('../../database/schema').SelectSourceInsight, 'embedding'>[]> { async getAllInsights(): Promise<Omit<import('../../database/schema').SelectSourceInsight, 'embedding'>[]> {
if (!this.embeddingModel || !this.insightManager) { if (!this.embeddingModel || !this.insightManager) {
console.warn('TransEngine: embedding model or insight manager not available') console.warn('TransEngine: embedding model or insight manager not available')
console.log("embeddingModel:", this.embeddingModel ? "已初始化" : "未初始化");
console.log("insightManager:", this.insightManager ? "已初始化" : "未初始化");
console.log("embeddingModelId:", this.settings.embeddingModelId);
console.log("embeddingModelProvider:", this.settings.embeddingModelProvider);
console.log("提示:请在插件设置中配置嵌入模型,或点击'一键配置'按钮");
return [] return []
} }

View File

@ -640,7 +640,7 @@ export default class InfioPlugin extends Plugin {
if (!this.transEngineInitPromise) { if (!this.transEngineInitPromise) {
this.transEngineInitPromise = (async () => { this.transEngineInitPromise = (async () => {
const dbManager = await this.getDbManager() const dbManager = await this.getDbManager()
this.transEngine = new TransEngine(this.app, this.settings, dbManager) this.transEngine = new TransEngine(this.app, this.settings, dbManager, this.embeddingManager)
return this.transEngine return this.transEngine
})() })()
} }