diff --git a/frontend/src/api/knowledge-base/index.ts b/frontend/src/api/knowledge-base/index.ts index 9e9ae5b77..5cc5fa6ee 100644 --- a/frontend/src/api/knowledge-base/index.ts +++ b/frontend/src/api/knowledge-base/index.ts @@ -54,7 +54,7 @@ export function createManualKnowledge(kbId: string, data: { title: string; conte export function listKnowledgeFiles( kbId: string, - params: { page: number; page_size: number; tag_id?: string }, + params: { page: number; page_size: number; tag_id?: string; keyword?: string }, ) { const query = new URLSearchParams(); query.append('page', String(params.page)); @@ -62,6 +62,9 @@ export function listKnowledgeFiles( if (params.tag_id) { query.append('tag_id', params.tag_id); } + if (params.keyword) { + query.append('keyword', params.keyword); + } const qs = query.toString(); return get(`/api/v1/knowledge-bases/${kbId}/knowledge?${qs}`); } diff --git a/frontend/src/hooks/useKnowledgeBase.ts b/frontend/src/hooks/useKnowledgeBase.ts index 1c2c78ca6..61774325e 100644 --- a/frontend/src/hooks/useKnowledgeBase.ts +++ b/frontend/src/hooks/useKnowledgeBase.ts @@ -28,7 +28,7 @@ export default function (knowledgeBaseId?: string) { file_type: "" }); const getKnowled = ( - query: { page: number; page_size: number; tag_id?: string } = { page: 1, page_size: 35 }, + query: { page: number; page_size: number; tag_id?: string; keyword?: string } = { page: 1, page_size: 35 }, kbId?: string, ) => { const targetKbId = kbId || knowledgeBaseId; diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index c50d80c36..e6de0007b 100644 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -182,6 +182,7 @@ export default { textMarkdownFormat: 'text, markdown format files, max 200K', dragFileNotText: 'Please drag files instead of text or links', searchPlaceholder: 'Search knowledge bases...', + docSearchPlaceholder: 'Search document names...', noMatch: 'No matching knowledge base found', noKnowledge: 'No knowledge bases available', loadingFailed: 'Failed to load knowledge bases' diff --git a/frontend/src/i18n/locales/ru-RU.ts b/frontend/src/i18n/locales/ru-RU.ts index 8f7650710..9d2dd25d6 100644 --- a/frontend/src/i18n/locales/ru-RU.ts +++ b/frontend/src/i18n/locales/ru-RU.ts @@ -182,6 +182,7 @@ export default { textMarkdownFormat: 'Файлы text, markdown формата, не более 200 КБ', dragFileNotText: 'Пожалуйста, перетащите файлы, а не текст или ссылки', searchPlaceholder: 'Поиск по базам знаний...', + docSearchPlaceholder: 'Поиск документов...', noMatch: 'Совпадающих баз знаний не найдено', noKnowledge: 'Нет доступных баз знаний', loadingFailed: 'Не удалось загрузить базы знаний' diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 5b597bdb8..2d28b46ef 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -185,6 +185,7 @@ export default { textMarkdownFormat: "text、markdown格式文件,不超过200K", dragFileNotText: "请拖拽文件而不是文本或链接", searchPlaceholder: "搜索知识库...", + docSearchPlaceholder: "搜索文档名称...", noMatch: "未找到匹配的知识库", noKnowledge: "暂无可用知识库", loadingFailed: "加载知识库失败", diff --git a/frontend/src/views/knowledge/KnowledgeBase.vue b/frontend/src/views/knowledge/KnowledgeBase.vue index 65793acd1..e576344d1 100644 --- a/frontend/src/views/knowledge/KnowledgeBase.vue +++ b/frontend/src/views/knowledge/KnowledgeBase.vue @@ -57,6 +57,8 @@ const tagHasMore = ref(false); const tagLoadingMore = ref(false); const tagTotal = ref(0); let tagSearchDebounce: ReturnType | null = null; +let docSearchDebounce: ReturnType | null = null; +const docSearchKeyword = ref(''); type TagInputInstance = ComponentPublicInstance<{ focus: () => void; select: () => void }>; const tagDropdownOptions = computed(() => { const options = [ @@ -147,6 +149,7 @@ const loadKnowledgeFiles = (kbIdValue: string) => { page: 1, page_size: pageSize, tag_id: selectedTagId.value || undefined, + keyword: docSearchKeyword.value ? docSearchKeyword.value.trim() : undefined, }, kbIdValue, ); @@ -443,6 +446,20 @@ watch(tagSearchQuery, (newVal, oldVal) => { }, 300); }); +// 监听文档搜索关键词变化 +watch(docSearchKeyword, (newVal, oldVal) => { + if (newVal === oldVal) return; + if (docSearchDebounce) { + clearTimeout(docSearchDebounce); + } + docSearchDebounce = window.setTimeout(() => { + if (kbId.value) { + page = 1; + loadKnowledgeFiles(kbId.value); + } + }, 300); +}); + // 监听文件上传事件 const handleFileUploaded = (event: CustomEvent) => { const uploadedKbId = event.detail.kbId; @@ -819,7 +836,7 @@ const handleScroll = () => { if (scrollTop + clientHeight >= scrollHeight) { page++; if (cardList.value.length < total.value && page <= pageNum) { - getKnowled({ page, page_size: pageSize, tag_id: selectedTagId.value || undefined }); + getKnowled({ page, page_size: pageSize, tag_id: selectedTagId.value || undefined, keyword: docSearchKeyword.value ? docSearchKeyword.value.trim() : undefined }); } } } @@ -1108,6 +1125,20 @@ async function createNewSession(value: string): Promise {
+ +
{ min-height: 0; } +.doc-search-bar { + padding: 0px 0px 10px 0px; + flex-shrink: 0; + + :deep(.t-input) { + font-size: 13px; + background-color: #f7f9fc; + border-color: #e5e9f2; + border-radius: 6px; + + &:hover, + &:focus, + &.t-is-focused { + border-color: #4080ff; + background-color: #fff; + } + } +} + .doc-scroll-container { flex: 1; min-height: 0; diff --git a/internal/application/repository/knowledge.go b/internal/application/repository/knowledge.go index e6a01e4aa..165506a6b 100644 --- a/internal/application/repository/knowledge.go +++ b/internal/application/repository/knowledge.go @@ -62,6 +62,7 @@ func (r *knowledgeRepository) ListPagedKnowledgeByKnowledgeBaseID( kbID string, page *types.Pagination, tagID string, + keyword string, ) ([]*types.Knowledge, int64, error) { var knowledges []*types.Knowledge var total int64 @@ -71,6 +72,9 @@ func (r *knowledgeRepository) ListPagedKnowledgeByKnowledgeBaseID( if tagID != "" { query = query.Where("tag_id = ?", tagID) } + if keyword != "" { + query = query.Where("file_name LIKE ?", "%"+keyword+"%") + } // Query total count first if err := query.Count(&total).Error; err != nil { @@ -83,6 +87,9 @@ func (r *knowledgeRepository) ListPagedKnowledgeByKnowledgeBaseID( if tagID != "" { dataQuery = dataQuery.Where("tag_id = ?", tagID) } + if keyword != "" { + dataQuery = dataQuery.Where("file_name LIKE ?", "%"+keyword+"%") + } if err := dataQuery. Order("created_at DESC"). diff --git a/internal/application/service/agent_service.go b/internal/application/service/agent_service.go index fef915d2f..295b7bab2 100644 --- a/internal/application/service/agent_service.go +++ b/internal/application/service/agent_service.go @@ -331,7 +331,7 @@ func (s *agentService) getKnowledgeBaseInfos(ctx context.Context, kbIDs []string pageResult, err := s.knowledgeService.ListPagedKnowledgeByKnowledgeBaseID(ctx, kbID, &types.Pagination{ Page: 1, PageSize: 10, - }, "") + }, "", "") if err == nil && pageResult != nil { docCount = int(pageResult.Total) diff --git a/internal/application/service/knowledge.go b/internal/application/service/knowledge.go index 9525c76d3..0c04c50c8 100644 --- a/internal/application/service/knowledge.go +++ b/internal/application/service/knowledge.go @@ -699,10 +699,10 @@ func (s *knowledgeService) ListKnowledgeByKnowledgeBaseID(ctx context.Context, // ListPagedKnowledgeByKnowledgeBaseID returns paginated knowledge entries in a knowledge base func (s *knowledgeService) ListPagedKnowledgeByKnowledgeBaseID(ctx context.Context, - kbID string, page *types.Pagination, tagID string, + kbID string, page *types.Pagination, tagID string, keyword string, ) (*types.PageResult, error) { knowledges, total, err := s.repo.ListPagedKnowledgeByKnowledgeBaseID(ctx, - ctx.Value(types.TenantIDContextKey).(uint64), kbID, page, tagID) + ctx.Value(types.TenantIDContextKey).(uint64), kbID, page, tagID, keyword) if err != nil { return nil, err } diff --git a/internal/handler/initialization.go b/internal/handler/initialization.go index 43129d726..8a1a7be45 100644 --- a/internal/handler/initialization.go +++ b/internal/handler/initialization.go @@ -232,7 +232,7 @@ func (h *InitializationHandler) UpdateKBConfig(c *gin.Context) { kbIdStr, &types.Pagination{ Page: 1, PageSize: 1, - }, "") + }, "", "") if err == nil && knowledgeList != nil && knowledgeList.Total > 0 { logger.Error(ctx, "Cannot change embedding model when files exist") c.Error(errors.NewBadRequestError("知识库中已有文件,无法修改Embedding模型")) @@ -1176,7 +1176,7 @@ func (h *InitializationHandler) GetCurrentConfigByKB(c *gin.Context) { kbIdStr, &types.Pagination{ Page: 1, PageSize: 1, - }, "") + }, "", "") hasFiles := err == nil && knowledgeList != nil && knowledgeList.Total > 0 // 构建配置响应 diff --git a/internal/handler/knowledge.go b/internal/handler/knowledge.go index 5f73f375c..0f2790f1d 100644 --- a/internal/handler/knowledge.go +++ b/internal/handler/knowledge.go @@ -318,18 +318,20 @@ func (h *KnowledgeHandler) ListKnowledge(c *gin.Context) { } tagID := c.Query("tag_id") + keyword := c.Query("keyword") logger.Infof( ctx, - "Retrieving knowledge list under knowledge base, knowledge base ID: %s, tag_id: %s, page: %d, page size: %d", + "Retrieving knowledge list under knowledge base, knowledge base ID: %s, tag_id: %s, keyword: %s, page: %d, page size: %d", secutils.SanitizeForLog(kbID), secutils.SanitizeForLog(tagID), + secutils.SanitizeForLog(keyword), pagination.Page, pagination.PageSize, ) // Retrieve paginated knowledge entries - result, err := h.kgService.ListPagedKnowledgeByKnowledgeBaseID(ctx, kbID, &pagination, tagID) + result, err := h.kgService.ListPagedKnowledgeByKnowledgeBaseID(ctx, kbID, &pagination, tagID, keyword) if err != nil { logger.ErrorWithFields(ctx, err, nil) c.Error(errors.NewInternalServerError(err.Error())) diff --git a/internal/types/interfaces/knowledge.go b/internal/types/interfaces/knowledge.go index 20a26594a..22dcf65e5 100644 --- a/internal/types/interfaces/knowledge.go +++ b/internal/types/interfaces/knowledge.go @@ -45,11 +45,13 @@ type KnowledgeService interface { ListKnowledgeByKnowledgeBaseID(ctx context.Context, kbID string) ([]*types.Knowledge, error) // ListPagedKnowledgeByKnowledgeBaseID lists all knowledge under a knowledge base with pagination. // When tagID is non-empty, results are filtered by tag_id. + // When keyword is non-empty, results are filtered by file_name. ListPagedKnowledgeByKnowledgeBaseID( ctx context.Context, kbID string, page *types.Pagination, tagID string, + keyword string, ) (*types.PageResult, error) // DeleteKnowledge deletes knowledge by ID. DeleteKnowledge(ctx context.Context, id string) error @@ -112,8 +114,9 @@ type KnowledgeRepository interface { ListKnowledgeByKnowledgeBaseID(ctx context.Context, tenantID uint64, kbID string) ([]*types.Knowledge, error) // ListPagedKnowledgeByKnowledgeBaseID lists all knowledge in a knowledge base with pagination. // When tagID is non-empty, results are filtered by tag_id. + // When keyword is non-empty, results are filtered by file_name. ListPagedKnowledgeByKnowledgeBaseID(ctx context.Context, - tenantID uint64, kbID string, page *types.Pagination, tagID string, + tenantID uint64, kbID string, page *types.Pagination, tagID string, keyword string, ) ([]*types.Knowledge, int64, error) UpdateKnowledge(ctx context.Context, knowledge *types.Knowledge) error // UpdateKnowledgeBatch updates knowledge items in batch