Procházet zdrojové kódy

feat: 增强知识库管理功能

新增样本中心知识库类型,添加知识库用量统计 API,前端知识库设置
页面显示文档数/段落数/命中次数统计。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
mengboxin137-blip před 6 dny
rodič
revize
81235ee4a1

+ 1 - 0
apps/knowledge/models/knowledge.py

@@ -25,6 +25,7 @@ class KnowledgeType(models.IntegerChoices):
     LARK = 2, '飞书类型'
     YUQUE = 3, '语雀类型'
     WORKFLOW = 4, '工作流类型'
+    SAMPLE_CENTER = 5, '样本中心'
 
 
 class TaskType(Enum):

+ 7 - 0
apps/knowledge/urls.py

@@ -1,10 +1,16 @@
 from django.urls import path
 
 from . import views
+from .views.sample_center import SampleCenterView
 
 app_name = "knowledge"
 # @formatter:off
 urlpatterns = [
+    # 样本中心对接
+    path('workspace/sample-center/knowledge-bases', SampleCenterView.ListKnowledgeBases.as_view()),
+    path('workspace/sample-center/knowledge-bases/detail', SampleCenterView.GetKnowledgeBase.as_view()),
+    path('workspace/sample-center/knowledge-bases/batch-import', SampleCenterView.BatchImport.as_view()),
+    path('workspace/sample-center/batch-import/task', SampleCenterView.GetImportTask.as_view()),
     path('workspace/knowledge/document/template/export', views.Template.as_view()),
     path('workspace/knowledge/document/table_template/export', views.TableTemplate.as_view()),
     path('workspace/store/knowledge_template', views.KnowledgeView.StoreKnowledge.as_view()),
@@ -92,4 +98,5 @@ urlpatterns = [
     path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/knowledge_version', views.KnowledgeWorkflowVersionView.as_view()),
     path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/knowledge_version/<int:current_page>/<int:page_size>', views.KnowledgeWorkflowVersionView.Page.as_view()),
     path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/knowledge_version/<str:knowledge_version_id>', views.KnowledgeWorkflowVersionView.Operate.as_view()),
+    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/statistics', views.KnowledgeView.Statistics.as_view()),
 ]

+ 31 - 3
apps/knowledge/views/knowledge.py

@@ -12,13 +12,12 @@ from common import result
 from knowledge.api.knowledge import KnowledgeBaseCreateAPI, KnowledgeWebCreateAPI, KnowledgeTreeReadAPI, \
     KnowledgeEditAPI, KnowledgeReadAPI, KnowledgePageAPI, SyncWebAPI, GenerateRelatedAPI, HitTestAPI, EmbeddingAPI, \
     GetModelAPI, KnowledgeExportAPI, KnowledgeBatchOperateAPI, KnowledgeImportAPI
-from knowledge.models import KnowledgeScope
+from knowledge.models import KnowledgeScope, Knowledge, Document, Paragraph
 from knowledge.serializers.common import get_knowledge_operation_object
 from knowledge.serializers.knowledge import KnowledgeSerializer, KnowledgeBatchOperateSerializer
 from models_provider.serializers.model_serializer import ModelSerializer
 from tools.api.tool import GetInternalToolAPI
-from django.db.models import QuerySet
-from knowledge.models import Knowledge
+from django.db.models import QuerySet, Count, Sum
 
 def get_knowledge_operation_object_batch(knowledge_id_list):
     knowledge_model_list = QuerySet(model=Knowledge).filter(id__in=knowledge_id_list)
@@ -583,6 +582,35 @@ class KnowledgeView(APIView):
                 'knowledge_ids': request.query_params.getlist('knowledge_ids[]')
             }).list())
 
+    class Statistics(APIView):
+        """知识库用量统计"""
+        authentication_classes = [TokenAuth]
+
+        @extend_schema(
+            methods=['GET'],
+            description=_("Get knowledge base usage statistics"),
+            summary=_("Get knowledge base usage statistics"),
+            operation_id=_("Get knowledge base usage statistics"),
+            tags=[_('Knowledge Base')]
+        )
+        @has_permissions(
+            PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(),
+            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
+            RoleConstants.USER.get_workspace_role()
+        )
+        def get(self, request: Request, workspace_id: str, knowledge_id: str):
+            doc_count = Document.objects.filter(knowledge_id=knowledge_id).count()
+            para_stats = Paragraph.objects.filter(knowledge_id=knowledge_id).aggregate(
+                total_count=Count('id'),
+                total_hit=Sum('hit_num')
+            )
+
+            return result.success({
+                'document_count': doc_count,
+                'paragraph_count': para_stats['total_count'] or 0,
+                'total_hit_count': para_stats['total_hit'] or 0
+            })
+
 
 class KnowledgeBaseView(APIView):
     authentication_classes = [TokenAuth]

+ 17 - 0
ui/src/api/knowledge/knowledge.ts

@@ -553,6 +553,22 @@ const putMulMoveKnowledge: (data: any, loading?: Ref<boolean>) => Promise<Result
   return put(`${prefix.value}/batch_move`, data, undefined, loading)
 }
 
