update metadata

This commit is contained in:
duanfuxiang
2025-07-01 17:33:59 +08:00
parent 553f42652d
commit b69f1e3865

View File

@@ -1,4 +1,4 @@
import { App, MarkdownView, TAbstractFile, TFile, TFolder, Vault, getLanguage, htmlToMarkdown, normalizePath, requestUrl } from 'obsidian' import { App, TAbstractFile, TFile, TFolder, Vault, getLanguage, htmlToMarkdown, normalizePath, requestUrl } from 'obsidian'
import { editorStateToPlainText } from '../components/chat-view/chat-input/utils/editor-state-to-plain-text' import { editorStateToPlainText } from '../components/chat-view/chat-input/utils/editor-state-to-plain-text'
import { QueryProgressState } from '../components/chat-view/QueryProgress' import { QueryProgressState } from '../components/chat-view/QueryProgress'
@@ -278,15 +278,15 @@ export class PromptGenerator {
private async getCurrentNoteContext(): Promise<string | null> { private async getCurrentNoteContext(): Promise<string | null> {
const currentNote = this.app.workspace.getActiveFile() const currentNote = this.app.workspace.getActiveFile()
if (!currentNote) { if (!currentNote) {
return formatSection("Obsidian Current Note", "(No current note)") return formatSection("Current Tab Note", "(No current tab active)")
} }
const fileCache = this.app.metadataCache.getFileCache(currentNote) const fileCache = this.app.metadataCache.getFileCache(currentNote)
if (!fileCache) { if (!fileCache) {
return formatSection("Obsidian Current Note", currentNote.path) return formatSection("Current Tab Note", currentNote.path)
} }
let context = `Note Path: ${currentNote.path}` let context = ``
if (fileCache.frontmatter) { if (fileCache.frontmatter) {
const frontmatterString = Object.entries(fileCache.frontmatter) const frontmatterString = Object.entries(fileCache.frontmatter)
@@ -308,6 +308,96 @@ export class PromptGenerator {
return formatSection("Current Note Context", context) return formatSection("Current Note Context", context)
} }
private async getFileMetadataContext(file: TFile): Promise<string> {
const fileCache = this.app.metadataCache.getFileCache(file)
if (!fileCache) {
return `None Found`
}
let context = ``
if (fileCache.frontmatter) {
const frontmatterString = Object.entries(fileCache.frontmatter)
.filter(([key]) => key !== 'position')
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join('\n')
if (frontmatterString) {
context += `\n\n## Metadata (Frontmatter)\n${frontmatterString}`
}
}
if (fileCache.headings && fileCache.headings.length > 0) {
const outline = fileCache.headings
.map(h => `${' '.repeat(h.level - 1)}- ${h.heading}`)
.join('\n')
context += `\n\n## Outline\n${outline}`
}
return context
}
private async getFileOrFolderMetadata(path: TAbstractFile): Promise<string> {
if (path instanceof TFile) {
// 对于所有文件类型,都尝试获取元信息
const fileCache = this.app.metadataCache.getFileCache(path)
if (!fileCache) {
// 如果没有缓存的元信息,只返回文件路径
return `Note Path: ${path.path}`
}
let context = `Note Path: ${path.path}`
if (fileCache.frontmatter) {
const frontmatterString = Object.entries(fileCache.frontmatter)
.filter(([key]) => key !== 'position')
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join('\n')
if (frontmatterString) {
context += `\n\n## Metadata (Frontmatter)\n${frontmatterString}`
}
}
if (fileCache.headings && fileCache.headings.length > 0) {
const outline = fileCache.headings
.map(h => `${' '.repeat(h.level - 1)}- ${h.heading}`)
.join('\n')
context += `\n\n## Outline\n${outline}`
}
return context
} else if (path instanceof TFolder) {
const entries = path.children
let folderContent = ""
// 首先,构建文件夹的树状结构
entries.forEach((entry, index) => {
const isLast = index === entries.length - 1
const linePrefix = isLast ? "└── " : "├── "
if (entry instanceof TFile) {
folderContent += `${linePrefix}${entry.name}\n`
} else if (entry instanceof TFolder) {
folderContent += `${linePrefix}${entry.name}/\n`
} else {
folderContent += `${linePrefix}${entry.name}\n`
}
})
// 然后为文件夹内的所有文件附加元数据不仅仅是Markdown文件
const fileMetadataPromises = entries
.filter((entry): entry is TFile => entry instanceof TFile)
.map(async (file) => {
const metadata = await this.getFileMetadataContext(file)
return `<file_metadata path="${file.path}">\n${metadata}\n</file_metadata>`
})
const fileMetadataContents = (await Promise.all(fileMetadataPromises)).join("\n\n")
return `${folderContent}\n${fileMetadataContents}`.trim()
} else {
return `(Failed to read metadata of ${path.path})`
}
}
private async getNoteConnectivity(): Promise<string | null> { private async getNoteConnectivity(): Promise<string | null> {
const currentFile = this.app.workspace.getActiveFile() const currentFile = this.app.workspace.getActiveFile()
if (!currentFile) { if (!currentFile) {
@@ -365,15 +455,15 @@ export class PromptGenerator {
overview += `\n\n## Recently Edited Notes\n${recentFiles}` overview += `\n\n## Recently Edited Notes\n${recentFiles}`
} }
// @ts-ignore - getTags() is not in the public API but is widely used. // @ts-expect-error - getTags() is not in the public API but is widely used.
const tags = this.app.metadataCache.getTags() const tags: Record<string, number> = this.app.metadataCache.getTags()
const sortedTags = Object.entries(tags) const sortedTags = Object.entries(tags)
.sort(([, a], [, b]) => b - a) .sort(([, a], [, b]) => b - a)
.slice(0, 20) .slice(0, 10)
.map(([tag, count]) => `- ${tag} (${count})`) .map(([tag, count]) => `- ${tag} (${count})`)
.join('\n') .join('\n')
if (sortedTags) { if (sortedTags) {
overview += `\n\n## Global Tag Cloud (Top 20)\n${sortedTags}` overview += `\n\n## Global Tag Cloud (Top 10)\n${sortedTags}`
} }
return formatSection('Workspace Overview', overview) return formatSection('Workspace Overview', overview)
@@ -421,10 +511,7 @@ export class PromptGenerator {
fileReadResults?: Array<{ path: string, content: string }> fileReadResults?: Array<{ path: string, content: string }>
websiteReadResults?: Array<{ url: string, content: string }> websiteReadResults?: Array<{ url: string, content: string }>
}> { }> {
// Add environment details
// const environmentDetails = isNewChat
// ? await this.getEnvironmentDetails()
// : undefined
const environmentDetails = await this.getEnvironmentDetails() const environmentDetails = await this.getEnvironmentDetails()
// if isToolCallReturn, add read_file_content to promptContent // if isToolCallReturn, add read_file_content to promptContent
@@ -490,23 +577,18 @@ export class PromptGenerator {
let markdownFilePath = '' let markdownFilePath = ''
if (file.extension !== 'md' && mcpHub?.isBuiltInServerAvailable()) { if (file.extension !== 'md' && mcpHub?.isBuiltInServerAvailable()) {
[content, markdownFilePath] = await this.callMcpToolConvertDocument(file, mcpHub) [content, markdownFilePath] = await this.callMcpToolConvertDocument(file, mcpHub)
} else {
content = await getFileOrFolderContent(
file,
this.app.vault,
this.app
)
}
// 创建Markdown文件 // 创建Markdown文件
markdownFilePath = markdownFilePath || await this.createMarkdownFileForContent( markdownFilePath = markdownFilePath || await this.createMarkdownFileForContent(
file.path, file.path,
content, content,
false false
) )
} else {
content = await this.getFileOrFolderMetadata(file)
}
completedFiles++ completedFiles++
fileContents.push(`<file_content path="${file.path}">\n${content}\n</file_content>`) fileContents.push(`<user_mention_file path="${file.path}">\n${content}\n</user_mention_file>`)
fileContentsForProgress.push({ path: markdownFilePath, content }) fileContentsForProgress.push({ path: markdownFilePath, content })
allFileReadResults.push({ path: markdownFilePath, content }) allFileReadResults.push({ path: markdownFilePath, content })
} }
@@ -551,21 +633,10 @@ export class PromptGenerator {
completedFiles: completedFolders completedFiles: completedFolders
}) })
const content = await getFileOrFolderContent( const content = await this.getFileOrFolderMetadata(folder)
folder,
this.app.vault,
this.app
)
// // 为文件夹内容创建Markdown文件
// const markdownFilePath = await this.createMarkdownFileForContent(
// `${folder.path}/folder-contents`,
// content,
// false
// )
completedFolders++ completedFolders++
folderContents.push(`<folder_content path="${folder.path}">\n${content}\n</folder_content>`) folderContents.push(`<user_mention_folder path="${folder.path}">\n${content}\n</user_mention_folder>`)
folderContentsForProgress.push({ path: folder.path, content }) folderContentsForProgress.push({ path: folder.path, content })
allFileReadResults.push({ path: folder.path, content }) allFileReadResults.push({ path: folder.path, content })
} }
@@ -592,7 +663,7 @@ export class PromptGenerator {
? blocks ? blocks
.map(({ file, content, startLine, endLine }) => { .map(({ file, content, startLine, endLine }) => {
const content_with_line_numbers = addLineNumbers(content, startLine) const content_with_line_numbers = addLineNumbers(content, startLine)
return `<file_block_content location="${file.path}#L${startLine}-${endLine}">\n${content_with_line_numbers}\n</file_block_content>` return `<user_mention_blocks location="${file.path}#L${startLine}-${endLine}">\n${content_with_line_numbers}\n</user_mention_blocks>`
}) })
.join('\n') .join('\n')
: undefined : undefined
@@ -654,7 +725,7 @@ export class PromptGenerator {
const urlContentsPrompt = urlContents.length > 0 const urlContentsPrompt = urlContents.length > 0
? urlContents ? urlContents
.map(({ url, content }) => ( .map(({ url, content }) => (
`<file_content path="${url}">\n${content}\n</file_content>` `<user_mention_url path="${url}">\n${content}\n</user_mention_url>`
)) ))
.join('\n') : undefined .join('\n') : undefined
@@ -681,20 +752,15 @@ export class PromptGenerator {
const [mcpCurrFileContent, mcpCurrFileContentPath] = await this.callMcpToolConvertDocument(currentFile.file, mcpHub) const [mcpCurrFileContent, mcpCurrFileContentPath] = await this.callMcpToolConvertDocument(currentFile.file, mcpHub)
currentFileContent = mcpCurrFileContent currentFileContent = mcpCurrFileContent
currentMarkdownFilePath = mcpCurrFileContentPath currentMarkdownFilePath = mcpCurrFileContentPath
} else {
currentFileContent = await getFileOrFolderContent(
currentFile.file,
this.app.vault,
this.app
)
}
// 为当前文件创建Markdown文件 // 为当前文件创建Markdown文件
currentMarkdownFilePath = currentMarkdownFilePath || await this.createMarkdownFileForContent( currentMarkdownFilePath = currentMarkdownFilePath || await this.createMarkdownFileForContent(
currentFile.file.path, currentFile.file.path,
currentFileContent, currentFileContent,
false false
) )
} else {
currentFileContent = await this.getFileOrFolderMetadata(currentFile.file)
}
// 添加当前文件到读取结果中 // 添加当前文件到读取结果中
allFileReadResults.push({ path: currentMarkdownFilePath, content: currentFileContent }) allFileReadResults.push({ path: currentMarkdownFilePath, content: currentFileContent })
@@ -719,7 +785,7 @@ export class PromptGenerator {
shouldIncludeCurrentFile = true shouldIncludeCurrentFile = true
} else { } else {
// For continuing chats, check if current file content already exists in history // For continuing chats, check if current file content already exists in history
const currentFilePromptTag = `<current_file_content path="${currentFile.file.path}">` const currentFilePromptTag = `<current_tab_note path="${currentFile.file.path}">`
const hasCurrentFileInHistory = messages?.some((msg) => { const hasCurrentFileInHistory = messages?.some((msg) => {
if (msg.role === 'user' && msg.promptContent) { if (msg.role === 'user' && msg.promptContent) {
if (typeof msg.promptContent === 'string') { if (typeof msg.promptContent === 'string') {
@@ -743,7 +809,7 @@ export class PromptGenerator {
} }
const currentFileContentPrompt = shouldIncludeCurrentFile const currentFileContentPrompt = shouldIncludeCurrentFile
? `<current_file_content path="${currentFile.file.path}">\n${currentFileContent}\n</current_file_content>` ? `<current_tab_note path="${currentFile.file.path}">\n${currentFileContent}\n</current_tab_note>`
: undefined : undefined
// Count file and folder tokens // Count file and folder tokens
@@ -759,15 +825,15 @@ export class PromptGenerator {
if (isOverThreshold) { if (isOverThreshold) {
console.debug("isOverThreshold", isOverThreshold) console.debug("isOverThreshold", isOverThreshold)
fileContentsPrompts = files.map((file) => { fileContentsPrompts = files.map((file) => {
return `<file_content path="${file.path}">\n(Content omitted due to token limit. Relevant sections will be provided by semantic search below.)\n</file_content>` return `<user_mention_file path="${file.path}">\n(Content omitted due to token limit.\n</user_mention_file>`
}).join('\n') }).join('\n')
folderContentsPrompts = (await Promise.all(folders.map(async (folder) => { folderContentsPrompts = (await Promise.all(folders.map(async (folder) => {
const tree_content = await getFolderTreeContent(folder) const tree_content = await getFolderTreeContent(folder)
return `<folder_content path="${folder.path}">\n${tree_content}\n(Content omitted due to token limit. Relevant sections will be provided by semantic search below.)\n</folder_content>` return `<user_mention_folder path="${folder.path}">\n${tree_content}\n(Content omitted due to token limit.\n</user_mention_folder>`
}))).join('\n') }))).join('\n')
} }
const shouldUseRAG = useVaultSearch || isOverThreshold const shouldUseRAG = useVaultSearch
let similaritySearchContents let similaritySearchContents
if (shouldUseRAG) { if (shouldUseRAG) {
// 重置进度状态准备进入RAG阶段 // 重置进度状态准备进入RAG阶段