feat: update knowledge and agent UI
This commit is contained in:
1 parent
bebe4feccf
commit
7efd811e75
27 files changed
+2949
-782
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { Search } from "lucide-react";
|
||||
import { ChevronDown, ChevronRight, Search, X } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import type { GraphNode, GraphStats, NodeKind } from "./graphTypes";
|
||||
import {
|
||||
@@ -24,9 +25,56 @@ interface GraphSidebarProps {
|
||||
onToggleEdgeLabel: (label: string) => void;
|
||||
onRestoreHidden: () => void;
|
||||
onPickNode: (id: string) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type SectionKey = "nodeTypes" | "edgeTypes" | "graphStats" | "coreNodes";
|
||||
|
||||
interface FilterSectionProps {
|
||||
title: string;
|
||||
extra?: string;
|
||||
collapsed: boolean;
|
||||
onToggle: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function FilterSection(props: FilterSectionProps) {
|
||||
return (
|
||||
<section className="kg-filter-section">
|
||||
<button
|
||||
type="button"
|
||||
className="kg-filter-section-header"
|
||||
onClick={props.onToggle}
|
||||
aria-expanded={!props.collapsed}
|
||||
>
|
||||
<span>{props.title}</span>
|
||||
<span className="kg-filter-section-extra">
|
||||
{props.extra}
|
||||
{props.collapsed ? <ChevronRight size={14} /> : <ChevronDown size={14} />}
|
||||
</span>
|
||||
</button>
|
||||
{!props.collapsed ? (
|
||||
<div className="kg-filter-section-body">{props.children}</div>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function GraphSidebar(props: GraphSidebarProps) {
|
||||
// 节点类型 / 关系类型默认收起,统计与核心节点默认展开。
|
||||
const [collapsedSections, setCollapsedSections] = useState<
|
||||
Record<SectionKey, boolean>
|
||||
>({
|
||||
nodeTypes: true,
|
||||
edgeTypes: true,
|
||||
graphStats: false,
|
||||
coreNodes: false,
|
||||
});
|
||||
|
||||
const toggleFilterSection = (key: SectionKey) => {
|
||||
setCollapsedSections((prev) => ({ ...prev, [key]: !prev[key] }));
|
||||
};
|
||||
|
||||
const kinds = NODE_KIND_ORDER.filter((k) => (props.nodeKindCounts.get(k) ?? 0) > 0);
|
||||
const edgeLabels = [...props.edgeLabelCounts.entries()].sort((a, b) => b[1] - a[1]);
|
||||
|
||||
@@ -34,15 +82,25 @@ export function GraphSidebar(props: GraphSidebarProps) {
|
||||
<aside className="kg-panel kg-sidebar">
|
||||
<div className="kg-panel-header">
|
||||
<span className="kg-panel-title">搜索与筛选</span>
|
||||
{props.hiddenCount > 0 ? (
|
||||
<div className="kg-panel-header-actions">
|
||||
{props.hiddenCount > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="kg-text-button"
|
||||
onClick={props.onRestoreHidden}
|
||||
>
|
||||
恢复 {props.hiddenCount} 个隐藏节点
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
className="kg-text-button"
|
||||
onClick={props.onRestoreHidden}
|
||||
className="kg-panel-close-btn"
|
||||
title="收起筛选面板"
|
||||
onClick={props.onClose}
|
||||
>
|
||||
恢复 {props.hiddenCount} 个隐藏节点
|
||||
<X size={15} />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="kg-panel-content">
|
||||
<div className="kg-search-wrap">
|
||||
@@ -67,8 +125,12 @@ export function GraphSidebar(props: GraphSidebarProps) {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<section className="kg-filter-section">
|
||||
<div className="kg-filter-title">节点类型</div>
|
||||
<FilterSection
|
||||
title="节点类型"
|
||||
extra={`${kinds.length} 类`}
|
||||
collapsed={collapsedSections.nodeTypes}
|
||||
onToggle={() => toggleFilterSection("nodeTypes")}
|
||||
>
|
||||
{kinds.map((kind) => (
|
||||
<label key={kind} className="kg-check-row">
|
||||
<span className="kg-check-row-main">
|
||||
@@ -86,11 +148,15 @@ export function GraphSidebar(props: GraphSidebarProps) {
|
||||
<span className="kg-count">{props.nodeKindCounts.get(kind)}</span>
|
||||
</label>
|
||||
))}
|
||||
</section>
|
||||
</FilterSection>
|
||||
|
||||
{edgeLabels.length > 0 ? (
|
||||
<section className="kg-filter-section">
|
||||
<div className="kg-filter-title">关系类型</div>
|
||||
<FilterSection
|
||||
title="关系类型"
|
||||
extra={`${edgeLabels.length} 类`}
|
||||
collapsed={collapsedSections.edgeTypes}
|
||||
onToggle={() => toggleFilterSection("edgeTypes")}
|
||||
>
|
||||
{edgeLabels.map(([label, count]) => {
|
||||
const info = getEdgeInfo(label);
|
||||
return (
|
||||
@@ -114,11 +180,14 @@ export function GraphSidebar(props: GraphSidebarProps) {
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</FilterSection>
|
||||
) : null}
|
||||
|
||||
<section className="kg-filter-section">
|
||||
<div className="kg-filter-title">图谱统计</div>
|
||||
<FilterSection
|
||||
title="图谱统计"
|
||||
collapsed={collapsedSections.graphStats}
|
||||
onToggle={() => toggleFilterSection("graphStats")}
|
||||
>
|
||||
<div className="kg-stats-grid">
|
||||
<div className="kg-stat-card">
|
||||
<div className="kg-stat-card-value">{props.stats.nodes}</div>
|
||||
@@ -139,11 +208,15 @@ export function GraphSidebar(props: GraphSidebarProps) {
|
||||
<div className="kg-stat-card-label">最大连通分量</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</FilterSection>
|
||||
|
||||
{props.topNodes.length > 0 ? (
|
||||
<section className="kg-filter-section">
|
||||
<div className="kg-filter-title">核心节点</div>
|
||||
<FilterSection
|
||||
title="核心节点"
|
||||
extra={`${props.topNodes.length} 个`}
|
||||
collapsed={collapsedSections.coreNodes}
|
||||
onToggle={() => toggleFilterSection("coreNodes")}
|
||||
>
|
||||
{props.topNodes.map(({ node, degree }, i) => (
|
||||
<div
|
||||
key={node.id}
|
||||
@@ -155,7 +228,7 @@ export function GraphSidebar(props: GraphSidebarProps) {
|
||||
<span className="kg-rank-degree">{degree} 度</span>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</FilterSection>
|
||||
) : null}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
Reference in new issue
Block a user