diff --git a/ui/web/src/i18n/locales/en/memory.json b/ui/web/src/i18n/locales/en/memory.json index b6ca0ac1..df0d44a4 100644 --- a/ui/web/src/i18n/locales/en/memory.json +++ b/ui/web/src/i18n/locales/en/memory.json @@ -143,7 +143,11 @@ "source": "Source:", "properties": "Properties:", "relations": "Relations", - "traverse": "Traverse (2-hop)", + "traverse": "Traverse (3-hop)", + "tabs": { + "table": "Table", + "graph": "Graph" + }, "traversing": "Traversing...", "loading": "Loading...", "noRelations": "No relations found.", diff --git a/ui/web/src/i18n/locales/vi/memory.json b/ui/web/src/i18n/locales/vi/memory.json index 2b8c0861..628ca51a 100644 --- a/ui/web/src/i18n/locales/vi/memory.json +++ b/ui/web/src/i18n/locales/vi/memory.json @@ -143,7 +143,11 @@ "source": "Nguồn:", "properties": "Thuộc tính:", "relations": "Quan hệ", - "traverse": "Duyệt (2 bước)", + "traverse": "Duyệt (3 bước)", + "tabs": { + "table": "Bảng", + "graph": "Đồ thị" + }, "traversing": "Đang duyệt...", "loading": "Đang tải...", "noRelations": "Không tìm thấy quan hệ.", diff --git a/ui/web/src/i18n/locales/zh/memory.json b/ui/web/src/i18n/locales/zh/memory.json index cdc119d0..54aaf1c0 100644 --- a/ui/web/src/i18n/locales/zh/memory.json +++ b/ui/web/src/i18n/locales/zh/memory.json @@ -143,7 +143,11 @@ "source": "来源:", "properties": "属性:", "relations": "关系", - "traverse": "遍历(2 跳)", + "traverse": "遍历(3 跳)", + "tabs": { + "table": "表格", + "graph": "图谱" + }, "traversing": "遍历中...", "loading": "加载中...", "noRelations": "未找到关系。", diff --git a/ui/web/src/pages/memory/kg-entity-detail-dialog.tsx b/ui/web/src/pages/memory/kg-entity-detail-dialog.tsx index 1986f3e2..5bbaeecf 100644 --- a/ui/web/src/pages/memory/kg-entity-detail-dialog.tsx +++ b/ui/web/src/pages/memory/kg-entity-detail-dialog.tsx @@ -1,15 +1,17 @@ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { GitFork } from "lucide-react"; import { useTranslation } from "react-i18next"; import { useKGTraversal } from "./hooks/use-knowledge-graph"; +import { KGGraphView } from "./kg-graph-view"; import type { KGEntity, KGRelation } from "@/types/knowledge-graph"; interface KGEntityDetailDialogProps { @@ -45,14 +47,44 @@ export function KGEntityDetailDialog({ open, onOpenChange, agentId, entity, getE } }, [open, entity, loadRelations]); - const handleTraverse = () => { + const handleTraverse = useCallback(() => { if (!entity) return; - traverse(entity.id, entity.user_id, 2); - }; + traverse(entity.id, entity.user_id, 3); + }, [entity, traverse]); + + // Auto-traverse on open + useEffect(() => { + if (open && entity && traversalResults.length === 0 && !traversing) { + traverse(entity.id, entity.user_id, 3); + } + }, [open, entity]); // eslint-disable-line react-hooks/exhaustive-deps + + // Build graph data from traversal results + const graphData = useMemo(() => { + if (!entity || traversalResults.length === 0) return null; + const entities = [entity, ...traversalResults.map((r) => r.entity)]; + const graphRelations: KGRelation[] = traversalResults + .filter((r) => r.path.length >= 2 && r.via && r.path[r.path.length - 2]) + .map((r, i) => { + const parentId = r.path[r.path.length - 2]!; + const isReverse = r.via.startsWith("~"); + const relType = isReverse ? r.via.slice(1) : r.via; + return { + id: `trav-${i}`, + agent_id: entity.agent_id, + source_entity_id: isReverse ? r.entity.id : parentId, + relation_type: relType, + target_entity_id: isReverse ? parentId : r.entity.id, + confidence: r.entity.confidence, + created_at: 0, + }; + }); + return { entities, relations: graphRelations }; + }, [entity, traversalResults]); return ( !traversing && onOpenChange(v)}> - + {entity?.name} @@ -97,7 +129,7 @@ export function KGEntityDetailDialog({ open, onOpenChange, agentId, entity, getE )} - {/* Relations */} + {/* Relations tabs */}

{t("kg.entity.relations")}

@@ -106,36 +138,66 @@ export function KGEntityDetailDialog({ open, onOpenChange, agentId, entity, getE {traversing ? t("kg.entity.traversing") : t("kg.entity.traverse")}
- {loadingRels ? ( -

{t("kg.entity.loading")}

- ) : relations.length === 0 ? ( -

{t("kg.entity.noRelations")}

- ) : ( - - )} -
- {/* Traversal results */} - {traversalResults.length > 0 && ( -
-

- {t("kg.entity.traversalResults", { count: traversalResults.length })} -

-
- {traversalResults.map((tr, i) => ( -
- depth {tr.depth} - {tr.via && —[{tr.via}]→} - {tr.entity.name} - {tr.entity.entity_type} - {tr.entity.description && ( - {tr.entity.description} - )} + + + {t("kg.entity.tabs.table")} + {t("kg.entity.tabs.graph")} + + + + {loadingRels ? ( +

{t("kg.entity.loading")}

+ ) : relations.length === 0 ? ( +

{t("kg.entity.noRelations")}

+ ) : ( + + )} + + {/* Traversal results */} + {traversalResults.length > 0 && ( +
+

+ {t("kg.entity.traversalResults", { count: traversalResults.length })} +

+
+ {traversalResults.map((tr, i) => ( +
+ depth {tr.depth} + {tr.via && ( + tr.via.startsWith("~") + ? ←[{tr.via.slice(1)}]— + : —[{tr.via}]→ + )} + {tr.entity.name} + {tr.entity.entity_type} + {tr.entity.description && ( + {tr.entity.description} + )} +
+ ))} +
- ))} -
-
- )} + )} + + + + {traversing ? ( +
+ {t("kg.entity.traversing")}... +
+ ) : graphData ? ( +
+ +
+ ) : ( +
+ {t("kg.entity.noRelations")} +
+ )} +
+ +