返回 DeepSeek-Reasonix
useProjectTreeListRuntime.ts
根目录 / desktop / frontend / src / lib / useProjectTreeListRuntime.ts
1 import { useCallback, useRef, useState } from "react";
2 import {
3 createProjectTreeRequestLimiter,
4 projectTreeRuntimeWindowLimits,
5 resetProjectTreeRuntimeWindowLimits,
6 type ProjectTreeListPageState,
7 } from "./projectTreeWindow";
8
9 export function useProjectTreeListRuntime() {
10 const topicRevisionRef = useRef<Record<string, number>>({});
11 const topicCompletePageRef = useRef<Record<string, { signature: string; revision: number }>>({});
12 const [topicPageState, setTopicPageState] = useState<Record<string, ProjectTreeListPageState>>({});
13 const topicPageStateRef = useRef(topicPageState);
14 const updateTopicPageState = useCallback((key: string, next: ProjectTreeListPageState) => {
15 // Publish synchronously so sibling effects see the same request/cache state
16 // before React commits the corresponding render.
17 const updated = { ...topicPageStateRef.current, [key]: next };
18 topicPageStateRef.current = updated;
19 setTopicPageState(updated);
20 }, []);
21
22 const [topicWindowLimits, setTopicWindowLimits] = useState<Record<string, number>>(projectTreeRuntimeWindowLimits);
23 const topicWindowLimitsRef = useRef(topicWindowLimits);
24 topicWindowLimitsRef.current = topicWindowLimits;
25 const resetTopicWindowLimits = useCallback((projectKey?: string) => {
26 resetProjectTreeRuntimeWindowLimits(projectKey);
27 setTopicWindowLimits((current) => {
28 const prefix = projectKey ? `${projectKey}\u001f` : "";
29 const next = projectKey
30 ? Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(prefix)))
31 : {};
32 if (Object.keys(next).length === Object.keys(current).length) return current;
33 topicWindowLimitsRef.current = next;
34 return next;
35 });
36 }, []);
37
38 const topicLoadSeqRef = useRef<Record<string, number>>({});
39 const topicLoadPendingRef = useRef<Record<string, number>>({});
40 const topicRequestLimiterRef = useRef(createProjectTreeRequestLimiter(4));
41 const topicLoadErrorRef = useRef<Record<string, string>>({});
42 const invalidateProjectTopicLists = useCallback((projectKey: string) => {
43 const prefix = `${projectKey}\u001f`;
44 for (const key of Object.keys(topicLoadSeqRef.current)) {
45 if (key.startsWith(prefix)) topicLoadSeqRef.current[key] += 1;
46 }
47 for (const records of [topicLoadPendingRef.current, topicRevisionRef.current, topicCompletePageRef.current]) {
48 for (const key of Object.keys(records)) if (key.startsWith(prefix)) delete records[key];
49 }
50 const next = Object.fromEntries(Object.entries(topicPageStateRef.current).map(([key, state]) => key.startsWith(prefix)
51 ? [key, { ...state, nextCursor: undefined, loading: false, initialized: false, error: undefined }]
52 : [key, state]));
53 topicPageStateRef.current = next;
54 setTopicPageState(next);
55 }, []);
56
57 return {
58 topicRevisionRef,
59 topicCompletePageRef,
60 topicPageState,
61 setTopicPageState,
62 topicPageStateRef,
63 updateTopicPageState,
64 topicWindowLimits,
65 setTopicWindowLimits,
66 topicWindowLimitsRef,
67 resetTopicWindowLimits,
68 topicLoadSeqRef,
69 topicLoadPendingRef,
70 topicRequestLimiterRef,
71 topicLoadErrorRef,
72 invalidateProjectTopicLists,
73 };
74 }
75
75 lines TYPESCRIPT