mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
Merge pull request #132 from Xingkai98/feat/dashboard-file-class-views
feat(dashboard): add file/class dual-view toggle to reduce graph clutter
This commit is contained in:
@@ -109,6 +109,10 @@ function Dashboard({ accessToken }: { accessToken: string }) {
|
||||
const togglePathFinder = useDashboardStore((s) => s.togglePathFinder);
|
||||
const nodeTypeFilters = useDashboardStore((s) => s.nodeTypeFilters);
|
||||
const toggleNodeTypeFilter = useDashboardStore((s) => s.toggleNodeTypeFilter);
|
||||
const detailLevel = useDashboardStore((s) => s.detailLevel);
|
||||
const setDetailLevel = useDashboardStore((s) => s.setDetailLevel);
|
||||
const showFunctionsInClassView = useDashboardStore((s) => s.showFunctionsInClassView);
|
||||
const toggleShowFunctionsInClassView = useDashboardStore((s) => s.toggleShowFunctionsInClassView);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
const [graphIssues, setGraphIssues] = useState<GraphIssue[]>([]);
|
||||
const [showKeyboardHelp, setShowKeyboardHelp] = useState(false);
|
||||
@@ -443,6 +447,52 @@ function Dashboard({ accessToken }: { accessToken: string }) {
|
||||
<div className="flex-1 min-w-0 overflow-x-auto scrollbar-hide">
|
||||
<div className="flex items-center gap-4 w-max">
|
||||
<DiffToggle />
|
||||
{/* Detail level: file view (architecture) / class view (code structure) */}
|
||||
{!isKnowledgeGraph && viewMode !== "domain" && (
|
||||
<>
|
||||
<div className="w-px h-5 bg-border-subtle" />
|
||||
<div className="flex items-center bg-elevated rounded-lg p-0.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDetailLevel("file")}
|
||||
title="Files only — architecture-level dependencies (fast)"
|
||||
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
||||
detailLevel === "file"
|
||||
? "bg-accent/20 text-accent"
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
Files
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDetailLevel("class")}
|
||||
title="Files + Classes — code structure with inheritance"
|
||||
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
||||
detailLevel === "class"
|
||||
? "bg-accent/20 text-accent"
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
+Classes
|
||||
</button>
|
||||
</div>
|
||||
{detailLevel === "class" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleShowFunctionsInClassView}
|
||||
title="Toggle function nodes (may slow down rendering)"
|
||||
className={`text-[10px] font-semibold uppercase tracking-wider px-2 py-1 rounded border transition-colors ${
|
||||
showFunctionsInClassView
|
||||
? "border-amber-500/50 bg-amber-500/10 text-amber-400"
|
||||
: "border-border-medium bg-elevated text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
fn
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center gap-1">
|
||||
{(isKnowledgeGraph ? [
|
||||
{ key: "knowledge" as const, label: "All", color: "var(--color-node-article)" },
|
||||
|
||||
@@ -366,6 +366,7 @@ function useLayerDetailTopology(): LayerDetailTopology & {
|
||||
layoutStatus: "computing" | "ready";
|
||||
} {
|
||||
const graph = useDashboardStore((s) => s.graph);
|
||||
const nodesById = useDashboardStore((s) => s.nodesById);
|
||||
const activeLayerId = useDashboardStore((s) => s.activeLayerId);
|
||||
const selectNode = useDashboardStore((s) => s.selectNode);
|
||||
const persona = useDashboardStore((s) => s.persona);
|
||||
@@ -375,6 +376,8 @@ function useLayerDetailTopology(): LayerDetailTopology & {
|
||||
const focusNodeId = useDashboardStore((s) => s.focusNodeId);
|
||||
const nodeTypeFilters = useDashboardStore((s) => s.nodeTypeFilters);
|
||||
const drillIntoLayer = useDashboardStore((s) => s.drillIntoLayer);
|
||||
const detailLevel = useDashboardStore((s) => s.detailLevel);
|
||||
const showFunctionsInClassView = useDashboardStore((s) => s.showFunctionsInClassView);
|
||||
|
||||
const handleNodeSelect = useCallback(
|
||||
(nodeId: string) => {
|
||||
@@ -404,10 +407,20 @@ function useLayerDetailTopology(): LayerDetailTopology & {
|
||||
|
||||
// Expand layer membership to include sub-file nodes (function/class)
|
||||
// whose parent file is in this layer. Joined via "contains" edges.
|
||||
// File view: file nodes only (architecture-level dependencies).
|
||||
// Class view: file nodes + class nodes, functions only when toggle is on.
|
||||
const expandedLayerNodeIds = new Set(layerNodeIds);
|
||||
for (const edge of graph.edges) {
|
||||
if (edge.type === "contains" && layerNodeIds.has(edge.source)) {
|
||||
expandedLayerNodeIds.add(edge.target);
|
||||
if (detailLevel !== "file") {
|
||||
for (const edge of graph.edges) {
|
||||
if (edge.type === "contains" && layerNodeIds.has(edge.source)) {
|
||||
const child = nodesById.get(edge.target);
|
||||
if (!child) continue;
|
||||
if (child.type === "class") {
|
||||
expandedLayerNodeIds.add(edge.target);
|
||||
} else if (child.type === "function" && showFunctionsInClassView) {
|
||||
expandedLayerNodeIds.add(edge.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,6 +639,7 @@ function useLayerDetailTopology(): LayerDetailTopology & {
|
||||
};
|
||||
}, [
|
||||
graph,
|
||||
nodesById,
|
||||
activeLayerId,
|
||||
persona,
|
||||
diffMode,
|
||||
@@ -634,6 +648,8 @@ function useLayerDetailTopology(): LayerDetailTopology & {
|
||||
focusNodeId,
|
||||
nodeTypeFilters,
|
||||
drillIntoLayer,
|
||||
detailLevel,
|
||||
showFunctionsInClassView,
|
||||
handleNodeSelect,
|
||||
handleContainerToggle,
|
||||
]);
|
||||
|
||||
@@ -15,6 +15,7 @@ export type NodeType = "file" | "function" | "class" | "module" | "concept" | "c
|
||||
export type Complexity = "simple" | "moderate" | "complex";
|
||||
export type EdgeCategory = "structural" | "behavioral" | "data-flow" | "dependencies" | "semantic" | "infrastructure" | "domain" | "knowledge";
|
||||
export type ViewMode = "structural" | "domain" | "knowledge";
|
||||
export type DetailLevel = "file" | "class";
|
||||
|
||||
export interface FilterState {
|
||||
nodeTypes: Set<NodeType>;
|
||||
@@ -146,6 +147,13 @@ interface DashboardStore {
|
||||
nodeTypeFilters: Record<NodeCategory, boolean>;
|
||||
toggleNodeTypeFilter: (category: NodeCategory) => void;
|
||||
|
||||
// Detail level: "file" shows only file nodes (architecture view),
|
||||
// "class" shows files + class nodes (code structure view) with optional function expansion.
|
||||
detailLevel: DetailLevel;
|
||||
setDetailLevel: (level: DetailLevel) => void;
|
||||
showFunctionsInClassView: boolean;
|
||||
toggleShowFunctionsInClassView: () => void;
|
||||
|
||||
setGraph: (graph: KnowledgeGraph) => void;
|
||||
selectNode: (nodeId: string | null) => void;
|
||||
navigateToNode: (nodeId: string) => void;
|
||||
@@ -331,6 +339,29 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
pendingFocusContainer: null,
|
||||
})),
|
||||
|
||||
detailLevel: "file",
|
||||
setDetailLevel: (level) =>
|
||||
set({
|
||||
detailLevel: level,
|
||||
// Detail level changes which nodes are visible; cached positions stale.
|
||||
// Reset fn toggle so it doesn't resurrect when re-entering class view.
|
||||
showFunctionsInClassView: false,
|
||||
containerLayoutCache: new Map(),
|
||||
containerSizeMemory: new Map(),
|
||||
expandedContainers: new Set(),
|
||||
pendingFocusContainer: null,
|
||||
}),
|
||||
|
||||
showFunctionsInClassView: false,
|
||||
toggleShowFunctionsInClassView: () =>
|
||||
set((state) => ({
|
||||
showFunctionsInClassView: !state.showFunctionsInClassView,
|
||||
containerLayoutCache: new Map(),
|
||||
containerSizeMemory: new Map(),
|
||||
expandedContainers: new Set(),
|
||||
pendingFocusContainer: null,
|
||||
})),
|
||||
|
||||
setGraph: (graph) => {
|
||||
const searchEngine = new SearchEngine(graph.nodes);
|
||||
const query = get().searchQuery;
|
||||
|
||||
Reference in New Issue
Block a user