diff --git a/src/knowledge/base.py b/src/knowledge/base.py index c652114b..707a7de1 100644 --- a/src/knowledge/base.py +++ b/src/knowledge/base.py @@ -886,7 +886,7 @@ class KnowledgeBase(ABC): def make_retriever(db_id): async def retriever(query_text, **kwargs): - return await self.aquery(query_text, db_id, **kwargs) + return await self.aquery(query_text, db_id, agent_call=True, **kwargs) return retriever diff --git a/src/knowledge/implementations/lightrag.py b/src/knowledge/implementations/lightrag.py index 97be526b..f91cd67a 100644 --- a/src/knowledge/implementations/lightrag.py +++ b/src/knowledge/implementations/lightrag.py @@ -418,7 +418,7 @@ class LightRagKB(KnowledgeBase): return processed_items_info - async def aquery(self, query_text: str, db_id: str, **kwargs) -> str: + async def aquery(self, query_text: str, db_id: str, agent_call: bool = False, **kwargs) -> str: """异步查询知识库""" rag = await self._get_lightrag_instance(db_id) if not rag: @@ -459,8 +459,11 @@ class LightRagKB(KnowledgeBase): param = QueryParam(**params_dict) # 执行查询 - response = await rag.aquery(query_text, param) - logger.debug(f"Query response: {response}") + response = await rag.aquery_data(query_text, param) + logger.debug(f"Query response: {str(response)[:1000]}...") + + if agent_call: + return response["data"]["chunks"] return response diff --git a/src/knowledge/implementations/milvus.py b/src/knowledge/implementations/milvus.py index 39073787..7efe12c6 100644 --- a/src/knowledge/implementations/milvus.py +++ b/src/knowledge/implementations/milvus.py @@ -458,7 +458,7 @@ class MilvusKB(KnowledgeBase): return processed_items_info - async def aquery(self, query_text: str, db_id: str, **kwargs) -> list[dict]: + async def aquery(self, query_text: str, db_id: str, agent_call: bool = False, **kwargs) -> list[dict]: """异步查询知识库""" collection = await self._get_milvus_collection(db_id) if not collection: diff --git a/web/src/components/QuerySection.vue b/web/src/components/QuerySection.vue index 57eaca3b..712217a0 100644 --- a/web/src/components/QuerySection.vue +++ b/web/src/components/QuerySection.vue @@ -53,7 +53,6 @@
@@ -85,8 +84,183 @@
+ +
+ + + + + + + +
+
+
+ {{ entity.entity_name }} + {{ entity.entity_type }} +
+
+ 描述: {{ entity.description }} +
+
+ + 来源: {{ formatSourceIds(entity.source_id) }} + + + + 查看文件 + +
+
+
+
+ + + + +
+
+
+ {{ rel.src_id }} + + {{ rel.tgt_id }} + + 权重: {{ rel.weight.toFixed(2) }} + +
+
+ 描述: {{ rel.description }} +
+
+ + {{ rel.keywords }} +
+
+ + 来源: {{ rel.source_id }} + + + + 查看文件 + +
+
+
+
+ + + + +
+
+
+ 引用 [{{ chunk.reference_id }}] + {{ chunk.chunk_id }} +
+
+ {{ chunk.content }} +
+ +
+
+
+ + + + +
+
+ [{{ ref.reference_id }}] + + {{ extractFileName(ref.file_path) }} + +
+
+
+
+
+ -
+
{{ queryResult }}
@@ -152,11 +326,15 @@ import { ref, computed, onMounted, onUnmounted, watch, h } from 'vue'; import { useDatabaseStore } from '@/stores/database'; import { message } from 'ant-design-vue'; import { queryApi } from '@/apis/knowledge_api'; +import { SearchOutlined } from '@ant-design/icons-vue'; import { - SearchOutlined, - ReloadOutlined, -} from '@ant-design/icons-vue'; -import { Braces } from 'lucide-vue-next'; + Braces, + Tags, + Network, + Link, + FileText, + ArrowRight, +} from 'lucide-vue-next'; const store = useDatabaseStore(); @@ -181,6 +359,14 @@ const showRawData = ref(false); // 判断是否为 LightRAG 类型知识库 const isLightRAG = computed(() => store.database?.kb_type?.toLowerCase() === 'lightrag'); +// 判断是否是 LightRAG 格式的查询结果 +const isLightRAGResult = computed(() => { + return queryResult.value && + typeof queryResult.value === 'object' && + queryResult.value.data && + (queryResult.value.data.entities || queryResult.value.data.relationships); +}); + // 查询测试 const queryText = ref(''); @@ -194,8 +380,32 @@ const searchConfigModalVisible = ref(false); // 示例轮播相关 let exampleCarouselInterval = null; +// LightRAG 折叠面板激活的 key +const lightragActiveKeys = ref(['entities', 'relationships', 'chunks']); + // 方法定义 +// 格式化 source_id(限制显示长度) +const formatSourceIds = (sourceId) => { + if (!sourceId) return ''; + const ids = sourceId.split(''); + if (ids.length > 1) { + return `${ids[0]} ... (+${ids.length - 1} 个来源)`; + } + return sourceId; +}; + +// 从文件路径中提取文件名 +const extractFileName = (filePath) => { + if (!filePath) return ''; + try { + const parts = filePath.split('/'); + return parts[parts.length - 1]; + } catch { + return filePath; + } +}; + // 打开检索配置弹窗 const openSearchConfigModal = () => { searchConfigModalVisible.value = true; @@ -499,15 +709,14 @@ defineExpose({ .query-results { flex: 1; overflow-y: auto; - padding: 16px; background-color: var(--gray-25); min-height: 0; .result-raw { + padding: 16px; background-color: var(--gray-50); border: 1px solid var(--gray-200); border-radius: 6px; - padding: 16px; overflow-x: auto; pre { @@ -522,6 +731,7 @@ defineExpose({ } .result-text { + padding: 16px; white-space: pre-wrap; word-break: break-word; line-height: 1.6; @@ -529,6 +739,8 @@ defineExpose({ } .result-list { + padding: 16px; + .no-results { text-align: center; padding: 32px; @@ -537,11 +749,11 @@ defineExpose({ .result-summary { margin-bottom: 12px; - padding: 8px 12px; + padding: 10px 14px; background-color: var(--main-50); - border-left: 3px solid var(--main-color); - border-radius: 2px; + border-radius: 6px; color: var(--gray-800); + font-weight: 500; } .result-item { @@ -621,6 +833,8 @@ defineExpose({ } .result-unknown { + padding: 16px; + pre { background-color: var(--gray-0); border: 1px solid var(--gray-200); @@ -696,4 +910,345 @@ defineExpose({ opacity: 0; } +// LightRAG 结果样式 +.lightrag-metadata { + padding: 12px 16px; + background-color: var(--gray-0); + border-radius: 8px; + margin: 16px; + border: 1px solid var(--gray-200); + + .metadata-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + + &:last-child { + margin-bottom: 0; + } + + .metadata-label { + font-weight: 600; + color: var(--gray-700); + font-size: 13px; + } + + .metadata-value { + color: var(--gray-700); + font-size: 13px; + } + + .query-mode { + padding: 2px 8px; + background-color: var(--main-100); + color: var(--main-700); + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + } + + .keywords-text { + color: var(--gray-700); + font-size: 13px; + line-height: 1.5; + } +} + +.collapse-header { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + color: var(--gray-800); +} + +.lightrag-entities { + display: flex; + flex-direction: column; + gap: 8px; +} + +.lightrag-entity-card { + background-color: var(--gray-0); + border: 1px solid var(--gray-200); + border-radius: 6px; + padding: 12px; + transition: all 0.2s ease; + + &:hover { + border-color: var(--main-300); + } + + .entity-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + padding-bottom: 8px; + border-bottom: 1px solid var(--gray-150); + + .entity-name { + font-weight: 600; + color: var(--main-color); + font-size: 14px; + } + + .entity-type { + font-size: 12px; + color: var(--gray-600); + font-weight: 400; + } + } + + .entity-description { + margin-bottom: 8px; + line-height: 1.6; + color: var(--gray-900); + font-size: 13px; + } + + .entity-meta { + display: flex; + align-items: center; + gap: 12px; + font-size: 12px; + color: var(--gray-700); + + .meta-item { + strong { + color: var(--gray-500); + font-weight: 500; + margin-right: 4px; + } + } + + .meta-link { + display: flex; + align-items: center; + gap: 4px; + color: var(--main-color); + text-decoration: none; + transition: color 0.2s ease; + + &:hover { + color: var(--main-bright); + } + } + } +} + +.lightrag-relationships { + display: flex; + flex-direction: column; + gap: 12px; +} + +.lightrag-relationship-card { + background-color: var(--gray-0); + border: 1px solid var(--gray-200); + border-radius: 6px; + padding: 12px; + transition: all 0.2s ease; + + &:hover { + border-color: var(--main-300); + box-shadow: 0 2px 8px rgba(1, 97, 121, 0.08); + } + + .relationship-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + padding-bottom: 8px; + border-bottom: 1px solid var(--gray-150); + + .rel-src, + .rel-tgt { + font-weight: 600; + color: var(--main-color); + font-size: 14px; + } + + .rel-arrow { + color: var(--gray-500); + font-size: 12px; + } + + .rel-weight { + font-size: 12px; + color: var(--gray-600); + font-weight: 400; + margin-left: auto; + } + } + + .relationship-description { + margin-bottom: 8px; + line-height: 1.6; + color: var(--gray-900); + font-size: 13px; + } + + .relationship-keywords { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 8px; + padding: 6px 8px; + background-color: var(--gray-50); + border-radius: 4px; + font-size: 12px; + color: var(--gray-700); + + .keywords-text { + flex: 1; + } + } + + .relationship-meta { + display: flex; + align-items: center; + gap: 12px; + font-size: 12px; + color: var(--gray-700); + + .meta-item { + strong { + color: var(--gray-500); + font-weight: 500; + margin-right: 4px; + } + } + + .meta-link { + display: flex; + align-items: center; + gap: 4px; + color: var(--main-color); + text-decoration: none; + transition: color 0.2s ease; + + &:hover { + color: var(--main-bright); + } + } + } +} + +.lightrag-chunks { + display: flex; + flex-direction: column; + gap: 12px; +} + +.lightrag-chunk-card { + background-color: var(--gray-0); + border: 1px solid var(--gray-200); + border-radius: 6px; + padding: 12px; + transition: all 0.2s ease; + + &:hover { + border-color: var(--main-300); + box-shadow: 0 2px 8px rgba(1, 97, 121, 0.08); + } + + .chunk-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; + padding-bottom: 8px; + border-bottom: 1px solid var(--gray-150); + + .chunk-ref { + font-weight: 600; + color: var(--main-color); + font-size: 13px; + } + + .chunk-id { + font-size: 11px; + color: var(--gray-500); + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + } + } + + .chunk-content { + margin-bottom: 8px; + padding: 8px; + background-color: var(--gray-50); + border-radius: 4px; + line-height: 1.6; + color: var(--gray-900); + font-size: 13px; + white-space: pre-wrap; + word-break: break-word; + max-height: 200px; + overflow-y: auto; + } + + .chunk-meta { + display: flex; + align-items: center; + gap: 12px; + + .meta-link { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--main-color); + text-decoration: none; + transition: color 0.2s ease; + + &:hover { + color: var(--main-bright); + } + } + } +} + +.lightrag-references { + display: flex; + flex-direction: column; + gap: 8px; +} + +.reference-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background-color: var(--gray-0); + border: 1px solid var(--gray-200); + border-radius: 4px; + transition: all 0.2s ease; + + &:hover { + border-color: var(--main-300); + background-color: var(--gray-50); + } + + .reference-id { + font-size: 12px; + color: var(--gray-600); + font-weight: 500; + } + + .reference-link { + flex: 1; + color: var(--gray-900); + text-decoration: none; + font-size: 13px; + transition: color 0.2s ease; + + &:hover { + color: var(--main-color); + } + } +}