+/**
+ * 获取知识库用量统计
+ * @param knowledge_id 知识库id
+ * @param loading 加载器
+ * @returns 统计数据
+ */
+const getKnowledgeStatistics: (
+  knowledge_id: string,
+  loading?: Ref<boolean>,
+) => Promise<Result<{
+  document_count: number
+  paragraph_count: number
+  total_hit_count: number
+}>> = (knowledge_id, loading) => {
+  return get(`${prefix.value}/${knowledge_id}/statistics`, undefined, loading)
+}
 
 export default {
   getKnowledgeList,
@@ -597,4 +613,5 @@ export default {
   importKnowledgeBundle,
   delMulKnowledge,
   putMulMoveKnowledge,
+  getKnowledgeStatistics
 }

+ 57 - 0
ui/src/views/knowledge/KnowledgeSetting.vue

@@ -8,6 +8,30 @@
             <h4 class="title-decoration-1 mb-16">
               {{ $t('common.info') }}
             </h4>
+
+            <el-card shadow="never" class="mb-16" v-loading="statsLoading">
+              <el-row :gutter="24">
+                <el-col :span="8">
+                  <div class="stat-item">
+                    <div class="stat-value">{{ stats.document_count }}</div>
+                    <div class="stat-label">{{ $t('views.knowledge.statistics.documentCount') }}</div>
+                  </div>
+                </el-col>
+                <el-col :span="8">
+                  <div class="stat-item">
+                    <div class="stat-value">{{ stats.paragraph_count }}</div>
+                    <div class="stat-label">{{ $t('views.knowledge.statistics.paragraphCount') }}</div>
+                  </div>
+                </el-col>
+                <el-col :span="8">
+                  <div class="stat-item">
+                    <div class="stat-value">{{ stats.total_hit_count }}</div>
+                    <div class="stat-label">{{ $t('views.knowledge.statistics.hitCount') }}</div>
+                  </div>
+                </el-col>
+              </el-row>
+            </el-card>
+
             <BaseForm ref="BaseFormRef" :data="detail" :apiType="apiType" />
 
             <el-form
@@ -244,6 +268,13 @@ const loading = ref(false)
 const detail = ref<any>({})
 const cloneModelId = ref('')
 
+const statsLoading = ref(false)
+const stats = ref({
+  document_count: 0,
+  paragraph_count: 0,
+  total_hit_count: 0,
+})
+
 const form = ref<any>({
   source_url: '',
   selector: '',
@@ -367,13 +398,39 @@ function getDetail() {
     })
 }
 
+function fetchStats() {
+  loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })
+    .getKnowledgeStatistics(id, statsLoading)
+    .then((res: any) => {
+      stats.value = res.data
+    })
+}
+
 onMounted(() => {
   getDetail()
+  fetchStats()
 })
 </script>
 <style lang="scss" scoped>
 .knowledge-setting {
   width: 70%;
   margin: 0 auto;
+
+  .stat-item {
+    text-align: center;
+    padding: 8px 0;
+
+    .stat-value {
+      font-size: 24px;
+      font-weight: 600;
+      color: var(--el-text-color-primary);
+    }
+
+    .stat-label {
+      font-size: 12px;
+      color: var(--el-text-color-secondary);
+      margin-top: 4px;
+    }
+  }
 }
 </style>

+ 27 - 0
ui/src/views/knowledge/component/KnowledgeListContainer.vue

@@ -135,6 +135,19 @@
                     </div>
                   </div>
                 </el-dropdown-item>
+                <el-dropdown-item @click="openSampleCenterDialog">
+                  <div class="flex">
+                    <el-avatar class="avatar-green mt-4" shape="square" :size="32">
+                      <AppIcon iconName="app-template-center" style="font-size: 20px"></AppIcon>
+                    </el-avatar>
+                    <div class="pre-wrap ml-8">
+                      <div class="lighter">样本中心知识库</div>
+                      <el-text type="info" size="small" class="color-secondary"
+                        >从样本中心导入专业知识库</el-text
+                      >
+                    </div>
+                  </div>
+                </el-dropdown-item>
                 <el-upload
                   ref="importKnowledgeUploadRef"
                   :file-list="[]"
@@ -430,6 +443,7 @@
   />
   <TemplateStoreDialog ref="templateStoreDialogRef" :api-type="apiType" @refresh="getList" />
   <ResourceMappingDrawer ref="resourceMappingDrawerRef"></ResourceMappingDrawer>
+  <CreateSampleCenterKnowledgeDialog ref="sampleCenterDialogRef" @select="handleSampleCenterSelect" />
 </template>
 
 <script lang="ts" setup>
@@ -441,6 +455,7 @@ import CreateKnowledgeDialog from '@/views/knowledge/create-component/CreateKnow
 import CreateWebKnowledgeDialog from '@/views/knowledge/create-component/CreateWebKnowledgeDialog.vue'
 import CreateLarkKnowledgeDialog from '@/views/knowledge/create-component/CreateLarkKnowledgeDialog.vue'
 import CreateWorkflowKnowledgeDialog from '@/views/knowledge/create-component/CreateWorkflowKnowledgeDialog.vue'
+import CreateSampleCenterKnowledgeDialog from '@/views/knowledge/create-component/CreateSampleCenterKnowledgeDialog.vue'
 import SyncWebDialog from '@/views/knowledge/component/SyncWebDialog.vue'
 import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
 import MoveToDialog from '@/components/folder-tree/MoveToDialog.vue'
@@ -652,6 +667,18 @@ function openCreateDialog(data: any) {
   })
 }
 
+const sampleCenterDialogRef = ref<InstanceType<typeof CreateSampleCenterKnowledgeDialog>>()
+
+function openSampleCenterDialog() {
+  sampleCenterDialogRef.value?.open()
+}
+
+function handleSampleCenterSelect(data: any) {
+  // TODO: 实现从样本中心创建知识库的逻辑
+  console.log('Selected sample center KB:', data)
+  MsgSuccess('样本中心知识库选择成功,待实现入库逻辑')
+}
+
 function reEmbeddingKnowledge(row: any) {
   loadSharedApi({ type: 'knowledge', systemType: apiType.value })
     .putReEmbeddingKnowledge(row.id)