返回 DeepSeek-Reasonix
projectTreeDiagnostics.ts
根目录 / desktop / frontend / src / lib / projectTreeDiagnostics.ts
1 import { asArray } from "./array";
2 import { isRuntimeSessionNode, isTopicNode } from "./projectTreeTopic";
3 import type { ProjectNode } from "./types";
4
5 export type ProjectTreeSessionDiagnosticSummary = {
6 workspaceSessions: number;
7 visibleSessions: number;
8 hiddenSessions: number;
9 hiddenByFilter: number;
10 hiddenByCollapsed: number;
11 hiddenByTruncation: number;
12 runtimeSessions: number;
13 runtimeOnlySessions: number;
14 recoveryOnlySessions: number;
15 recoveryCopySessions: number;
16 recoveryCopies: number;
17 runningSessions: number;
18 unreadSessions: number;
19 pinnedSessions: number;
20 activeSessions: number;
21 activeVisibleSessions: number;
22 folderCount: number;
23 expandedFolders: number;
24 showAllFolders: number;
25 };
26
27 export type ProjectTreeSessionDiagnosticOptions = {
28 tree: ProjectNode[];
29 visibleTree: ProjectNode[];
30 expanded: ReadonlySet<string>;
31 queryActive: boolean;
32 expandedWindowCount?: number;
33 folderProjection?: (folder: ProjectNode, children: ProjectNode[]) => {
34 visible: ProjectNode[];
35 collapsed?: ProjectNode[];
36 };
37 projectNodeKey: (node: ProjectNode, depth: number) => string;
38 isActive?: (node: ProjectNode) => boolean;
39 isUnread?: (node: ProjectNode) => boolean;
40 };
41
42 type Counters = Omit<ProjectTreeSessionDiagnosticSummary, "visibleSessions" | "hiddenSessions" | "hiddenByFilter" | "hiddenByCollapsed" | "hiddenByTruncation" | "activeVisibleSessions" | "expandedFolders" | "showAllFolders">;
43
44 function isFolder(node: ProjectNode): boolean {
45 return node.kind === "project" || node.kind === "global_folder";
46 }
47
48 function isSessionRow(node: ProjectNode): boolean {
49 return isTopicNode(node) || isRuntimeSessionNode(node);
50 }
51
52 function countSessionRows(nodes: ProjectNode[]): number {
53 let count = 0;
54 for (const node of nodes) {
55 if (isSessionRow(node)) count += 1;
56 count += countSessionRows(asArray(node.children));
57 }
58 return count;
59 }
60
61 function collectCounters(
62 nodes: ProjectNode[],
63 counters: Counters,
64 isActive: (node: ProjectNode) => boolean,
65 isUnread: (node: ProjectNode) => boolean,
66 ): void {
67 for (const node of nodes) {
68 if (isFolder(node)) counters.folderCount += 1;
69 if (isSessionRow(node)) {
70 counters.workspaceSessions += 1;
71 if (isRuntimeSessionNode(node)) counters.runtimeSessions += 1;
72 if (node.runtimeOnly) counters.runtimeOnlySessions += 1;
73 if (node.recoveryState === "recovery_only") counters.recoveryOnlySessions += 1;
74 if (node.running) counters.runningSessions += 1;
75 if (isUnread(node)) counters.unreadSessions += 1;
76 if (node.pinned) counters.pinnedSessions += 1;
77 if (isActive(node)) counters.activeSessions += 1;
78 }
79 collectCounters(asArray(node.children), counters, isActive, isUnread);
80 }
81 }
82
83 function collectVisible(
84 nodes: ProjectNode[],
85 depth: number,
86 parentVisible: boolean,
87 options: ProjectTreeSessionDiagnosticOptions,
88 counters: { visibleSessions: number; activeVisibleSessions: number; hiddenByCollapsed: number; hiddenByTruncation: number },
89 ): void {
90 for (const node of nodes) {
91 const children = asArray(node.children);
92 const visible = parentVisible;
93 if (isSessionRow(node) && visible) {
94 counters.visibleSessions += 1;
95 if (options.isActive?.(node)) counters.activeVisibleSessions += 1;
96 }
97
98 const key = options.projectNodeKey(node, depth);
99 const isExpanded = options.queryActive || options.expanded.has(key);
100 if (!isExpanded) {
101 counters.hiddenByCollapsed += countSessionRows(children);
102 continue;
103 }
104
105 if (!visible) {
106 // The nearest collapsed ancestor owns the hidden-reason bucket. This
107 // keeps filtered, collapsed, and classic-window counts disjoint.
108 counters.hiddenByCollapsed += countSessionRows(children);
109 continue;
110 }
111
112 let childNodes = children;
113 if (isFolder(node) && options.folderProjection) {
114 const projection = options.folderProjection(node, children);
115 const visibleKeys = new Set(projection.visible.map((child) => child.key));
116 const collapsedKeys = new Set((projection.collapsed ?? []).map((child) => child.key));
117 counters.hiddenByCollapsed += countSessionRows(children.filter((child) => collapsedKeys.has(child.key)));
118 counters.hiddenByTruncation += countSessionRows(children.filter((child) => !visibleKeys.has(child.key) && !collapsedKeys.has(child.key)));
119 childNodes = projection.visible;
120 }
121 collectVisible(childNodes, depth + 1, visible, options, counters);
122 }
123 }
124
125 export function summarizeProjectTreeSessions(options: ProjectTreeSessionDiagnosticOptions): ProjectTreeSessionDiagnosticSummary {
126 const counters: Counters = {
127 workspaceSessions: 0,
128 runtimeSessions: 0,
129 runtimeOnlySessions: 0,
130 recoveryOnlySessions: 0,
131 recoveryCopySessions: 0,
132 recoveryCopies: 0,
133 runningSessions: 0,
134 unreadSessions: 0,
135 pinnedSessions: 0,
136 activeSessions: 0,
137 folderCount: 0,
138 };
139 const isActive = options.isActive ?? (() => false);
140 const isUnread = options.isUnread ?? (() => false);
141 collectCounters(options.tree, counters, isActive, isUnread);
142
143 const visible = {
144 visibleSessions: 0,
145 activeVisibleSessions: 0,
146 hiddenByCollapsed: 0,
147 hiddenByTruncation: 0,
148 };
149 collectVisible(options.visibleTree, 0, true, options, visible);
150
151 const hiddenByFilter = Math.max(0, counters.workspaceSessions - countSessionRows(options.visibleTree));
152 const hiddenSessions = Math.max(0, counters.workspaceSessions - visible.visibleSessions);
153 let expandedFolders = 0;
154 const showAllFolders = options.expandedWindowCount ?? 0;
155 for (const node of options.tree) {
156 const walk = (current: ProjectNode[], depth: number) => {
157 for (const item of current) {
158 if (isFolder(item)) {
159 const key = options.projectNodeKey(item, depth);
160 if (options.queryActive || options.expanded.has(key)) expandedFolders += 1;
161 }
162 walk(asArray(item.children), depth + 1);
163 }
164 };
165 walk([node], 0);
166 }
167
168 return {
169 ...counters,
170 visibleSessions: visible.visibleSessions,
171 hiddenSessions,
172 hiddenByFilter,
173 hiddenByCollapsed: Math.min(hiddenSessions, visible.hiddenByCollapsed),
174 hiddenByTruncation: Math.min(hiddenSessions, visible.hiddenByTruncation),
175 activeVisibleSessions: visible.activeVisibleSessions,
176 expandedFolders,
177 showAllFolders,
178 };
179 }
180
180 lines TYPESCRIPT