Переглянути джерело

检索和知识库相关的更新

linyang 1 місяць тому
батько
коміт
9b780fc571

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

@@ -19,11 +19,28 @@ export interface KnowledgeBaseParams {
   status?: string
 }
 
+export interface MetadataField {
+  field_zh_name: string
+  field_en_name: string
+  field_type: string
+  remark?: string
+}
+
+export interface CustomSchemaField {
+  field_name: string
+  field_type: string
+  max_length?: number
+  is_primary?: boolean
+  description?: string
+}
+
 export interface CreateKnowledgeBaseData {
   name: string
   collection_name: string
   description?: string
   dimension?: number
+  metadata_fields?: MetadataField[]
+  custom_schemas?: CustomSchemaField[]
 }
 
 export interface UpdateKnowledgeBaseData {
@@ -65,8 +82,22 @@ export const updateKnowledgeBaseStatus = (id: string, status: string) => {
 }
 
 export const deleteKnowledgeBase = (id: string) => {
-  return request({
-    url: '/api/v1/sample/knowledge-base/' + id + '/delete',
+  return request<any>({
+    url: `/api/v1/sample/knowledge-base/${id}/delete`,
     method: 'post'
   })
 }
+
+export const syncKnowledgeBase = (id: string) => {
+  return request<any>({
+    url: `/api/v1/sample/knowledge-base/${id}/sync`,
+    method: 'post'
+  })
+}
+
+export const getKnowledgeBaseMetadata = (id: string) => {
+  return request<any>({
+    url: `/api/v1/sample/knowledge-base/${id}/metadata`,
+    method: 'get'
+  })
+}

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

@@ -46,8 +46,12 @@ export interface KBSearchRequest {
   query: string
   metadata_field?: string
   metadata_value?: string
+  filters?: { field: string, value: string }[] // 新增多重过滤支持
   top_k?: number
   score_threshold?: number
+  metric_type?: string
+  page?: number
+  page_size?: number
 }
 
 export interface KBSearchResultItem {

+ 2 - 0
src/api/snippet.ts

@@ -27,12 +27,14 @@ export interface CreateSnippetData {
   doc_name: string
   content: string
   meta_info?: string
+  custom_fields?: Record<string, any> // 用户自定义字段值
 }
 
 export interface UpdateSnippetData {
   collection_name: string
   doc_name?: string
   content: string
+  custom_fields?: Record<string, any>
 }
 
 export const getSnippets = (params: SnippetParams) => {

+ 29 - 1
src/layouts/MainLayout.vue

@@ -218,7 +218,35 @@ const getDefaultMenus = (): MenuItem[] => {
       title: '文档管理中心',
       path: '/admin/documents',
       icon: 'Document',
-      children: []
+      children: [
+        {
+            id: 'kb-list',
+            name: 'kb-list',
+            title: '知识库管理',
+            path: '/admin/documents/kb',
+            icon: 'Collection',
+            menu_type: 'menu',
+            is_hidden: false
+        },
+        {
+            id: 'kb-snippet',
+            name: 'kb-snippet',
+            title: '知识片段',
+            path: '/admin/documents/snippet',
+            icon: 'Tickets',
+            menu_type: 'menu',
+            is_hidden: false
+        },
+        {
+            id: 'search-engine',
+            name: 'search-engine',
+            title: '检索引擎',
+            path: '/admin/documents/search-engine',
+            icon: 'Search',
+            menu_type: 'menu',
+            is_hidden: false
+        }
+      ]
     })
 
     defaultMenus.push({

+ 146 - 4
src/views/documents/KnowledgeBase.vue

@@ -80,6 +80,11 @@
                     <el-icon><Edit /></el-icon>
                 </el-button>
             </el-tooltip>
+            <el-tooltip content="同步到Milvus" placement="top">
+                <el-button link type="warning" @click="handleSync(row)">
+                    <el-icon><Refresh /></el-icon>
+                </el-button>
+            </el-tooltip>
             <el-tooltip content="删除" placement="top">
                 <el-button link type="danger" @click="handleDelete(row)">
                     <el-icon><Delete /></el-icon>
@@ -139,6 +144,88 @@
         <el-form-item label="维度" prop="dimension" v-if="dialogType === 'create'">
           <el-input-number v-model="formData.dimension" :min="1" :step="1" />
         </el-form-item>
+        <el-form-item label="自定义Schema" v-if="dialogType === 'create'">
+            <div class="metadata-fields-container">
+                <div v-for="(schema, index) in formData.custom_schemas" :key="index" class="metadata-field-row">
+                    <div class="field-inputs">
+                        <el-row :gutter="10">
+                            <el-col :span="6">
+                                <el-form-item :prop="'custom_schemas.' + index + '.field_name'" :rules="[
+                                    { required: true, message: '请输入字段名', trigger: 'blur' },
+                                    { pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: '以字母/下划线开头', trigger: 'blur' }
+                                ]" style="margin-bottom: 0;">
+                                    <el-input v-model="schema.field_name" placeholder="Schema字段名(英文)" />
+                                </el-form-item>
+                            </el-col>
+                            <el-col :span="6">
+                                <el-select v-model="schema.field_type" placeholder="Schema类型">
+                                    <el-option label="INT64" value="INT64" />
+                                    <el-option label="INT32" value="INT32" />
+                                    <el-option label="FLOAT" value="FLOAT" />
+                                    <el-option label="VARCHAR" value="VARCHAR" />
+                                    <el-option label="BOOL" value="BOOL" />
+                                    <el-option label="JSON" value="JSON" />
+                                </el-select>
+                            </el-col>
+                            <el-col :span="6">
+                                <el-input v-model="schema.description" placeholder="Schema描述" />
+                            </el-col>
+                            <el-col :span="6" v-if="schema.field_type === 'VARCHAR'">
+                                <el-input-number v-model="schema.max_length" :min="1" placeholder="Max Len" style="width: 100%" />
+                            </el-col>
+                        </el-row>
+                    </div>
+                    <el-button 
+                        type="danger" 
+                        link 
+                        :icon="Delete"
+                        @click="removeCustomSchema(index)"
+                        class="delete-btn"
+                    />
+                </div>
+                <div class="add-field-btn">
+                    <el-button type="success" link :icon="Plus" @click="addCustomSchema">添加Schema字段</el-button>
+                </div>
+            </div>
+        </el-form-item>
+
+        <el-form-item label="元数据字段" v-if="dialogType === 'create'">
+            <div class="metadata-fields-container">
+                <div v-for="(field, index) in formData.metadata_fields" :key="index" class="metadata-field-row">
+                    <div class="field-inputs">
+                        <el-row :gutter="10">
+                            <el-col :span="6">
+                                <el-input v-model="field.field_zh_name" placeholder="中文名称" />
+                            </el-col>
+                            <el-col :span="6">
+                                <el-input v-model="field.field_en_name" placeholder="英文名称" />
+                            </el-col>
+                            <el-col :span="5">
+                                <el-select v-model="field.field_type" placeholder="类型">
+                                    <el-option label="文本" value="text" />
+                                    <el-option label="数字" value="num" />
+                                </el-select>
+                            </el-col>
+                            <el-col :span="7">
+                                <el-input v-model="field.remark" placeholder="备注" />
+                            </el-col>
+                        </el-row>
+                    </div>
+                    <!-- 元数据字段现在默认必须存在至少一个,但也允许用户删除 -->
+                    <el-button 
+                        v-if="formData.metadata_fields.length > 1"
+                        type="danger" 
+                        link 
+                        :icon="Delete"
+                        @click="removeMetadataField(index)"
+                        class="delete-btn"
+                    />
+                </div>
+                <div class="add-field-btn">
+                    <el-button type="primary" link :icon="Plus" @click="addMetadataField">添加元数据字段</el-button>
+                </div>
+            </div>
+        </el-form-item>
         <el-form-item label="状态" prop="status">
             <el-radio-group v-model="formData.status">
                 <el-radio label="normal">正常</el-radio>
@@ -159,7 +246,7 @@
 
 <script setup lang="ts">
 import { ref, reactive, onMounted } from 'vue'
-import { Search, Plus, Collection, View, Edit, Delete, MoreFilled } from '@element-plus/icons-vue'
+import { Search, Plus, Collection, View, Edit, Delete, MoreFilled, Refresh } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
 import { 
@@ -168,6 +255,7 @@ import {
     updateKnowledgeBase, 
     updateKnowledgeBaseStatus, 
     deleteKnowledgeBase,
+    syncKnowledgeBase,
     type KnowledgeBase 
 } from '@/api/knowledge-base'
 import dayjs from 'dayjs'
@@ -200,14 +288,16 @@ const formData = reactive({
   collection_name: '',
   description: '',
   dimension: 768,
-  status: 'normal'
+  status: 'normal',
+  metadata_fields: [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }],
+  custom_schemas: [] as any[]
 })
 
 const rules: FormRules = {
   name: [{ required: true, message: '请输入知识库名称', trigger: 'blur' }],
   collection_name: [
     { required: true, message: '请输入集合名称', trigger: 'blur' },
-    { pattern: /^[a-zA-Z0-9_]+$/, message: '只能包含字母、数字和下划线', trigger: 'blur' }
+    { pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: '必须以字母或下划线开头,只能包含字母、数字和下划线', trigger: 'blur' }
   ],
   dimension: [{ required: true, message: '请输入维度', trigger: 'blur' }]
 }
@@ -258,6 +348,8 @@ const handleAdd = () => {
   formData.description = ''
   formData.dimension = 768
   formData.status = 'normal'
+  formData.metadata_fields = [{ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' }]
+  formData.custom_schemas = []
   dialogVisible.value = true
 }
 
@@ -272,6 +364,16 @@ const handleEdit = (row: KnowledgeBase) => {
   dialogVisible.value = true
 }
 
+const handleSync = async (row: KnowledgeBase) => {
+    try {
+        await syncKnowledgeBase(row.id)
+        ElMessage.success('同步成功,Milvus集合已创建')
+        loadData()
+    } catch (error) {
+        // error handled by request interceptor
+    }
+}
+
 const handleView = (row: KnowledgeBase) => {
     ElMessage.info(`查看知识库: ${row.name}`)
 }
@@ -311,6 +413,22 @@ const handleCommand = async (command: string, row: KnowledgeBase) => {
     }
 }
 
+const addCustomSchema = () => {
+    formData.custom_schemas.push({ field_name: '', field_type: 'VARCHAR', max_length: 256, is_primary: false, description: '' })
+}
+
+const removeCustomSchema = (index: number) => {
+    formData.custom_schemas.splice(index, 1)
+}
+
+const addMetadataField = () => {
+    formData.metadata_fields.push({ field_zh_name: '', field_en_name: '', field_type: 'text', remark: '' })
+}
+
+const removeMetadataField = (index: number) => {
+    formData.metadata_fields.splice(index, 1)
+}
+
 const handleSubmit = async () => {
   if (!formRef.value) return
   await formRef.value.validate(async (valid) => {
@@ -322,7 +440,9 @@ const handleSubmit = async () => {
             name: formData.name,
             collection_name: formData.collection_name,
             description: formData.description,
-            dimension: formData.dimension
+            dimension: formData.dimension,
+            metadata_fields: formData.metadata_fields.filter(f => f.field_zh_name && f.field_en_name),
+            custom_schemas: formData.custom_schemas.filter(s => s.field_name)
           })
           ElMessage.success('创建成功')
         } else {
@@ -462,4 +582,26 @@ onMounted(() => {
     color: #909399;
     font-size: 13px;
 }
+
+.metadata-fields-container {
+    width: 100%;
+}
+
+.metadata-field-row {
+    display: flex;
+    align-items: center;
+    margin-bottom: 10px;
+}
+
+.field-inputs {
+    flex: 1;
+}
+
+.delete-btn {
+    margin-left: 10px;
+}
+
+.add-field-btn {
+    margin-top: 5px;
+}
 </style>

+ 85 - 7
src/views/documents/KnowledgeSnippet.vue

@@ -151,7 +151,12 @@
     >
       <el-form :model="formData" label-width="100px">
         <el-form-item label="所属知识库" required>
-          <el-select v-model="formData.collection_name" placeholder="请选择知识库" :disabled="dialogType === 'edit'">
+          <el-select 
+            v-model="formData.collection_name" 
+            placeholder="请选择知识库" 
+            :disabled="dialogType === 'edit'"
+            @change="handleKbChange"
+          >
             <el-option
               v-for="item in kbOptions"
               :key="item.value"
@@ -163,6 +168,34 @@
         <el-form-item label="文档名称">
           <el-input v-model="formData.doc_name" placeholder="请输入文档来源名称" />
         </el-form-item>
+        
+        <!-- 动态渲染自定义Schema字段 -->
+        <template v-if="currentKbSchema.length > 0">
+            <el-divider content-position="left">自定义字段</el-divider>
+            <el-form-item 
+                v-for="schema in currentKbSchema" 
+                :key="schema.field_name"
+                :label="schema.field_name"
+            >
+                <!-- 根据类型渲染不同输入框 -->
+                <el-input-number 
+                    v-if="['INT64', 'INT32', 'INT16', 'INT8'].includes(schema.field_type)"
+                    v-model="formData.custom_fields[schema.field_name]"
+                    style="width: 100%"
+                />
+                <el-switch
+                    v-else-if="schema.field_type === 'BOOL'"
+                    v-model="formData.custom_fields[schema.field_name]"
+                />
+                <el-input 
+                    v-else 
+                    v-model="formData.custom_fields[schema.field_name]"
+                    :placeholder="schema.description || `请输入${schema.field_name}`"
+                />
+            </el-form-item>
+            <el-divider />
+        </template>
+
         <el-form-item label="片段内容" required>
           <el-input
             v-model="formData.content"
@@ -195,7 +228,7 @@ import {
     deleteSnippet,
     type Snippet 
 } from '@/api/snippet'
-import { getKnowledgeBases } from '@/api/knowledge-base'
+import { getKnowledgeBases, getKnowledgeBaseMetadata, type KnowledgeBase } from '@/api/knowledge-base'
 
 // Table Data
 const tableData = ref<Snippet[]>([])
@@ -224,9 +257,41 @@ const formData = reactive({
     id: '',
     collection_name: '',
     doc_name: '',
-    content: ''
+    content: '',
+    custom_fields: {} as Record<string, any>
 })
 
+const currentKbSchema = ref<any[]>([]) // 当前选中知识库的自定义Schema
+
+// 当选择知识库变化时,加载对应的 Schema 字段
+const handleKbChange = async (collection_name: string) => {
+    // 找到对应的 KB ID
+    const kb = kbOptions.value.find(k => k.value === collection_name)
+    if (!kb) {
+        currentKbSchema.value = []
+        return
+    }
+    
+    try {
+        const res = await getKnowledgeBaseMetadata(kb.id)
+        if (res.code === 0 && res.data) {
+            // 后端返回结构 { metadata_fields: [], custom_schemas: [] }
+            currentKbSchema.value = res.data.custom_schemas || []
+            
+            // 初始化 custom_fields
+            const newFields: Record<string, any> = {}
+            currentKbSchema.value.forEach(schema => {
+                // 如果是编辑模式且已有值,保留;否则初始化为空
+                newFields[schema.field_name] = formData.custom_fields[schema.field_name] || ''
+            })
+            formData.custom_fields = newFields
+        }
+    } catch (error) {
+        console.error("加载Schema失败", error)
+        currentKbSchema.value = []
+    }
+}
+
 // Methods
 const loadData = async () => {
     loading.value = true
@@ -257,14 +322,15 @@ const loadData = async () => {
 }
 
 // 加载知识库列表供筛选
-const kbOptions = ref<{label: string, value: string}[]>([])
+const kbOptions = ref<{label: string, value: string, id: string}[]>([])
 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
+                value: item.collection_name,
+                id: item.id
             }))
             // 修改为默认不选中(查询所有),直接加载数据
             if (!queryParams.kb) {
@@ -302,6 +368,14 @@ const handleEdit = (row: Snippet) => {
     formData.collection_name = row.collection_name
     formData.doc_name = row.doc_name
     formData.content = row.content
+    formData.custom_fields = {} // 编辑时也需要回填,但目前列表数据中可能没有这些字段
+    
+    // 触发加载 Schema,这里需要确保加载完成后再回填数据(如果列表里有数据的话)
+    handleKbChange(row.collection_name)
+    
+    // TODO: 如果 row 中包含 custom_fields 数据,需要回填到 formData.custom_fields
+    // 目前列表接口 _format_snippet 可能没有返回所有自定义字段
+    
     dialogVisible.value = true
 }
 
@@ -318,14 +392,16 @@ const handleSubmit = async () => {
                 collection_name: formData.collection_name,
                 doc_name: formData.doc_name || '手动添加',
                 content: formData.content,
-                meta_info: ''
+                meta_info: '',
+                custom_fields: formData.custom_fields
             })
             ElMessage.success('创建成功')
         } else {
             await updateSnippet(formData.id, {
                 collection_name: formData.collection_name,
                 doc_name: formData.doc_name,
-                content: formData.content
+                content: formData.content,
+                custom_fields: formData.custom_fields
             })
             ElMessage.success('更新成功')
         }
@@ -343,6 +419,8 @@ const resetForm = () => {
     formData.collection_name = ''
     formData.doc_name = ''
     formData.content = ''
+    formData.custom_fields = {}
+    currentKbSchema.value = []
 }
 
 const handleDelete = (row: Snippet) => {

+ 215 - 73
src/views/documents/SearchEngine.vue

@@ -13,65 +13,105 @@
 
     <!-- Search Form Card -->
     <el-card class="search-card" shadow="never">
-      <div class="search-form-row">
-        <div class="form-item kb-select">
-          <div class="label">知识库</div>
-          <el-select 
-            v-model="searchForm.kb_id" 
-            placeholder="选择知识库" 
-            style="width: 100%"
-            clearable
-            @change="handleKbChange"
-          >
-            <el-option 
-              v-for="kb in kbList" 
-              :key="kb.id" 
-              :label="kb.name" 
-              :value="kb.collection_name" 
-            />
-          </el-select>
-        </div>
-        
-        <div class="form-item meta-select">
-          <div class="label">元数据字典</div>
-          <el-select 
-            v-model="searchForm.metadata_field" 
-            placeholder="选择元数据" 
-            style="width: 100%"
-            clearable
-            :disabled="!searchForm.kb_id"
-          >
-             <!-- 这里的元数据字段应该是根据知识库动态获取的,暂时写死或留空 -->
-             <el-option label="类型" value="type" />
-             <el-option label="作者" value="author" />
-             <el-option label="来源" value="source" />
-          </el-select>
-        </div>
-
-        <div class="form-item meta-value">
-          <div class="label">元素字典值</div>
-          <el-input 
-            v-model="searchForm.metadata_value" 
-            placeholder="输入字典值" 
-            :disabled="!searchForm.metadata_field"
-          />
-        </div>
+      <div class="search-form-container">
+        <!-- 顶部行:知识库 + 检索模式 + 关键字 -->
+        <div class="search-form-row main-row">
+            <div class="form-item kb-select">
+                <div class="label">知识库</div>
+                <el-select 
+                    v-model="searchForm.kb_id" 
+                    placeholder="选择知识库" 
+                    style="width: 100%"
+                    clearable
+                    @change="handleKbChange"
+                >
+                    <el-option 
+                        v-for="kb in kbList" 
+                        :key="kb.id" 
+                        :label="kb.name" 
+                        :value="kb.collection_name" 
+                    />
+                </el-select>
+            </div>
+            
+            <div class="form-item search-mode">
+                <div class="label">检索模式</div>
+                <el-select 
+                    v-model="searchForm.mode" 
+                    placeholder="请选择" 
+                    style="width: 100%"
+                    @change="handleModeChange"
+                >
+                    <el-option label="简单模式" value="simple" />
+                    <el-option label="高级模式" value="advanced" />
+                </el-select>
+            </div>
 
-        <div class="form-item keyword-input">
-          <div class="label">检索关键字</div>
-          <el-input 
-            v-model="searchForm.query" 
-            placeholder="输入检索关键字" 
-            clearable
-            @keyup.enter="handleSearch"
-            :disabled="!searchForm.kb_id"
-          />
+            <div class="form-item keyword-input">
+                <div class="label">检索关键字</div>
+                <el-input 
+                    v-model="searchForm.query" 
+                    placeholder="请输入检索内容..." 
+                    clearable
+                    @keyup.enter="handleSearch"
+                    :disabled="!searchForm.kb_id"
+                >
+                    <template #append>
+                        <el-button :icon="Search" @click="handleSearch" :disabled="!searchForm.kb_id">
+                            检索
+                        </el-button>
+                    </template>
+                </el-input>
+            </div>
         </div>
 
-        <div class="search-btn">
-          <el-button type="primary" :icon="Search" @click="handleSearch" :disabled="!searchForm.kb_id">
-            检索
-          </el-button>
+        <!-- 高级过滤区域 (仅在高级模式显示) -->
+        <div v-if="searchForm.mode === 'advanced'" class="advanced-filter-area">
+            <div class="filter-header">
+                <span class="filter-title">元数据过滤条件</span>
+                <el-button type="primary" link :icon="Plus" size="small" @click="addFilter">添加条件</el-button>
+            </div>
+            
+            <div class="filter-list">
+                <div v-for="(filter, index) in searchForm.filters" :key="index" class="filter-item">
+                    <el-row :gutter="10" align="middle">
+                        <el-col :span="10">
+                            <el-select 
+                                v-model="filter.field" 
+                                placeholder="选择字段" 
+                                style="width: 100%"
+                                clearable
+                                :disabled="!searchForm.kb_id"
+                            >
+                                <el-option 
+                                    v-for="field in metadataFields" 
+                                    :key="field.id" 
+                                    :label="field.field_zh_name + ' (' + field.field_en_name + ')'" 
+                                    :value="field.field_en_name" 
+                                />
+                            </el-select>
+                        </el-col>
+                        <el-col :span="1" style="text-align: center; color: #909399;">=</el-col>
+                        <el-col :span="11">
+                            <el-input 
+                                v-model="filter.value" 
+                                placeholder="输入值" 
+                                :disabled="!filter.field"
+                                @keyup.enter="handleSearch"
+                            />
+                        </el-col>
+                        <el-col :span="2" style="text-align: center;">
+                            <el-button 
+                                v-if="searchForm.filters.length > 1"
+                                type="danger" 
+                                link 
+                                :icon="Delete" 
+                                @click="removeFilter(index)"
+                            />
+                        </el-col>
+                    </el-row>
+                </div>
+            </div>
         </div>
       </div>
     </el-card>
@@ -153,13 +193,14 @@
 
 <script setup lang="ts">
 import { ref, reactive, onMounted } from 'vue'
-import { Search } from '@element-plus/icons-vue'
+import { Search, Plus, Delete } from '@element-plus/icons-vue'
 import { ElMessage } from 'element-plus'
-import { getKnowledgeBases, type KnowledgeBase } from '@/api/knowledge-base'
+import { getKnowledgeBases, getKnowledgeBaseMetadata, type KnowledgeBase } from '@/api/knowledge-base'
 import { searchKnowledgeBase, type KBSearchResultItem } from '@/api/search-engine'
 
 // Data
 const kbList = ref<KnowledgeBase[]>([])
+const metadataFields = ref<any[]>([]) // Store available metadata fields for selected KB
 const loading = ref(false)
 const hasSearched = ref(false)
 const tableData = ref<KBSearchResultItem[]>([])
@@ -169,8 +210,8 @@ const pageSize = ref(10)
 
 const searchForm = reactive({
   kb_id: '',
-  metadata_field: '',
-  metadata_value: '',
+  mode: 'simple',
+  filters: [{ field: '', value: '' }] as { field: string, value: string }[],
   query: ''
 })
 
@@ -178,6 +219,21 @@ const detailVisible = ref(false)
 const currentDetail = ref<KBSearchResultItem | null>(null)
 
 // Methods
+const handleModeChange = () => {
+    // Reset advanced fields when switching to simple
+    if (searchForm.mode === 'simple') {
+        searchForm.filters = [{ field: '', value: '' }]
+    }
+}
+
+const addFilter = () => {
+    searchForm.filters.push({ field: '', value: '' })
+}
+
+const removeFilter = (index: number) => {
+    searchForm.filters.splice(index, 1)
+}
+
 const loadKBs = async () => {
   try {
     const res = await getKnowledgeBases({ page: 1, page_size: 100 }) // Load all KBs (simplified)
@@ -187,11 +243,28 @@ const loadKBs = async () => {
   }
 }
 
-const handleKbChange = () => {
+const handleKbChange = async () => {
     // Reset search when KB changes
     hasSearched.value = false
     tableData.value = []
     total.value = 0
+    
+    // Reset metadata selection
+    searchForm.filters = [{ field: '', value: '' }]
+    metadataFields.value = []
+    
+    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)
+        if (selectedKb) {
+            try {
+                const res = await getKnowledgeBaseMetadata(selectedKb.id)
+                metadataFields.value = res.data
+            } catch (error) {
+                console.error("Failed to load metadata fields", error)
+            }
+        }
+    }
 }
 
 const handleSearch = async () => {
@@ -208,21 +281,36 @@ const handleSearch = async () => {
   hasSearched.value = true
   
   try {
+    // 处理多重过滤
+    // 后端目前可能只支持单一 metadata_field/value,或者我们需要修改后端接口支持 filters 数组
+    // 这里我们先转换成后端能理解的格式,或者假设后端已更新
+    // 假设后端接口 searchKnowledgeBase 支持 filters 参数: { field: string, value: string }[]
+    
+    // 过滤掉空的条件
+    const validFilters = searchForm.mode === 'advanced' 
+        ? searchForm.filters.filter(f => f.field && f.value)
+        : []
+        
     const res = await searchKnowledgeBase({
         kb_id: searchForm.kb_id,
-        query: searchForm.query || '', // Allow empty query for "list all" or similar if supported
-        metadata_field: searchForm.metadata_field || undefined,
-        metadata_value: searchForm.metadata_value || undefined,
-        top_k: pageSize.value, // Simplified pagination logic for vector search
-        // Note: Real vector search pagination is complex. Here we just fetch top K.
-        // If we want real paging, we need to ask backend for offset, but Milvus usually just does top_k.
-        // For UI consistency, we just use pageSize as top_k here for demo.
+        query: searchForm.query || '', 
+        // 传递 filters 数组 (需要后端支持,或者我们在前端做兼容处理)
+        // 为了兼容旧接口,如果只有一个 filter,传旧参数;如果有多个,传新参数 filters
+        metadata_field: validFilters.length === 1 ? validFilters[0].field : undefined,
+        metadata_value: validFilters.length === 1 ? validFilters[0].value : undefined,
+        filters: validFilters.length > 1 ? validFilters : undefined,
+        
+        top_k: pageSize.value, 
+        page: currentPage.value,
+        page_size: pageSize.value,
+        metric_type: 'hybrid', 
     })
     
     tableData.value = res.data.results
     total.value = res.data.total
-  } catch (error) {
+  } catch (error: any) {
     console.error(error)
+    ElMessage.error(error.message || '检索失败,请检查配置或稍后重试')
   } finally {
     loading.value = false
   }
@@ -292,18 +380,72 @@ onMounted(() => {
   align-items: flex-end;
 }
 
-.form-item {
-  flex: 1;
+.search-form-row.main-row {
+    align-items: flex-end;
+    margin-bottom: 0;
+}
+
+.kb-select {
+    flex: 0 0 250px;
+}
+
+.search-mode {
+    flex: 0 0 150px;
+}
+
+.keyword-input {
+    flex: 1;
 }
 
 .form-item .label {
   font-size: 13px;
   color: #606266;
   margin-bottom: 8px;
+  font-weight: 500;
+}
+
+.advanced-filter-area {
+    margin-top: 20px;
+    padding-top: 20px;
+    border-top: 1px dashed #dcdfe6;
+}
+
+.filter-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 12px;
+}
+
+.filter-title {
+    font-size: 13px;
+    font-weight: 600;
+    color: #606266;
+}
+
+.filter-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+}
+
+.filter-item {
+    width: calc(33.33% - 8px); /* 一行三个 */
+    background-color: #f5f7fa;
+    padding: 10px;
+    border-radius: 4px;
+}
+
+@media screen and (max-width: 1200px) {
+    .filter-item {
+        width: calc(50% - 6px); /* 一行两个 */
+    }
 }
 
-.search-btn {
-  margin-bottom: 1px;
+@media screen and (max-width: 768px) {
+    .filter-item {
+        width: 100%; /* 一行一个 */
+    }
 }
 
 .result-card {