mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-05-10 00:47:46 +00:00
update metadata
This commit is contained in:
@@ -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 {
|
// 创建Markdown文件
|
||||||
content = await getFileOrFolderContent(
|
markdownFilePath = markdownFilePath || await this.createMarkdownFileForContent(
|
||||||
file,
|
file.path,
|
||||||
this.app.vault,
|
content,
|
||||||
this.app
|
false
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
content = await this.getFileOrFolderMetadata(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建Markdown文件
|
|
||||||
markdownFilePath = markdownFilePath || await this.createMarkdownFileForContent(
|
|
||||||
file.path,
|
|
||||||
content,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
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,21 +752,16 @@ 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 {
|
// 为当前文件创建Markdown文件
|
||||||
currentFileContent = await getFileOrFolderContent(
|
currentMarkdownFilePath = currentMarkdownFilePath || await this.createMarkdownFileForContent(
|
||||||
currentFile.file,
|
currentFile.file.path,
|
||||||
this.app.vault,
|
currentFileContent,
|
||||||
this.app
|
false
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
currentFileContent = await this.getFileOrFolderMetadata(currentFile.file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为当前文件创建Markdown文件
|
|
||||||
currentMarkdownFilePath = currentMarkdownFilePath || await this.createMarkdownFileForContent(
|
|
||||||
currentFile.file.path,
|
|
||||||
currentFileContent,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
// 添加当前文件到读取结果中
|
// 添加当前文件到读取结果中
|
||||||
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阶段
|
||||||
|
|||||||
Reference in New Issue
Block a user