linyang 3 hafta önce
ebeveyn
işleme
f8875f6703

+ 1 - 0
src/api/search-engine.ts

@@ -61,6 +61,7 @@ export interface KBSearchResultItem {
   content: string
   meta_info: string
   document_id?: string
+  parent_id?: string
   metadata?: Record<string, any>
   score: number
 }

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

@@ -386,7 +386,7 @@ const formData = reactive({
   collection_name_parent: '',
   collection_name_children: '',
   description: '',
-  dimension: 768,
+  dimension: 4096,
   status: 'disabled',
   enable_parent_collection: true,
   metadata_fields: [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
@@ -528,8 +528,8 @@ const handleAdd = () => {
   formData.collection_name_parent = ''
   formData.collection_name_children = ''
   formData.description = ''
-  formData.dimension = 768
-  formData.status = 'normal'
+  formData.dimension = 4096
+  formData.status = 'disabled'
   formData.enable_parent_collection = true
   formData.metadata_fields = [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
   dialogVisible.value = true

+ 42 - 20
src/views/documents/KnowledgeSnippet.vue

@@ -193,7 +193,14 @@
             </el-col>
             <el-col :span="12">
                 <el-form-item label="文档名称" required>
+                  <el-input
+                    v-if="dialogType === 'edit'"
+                    v-model="formData.doc_name"
+                    style="width: 100%"
+                    disabled
+                  />
                   <el-select 
+                    v-else
                     v-model="formData.doc_name" 
                     placeholder="请输入文档名称进行搜索" 
                     filterable 
@@ -289,19 +296,9 @@
       class="view-dialog"
     >
       <el-descriptions :column="2" border class="detail-descriptions">
-        <el-descriptions-item label="系统ID (PK)" label-class-name="desc-label">
-            <span class="copyable-text">{{ viewData.id }}</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="片段编号" label-class-name="desc-label">{{ viewData.code }}</el-descriptions-item>
         <el-descriptions-item label="文档名称" label-class-name="desc-label">{{ viewData.doc_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">{{ getKbName(viewData.collection_name) }}</el-descriptions-item>
         <el-descriptions-item label="字符数量" label-class-name="desc-label">{{ viewData.char_count }}</el-descriptions-item>
-        <el-descriptions-item label="Document ID" label-class-name="desc-label">
-            <span class="copyable-text">{{ viewData.document_id || '-' }}</span>
-        </el-descriptions-item>
-        <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">{{ viewData.created_at || '-' }}</el-descriptions-item>
         <el-descriptions-item label="标签" :span="2" label-class-name="desc-label">
             <div class="tags-container">
@@ -324,10 +321,23 @@
       <div class="view-content-section" v-if="viewData.parent_id && viewData.parent_id !== '0'">
         <div class="section-title">
             <el-icon><Connection /></el-icon> 父段内容 
-            <span class="sub-title">(ID: {{ viewData.parent_id }})</span>
         </div>
-        <div class="content-box parent-content" v-loading="parentLoading">
-            {{ parentContent || '暂无内容' }}
+        <div v-loading="parentLoading">
+            <el-tabs v-if="parentSegments.length > 1" v-model="activeParentTab" type="card">
+                <el-tab-pane
+                    v-for="(seg, idx) in parentSegments"
+                    :key="seg.id || idx"
+                    :label="`父段片段 ${idx + 1}`"
+                    :name="String(idx)"
+                >
+                    <div class="content-box parent-content">
+                        {{ seg.content || '暂无内容' }}
+                    </div>
+                </el-tab-pane>
+            </el-tabs>
+            <div v-else class="content-box parent-content">
+                {{ parentSegments[0]?.content || '暂无内容' }}
+            </div>
         </div>
       </div>
 
@@ -472,7 +482,8 @@ const loadDocOptions = async (query: string) => {
 // View Dialog
 const viewDialogVisible = ref(false)
 const viewData = ref<Snippet>({} as Snippet)
-const parentContent = ref('')
+const parentSegments = ref<any[]>([])
+const activeParentTab = ref('0')
 const parentLoading = ref(false)
 
 const currentKbSchema = ref<any[]>([]) // 当前选中知识库的自定义Schema
@@ -796,7 +807,6 @@ const handleSubmit = async () => {
              // 编辑模式下的处理...
             await updateSnippet(formData.id, {
                 collection_name: formData.collection_name,
-                doc_name: formData.doc_name, // 编辑时通常不改文档归属,或者需要同样的逻辑
                 content: formData.content,
                 custom_fields: {
                     ...formData.custom_fields,
@@ -853,19 +863,26 @@ const handleView = async (row: Snippet) => {
     viewData.value = { ...row }
     
     // 加载父段内容
-    parentContent.value = ''
+    parentSegments.value = []
+    activeParentTab.value = '0'
     if (row.parent_id && row.parent_id !== '0' && row.parent_id !== '') {
         parentLoading.value = true
         try {
             const res = await getSnippetDetail(row.collection_name, row.parent_id)
             if (res.code === 0 && res.data) {
-                parentContent.value = res.data.content || '无内容'
+                if (Array.isArray(res.data.parent_segments) && res.data.parent_segments.length > 0) {
+                    parentSegments.value = res.data.parent_segments
+                } else if (res.data.parent_content) {
+                    parentSegments.value = [{ id: '', content: res.data.parent_content }]
+                } else {
+                    parentSegments.value = []
+                }
             } else {
-                parentContent.value = '未找到父段内容'
+                parentSegments.value = []
             }
         } catch (e) {
             console.error(e)
-            parentContent.value = '加载失败'
+            parentSegments.value = []
         } finally {
             parentLoading.value = false
         }
@@ -878,6 +895,11 @@ const handleSelectionChange = (val: any) => {
     console.log(val)
 }
 
+const getKbName = (collectionName: string | undefined) => {
+    if (!collectionName) return '-'
+    return kbOptions.value.find(k => k.value === collectionName)?.label || '未知知识库'
+}
+
 const formatMetaInfo = (row: Snippet) => {
     // 优先展示 metadata 字段中的信息
     if (row.metadata) {

+ 56 - 3
src/views/documents/SearchEngine.vue

@@ -150,7 +150,11 @@
     <!-- Results Table Card -->
     <el-card class="result-card" shadow="never" v-if="hasSearched">
       <el-table :data="tableData" v-loading="loading" style="width: 100%">
-        <el-table-column prop="kb_name" label="知识库" width="150" show-overflow-tooltip />
+        <el-table-column label="知识库" width="150" show-overflow-tooltip>
+            <template #default="{ row }">
+                <span>{{ getKbName(row.kb_name) }}</span>
+            </template>
+        </el-table-column>
         <el-table-column label="文档名称" width="200" show-overflow-tooltip>
              <template #default="{ row }">
                  <span v-if="docNamesMap[row.document_id]">{{ docNamesMap[row.document_id] }}</span>
@@ -213,11 +217,11 @@
     </div>
 
     <!-- Detail Dialog -->
-    <el-dialog v-model="detailVisible" title="片段详情" width="600px">
+    <el-dialog v-model="detailVisible" title="片段详情" width="800px">
         <div class="detail-content">
             <div class="detail-item">
                 <span class="label">知识库:</span>
-                <span>{{ currentDetail?.kb_name }}</span>
+                <span>{{ getKbName(currentDetail?.kb_name) }}</span>
             </div>
             <div class="detail-item">
                 <span class="label">文档:</span>
@@ -227,6 +231,22 @@
                 <span class="label">相似度:</span>
                 <span>{{ currentDetail?.score }}%</span>
             </div>
+            <div class="detail-item full-content" v-if="currentDetail?.parent_id">
+                <span class="label">父段内容:</span>
+                <div class="text-box" v-loading="parentLoading">
+                    <el-tabs v-if="parentSegments.length > 1" v-model="activeParentTab" type="card">
+                        <el-tab-pane
+                            v-for="(seg, idx) in parentSegments"
+                            :key="seg.id || idx"
+                            :label="`父段片段 ${idx + 1}`"
+                            :name="String(idx)"
+                        >
+                            <div class="parent-tab-content">{{ seg.content || '暂无内容' }}</div>
+                        </el-tab-pane>
+                    </el-tabs>
+                    <div v-else class="parent-tab-content">{{ parentSegments[0]?.content || '暂无内容' }}</div>
+                </div>
+            </div>
             <div class="detail-item full-content">
                 <span class="label">完整内容:</span>
                 <div class="text-box">{{ currentDetail?.content }}</div>
@@ -246,6 +266,7 @@ import { Search, Plus, Delete } from '@element-plus/icons-vue'
 import { ElMessage } from 'element-plus'
 import { getKnowledgeBases, getKnowledgeBaseMetadata, type KnowledgeBase } from '@/api/knowledge-base'
 import { searchKnowledgeBase, type KBSearchResultItem } from '@/api/search-engine'
+import { getSnippetDetail } from '@/api/snippet'
 import { documentApi } from '@/api/document'
 
 // Data
@@ -295,6 +316,9 @@ const loadDocOptions = async (query: string) => {
 
 const detailVisible = ref(false)
 const currentDetail = ref<KBSearchResultItem | null>(null)
+const parentSegments = ref<any[]>([])
+const parentLoading = ref(false)
+const activeParentTab = ref('0')
 
 // Methods
 const handleModeChange = () => {
@@ -497,9 +521,38 @@ const handleSearch = async () => {
 
 const handleDetail = (row: KBSearchResultItem) => {
     currentDetail.value = row
+    parentSegments.value = []
+    parentLoading.value = false
+    activeParentTab.value = '0'
+    if (row.parent_id) {
+        parentLoading.value = true
+        getSnippetDetail(row.kb_name, row.parent_id)
+            .then((res: any) => {
+                if (res.code === 0 && res.data) {
+                    if (Array.isArray(res.data.parent_segments) && res.data.parent_segments.length > 0) {
+                        parentSegments.value = res.data.parent_segments
+                    } else if (res.data.parent_content) {
+                        parentSegments.value = [{ id: '', content: res.data.parent_content }]
+                    }
+                }
+            })
+            .finally(() => {
+                parentLoading.value = false
+            })
+    }
     detailVisible.value = true
 }
 
+const getKbName = (collectionName?: string) => {
+    if (!collectionName) return '-'
+    const matched = kbList.value.find(k =>
+        k.collection_name_children === collectionName ||
+        k.collection_name_parent === collectionName ||
+        (k as any).collection_name === collectionName
+    )
+    return matched?.name || collectionName
+}
+
 const getScoreType = (score: number) => {
     if (score >= 90) return 'success'
     if (score >= 70) return 'warning'