From e853605ceabfc715884750aae9b8391e8c62a3cb Mon Sep 17 00:00:00 2001 From: wizardchen Date: Sun, 23 Nov 2025 17:27:30 +0800 Subject: [PATCH 1/3] feat: Enhance FAQ management and search functionality - Added a new menu item for searching FAQs directly from the menu, improving user accessibility. - Implemented batch action options for selected FAQs, allowing users to perform bulk operations such as enabling, disabling, and deleting entries. - Updated localization strings to support new features and improve user experience across multiple languages. - Enhanced the handling of FAQ selection changes to dynamically update the UI based on user interactions. --- frontend/src/components/menu.vue | 84 +++++++++++-- frontend/src/i18n/locales/en-US.ts | 3 + frontend/src/i18n/locales/ru-RU.ts | 3 + frontend/src/i18n/locales/zh-CN.ts | 3 + frontend/src/types/tool-results.ts | 31 ++++- frontend/src/utils/tool-icons.ts | 2 + .../chat/components/AgentStreamDisplay.vue | 28 +++++ .../chat/components/ToolResultRenderer.vue | 10 +- internal/agent/tools/definitions.go | 6 +- internal/application/service/agent_service.go | 8 +- internal/application/service/knowledge.go | 28 +++-- internal/application/service/knowledgebase.go | 111 +++++++++++++++++- internal/application/service/session.go | 2 +- internal/handler/knowledge.go | 14 ++- internal/handler/tenant.go | 4 +- internal/types/interfaces/knowledge.go | 1 + 16 files changed, 303 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/menu.vue b/frontend/src/components/menu.vue index 0aa41486..ae82f0cc 100644 --- a/frontend/src/components/menu.vue +++ b/frontend/src/components/menu.vue @@ -758,13 +758,39 @@ const handleDocFolderChange = async (event: Event) => { return } + // 检查是否启用了VLM + const vlmEnabled = currentKbInfo.value?.vlm_config?.enabled || false + // 过滤有效文件(文件夹上传始终使用静默模式) const validFiles: File[] = [] let invalidCount = 0 + let hiddenFileCount = 0 + let imageFilteredCount = 0 for (let i = 0; i < files.length; i++) { const file = files[i] - // 文件夹上传时始终静默过滤 + const relativePath = (file as any).webkitRelativePath || file.name + + // 1. 过滤隐藏文件和隐藏文件夹 + // 检查路径中是否包含以 . 开头的文件或文件夹 + const pathParts = relativePath.split('/') + const hasHiddenComponent = pathParts.some((part: string) => part.startsWith('.')) + if (hasHiddenComponent) { + hiddenFileCount++ + continue + } + + // 2. 如果未启用VLM,过滤图片文件 + if (!vlmEnabled) { + const fileExt = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase() + const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'] + if (imageTypes.includes(fileExt)) { + imageFilteredCount++ + continue + } + } + + // 3. 文件类型验证(文件夹上传时始终静默过滤) if (kbFileTypeVerification(file, true)) { invalidCount++ } else { @@ -774,8 +800,19 @@ const handleDocFolderChange = async (event: Event) => { // 如果没有有效文件,直接返回 if (validFiles.length === 0) { - if (invalidCount > 0) { - MessagePlugin.error(t('knowledgeBase.noValidFilesInFolder', { total: files.length })) + const totalFiltered = invalidCount + hiddenFileCount + imageFilteredCount + if (totalFiltered > 0) { + let filterReasons = [] + if (hiddenFileCount > 0) { + filterReasons.push(t('knowledgeBase.hiddenFilesFiltered', { count: hiddenFileCount })) + } + if (imageFilteredCount > 0) { + filterReasons.push(t('knowledgeBase.imagesFilteredNoVLM', { count: imageFilteredCount })) + } + if (invalidCount > 0) { + filterReasons.push(t('knowledgeBase.invalidFilesFiltered', { count: invalidCount })) + } + MessagePlugin.warning(t('knowledgeBase.noValidFilesInFolder', { total: files.length }) + '\n' + filterReasons.join('\n')) } else { MessagePlugin.error(t('knowledgeBase.noValidFiles')) } @@ -785,11 +822,24 @@ const handleDocFolderChange = async (event: Event) => { // 显示过滤后的上传提示 const totalCount = validFiles.length - if (invalidCount > 0) { - MessagePlugin.info(t('knowledgeBase.uploadingValidFiles', { - valid: totalCount, - total: files.length - })) + const totalFiltered = invalidCount + hiddenFileCount + imageFilteredCount + if (totalFiltered > 0) { + let filterInfo = [] + if (hiddenFileCount > 0) { + filterInfo.push(t('knowledgeBase.hiddenFilesFiltered', { count: hiddenFileCount })) + } + if (imageFilteredCount > 0) { + filterInfo.push(t('knowledgeBase.imagesFilteredNoVLM', { count: imageFilteredCount })) + } + if (invalidCount > 0) { + filterInfo.push(t('knowledgeBase.invalidFilesFiltered', { count: invalidCount })) + } + MessagePlugin.info( + t('knowledgeBase.uploadingValidFiles', { + valid: totalCount, + total: files.length + }) + '\n' + filterInfo.join(', ') + ) } else { MessagePlugin.info(t('knowledgeBase.uploadingFolder', { total: totalCount })) } @@ -801,7 +851,23 @@ const handleDocFolderChange = async (event: Event) => { for (const file of validFiles) { try { - await uploadKnowledgeFile(kbId, { file }) + // 获取文件的相对路径(webkitRelativePath) + const relativePath = (file as any).webkitRelativePath + let fileName = file.name + + // 如果存在相对路径,提取子文件夹路径并拼接到文件名前 + if (relativePath) { + // webkitRelativePath 格式: "文件夹名/子文件夹/文件名.ext" + // 我们需要去掉第一层文件夹名,保留子路径 + const pathParts = relativePath.split('/') + if (pathParts.length > 2) { + // 有子文件夹,拼接子路径到文件名 + const subPath = pathParts.slice(1, -1).join('/') // 去掉顶层文件夹和文件名本身 + fileName = `${subPath}/${file.name}` + } + } + + await uploadKnowledgeFile(kbId, { file, fileName }) successCount++ } catch (error: any) { failCount++ diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 2eb70ec7..5f6b3cdc 100644 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -59,6 +59,9 @@ export default { noValidFiles: 'No valid files', noValidFilesInFolder: 'All {total} files in folder are unsupported', noValidFilesSelected: 'All selected files are unsupported', + hiddenFilesFiltered: 'Filtered {count} hidden files', + imagesFilteredNoVLM: 'Filtered {count} image files (VLM not enabled)', + invalidFilesFiltered: 'Filtered {count} unsupported files', unsupportedFileType: 'Unsupported file type', failedFilesList: 'Failed files:', andMoreFiles: '...and {count} more files', diff --git a/frontend/src/i18n/locales/ru-RU.ts b/frontend/src/i18n/locales/ru-RU.ts index 671b7025..ef145bdd 100644 --- a/frontend/src/i18n/locales/ru-RU.ts +++ b/frontend/src/i18n/locales/ru-RU.ts @@ -59,6 +59,9 @@ export default { noValidFiles: 'Нет действительных файлов', noValidFilesInFolder: 'Все {total} файлов в папке не поддерживаются', noValidFilesSelected: 'Все выбранные файлы не поддерживаются', + hiddenFilesFiltered: 'Отфильтровано {count} скрытых файлов', + imagesFilteredNoVLM: 'Отфильтровано {count} изображений (VLM не включен)', + invalidFilesFiltered: 'Отфильтровано {count} неподдерживаемых файлов', unsupportedFileType: 'Неподдерживаемый тип файла', failedFilesList: 'Неудавшиеся файлы:', andMoreFiles: '...и ещё {count} файлов', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index b5e134b8..9124ec4b 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -58,6 +58,9 @@ export default { noValidFiles: "没有有效的文件", noValidFilesInFolder: "文件夹中的 {total} 个文件均不支持", noValidFilesSelected: "选中的文件均不支持", + hiddenFilesFiltered: "已过滤 {count} 个隐藏文件", + imagesFilteredNoVLM: "已过滤 {count} 个图片文件(未启用VLM)", + invalidFilesFiltered: "已过滤 {count} 个不支持的文件", unsupportedFileType: "不支持的文件格式", failedFilesList: "失败文件列表:", andMoreFiles: "...及其他 {count} 个文件", diff --git a/frontend/src/types/tool-results.ts b/frontend/src/types/tool-results.ts index fa96905f..5808faaf 100644 --- a/frontend/src/types/tool-results.ts +++ b/frontend/src/types/tool-results.ts @@ -18,7 +18,8 @@ export type DisplayType = | 'plan' | 'database_query' | 'web_search_results' - | 'web_fetch_results'; + | 'web_fetch_results' + | 'grep_results'; // Search result item export interface SearchResultItem { @@ -192,6 +193,31 @@ export interface WebFetchResultsData { count?: number; } +// Grep chunk result item +export interface GrepChunkItem { + id: string; + content: string; + chunk_index: number; + knowledge_id: string; + knowledge_base_id: string; + chunk_type?: string; + knowledge_title?: string; +} + +// Grep results data +export interface GrepResultsData { + display_type: 'grep_results'; + pattern: string; + case_sensitive: boolean; + use_regex: boolean; + results: GrepChunkItem[]; + result_count: number; + total_matches: number; + knowledge_base_ids?: string[]; + knowledge_ids?: string[]; + limit: number; +} + // Union type for all tool result data export type ToolResultData = | SearchResultsData @@ -204,7 +230,8 @@ export type ToolResultData = | PlanData | DatabaseQueryData | WebSearchResultsData - | WebFetchResultsData; + | WebFetchResultsData + | GrepResultsData; // Action data (from index.vue) export interface ActionData { diff --git a/frontend/src/utils/tool-icons.ts b/frontend/src/utils/tool-icons.ts index 8dd258f4..b5c68a4f 100644 --- a/frontend/src/utils/tool-icons.ts +++ b/frontend/src/utils/tool-icons.ts @@ -7,6 +7,7 @@ export const toolIcons: Record = { multi_kb_search: '🔍', knowledge_search: '📚', + grep_chunks: '🔎', get_chunk_detail: '📄', list_knowledge_bases: '📂', list_knowledge_chunks: '🧩', @@ -42,6 +43,7 @@ export function getToolDisplayName(toolName: string): string { const displayNames: Record = { multi_kb_search: '跨库搜索', knowledge_search: '知识库搜索', + grep_chunks: '文本模式搜索', get_chunk_detail: '获取片段详情', list_knowledge_chunks: '查看知识分块', list_knowledge_bases: '列出知识库', diff --git a/frontend/src/views/chat/components/AgentStreamDisplay.vue b/frontend/src/views/chat/components/AgentStreamDisplay.vue index 12eb68f1..a476d16b 100644 --- a/frontend/src/views/chat/components/AgentStreamDisplay.vue +++ b/frontend/src/views/chat/components/AgentStreamDisplay.vue @@ -117,6 +117,11 @@
+ +
+
+
+
@@ -153,7 +184,7 @@ + + diff --git a/frontend/src/views/chat/components/tool-results/SearchResults.vue b/frontend/src/views/chat/components/tool-results/SearchResults.vue index 7ebeacae..608aa6ab 100644 --- a/frontend/src/views/chat/components/tool-results/SearchResults.vue +++ b/frontend/src/views/chat/components/tool-results/SearchResults.vue @@ -25,9 +25,6 @@
#{{ result.result_index }} - - {{ getRelevanceLabel(result.relevance_level) }} - {{ result.knowledge_title }}
diff --git a/frontend/src/views/creatChat/creatChat.vue b/frontend/src/views/creatChat/creatChat.vue index ea028d26..3f60a0f0 100644 --- a/frontend/src/views/creatChat/creatChat.vue +++ b/frontend/src/views/creatChat/creatChat.vue @@ -7,6 +7,16 @@ + + +