feat: Enhance knowledge base functionality by adding document search capability and updating related API and UI components

This commit is contained in:
wizardchen
2025-12-04 00:13:34 +08:00
parent dafb95d46a
commit bee699f5bf
12 changed files with 79 additions and 11 deletions
+4 -1
View File
@@ -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}`);
}
+1 -1
View File
@@ -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;
+1
View File
@@ -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'
+1
View File
@@ -182,6 +182,7 @@ export default {
textMarkdownFormat: 'Файлы text, markdown формата, не более 200 КБ',
dragFileNotText: 'Пожалуйста, перетащите файлы, а не текст или ссылки',
searchPlaceholder: 'Поиск по базам знаний...',
docSearchPlaceholder: 'Поиск документов...',
noMatch: 'Совпадающих баз знаний не найдено',
noKnowledge: 'Нет доступных баз знаний',
loadingFailed: 'Не удалось загрузить базы знаний'
+1
View File
@@ -185,6 +185,7 @@ export default {
textMarkdownFormat: "text、markdown格式文件,不超过200K",
dragFileNotText: "请拖拽文件而不是文本或链接",
searchPlaceholder: "搜索知识库...",
docSearchPlaceholder: "搜索文档名称...",
noMatch: "未找到匹配的知识库",
noKnowledge: "暂无可用知识库",
loadingFailed: "加载知识库失败",
+51 -1
View File
@@ -57,6 +57,8 @@ const tagHasMore = ref(false);
const tagLoadingMore = ref(false);
const tagTotal = ref(0);
let tagSearchDebounce: ReturnType<typeof setTimeout> | null = null;
let docSearchDebounce: ReturnType<typeof setTimeout> | 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<void> {
</aside>
<div class="tag-content">
<div class="doc-card-area">
<!-- 搜索栏 -->
<div class="doc-search-bar">
<t-input
v-model.trim="docSearchKeyword"
:placeholder="$t('knowledgeBase.docSearchPlaceholder')"
clearable
@clear="loadKnowledgeFiles(kbId)"
@keydown.enter="loadKnowledgeFiles(kbId)"
>
<template #prefix-icon>
<t-icon name="search" size="16px" />
</template>
</t-input>
</div>
<div
class="doc-scroll-container"
:class="{ 'is-empty': !cardList.length }"
@@ -1750,6 +1781,25 @@ async function createNewSession(value: string): Promise<void> {
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;
@@ -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").
@@ -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)
+2 -2
View File
@@ -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
}
+2 -2
View File
@@ -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
// 构建配置响应
+4 -2
View File
@@ -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()))
+4 -1
View File
@@ -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