返回 presentation-ai
infographic-streaming-state.ts
根目录 / src / states / infographic-streaming-state.ts
1 import { create } from "zustand";
2
3 let activeInfographicStreamingSessions = 0;
4 let pendingInfographicStreamingReset:
5 | ReturnType<typeof setTimeout>
6 | null = null;
7
8 type InfographicStreamingState = {
9 completedInfographicIds: Record<string, true>;
10 startedGenerationRequests: Record<string, true>;
11 beginStreamingSession: () => void;
12 endStreamingSession: () => void;
13 setCompletedInfographicIds: (ids: string[]) => void;
14 tryStartGenerationRequest: (requestId: string) => boolean;
15 clearGenerationRequestStarted: (requestId: string) => void;
16 resetInfographicStreaming: () => void;
17 };
18
19 function toCompletedInfographicMap(ids: string[]): Record<string, true> {
20 const nextState: Record<string, true> = {};
21
22 for (const id of ids) {
23 const trimmedId = id.trim();
24
25 if (trimmedId) {
26 nextState[trimmedId] = true;
27 }
28 }
29
30 return nextState;
31 }
32
33 function areCompletedInfographicMapsEqual(
34 left: Record<string, true>,
35 right: Record<string, true>,
36 ): boolean {
37 const leftKeys = Object.keys(left);
38 const rightKeys = Object.keys(right);
39
40 if (leftKeys.length !== rightKeys.length) {
41 return false;
42 }
43
44 return leftKeys.every((key) => right[key] === true);
45 }
46
47 export const useInfographicStreamingState = create<InfographicStreamingState>(
48 (set, get) => ({
49 completedInfographicIds: {},
50 startedGenerationRequests: {},
51 beginStreamingSession: () => {
52 activeInfographicStreamingSessions += 1;
53
54 if (pendingInfographicStreamingReset !== null) {
55 clearTimeout(pendingInfographicStreamingReset);
56 pendingInfographicStreamingReset = null;
57 }
58 },
59 endStreamingSession: () => {
60 activeInfographicStreamingSessions = Math.max(
61 0,
62 activeInfographicStreamingSessions - 1,
63 );
64
65 if (
66 activeInfographicStreamingSessions > 0
67 || pendingInfographicStreamingReset !== null
68 ) {
69 return;
70 }
71
72 pendingInfographicStreamingReset = setTimeout(() => {
73 pendingInfographicStreamingReset = null;
74
75 if (activeInfographicStreamingSessions > 0) {
76 return;
77 }
78
79 get().resetInfographicStreaming();
80 }, 0);
81 },
82 setCompletedInfographicIds: (ids: string[]) =>
83 set((state) => {
84 const nextCompletedInfographicIds = toCompletedInfographicMap(ids);
85
86 if (
87 areCompletedInfographicMapsEqual(
88 state.completedInfographicIds,
89 nextCompletedInfographicIds,
90 )
91 ) {
92 return state;
93 }
94
95 return { completedInfographicIds: nextCompletedInfographicIds };
96 }),
97 tryStartGenerationRequest: (requestId: string) => {
98 if (!requestId) {
99 return false;
100 }
101
102 if (get().startedGenerationRequests[requestId] === true) {
103 return false;
104 }
105
106 set((state) => ({
107 startedGenerationRequests: {
108 ...state.startedGenerationRequests,
109 [requestId]: true,
110 },
111 }));
112
113 return true;
114 },
115 clearGenerationRequestStarted: (requestId: string) =>
116 set((state) => {
117 if (!requestId || !(requestId in state.startedGenerationRequests)) {
118 return state;
119 }
120
121 const { [requestId]: _removed, ...rest } = state.startedGenerationRequests;
122
123 return {
124 startedGenerationRequests: rest,
125 };
126 }),
127 resetInfographicStreaming: () =>
128 set((state) => {
129 if (pendingInfographicStreamingReset !== null) {
130 clearTimeout(pendingInfographicStreamingReset);
131 pendingInfographicStreamingReset = null;
132 }
133
134 return Object.keys(state.completedInfographicIds).length > 0
135 || Object.keys(state.startedGenerationRequests).length > 0
136 ? { completedInfographicIds: {}, startedGenerationRequests: {} }
137 : state;
138 }),
139 }),
140 );
141
141 lines TYPESCRIPT