Ver código fonte

问题解决

linyang 3 semanas atrás
pai
commit
5fdeeeea3b

+ 4 - 2
src/api/knowledge-base.ts

@@ -4,7 +4,8 @@ import request from './request'
 export interface KnowledgeBase {
   id: string
   name: string
-  collection_name: string
+  collection_name1: string
+  collection_name2?: string
   description?: string
   status: string
   document_count: number
@@ -39,7 +40,8 @@ export interface CustomSchemaField {
 
 export interface CreateKnowledgeBaseData {
   name: string
-  collection_name: string
+  collection_name1: string
+  collection_name2?: string
   description?: string
   dimension?: number
   metadata_fields?: MetadataField[]

+ 46 - 46
src/views/documents/KnowledgeBase.vue

@@ -46,7 +46,8 @@
 
         <el-table-column label="知识库表名" min-width="150">
           <template #default="{ row }">
-            <div style="white-space: pre-wrap; line-height: 1.5;">{{ row.collection_name }}</div>
+            <div>{{ row.collection_name1 }}</div>
+            <div v-if="row.collection_name2" class="sub-collection">{{ row.collection_name2 }}</div>
           </template>
         </el-table-column>
         
@@ -86,9 +87,9 @@
                     <el-icon><Edit /></el-icon>
                 </el-button>
             </el-tooltip>
-            <el-tooltip :content="row.is_synced ? '已同步' : '同步到Milvus'" placement="top">
-                <el-button link type="warning" @click="handleSync(row)" :disabled="row.is_synced">
-                    <el-icon><Refresh /></el-icon>
+            <el-tooltip :content="syncingStates[row.id] ? '转换中' : (row.is_synced ? '已同步' : '同步到Milvus')" placement="top">
+                <el-button link type="warning" @click="handleSync(row)" :disabled="row.is_synced || syncingStates[row.id]">
+                    <el-icon :class="{'is-loading': syncingStates[row.id]}"><Refresh /></el-icon>
                 </el-button>
             </el-tooltip>
             <el-tooltip content="删除" placement="top">
@@ -149,18 +150,21 @@
                 </el-col>
                 <el-col :span="12">
                     <template v-if="dialogType === 'create'">
-                        <el-form-item label="父集合名称" prop="parent_collection_name">
-                            <el-input v-model="formData.parent_collection_name" placeholder="父集合名(英文)" />
-                        </el-form-item>
-                        <el-form-item label="子集合名称" prop="child_collection_name">
-                            <el-input v-model="formData.child_collection_name" placeholder="子集合名(英文)" />
-                        </el-form-item>
-                    </template>
-                    <template v-else>
-                        <el-form-item label="集合名称">
-                            <el-input v-model="formData.collection_name" disabled />
-                        </el-form-item>
-                    </template>
+            <el-form-item label="父集合名称" prop="collection_name1">
+                <el-input v-model="formData.collection_name1" placeholder="父集合名(英文)" />
+            </el-form-item>
+            <el-form-item label="子集合名称" prop="collection_name2">
+                <el-input v-model="formData.collection_name2" placeholder="子集合名(英文)" />
+            </el-form-item>
+        </template>
+        <template v-else>
+            <el-form-item label="父集合名称">
+                <el-input v-model="formData.collection_name1" disabled />
+            </el-form-item>
+            <el-form-item label="子集合名称" v-if="formData.collection_name2">
+                <el-input v-model="formData.collection_name2" disabled />
+            </el-form-item>
+        </template>
                 </el-col>
             </el-row>
             
@@ -273,7 +277,8 @@
     >
       <el-descriptions :column="2" border class="detail-descriptions">
         <el-descriptions-item label="知识库名称" label-class-name="desc-label">{{ viewData.name }}</el-descriptions-item>
-        <el-descriptions-item label="集合名称" label-class-name="desc-label">{{ viewData.collection_name }}</el-descriptions-item>
+        <el-descriptions-item label="父集合名称" label-class-name="desc-label">{{ viewData.collection_name1 }}</el-descriptions-item>
+        <el-descriptions-item label="子集合名称" label-class-name="desc-label">{{ viewData.collection_name2 || '-' }}</el-descriptions-item>
         <el-descriptions-item label="状态" label-class-name="desc-label">
             <el-tag :type="getStatusType(viewData.status)" effect="plain">
                 {{ getStatusText(viewData.status) }}
@@ -343,6 +348,7 @@ const queryParams = reactive({
 
 // Table Data
 const loading = ref(false)
+const syncingStates = ref<Record<string, boolean>>({})
 const tableData = ref<KnowledgeBase[]>([])
 const pagination = reactive({
   page: 1,
@@ -358,9 +364,8 @@ const formRef = ref<FormInstance>()
 const formData = reactive({
   id: '',
   name: '',
-  collection_name: '',
-  parent_collection_name: '',
-  child_collection_name: '',
+  collection_name1: '',
+  collection_name2: '',
   description: '',
   dimension: 768,
   status: 'normal',
@@ -398,15 +403,12 @@ const getDisabledOptions = (currentIndex: number) => {
 
 const rules: FormRules = {
   name: [{ required: true, message: '请输入知识库名称', trigger: 'blur' }],
-  collection_name: [
-    { required: false, message: '请输入集合名称', trigger: 'blur' }
-  ],
-  parent_collection_name: [
+  collection_name1: [
     { required: true, message: '请输入父集合名称', trigger: 'blur' },
     { pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: '必须以字母或下划线开头,只能包含字母、数字和下划线', trigger: 'blur' }
   ],
-  child_collection_name: [
-    { required: true, message: '请输入子集合名称', trigger: 'blur' },
+  collection_name2: [
+    { required: false, message: '请输入子集合名称', trigger: 'blur' },
     { pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: '必须以字母或下划线开头,只能包含字母、数字和下划线', trigger: 'blur' }
   ],
   dimension: [{ required: true, message: '请输入维度', trigger: 'blur' }]
@@ -474,9 +476,8 @@ const handleAdd = () => {
   dialogType.value = 'create'
   formData.id = ''
   formData.name = ''
-  formData.collection_name = ''
-  formData.parent_collection_name = ''
-  formData.child_collection_name = ''
+  formData.collection_name1 = ''
+  formData.collection_name2 = ''
   formData.description = ''
   formData.dimension = 768
   formData.status = 'normal'
@@ -487,8 +488,8 @@ const handleEdit = async (row: KnowledgeBase) => {
   dialogType.value = 'edit'
   formData.id = row.id
   formData.name = row.name
-  formData.collection_name = row.collection_name
-  // 暂时不需要回显 parent/child,因为编辑是对单个进行的
+  formData.collection_name1 = row.collection_name1
+  formData.collection_name2 = row.collection_name2 || ''
   formData.description = row.description || ''
   formData.status = row.status
   
@@ -512,12 +513,17 @@ const handleEdit = async (row: KnowledgeBase) => {
 }
 
 const handleSync = async (row: KnowledgeBase) => {
+    if (syncingStates.value[row.id]) return
+    
     try {
+        syncingStates.value[row.id] = true
         await syncKnowledgeBase(row.id)
         ElMessage.success('同步成功,Milvus集合已创建')
         loadData()
     } catch (error) {
         // error handled by request interceptor
+    } finally {
+        syncingStates.value[row.id] = false
     }
 }
 
@@ -613,24 +619,14 @@ const handleSubmit = async () => {
       submitLoading.value = true
       try {
         if (dialogType.value === 'create') {
-          // 1. 创建父集合 KB
-          await createKnowledgeBase({
-            name: formData.name + ' (父)',
-            collection_name: formData.parent_collection_name,
-            description: formData.description,
-            dimension: formData.dimension,
-            metadata_fields: formData.metadata_fields.filter(f => f.field_zh_name && f.field_en_name)
-          })
-
-          // 2. 创建子集合 KB
           await createKnowledgeBase({
-            name: formData.name + ' (子)',
-            collection_name: formData.child_collection_name,
+            name: formData.name,
+            collection_name1: formData.collection_name1,
+            collection_name2: formData.collection_name2,
             description: formData.description,
             dimension: formData.dimension,
             metadata_fields: formData.metadata_fields.filter(f => f.field_zh_name && f.field_en_name)
           })
-
           ElMessage.success('创建成功')
         } else {
           await updateKnowledgeBase(formData.id, {
@@ -645,8 +641,6 @@ const handleSubmit = async () => {
         loadData()
       } catch (error) {
         console.error(error)
-        // 注意:如果是第二个失败,第一个可能已经创建成功。
-        // 这里简单提示错误,用户可以刷新列表看到已创建的,然后手动处理。
       } finally {
         submitLoading.value = false
       }
@@ -761,6 +755,12 @@ onMounted(() => {
   overflow: hidden;
 }
 
+.sub-collection {
+    color: #909399;
+    font-size: 12px;
+    margin-top: 4px;
+}
+
 .pagination-container {
   display: flex;
   justify-content: space-between;

+ 31 - 27
src/views/documents/KnowledgeSnippet.vue

@@ -6,10 +6,6 @@
         <p class="subtitle">管理所有知识片段和相关内容</p>
       </div>
       <div class="action-buttons">
-        <el-select v-model="queryParams.status" placeholder="所有状态" clearable style="width: 120px; margin-right: 12px" @change="handleSearch">
-          <el-option label="启用" value="normal" />
-          <el-option label="禁用" value="disabled" />
-        </el-select>
         <el-select v-model="queryParams.kb" placeholder="所有知识库" clearable style="width: 150px; margin-right: 12px" @change="handleSearch">
           <el-option 
             v-for="item in kbOptions" 
@@ -117,14 +113,6 @@
             </template>
         </el-table-column>
 
-        <el-table-column prop="status" label="状态" width="80">
-          <template #default="{ row }">
-            <el-tag :type="row.status === 'normal' ? 'success' : 'info'" effect="plain" class="status-tag">
-                {{ row.status === 'normal' ? '启用' : '禁用' }}
-            </el-tag>
-          </template>
-        </el-table-column>
-
         <el-table-column prop="created_at" label="创建时间" width="160" />
         <el-table-column prop="updated_at" label="修改时间" width="160" />
 
@@ -314,11 +302,6 @@
         <el-descriptions-item label="Parent ID" label-class-name="desc-label">
             <span class="copyable-text">{{ viewData.parent_id || '-' }}</span>
         </el-descriptions-item>
-        <el-descriptions-item label="状态" label-class-name="desc-label">
-            <el-tag :type="viewData.status === 'normal' ? 'success' : 'info'" effect="plain">
-                {{ viewData.status === 'normal' ? '启用' : '禁用' }}
-            </el-tag>
-        </el-descriptions-item>
         <el-descriptions-item label="创建时间" label-class-name="desc-label">{{ viewData.created_at || '-' }}</el-descriptions-item>
         <el-descriptions-item label="标签" :span="2" label-class-name="desc-label">
             <div class="tags-container">
@@ -576,17 +559,38 @@ const loadKbOptions = async () => {
     try {
         const res = await getKnowledgeBases({ page_size: 100 })
         if (res.code === 0) {
-            kbOptions.value = res.data.map((item: any) => ({
-                label: item.name,
-                value: item.collection_name,
-                id: item.id
-            }))
-            // 修改为默认不选中(查询所有),直接加载数据
-            // if (!queryParams.kb) {
-            //     loadData()
-            // }
+            kbOptions.value = []
+            res.data.forEach((item: any) => {
+                // 根据需求:只选择知识库名称,且对应的是子表
+                // 如果有子集合,添加选项,显示知识库名称,值为子集合名称
+                if (item.collection_name2) {
+                    kbOptions.value.push({
+                        label: item.name, // 只显示知识库名称
+                        value: item.collection_name2, // 对应子表
+                        id: item.id
+                    })
+                }
+                // 如果没有子集合,尝试使用 collection_name1 (兼容旧数据或单表模式)
+                // 或者是用户意图是“只要有子表就用子表,没有子表是否用父表?”
+                // 假设:如果只有 collection_name1 (父表),也显示出来,否则用户无法操作
+                else if (item.collection_name1) {
+                     kbOptions.value.push({
+                        label: item.name,
+                        value: item.collection_name1,
+                        id: item.id
+                    })
+                }
+                // 兼容旧数据
+                else if (item.collection_name) {
+                     kbOptions.value.push({
+                        label: item.name,
+                        value: item.collection_name,
+                        id: item.id
+                    })
+                }
+            })
             
-            // 默认选中第一个知识库(如果存在),并加载数据
+            // 默认选中第一个
             if (kbOptions.value.length > 0 && !queryParams.kb) {
                 queryParams.kb = kbOptions.value[0].value
                 loadData()

+ 11 - 5
src/views/documents/SearchEngine.vue

@@ -29,7 +29,7 @@
                         v-for="kb in kbList" 
                         :key="kb.id" 
                         :label="kb.name" 
-                        :value="kb.collection_name" 
+                        :value="kb.collection_name2 || kb.collection_name1 || kb.collection_name" 
                     >
                         <span>{{ kb.name }}</span>
                     </el-option>
@@ -230,7 +230,7 @@
             </div>
             <div class="detail-item">
                 <span class="label">元数据:</span>
-                <span>{{ currentDetail?.meta_info }}</span>
+                <span>{{ formatMetaInfo(currentDetail) }}</span>
             </div>
         </div>
     </el-dialog>
@@ -330,7 +330,11 @@ const handleKbChange = async () => {
     
     if (searchForm.kb_id) {
         // Find selected KB object to get ID (kb_id in form is collection_name)
-        const selectedKb = kbList.value.find(k => k.collection_name === searchForm.kb_id)
+        const selectedKb = kbList.value.find(k => 
+            k.collection_name2 === searchForm.kb_id || 
+            k.collection_name1 === searchForm.kb_id ||
+            k.collection_name === searchForm.kb_id
+        )
         if (selectedKb) {
             try {
                 const res = await getKnowledgeBaseMetadata(selectedKb.id)
@@ -364,7 +368,7 @@ const formatMetaInfo = (row: any) => {
         if (typeof row.metadata === 'object') {
              const displayParts = []
              for (const [key, value] of Object.entries(row.metadata)) {
-                 if (!['doc_name', 'file_name', 'title'].includes(key)) {
+                 if (!['doc_name', 'file_name', 'title', 'vector'].includes(key)) { // 排除 vector
                      const valStr = typeof value === 'object' ? JSON.stringify(value) : String(value)
                      displayParts.push(`${key}: ${valStr}`)
                  }
@@ -375,7 +379,7 @@ const formatMetaInfo = (row: any) => {
                 const metaObj = JSON.parse(row.metadata)
                 const displayParts = []
                 for (const [key, value] of Object.entries(metaObj)) {
-                    if (!['doc_name', 'file_name', 'title'].includes(key)) {
+                    if (!['doc_name', 'file_name', 'title', 'vector'].includes(key)) { // 排除 vector
                         const valStr = typeof value === 'object' ? JSON.stringify(value) : String(value)
                         displayParts.push(`${key}: ${valStr}`)
                     }
@@ -386,6 +390,8 @@ const formatMetaInfo = (row: any) => {
             }
         }
     }
+    // 如果 row 本身有 vector 属性,也不显示在 meta_info 中
+    // 这里只处理了 metadata 字段内的
     return row.meta_info || '-'
 }