From b2fda38e902b1e9aa742774e9999c97c505ac5a8 Mon Sep 17 00:00:00 2001 From: viettranx Date: Fri, 3 Apr 2026 20:09:24 +0700 Subject: [PATCH] feat(kg): node limit selector, remove auto-zoom, graph as default view - Add node limit selector (100/200/300/500) in graph stats bar - Graph API fetches 500 (server max), frontend clips by degree centrality - Remove auto zoom-to-fit on engine stop (was overriding user zoom/pan) - Keep manual zoom-to-fit button in stats bar - Default view mode changed from table to graph --- .../pages/memory/hooks/use-knowledge-graph.ts | 2 +- ui/web/src/pages/memory/kg-entities-tab.tsx | 2 +- ui/web/src/pages/memory/kg-graph-view.tsx | 28 ++++++++++++------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/ui/web/src/pages/memory/hooks/use-knowledge-graph.ts b/ui/web/src/pages/memory/hooks/use-knowledge-graph.ts index 382463ce..eee6eb31 100644 --- a/ui/web/src/pages/memory/hooks/use-knowledge-graph.ts +++ b/ui/web/src/pages/memory/hooks/use-knowledge-graph.ts @@ -140,7 +140,7 @@ export function useKGGraph(agentId: string, userId?: string) { queryKey: queryKeys.kg.graph(agentId, userId), queryFn: async () => { if (!agentId) return { entities: [], relations: [] }; - const params: Record = { limit: "200" }; + const params: Record = { limit: "500" }; if (userId) params.user_id = userId; return http.get(`/v1/agents/${agentId}/kg/graph`, params); }, diff --git a/ui/web/src/pages/memory/kg-entities-tab.tsx b/ui/web/src/pages/memory/kg-entities-tab.tsx index d85e930a..472637b0 100644 --- a/ui/web/src/pages/memory/kg-entities-tab.tsx +++ b/ui/web/src/pages/memory/kg-entities-tab.tsx @@ -31,7 +31,7 @@ export function KGEntitiesTab({ agentId, userId }: KGEntitiesTabProps) { const [deleteLoading, setDeleteLoading] = useState(false); const [extractOpen, setExtractOpen] = useState(false); const [dedupOpen, setDedupOpen] = useState(false); - const [viewMode, setViewMode] = useState("table"); + const [viewMode, setViewMode] = useState("graph"); const { entities, loading, fetching, refresh, deleteEntity, getEntityWithRelations, extractFromText } = useKnowledgeGraph({ agentId, diff --git a/ui/web/src/pages/memory/kg-graph-view.tsx b/ui/web/src/pages/memory/kg-graph-view.tsx index a2b44afe..ec3d4e07 100644 --- a/ui/web/src/pages/memory/kg-graph-view.tsx +++ b/ui/web/src/pages/memory/kg-graph-view.tsx @@ -6,7 +6,8 @@ import { useTranslation } from "react-i18next"; import { useUiStore } from "@/stores/use-ui-store"; import type { KGEntity, KGRelation } from "@/types/knowledge-graph"; -const GRAPH_LIMIT = 200; +const NODE_LIMIT_OPTIONS = [100, 200, 300, 500] as const; +const DEFAULT_NODE_LIMIT = 200; const NODE_R = 5; const DOUBLE_CLICK_MS = 280; @@ -63,6 +64,7 @@ export function KGGraphView({ entities: allEntities, relations: allRelations, on const containerRef = useRef(null); const [dimensions, setDimensions] = useState({ width: 600, height: 400 }); const [selectedNodeId, setSelectedNodeId] = useState(null); + const [nodeLimit, setNodeLimit] = useState(DEFAULT_NODE_LIMIT); // Double-click detection refs const clickTimerRef = useRef | null>(null); @@ -82,12 +84,12 @@ export function KGGraphView({ entities: allEntities, relations: allRelations, on // --- Limit entities by degree centrality --- const totalCount = allEntities.length; - const isLimited = totalCount > GRAPH_LIMIT; + const isLimited = totalCount > nodeLimit; const entities = useMemo(() => { - if (!isLimited) return allEntities; + if (totalCount <= nodeLimit) return allEntities; const deg = computeDegreeMap(allEntities, allRelations); - return [...allEntities].sort((a, b) => (deg.get(b.id) ?? 0) - (deg.get(a.id) ?? 0)).slice(0, GRAPH_LIMIT); - }, [allEntities, allRelations, isLimited]); + return [...allEntities].sort((a, b) => (deg.get(b.id) ?? 0) - (deg.get(a.id) ?? 0)).slice(0, nodeLimit); + }, [allEntities, allRelations, totalCount, nodeLimit]); const entityMap = useMemo(() => new Map(entities.map((e) => [e.id, e])), [entities]); @@ -235,9 +237,8 @@ export function KGGraphView({ entities: allEntities, relations: allRelations, on setSelectedNodeId(null); }, []); - // --- Zoom to fit after engine stops --- const handleEngineStop = useCallback(() => { - graphRef.current?.zoomToFit(300, 30); + // No auto zoom — initial view from warmupTicks is already good }, []); // --- Empty state --- @@ -291,11 +292,18 @@ export function KGGraphView({ entities: allEntities, relations: allRelations, on {t("kg.graphView.nodes", { count: totalCount })} {t("kg.graphView.edges", { count: allRelations.length })} {isLimited && ( - - · {t("kg.graphView.limitNote", { limit: GRAPH_LIMIT, total: totalCount })} - + · {t("kg.graphView.limitNote", { limit: nodeLimit, total: totalCount })} )}
+