返回 presentation-ai
useDebouncedSave.ts
根目录 / src / hooks / presentation / useDebouncedSave.ts
1 import { updatePresentation } from "@/app/_actions/notebook/presentation/presentationActions";
2 import { buildPresentationCustomization } from "@/lib/presentation/customization";
3 import { getPersistablePresentationTheme } from "@/lib/presentation/theme-resolution";
4 import { usePresentationState } from "@/states/presentation-state";
5 import debounce from "lodash.debounce";
6 import { useCallback, useEffect } from "react";
7
8 interface UseDebouncedSaveOptions {
9 /**
10 * Debounce delay in milliseconds
11 * @default 1000
12 */
13 delay?: number;
14 }
15
16 type SaveOptions = {
17 includeMetadata?: boolean;
18 };
19
20 /**
21 * Custom hook for debounced saving of presentation slides
22 * Automatically saves when slides are changed after the specified delay
23 * Will not save while content is being generated
24 */
25 export const useDebouncedSave = (options: UseDebouncedSaveOptions = {}) => {
26 const { delay = 1000 } = options;
27 const { setSavingStatus } = usePresentationState();
28
29 // Create debounced save function
30 const debouncedSave = useCallback(
31 debounce(
32 async () => {
33 // Get the latest state directly from the store
34 const {
35 slides,
36 currentPresentationId,
37 currentPresentationTitle,
38 outline,
39 imageSource,
40 presentationStyle,
41 language,
42 pageBackground,
43 thumbnailUrl,
44 customThemeData,
45 theme,
46 themeDataByTheme,
47 generatedThemeData,
48 pageStyle,
49 generationAspectRatio,
50 textContent,
51 tone,
52 audience,
53 scenario,
54 } = usePresentationState.getState();
55
56 // Don't save if there's no presentation or slides
57 if (!currentPresentationId || slides.length === 0) return;
58 try {
59 setSavingStatus("saving");
60
61 await updatePresentation({
62 id: currentPresentationId,
63 content: {
64 slides,
65 },
66 title: currentPresentationTitle ?? "",
67 theme: getPersistablePresentationTheme({
68 fallbackTheme: "mystique",
69 theme,
70 }),
71 outline,
72 imageSource,
73 presentationStyle,
74 language,
75 thumbnailUrl,
76 customization: buildPresentationCustomization({
77 customThemeData,
78 themeDataByTheme,
79 generatedThemeData,
80 theme,
81 pageStyle,
82 presentationStyle: presentationStyle ?? "",
83 generationAspectRatio,
84 textContent,
85 tone,
86 audience,
87 scenario,
88 pageBackground,
89 }),
90 });
91
92 setSavingStatus("saved");
93 // Reset to idle after 2 seconds
94 setTimeout(() => {
95 setSavingStatus("idle");
96 }, 2000);
97 } catch (error) {
98 console.error("Failed to save presentation:", error);
99 setSavingStatus("idle");
100 }
101 },
102 delay,
103 { maxWait: delay * 2 },
104 ),
105 [],
106 );
107
108 // Cleanup debounce on unmount
109 useEffect(() => {
110 return () => {
111 debouncedSave.cancel();
112 };
113 }, [debouncedSave]);
114
115 // Save slides immediately (useful for manual saves)
116 const saveImmediately = useCallback(async (_options?: SaveOptions) => {
117 debouncedSave.cancel();
118
119 // Get the latest state directly from the store
120 const {
121 slides,
122 currentPresentationId,
123 currentPresentationTitle,
124 outline,
125 imageSource,
126 presentationStyle,
127 language,
128 pageBackground,
129 thumbnailUrl,
130 customThemeData,
131 theme,
132 themeDataByTheme,
133 generatedThemeData,
134 pageStyle,
135 generationAspectRatio,
136 textContent,
137 tone,
138 audience,
139 scenario,
140 } = usePresentationState.getState();
141
142 // Don't save if there's no presentation
143 if (!currentPresentationId || slides.length === 0) return;
144
145 try {
146 setSavingStatus("saving");
147
148 await updatePresentation({
149 id: currentPresentationId,
150 content: {
151 slides,
152 },
153 title: currentPresentationTitle ?? "",
154 theme: getPersistablePresentationTheme({
155 fallbackTheme: "mystique",
156 theme,
157 }),
158 outline,
159 language,
160 imageSource,
161 presentationStyle,
162 thumbnailUrl,
163 customization: buildPresentationCustomization({
164 customThemeData,
165 themeDataByTheme,
166 generatedThemeData,
167 theme,
168 pageStyle,
169 presentationStyle: presentationStyle ?? "",
170 generationAspectRatio,
171 textContent,
172 tone,
173 audience,
174 scenario,
175 pageBackground,
176 }),
177 });
178
179 setSavingStatus("saved");
180 // Reset to idle after 2 seconds
181 setTimeout(() => {
182 setSavingStatus("idle");
183 }, 2000);
184 } catch (error) {
185 console.error("Failed to save presentation:", error);
186 setSavingStatus("idle");
187 }
188 }, [debouncedSave, setSavingStatus]);
189
190 // Trigger save function
191 const save = useCallback((_options?: SaveOptions) => {
192 setSavingStatus("saving");
193 void debouncedSave();
194 }, [debouncedSave, setSavingStatus]);
195
196 return {
197 save,
198 saveImmediately,
199 };
200 };
201
201 lines TYPESCRIPT