update release logs

This commit is contained in:
duanfuxiang 2025-07-02 11:59:16 +08:00
parent fea5b382cf
commit 98bc810b86
5 changed files with 294 additions and 89 deletions

View File

@ -36,6 +36,8 @@ import {
LLMModelNotSetException,
} from '../../core/llm/exception'
import { TransformationType } from '../../core/transformations/trans-engine'
import { Workspace } from '../../database/json/workspace/types'
import { WorkspaceManager } from '../../database/json/workspace/WorkspaceManager'
import { useChatHistory } from '../../hooks/use-chat-history'
import { useCustomModes } from '../../hooks/use-custom-mode'
import { t } from '../../lang/helpers'
@ -139,6 +141,10 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
return new PromptGenerator(getRAGEngine, app, settings, diffStrategy, customModePrompts, customModeList, getMcpHub)
}, [getRAGEngine, app, settings, diffStrategy, customModePrompts, customModeList, getMcpHub])
const workspaceManager = useMemo(() => {
return new WorkspaceManager(app)
}, [app])
const [inputMessage, setInputMessage] = useState<ChatUserMessage>(() => {
const newMessage = getNewInputMessage(app, settings.defaultMention)
if (props.selectedBlock) {
@ -618,8 +624,24 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
}
};
} else if (toolArgs.type === 'list_files') {
const files = await listFilesAndFolders(app.vault, toolArgs.filepath)
const formattedContent = `[list_files for '${toolArgs.filepath}'] Result:\n${files.join('\n')}\n`;
// 获取当前工作区
let currentWorkspace: Workspace | null = null
if (settings.workspace && settings.workspace !== 'vault') {
currentWorkspace = await workspaceManager.findByName(String(settings.workspace))
}
const files = await listFilesAndFolders(
app.vault,
toolArgs.filepath,
toolArgs.recursive,
currentWorkspace || undefined,
app
)
const contextInfo = currentWorkspace
? `workspace '${currentWorkspace.name}'`
: toolArgs.filepath || 'vault root'
const formattedContent = `[list_files for '${contextInfo}'] Result:\n${files.join('\n')}\n`;
return {
type: 'list_files',
applyMsgId,

View File

@ -1,10 +1,9 @@
import { ChevronDown, FolderOpen, Plus, Tag, Trash2, X } from 'lucide-react'
import { App, TFile, TFolder } from 'obsidian'
import { App, TFolder } from 'obsidian'
import { useEffect, useRef, useState } from 'react'
import { Workspace, WorkspaceContent } from '../../database/json/workspace/types'
import { t } from '../../lang/helpers'
import { createDataviewManager } from '../../utils/dataview'
interface WorkspaceEditModalProps {
workspace?: Workspace
@ -22,10 +21,10 @@ const WorkspaceEditModal = ({
onSave
}: WorkspaceEditModalProps) => {
// 生成默认工作区名称
const getDefaultWorkspaceName = () => {
const getDefaultWorkspaceName = (): string => {
const now = new Date()
const date = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')}`
return t('workspace.editModal.defaultName', { date })
return String(t('workspace.editModal.defaultName', { date }))
}
const [name, setName] = useState(workspace?.name || getDefaultWorkspaceName())
@ -51,11 +50,6 @@ const WorkspaceEditModal = ({
const folders: string[] = []
const addFolder = (folder: TFolder) => {
folders.push(folder.path)
// folder.children.forEach(child => {
// if (child instanceof TFolder) {
// addFolder(child)
// }
// })
}
app.vault.getAllFolders(false).forEach(folder => {
@ -64,68 +58,17 @@ const WorkspaceEditModal = ({
setAvailableFolders(folders.sort())
// 使用 dataview 查询获取所有标签
const dataviewManager = createDataviewManager(app)
if (dataviewManager.isDataviewAvailable()) {
try {
const result = await dataviewManager.executeQuery('TABLE file.tags FROM ""')
if (result.success && result.data) {
const tags = new Set<string>()
// 解析结果中的标签
const lines = result.data.split('\n')
lines.forEach(line => {
if (line.includes('#')) {
const tagMatches = line.match(/#[a-zA-Z0-9\u4e00-\u9fa5_-]+/g)
if (tagMatches) {
tagMatches.forEach(tag => tags.add(tag))
}
}
})
setAvailableTags(Array.from(tags).sort())
} else {
// 回退到传统方法
fallbackToTraditionalTagQuery()
}
} catch (error) {
console.error('Dataview 查询失败:', error)
// 回退到传统方法
fallbackToTraditionalTagQuery()
}
} else {
// 回退到传统方法
fallbackToTraditionalTagQuery()
// 直接使用 Obsidian 的内置接口获取所有标签
try {
const tagsObject = app.metadataCache.getTags() // 获取所有标签 {'#tag1': 2, '#tag2': 4}
const tags = Object.keys(tagsObject).sort()
setAvailableTags(tags)
} catch (error) {
console.error('获取标签失败:', error)
setAvailableTags([])
}
}
// 传统方法获取标签(作为回退方案)
const fallbackToTraditionalTagQuery = () => {
const tags = new Set<string>()
app.vault.getAllLoadedFiles().forEach(file => {
if (file instanceof TFile) {
const cache = app.metadataCache.getFileCache(file)
if (cache?.tags) {
cache.tags.forEach(tag => {
tags.add(tag.tag)
})
}
if (cache?.frontmatter?.tags) {
const frontmatterTags = cache.frontmatter.tags
if (Array.isArray(frontmatterTags)) {
frontmatterTags.forEach(tag => tags.add(`#${tag}`))
} else if (typeof frontmatterTags === 'string') {
tags.add(`#${frontmatterTags}`)
}
}
}
})
setAvailableTags(Array.from(tags).sort())
}
loadAvailableOptions()
}, [isOpen, app])
@ -161,9 +104,15 @@ const WorkspaceEditModal = ({
}
})
// 搜索匹配的标签
// 搜索匹配的标签
availableTags.forEach(tag => {
if (tag.toLowerCase().includes(searchTerm)) {
// 改善搜索匹配逻辑,支持中文和更灵活的匹配
const tagForSearch = tag.toLowerCase()
const shouldMatch = searchTerm.startsWith('#')
? tagForSearch.includes(searchTerm.toLowerCase()) // 如果搜索词以#开头,直接匹配
: tagForSearch.includes(searchTerm) || tagForSearch.includes(`#${searchTerm}`) // 否则同时匹配带#和不带#的情况
if (shouldMatch) {
// 检查是否已存在
const exists = content.some(item =>
item.type === 'tag' && item.content === tag
@ -190,7 +139,7 @@ const WorkspaceEditModal = ({
})
}
setFilteredSuggestions(suggestions.slice(0, 10)) // 限制显示数量
setFilteredSuggestions(suggestions.slice(0, 20)) // 限制显示数量
setShowSuggestions(suggestions.length > 0)
setSelectedSuggestionIndex(-1)
}, [inputValue, availableFolders, availableTags, content])

View File

@ -81,6 +81,40 @@ export class DataviewManager {
}
}
/**
* Dataview JS
*/
async executeJs(js: string): Promise<DataviewQueryResult> {
const api = this.getAPI();
if (!api) {
return {
success: false,
error: "Dataview 插件未安装或未启用"
};
}
try {
const result = await api.evaluate(js);
if (result.successful) {
return {
success: true,
data: result.value
};
} else {
return {
success: false,
error: String(result.error || 'JS 查询失败')
};
}
} catch (error) {
console.error('Dataview JS 执行失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '未知错误'
};
}
}
/**
*
*/

View File

@ -1,5 +1,7 @@
import { minimatch } from 'minimatch'
import { TFile, TFolder, Vault } from 'obsidian'
import { App, TFile, TFolder, Vault } from 'obsidian'
import { Workspace } from '../database/json/workspace/types'
export const findFilesMatchingPatterns = async (
patterns: string[],
@ -11,27 +13,216 @@ export const findFilesMatchingPatterns = async (
})
}
export const listFilesAndFolders = async (vault: Vault, path: string) => {
const folder = vault.getAbstractFileByPath(path)
const childrenFiles: string[] = []
const childrenFolders: string[] = []
if (folder instanceof TFolder) {
folder.children.forEach((child) => {
if (child instanceof TFile) {
childrenFiles.push(child.path)
} else if (child instanceof TFolder) {
childrenFolders.push(child.path + "/")
/**
*
*/
export const getFilesWithTag = (targetTag: string, app: App): string[] => {
// 确保输入的标签以 '#' 开头
if (!targetTag.startsWith('#')) {
targetTag = '#' + targetTag;
}
const filesWithTag: string[] = []; // 文件路径列表
// 1. 获取 Vault 中所有的 Markdown 文件
const allFiles = app.vault.getMarkdownFiles();
// 2. 遍历所有文件
for (const file of allFiles) {
// 3. 获取当前文件的元数据缓存
// 这个操作非常快,因为它读取的是内存中的缓存
const cache = app.metadataCache.getFileCache(file);
// 检查缓存是否存在,以及缓存中是否有 tags 属性
if (cache?.tags) {
// 4. 在文件的标签数组中查找目标标签
// cache.tags 是一个 TagCache[] 数组,每个对象的格式为 { tag: string; position: Pos; }
const found = cache.tags.find(tagObj => tagObj.tag === targetTag);
if (found) {
filesWithTag.push(file.path);
}
}
}
return filesWithTag;
}
/**
*
*/
export const listFilesAndFolders = async (
vault: Vault,
path?: string,
recursive = false,
workspace?: Workspace,
app?: App
): Promise<string[]> => {
const result: string[] = []
// 如果有工作区,使用工作区内容
if (workspace && app) {
result.push(`[Workspace: ${workspace.name}]`)
result.push('')
// 按类型分组处理工作区内容
const folders = workspace.content.filter(c => c.type === 'folder')
const tags = workspace.content.filter(c => c.type === 'tag')
// 处理文件夹
if (folders.length > 0) {
result.push('=== FOLDERS ===')
for (const folderItem of folders) {
const folder = vault.getAbstractFileByPath(folderItem.content)
if (folder && folder instanceof TFolder) {
result.push(`├── ${folder.path}/`)
if (recursive) {
// 递归显示文件夹内容
const subContent = await listFolderContentsRecursively(folder, '│ ')
result.push(...subContent)
} else {
// 只显示第一层内容
const subContent = await listFolderContentsFirstLevel(folder, '│ ')
result.push(...subContent)
}
}
}
// 如果还有标签,添加空行分隔
if (tags.length > 0) {
result.push('')
}
}
// 处理标签(使用平铺格式,不使用树状结构)
if (tags.length > 0) {
result.push('=== TAGS ===')
for (const tagItem of tags) {
const files = getFilesWithTag(tagItem.content, app)
if (files.length > 0) {
result.push(`${tagItem.content} (${files.length} files):`)
// 使用简单的列表格式显示文件
files.forEach((file) => {
result.push(`${file}`)
})
// 在标签组之间添加空行
result.push('')
} else {
result.push(`${tagItem.content} (0 files)`)
result.push('')
}
}
}
return result
}
// 原有的单个路径逻辑(保持向后兼容)
const startPath = path && path !== '' && path !== '.' && path !== '/' ? path : ''
const folder = startPath ? vault.getAbstractFileByPath(startPath) : vault.getRoot()
if (!folder || !(folder instanceof TFolder)) {
return []
}
const listFolderContents = (currentFolder: TFolder, prefix = '') => {
const children = [...currentFolder.children].sort((a, b) => {
if (a instanceof TFolder && b instanceof TFile) return -1
if (a instanceof TFile && b instanceof TFolder) return 1
return a.name.localeCompare(b.name)
})
children.forEach((child, index) => {
const isLast = index === children.length - 1
const currentPrefix = prefix + (isLast ? '└── ' : '├── ')
const nextPrefix = prefix + (isLast ? ' ' : '│ ')
if (child instanceof TFolder) {
result.push(`${currentPrefix}${child.path}/`)
if (recursive) {
listFolderContents(child, nextPrefix)
}
} else if (child instanceof TFile) {
result.push(`${currentPrefix}${child.path}`)
}
})
return [...childrenFolders, ...childrenFiles]
}
return []
if (startPath) {
result.push(`${folder.path}/`)
listFolderContents(folder, '')
} else {
result.push(`${vault.getName()}/`)
listFolderContents(folder, '')
}
return result
}
/**
*
*/
const listFolderContentsRecursively = async (folder: TFolder, prefix: string): Promise<string[]> => {
const result: string[] = []
const children = [...folder.children].sort((a, b) => {
if (a instanceof TFolder && b instanceof TFile) return -1
if (a instanceof TFile && b instanceof TFolder) return 1
return a.name.localeCompare(b.name)
})
for (let i = 0; i < children.length; i++) {
const child = children[i]
const isLast = i === children.length - 1
const currentPrefix = prefix + (isLast ? '└── ' : '├── ')
const nextPrefix = prefix + (isLast ? ' ' : '│ ')
if (child instanceof TFolder) {
result.push(`${currentPrefix}${child.path}/`)
const subContent = await listFolderContentsRecursively(child, nextPrefix)
result.push(...subContent)
} else if (child instanceof TFile) {
result.push(`${currentPrefix}${child.path}`)
}
}
return result
}
/**
*
*/
const listFolderContentsFirstLevel = async (folder: TFolder, prefix: string): Promise<string[]> => {
const result: string[] = []
const children = [...folder.children].sort((a, b) => {
if (a instanceof TFolder && b instanceof TFile) return -1
if (a instanceof TFile && b instanceof TFolder) return 1
return a.name.localeCompare(b.name)
})
children.forEach((child, index) => {
const isLast = index === children.length - 1
const currentPrefix = prefix + (isLast ? '└── ' : '├── ')
if (child instanceof TFolder) {
result.push(`${currentPrefix}${child.path}/`)
} else if (child instanceof TFile) {
result.push(`${currentPrefix}${child.path}`)
}
})
return result
}
export const matchSearchFiles = async (vault: Vault, path: string, query: string, file_pattern: string) => {
}
export const regexSearchFiles = async (vault: Vault, path: string, regex: string, file_pattern: string) => {
}

View File

@ -413,7 +413,16 @@ export class PromptGenerator {
if (currentWorkspaceName && currentWorkspaceName !== 'vault') {
const workspace = await this.workspaceManager.findByName(currentWorkspaceName)
if (workspace) {
overview += `\n\n# Current Workspace\n${workspace.name}`
// 使用 listFilesAndFolders 获取详细的工作区结构
const { listFilesAndFolders } = await import('./glob-utils')
const workspaceStructure = await listFilesAndFolders(
this.app.vault,
undefined,
false, // 非递归,只显示第一层
workspace,
this.app
)
overview += `\n\n# Current Workspace\n${workspaceStructure.join('\n')}`
}
} else {
overview += `\n\n# Current Workspace\n${this.app.vault.getName()} (entire vault)`