From 7efd811e7596952386bb4b40dcf146f8c1cfcbfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Thu, 11 Jun 2026 16:18:38 +0800 Subject: [PATCH] feat: update knowledge and agent UI --- .../knowledge-graph/GraphCanvas.tsx | 87 ++- .../knowledge-graph/GraphDetailPanel.tsx | 76 +- .../knowledge-graph/GraphHeader.tsx | 23 - .../knowledge-graph/GraphSidebar.tsx | 109 ++- .../knowledge-graph/graphStyles.css | 152 +++- .../components/knowledge-graph/graphTypes.ts | 12 + .../components/knowledge-graph/graphUtils.ts | 4 + .../src/components/wiki/Breadcrumb.tsx | 24 +- .../src/components/wiki/DocumentToolbar.tsx | 33 +- .../src/components/wiki/FloatingToc.tsx | 90 +++ .../src/components/wiki/KnowledgeSidebar.tsx | 83 +- .../src/components/wiki/KnowledgeTree.tsx | 25 +- .../src/components/wiki/RelatedDocs.tsx | 120 +++ frontend-web/src/components/wiki/TocPanel.tsx | 65 -- .../src/components/wiki/WikiContent.tsx | 373 +++++---- frontend-web/src/components/wiki/wiki.css | 87 ++- frontend-web/src/components/wiki/wikiUtils.ts | 61 ++ .../workspace/agent-info-sidebar.tsx | 224 ------ .../components/workspace/agent-welcome.tsx | 40 +- .../workspace/agents/agent-card.tsx | 78 +- .../workspace/agents/agent-detail-panel.tsx | 736 ++++++++++++++++++ .../agents/agent-question-editor.tsx | 120 +++ .../workspace/agents/enabled-skill-list.tsx | 153 ++++ .../workspace/agents/skill-picker-sheet.tsx | 436 +++++++++++ frontend-web/src/pages/AgentChatPage.tsx | 102 ++- frontend-web/src/pages/KnowledgeBasePage.tsx | 55 +- frontend-web/src/pages/KnowledgeGraphPage.tsx | 363 ++++++++- 27 files changed, 2949 insertions(+), 782 deletions(-) create mode 100644 frontend-web/src/components/wiki/FloatingToc.tsx create mode 100644 frontend-web/src/components/wiki/RelatedDocs.tsx delete mode 100644 frontend-web/src/components/wiki/TocPanel.tsx delete mode 100644 frontend-web/src/components/workspace/agent-info-sidebar.tsx create mode 100644 frontend-web/src/components/workspace/agents/agent-detail-panel.tsx create mode 100644 frontend-web/src/components/workspace/agents/agent-question-editor.tsx create mode 100644 frontend-web/src/components/workspace/agents/enabled-skill-list.tsx create mode 100644 frontend-web/src/components/workspace/agents/skill-picker-sheet.tsx diff --git a/frontend-web/src/components/knowledge-graph/GraphCanvas.tsx b/frontend-web/src/components/knowledge-graph/GraphCanvas.tsx index 5be8491b..a548e2e3 100644 --- a/frontend-web/src/components/knowledge-graph/GraphCanvas.tsx +++ b/frontend-web/src/components/knowledge-graph/GraphCanvas.tsx @@ -20,6 +20,7 @@ import type { import { applyCircularLayout, ForceSimulation, + getEdgeId, getEdgeInfo, getEdgeLabel, getLinkDistance, @@ -56,8 +57,20 @@ interface GraphCanvasProps { onSelectNode: (id: string | null) => void; onTooltip: (tooltip: TooltipState | null) => void; onZoomChange: (k: number) => void; + /** + * Explore-mode rendering context; null/undefined renders the normal full view. + * preview = no root picked yet: nodes are shown faded as pickable起点 candidates. + */ + explore?: { + preview: boolean; + rootId: string | null; + nodeDepthMap: Map; + expandedNodeIds: Set; + } | null; /** Floating pills rendered top-left over the canvas. */ toolbar?: ReactNode; + /** Small floating hint rendered bottom-center over the canvas. */ + hint?: ReactNode; /** Centered overlay (empty / loading states). */ overlay?: ReactNode; } @@ -153,7 +166,7 @@ export const GraphCanvas = forwardRef( if (!source || !target) continue; const label = getEdgeLabel(e); simEdges.push({ - id: e.id ?? `${e.source}->${e.target}:${label}`, + id: getEdgeId(e), source, target, label, @@ -213,7 +226,13 @@ export const GraphCanvas = forwardRef( // Redraw on highlight-affecting prop changes. useEffect(() => { requestDraw(); - }, [props.selectedNodeId, props.searchMatchIds, props.showLabels, requestDraw]); + }, [ + props.selectedNodeId, + props.searchMatchIds, + props.showLabels, + props.explore, + requestDraw, + ]); // ------------------------------------------------------------ transform @@ -361,6 +380,7 @@ export const GraphCanvas = forwardRef( const neighbors = activeId ? adjacencyRef.current.get(activeId) : undefined; const search = p.searchMatchIds; const k = t.k || 1; + const explore = p.explore; // ---- edges for (const e of sim.edges) { @@ -373,6 +393,13 @@ export const GraphCanvas = forwardRef( alpha = search.has(e.source.id) || search.has(e.target.id) ? 0.75 : 0.06; else alpha = 0.38; + if (explore && !explore.preview) { + const sourceDepth = explore.nodeDepthMap.get(e.source.id) ?? 0; + const targetDepth = explore.nodeDepthMap.get(e.target.id) ?? 0; + const edgeDepth = Math.max(sourceDepth, targetDepth); + alpha *= Math.max(0.48, 1 - edgeDepth * 0.12); + } + ctx.globalAlpha = alpha; ctx.strokeStyle = e.info.color; ctx.lineWidth = (touchesActive ? 2.4 : 1.2) / k; @@ -405,33 +432,73 @@ export const GraphCanvas = forwardRef( const isActive = n.id === activeId; const isSelected = n.id === p.selectedNodeId; const isNeighbor = neighbors?.has(n.id) ?? false; + const depth = explore?.nodeDepthMap.get(n.id); + const isExploreRoot = explore?.rootId === n.id; + const isExpanded = explore?.expandedNodeIds.has(n.id) ?? false; let alpha: number; if (activeId) alpha = isActive || isNeighbor ? 1 : 0.14; else if (search) alpha = search.has(n.id) ? 1 : 0.14; else alpha = 1; - const r = n.radius * (n.id === hover?.id ? 1.15 : 1); + let baseRadius = n.radius; + let labelByExplore = false; + let strokeWidth = 1.4 / k; + let strokeColor = "rgba(2, 6, 23, 0.55)"; + let fillColor = n.color; + if (explore?.preview) { + alpha *= 0.42; + baseRadius = Math.max(8, n.radius * 0.86); + labelByExplore = isActive || isSelected; + } else if (explore && depth != null) { + baseRadius = depth === 0 ? 34 : Math.max(14, 28 - depth * 4); + alpha *= Math.max(0.45, 1 - depth * 0.12); + labelByExplore = depth <= 2 || isActive || isSelected; + strokeWidth = (depth === 0 ? 4 : isExpanded ? 3 : 1.5) / k; + strokeColor = depth === 0 ? "#e0f2fe" : isExpanded ? "#7dd3fc" : "rgba(2, 6, 23, 0.55)"; + if (isExploreRoot) fillColor = "#38bdf8"; + } + + const r = baseRadius * (n.id === hover?.id ? 1.15 : 1); ctx.globalAlpha = alpha; // Glow for hubs and the active node. - if (alpha > 0.5 && (isActive || isSelected || n.degree >= labelThreshold)) { - ctx.shadowColor = n.color; - ctx.shadowBlur = isActive || isSelected ? 22 : 12; + if ( + alpha > 0.5 && + (isActive || isSelected || isExploreRoot || isExpanded || n.degree >= labelThreshold) + ) { + ctx.shadowColor = fillColor; + ctx.shadowBlur = isActive || isSelected || isExploreRoot ? 24 : 12; } else { ctx.shadowBlur = 0; } ctx.beginPath(); ctx.arc(n.x, n.y, r, 0, Math.PI * 2); - ctx.fillStyle = n.color; + ctx.fillStyle = fillColor; ctx.fill(); ctx.shadowBlur = 0; // Rim. - ctx.lineWidth = 1.4 / k; - ctx.strokeStyle = "rgba(2, 6, 23, 0.55)"; + ctx.lineWidth = strokeWidth; + ctx.strokeStyle = strokeColor; ctx.stroke(); + if (isExploreRoot) { + ctx.beginPath(); + ctx.arc(n.x, n.y, r + 8 / k, 0, Math.PI * 2); + ctx.lineWidth = 1.4 / k; + ctx.strokeStyle = "rgba(125, 211, 252, 0.9)"; + ctx.stroke(); + } else if (isExpanded) { + ctx.beginPath(); + ctx.arc(n.x + r * 0.58, n.y - r * 0.58, Math.max(4 / k, 3), 0, Math.PI * 2); + ctx.fillStyle = "#7dd3fc"; + ctx.fill(); + ctx.lineWidth = 1 / k; + ctx.strokeStyle = "rgba(2, 6, 23, 0.8)"; + ctx.stroke(); + } + // Selection: double ring. if (isSelected) { ctx.beginPath(); @@ -465,6 +532,7 @@ export const GraphCanvas = forwardRef( isActive || isSelected || isNeighbor || + labelByExplore || (search?.has(n.id) ?? false) || (p.showLabels && n.degree >= labelThreshold && k >= 0.45); if (wantsLabel && alpha > 0.5) { @@ -775,6 +843,7 @@ export const GraphCanvas = forwardRef( {props.toolbar ? (
{props.toolbar}
) : null} + {props.hint} {minimapVisible ? ( ) : null} diff --git a/frontend-web/src/components/knowledge-graph/GraphDetailPanel.tsx b/frontend-web/src/components/knowledge-graph/GraphDetailPanel.tsx index e265f106..9c533d90 100644 --- a/frontend-web/src/components/knowledge-graph/GraphDetailPanel.tsx +++ b/frontend-web/src/components/knowledge-graph/GraphDetailPanel.tsx @@ -1,4 +1,13 @@ -import { Crosshair, EyeOff, FileText, Loader2, MousePointerClick } from "lucide-react"; +import { + Compass, + Crosshair, + EyeOff, + FileText, + Loader2, + MousePointerClick, + Waypoints, + X, +} from "lucide-react"; import type { KnowledgeNote } from "@/core/knowledge/types"; @@ -37,10 +46,20 @@ interface GraphDetailPanelProps { /** Note detail (only for note-type nodes; undefined while absent/loading). */ note: KnowledgeNote | undefined; noteLoading: boolean; + /** Explore-mode context; null when in full mode. */ + explore: { + /** Depth of this node in the explore graph; null = not yet visible. */ + depth: number | null; + expanded: boolean; + isRoot: boolean; + } | null; onViewSource: (noteId: string) => void; onLocate: (id: string) => void; onHide: (id: string) => void; onPickNeighbor: (id: string) => void; + onExpandNode: (id: string) => void; + onSetExploreRoot: (id: string) => void; + onClose: () => void; } function formatTime(value: string | null | undefined): string { @@ -57,6 +76,14 @@ export function GraphDetailPanel(props: GraphDetailPanelProps) {