use-vibes 0.5.7 → 0.11.0-dev-preview1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +0 -7
  2. package/components/ControlsBar.d.ts +20 -0
  3. package/components/ControlsBar.js +80 -0
  4. package/components/ControlsBar.js.map +1 -0
  5. package/{dist/components → components}/ImgGen.css +21 -13
  6. package/components/ImgGen.d.ts +25 -0
  7. package/{dist/components → components}/ImgGen.js +25 -119
  8. package/components/ImgGen.js.map +1 -0
  9. package/{dist/components → components}/ImgGenUtils/ImgGenDisplay.d.ts +2 -2
  10. package/components/ImgGenUtils/ImgGenDisplay.js +192 -0
  11. package/components/ImgGenUtils/ImgGenDisplay.js.map +1 -0
  12. package/components/ImgGenUtils/ImgGenDisplayPlaceholder.d.ts +3 -0
  13. package/components/ImgGenUtils/ImgGenDisplayPlaceholder.js +107 -0
  14. package/components/ImgGenUtils/ImgGenDisplayPlaceholder.js.map +1 -0
  15. package/components/ImgGenUtils/ImgGenDisplayUtils.d.ts +27 -0
  16. package/{dist/components → components}/ImgGenUtils/ImgGenDisplayUtils.js +4 -36
  17. package/components/ImgGenUtils/ImgGenDisplayUtils.js.map +1 -0
  18. package/components/ImgGenUtils/ImgGenError.d.ts +3 -0
  19. package/components/ImgGenUtils/ImgGenError.js +6 -0
  20. package/components/ImgGenUtils/ImgGenError.js.map +1 -0
  21. package/components/ImgGenUtils/ImgGenFileDrop.d.ts +12 -0
  22. package/{dist/components → components}/ImgGenUtils/ImgGenFileDrop.js +2 -9
  23. package/components/ImgGenUtils/ImgGenFileDrop.js.map +1 -0
  24. package/components/ImgGenUtils/ImgGenModal.d.ts +24 -0
  25. package/components/ImgGenUtils/ImgGenModal.js +28 -0
  26. package/components/ImgGenUtils/ImgGenModal.js.map +1 -0
  27. package/components/ImgGenUtils/ImgGenModeUtils.d.ts +9 -0
  28. package/{dist/components → components}/ImgGenUtils/ImgGenModeUtils.js +0 -15
  29. package/components/ImgGenUtils/ImgGenModeUtils.js.map +1 -0
  30. package/components/ImgGenUtils/ImgGenPromptWaiting.d.ts +12 -0
  31. package/{dist/components → components}/ImgGenUtils/ImgGenPromptWaiting.js +3 -15
  32. package/components/ImgGenUtils/ImgGenPromptWaiting.js.map +1 -0
  33. package/components/ImgGenUtils/ImgGenUploadWaiting.d.ts +17 -0
  34. package/{dist/components → components}/ImgGenUtils/ImgGenUploadWaiting.js +3 -52
  35. package/components/ImgGenUtils/ImgGenUploadWaiting.js.map +1 -0
  36. package/components/ImgGenUtils/index.d.ts +6 -0
  37. package/components/ImgGenUtils/index.js +7 -0
  38. package/components/ImgGenUtils/index.js.map +1 -0
  39. package/components/ImgGenUtils/overlays/DeleteConfirmationOverlay.d.ts +9 -0
  40. package/{dist/components → components}/ImgGenUtils/overlays/DeleteConfirmationOverlay.js +8 -10
  41. package/components/ImgGenUtils/overlays/DeleteConfirmationOverlay.js.map +1 -0
  42. package/components/ImgGenUtils/overlays/ImageOverlay.d.ts +22 -0
  43. package/components/ImgGenUtils/overlays/ImageOverlay.js +8 -0
  44. package/components/ImgGenUtils/overlays/ImageOverlay.js.map +1 -0
  45. package/components/ImgGenUtils/types.d.ts +29 -0
  46. package/components/ImgGenUtils/types.js.map +1 -0
  47. package/components/ImgGenUtils.d.ts +2 -0
  48. package/components/ImgGenUtils.js +3 -0
  49. package/components/ImgGenUtils.js.map +1 -0
  50. package/components/PromptBar.d.ts +11 -0
  51. package/components/PromptBar.js +16 -0
  52. package/components/PromptBar.js.map +1 -0
  53. package/hooks/image-gen/image-generator.d.ts +11 -0
  54. package/{dist/hooks → hooks}/image-gen/image-generator.js +2 -42
  55. package/hooks/image-gen/image-generator.js.map +1 -0
  56. package/hooks/image-gen/index.d.ts +7 -0
  57. package/hooks/image-gen/index.js +6 -0
  58. package/hooks/image-gen/index.js.map +1 -0
  59. package/hooks/image-gen/types.d.ts +66 -0
  60. package/{dist/hooks → hooks}/image-gen/types.js.map +1 -1
  61. package/hooks/image-gen/use-image-gen.d.ts +4 -0
  62. package/hooks/image-gen/use-image-gen.js +544 -0
  63. package/hooks/image-gen/use-image-gen.js.map +1 -0
  64. package/hooks/image-gen/utils.d.ts +20 -0
  65. package/{dist/hooks → hooks}/image-gen/utils.js +11 -93
  66. package/hooks/image-gen/utils.js.map +1 -0
  67. package/hooks/use-image-gen.d.ts +1 -0
  68. package/hooks/use-image-gen.js +2 -0
  69. package/hooks/use-image-gen.js.map +1 -0
  70. package/index.d.ts +20 -0
  71. package/index.js +21 -0
  72. package/index.js.map +1 -0
  73. package/package.json +23 -24
  74. package/style-loader.d.ts +1 -0
  75. package/style-loader.js +23 -0
  76. package/style-loader.js.map +1 -0
  77. package/tsconfig.json +18 -0
  78. package/{dist/utils → utils}/base64.js +0 -4
  79. package/utils/base64.js.map +1 -0
  80. package/{dist/utils → utils}/debug.js +0 -1
  81. package/utils/debug.js.map +1 -0
  82. package/utils/style-utils.d.ts +17 -0
  83. package/utils/style-utils.js +15 -0
  84. package/utils/style-utils.js.map +1 -0
  85. package/dist/components/ControlsBar.d.ts +0 -32
  86. package/dist/components/ControlsBar.js +0 -109
  87. package/dist/components/ControlsBar.js.map +0 -1
  88. package/dist/components/ImgGen.d.ts +0 -42
  89. package/dist/components/ImgGen.js.map +0 -1
  90. package/dist/components/ImgGenUtils/ImgGenDisplay.js +0 -264
  91. package/dist/components/ImgGenUtils/ImgGenDisplay.js.map +0 -1
  92. package/dist/components/ImgGenUtils/ImgGenDisplayPlaceholder.d.ts +0 -3
  93. package/dist/components/ImgGenUtils/ImgGenDisplayPlaceholder.js +0 -125
  94. package/dist/components/ImgGenUtils/ImgGenDisplayPlaceholder.js.map +0 -1
  95. package/dist/components/ImgGenUtils/ImgGenDisplayUtils.d.ts +0 -44
  96. package/dist/components/ImgGenUtils/ImgGenDisplayUtils.js.map +0 -1
  97. package/dist/components/ImgGenUtils/ImgGenError.d.ts +0 -3
  98. package/dist/components/ImgGenUtils/ImgGenError.js +0 -9
  99. package/dist/components/ImgGenUtils/ImgGenError.js.map +0 -1
  100. package/dist/components/ImgGenUtils/ImgGenFileDrop.d.ts +0 -21
  101. package/dist/components/ImgGenUtils/ImgGenFileDrop.js.map +0 -1
  102. package/dist/components/ImgGenUtils/ImgGenModal.d.ts +0 -31
  103. package/dist/components/ImgGenUtils/ImgGenModal.js +0 -34
  104. package/dist/components/ImgGenUtils/ImgGenModal.js.map +0 -1
  105. package/dist/components/ImgGenUtils/ImgGenModeUtils.d.ts +0 -16
  106. package/dist/components/ImgGenUtils/ImgGenModeUtils.js.map +0 -1
  107. package/dist/components/ImgGenUtils/ImgGenPromptWaiting.d.ts +0 -17
  108. package/dist/components/ImgGenUtils/ImgGenPromptWaiting.js.map +0 -1
  109. package/dist/components/ImgGenUtils/ImgGenUploadWaiting.d.ts +0 -30
  110. package/dist/components/ImgGenUtils/ImgGenUploadWaiting.js.map +0 -1
  111. package/dist/components/ImgGenUtils/index.d.ts +0 -6
  112. package/dist/components/ImgGenUtils/index.js +0 -9
  113. package/dist/components/ImgGenUtils/index.js.map +0 -1
  114. package/dist/components/ImgGenUtils/overlays/DeleteConfirmationOverlay.d.ts +0 -10
  115. package/dist/components/ImgGenUtils/overlays/DeleteConfirmationOverlay.js.map +0 -1
  116. package/dist/components/ImgGenUtils/overlays/ImageOverlay.d.ts +0 -29
  117. package/dist/components/ImgGenUtils/overlays/ImageOverlay.js +0 -11
  118. package/dist/components/ImgGenUtils/overlays/ImageOverlay.js.map +0 -1
  119. package/dist/components/ImgGenUtils/types.d.ts +0 -43
  120. package/dist/components/ImgGenUtils/types.js.map +0 -1
  121. package/dist/components/ImgGenUtils.d.ts +0 -6
  122. package/dist/components/ImgGenUtils.js +0 -7
  123. package/dist/components/ImgGenUtils.js.map +0 -1
  124. package/dist/components/PromptBar.d.ts +0 -15
  125. package/dist/components/PromptBar.js +0 -23
  126. package/dist/components/PromptBar.js.map +0 -1
  127. package/dist/hooks/image-gen/image-generator.d.ts +0 -18
  128. package/dist/hooks/image-gen/image-generator.js.map +0 -1
  129. package/dist/hooks/image-gen/index.d.ts +0 -6
  130. package/dist/hooks/image-gen/index.js +0 -6
  131. package/dist/hooks/image-gen/index.js.map +0 -1
  132. package/dist/hooks/image-gen/types.d.ts +0 -75
  133. package/dist/hooks/image-gen/use-image-gen.d.ts +0 -12
  134. package/dist/hooks/image-gen/use-image-gen.js +0 -675
  135. package/dist/hooks/image-gen/use-image-gen.js.map +0 -1
  136. package/dist/hooks/image-gen/utils.d.ts +0 -61
  137. package/dist/hooks/image-gen/utils.js.map +0 -1
  138. package/dist/hooks/use-image-gen.d.ts +0 -5
  139. package/dist/hooks/use-image-gen.js +0 -7
  140. package/dist/hooks/use-image-gen.js.map +0 -1
  141. package/dist/index.d.ts +0 -6
  142. package/dist/index.js +0 -8
  143. package/dist/index.js.map +0 -1
  144. package/dist/style-loader.d.ts +0 -16
  145. package/dist/style-loader.js +0 -43
  146. package/dist/style-loader.js.map +0 -1
  147. package/dist/utils/base64.js.map +0 -1
  148. package/dist/utils/debug.js.map +0 -1
  149. package/dist/utils/style-utils.d.ts +0 -55
  150. package/dist/utils/style-utils.js +0 -42
  151. package/dist/utils/style-utils.js.map +0 -1
  152. /package/{dist/components → components}/ImgGenUtils/types.js +0 -0
  153. /package/{dist/hooks → hooks}/image-gen/types.js +0 -0
  154. /package/{dist/utils → utils}/base64.d.ts +0 -0
  155. /package/{dist/utils → utils}/debug.d.ts +0 -0
@@ -1,675 +0,0 @@
1
- import { useState, useEffect, useMemo, useRef } from 'react';
2
- import { useFireproof } from 'use-fireproof';
3
- import { hashInput, base64ToFile, addNewVersion, getVersionsFromDocument, getPromptsFromDocument, MODULE_STATE, cleanupRequestKey, getRelevantOptions, generateSafeFilename, } from './utils';
4
- import { createImageGenerator } from './image-generator';
5
- /**
6
- * Hook for generating images with call-ai's imageGen
7
- * Provides automatic caching, reactive updates, and progress handling
8
- *
9
- * The hook allows for two modes of operation:
10
- * 1. Generate a new image with a prompt (no _id provided)
11
- * 2. Load or update an existing image document (_id provided)
12
- */
13
- export function useImageGen({ prompt, _id, options = {}, database = 'ImgGen', skip = false, // Skip processing flag
14
- generationId, // Unique ID that changes for each new generation request
15
- editedPrompt, // Optional edited prompt that should override the document prompt on regeneration
16
- }) {
17
- // If both are provided, _id takes precedence
18
- // This silently prioritizes the document's internal prompt
19
- const [imageData, setImageData] = useState(null);
20
- const [loading, setLoading] = useState(false);
21
- const [progress, setProgress] = useState(0);
22
- const [error, setError] = useState(null);
23
- const [document, setDocument] = useState(null);
24
- const progressTimerRef = useRef(null);
25
- // Initialize Fireproof database
26
- const { database: db } = useFireproof(database);
27
- const size = options?.size || '1024x1024';
28
- const [width, height] = size.split('x').map(Number);
29
- // Memoize options to prevent unnecessary re-renders and regeneration
30
- const memoizedOptions = useMemo(() => ({
31
- size: options?.size,
32
- quality: options?.quality,
33
- model: options?.model,
34
- style: options?.style,
35
- }), [options?.size, options?.quality, options?.model, options?.style]);
36
- // Store reference to previous options to detect changes
37
- const prevOptionsRef = useRef(getRelevantOptions({}));
38
- // Determine if we should include options in generation ID based on regeneration flag
39
- const shouldConsiderOptions = useMemo(() => !generationId, [generationId]);
40
- // Create a unique request ID for loading by ID or new generation
41
- // For regeneration requests, we exclude options from the hash to prevent unwanted regeneration
42
- const requestId = useMemo(() => {
43
- return hashInput(prompt || _id || 'unknown', shouldConsiderOptions ? memoizedOptions : undefined);
44
- }, [prompt, _id, shouldConsiderOptions, memoizedOptions]);
45
- // Track ID and generation state changes
46
- const previousIdRef = useRef(_id);
47
- const previousGenerationIdRef = useRef(generationId);
48
- // Reset state when prompt, _id, or generationId changes
49
- useEffect(() => {
50
- // Keep track of whether _id has changed
51
- const idChanged = _id !== previousIdRef.current;
52
- // Detect when generationId changes - this indicates a request for regeneration
53
- const generationRequested = generationId !== previousGenerationIdRef.current;
54
- // Update refs for next check
55
- previousIdRef.current = _id;
56
- previousGenerationIdRef.current = generationId;
57
- // Only proceed with state resets when needed
58
- if (idChanged || generationRequested) {
59
- // Reset all state when inputs change
60
- setImageData(null);
61
- setError(null);
62
- setProgress(0);
63
- // Clear document state when ID changes
64
- // This ensures a clean start when navigating to a new document
65
- if (idChanged) {
66
- setDocument(null);
67
- }
68
- }
69
- // Clear any existing progress timer
70
- if (progressTimerRef.current) {
71
- clearInterval(progressTimerRef.current);
72
- progressTimerRef.current = null;
73
- }
74
- // Cleanup on unmount or when dependencies change
75
- return () => {
76
- if (progressTimerRef.current) {
77
- clearInterval(progressTimerRef.current);
78
- }
79
- };
80
- }, [prompt, _id, memoizedOptions]); // Dependencies that require state reset
81
- // Track the last request parameters to prevent duplicate requests
82
- const lastRequestRef = useRef('');
83
- // Generate the image when prompt or options change or load by ID
84
- useEffect(() => {
85
- let isMounted = true;
86
- // Skip processing if explicitly told to or if both prompt and _id are falsy
87
- if (skip || (!prompt && !_id)) {
88
- setLoading(false);
89
- if (!skip) {
90
- setError(new Error('Either prompt or _id must be provided'));
91
- }
92
- return;
93
- }
94
- // Track whether this is a case where both prompt prop and _id are provided
95
- // This helps us avoid the race condition where we might generate two images
96
- const hasBothPromptAndId = !!prompt && !!_id;
97
- // Create a request signature to deduplicate identical requests
98
- const requestSignature = JSON.stringify({
99
- prompt,
100
- _id,
101
- generationId,
102
- options: shouldConsiderOptions ? getRelevantOptions(memoizedOptions) : undefined,
103
- });
104
- // Prevent duplicate requests with identical parameters
105
- if (requestSignature === lastRequestRef.current && document) {
106
- return;
107
- }
108
- // Update the last request signature
109
- lastRequestRef.current = requestSignature;
110
- // Check if only options have changed and we have an existing document
111
- const currentRelevantOptions = getRelevantOptions(memoizedOptions);
112
- const previousRelevantOptions = prevOptionsRef.current;
113
- const optionsChanged = JSON.stringify(currentRelevantOptions) !== JSON.stringify(previousRelevantOptions);
114
- // When only options change and we aren't explicitly regenerating,
115
- // skip regeneration for existing documents to prevent duplicate generations
116
- if (optionsChanged && !generationId && document?._id) {
117
- // Update our reference without triggering regeneration
118
- prevOptionsRef.current = currentRelevantOptions;
119
- setLoading(false);
120
- return;
121
- }
122
- // Track current options for future comparison
123
- prevOptionsRef.current = currentRelevantOptions;
124
- setLoading(true);
125
- setProgress(0);
126
- setError(null);
127
- // Create a generator function with the current request ID
128
- const callImageGeneration = createImageGenerator(requestId);
129
- // Main function that handles the image loading/generation process
130
- const loadOrGenerateImage = async () => {
131
- try {
132
- // Start the progress animation only when loading starts
133
- // Set up progress timer simulation (45 seconds to completion)
134
- // This is just for visual feedback and doesn't reflect actual progress
135
- const timer = setInterval(() => {
136
- setProgress((prev) => {
137
- const next = prev + (100 - prev) * 0.04;
138
- return next > 99 ? 99 : next;
139
- });
140
- }, 1000);
141
- progressTimerRef.current = timer;
142
- let data = null;
143
- // Log the request for debugging
144
- try {
145
- // FIXED: Always try to load the document if we have an _id, regardless of regeneration state
146
- // We'll use the document for info even during regeneration
147
- const hasDocumentId = !!_id;
148
- // When both prompt and _id are provided, we want the document for its files and metadata,
149
- // but use the provided prompt for generation to avoid a race condition
150
- if (hasDocumentId) {
151
- const existingDoc = await db.get(_id).catch((err) => {
152
- console.error(`[loadOrGenerateImage] Failed to load document ${_id}:`, err);
153
- return null;
154
- });
155
- if (existingDoc && existingDoc._files) {
156
- // Document exists, set it
157
- setDocument(existingDoc);
158
- // Extract prompt information from the document
159
- const { prompts, currentPromptKey } = getPromptsFromDocument(existingDoc);
160
- // Determine which prompt to use - prioritize passed prompt prop over document prompt
161
- // If prompt prop is provided, use it instead of document's prompt
162
- const currentPromptText = prompt ||
163
- (currentPromptKey && prompts[currentPromptKey]?.text) ||
164
- existingDoc.prompt ||
165
- '';
166
- // Check if we have a document with only an original file but no output files
167
- // This happens when someone uploads an image but it hasn't been processed yet
168
- const { versions } = getVersionsFromDocument(existingDoc);
169
- const hasOutputFiles = versions.length > 0 && versions.some((v) => existingDoc._files?.[v.id]);
170
- const hasOnlyOriginalFile = !hasOutputFiles && existingDoc._files?.original;
171
- // If we have a prompt and only an original file, generate an image
172
- if (hasOnlyOriginalFile && currentPromptText && !generationId) {
173
- console.log(`Document ${_id} has only original file and prompt, generating edited image`);
174
- setLoading(true);
175
- // Create options that include the document for access to the original file
176
- const editOptions = {
177
- ...options,
178
- document: existingDoc,
179
- _regenerationId: Date.now(), // Ensure unique generation
180
- };
181
- // Generate a new image using the document's prompt and original file
182
- data = await callImageGeneration(currentPromptText, editOptions);
183
- if (data?.data?.[0]?.b64_json) {
184
- // Handle the generated image
185
- const filename = generateSafeFilename(currentPromptText);
186
- const newImageFile = base64ToFile(data.data[0].b64_json, filename);
187
- // Add as a new version to the document
188
- const updatedDoc = addNewVersion(existingDoc, newImageFile, currentPromptText);
189
- await db.put(updatedDoc);
190
- // Update component state
191
- const refreshedDoc = await db.get(_id);
192
- setDocument(refreshedDoc);
193
- setImageData(data.data[0].b64_json);
194
- setProgress(100);
195
- return;
196
- }
197
- }
198
- // If generationId is provided, we're creating a new version
199
- // Only attempt if we have a document with a prompt
200
- // Note: currentPromptText already prioritizes the prop prompt over the document prompt
201
- if (generationId && currentPromptText) {
202
- // Create a completely unique key for the regeneration request to avoid deduplication
203
- const timestamp = Date.now();
204
- const regenerationOptions = {
205
- ...options,
206
- _regenerationId: timestamp, // Add timestamp for uniqueness
207
- };
208
- // Clear any existing request with the same prompt from the cache
209
- // This ensures we don't get a cached result
210
- const requestKey = `${currentPromptText}-${JSON.stringify(getRelevantOptions(options))}`;
211
- cleanupRequestKey(requestKey);
212
- // Generate a new image using the document's prompt
213
- data = await callImageGeneration(currentPromptText, regenerationOptions);
214
- if (data?.data?.[0]?.b64_json) {
215
- // Create a File object from the base64 data
216
- // Generate a filename based on the prompt text
217
- const filename = generateSafeFilename(currentPromptText);
218
- const newImageFile = base64ToFile(data.data[0].b64_json, filename);
219
- // Ensure we preserve the original document ID
220
- const originalDocId = _id;
221
- // Add the new version to the document
222
- const updatedDoc = addNewVersion(existingDoc, newImageFile, currentPromptText);
223
- // Make sure the _id is preserved exactly
224
- updatedDoc._id = originalDocId;
225
- // Save the updated document
226
- await db.put(updatedDoc);
227
- // Get the updated document with the new version using the original ID
228
- const refreshedDoc = await db.get(originalDocId);
229
- setDocument(refreshedDoc);
230
- // Set the image data from the new version
231
- const reader = new FileReader();
232
- reader.readAsDataURL(newImageFile);
233
- await new Promise((resolve) => {
234
- reader.onloadend = () => {
235
- if (typeof reader.result === 'string') {
236
- setImageData(reader.result);
237
- }
238
- resolve();
239
- };
240
- });
241
- // Set progress to 100%
242
- setProgress(100);
243
- return;
244
- }
245
- }
246
- try {
247
- // Select the current version's file - extract directly instead of storing in state
248
- // This ensures we always have the latest version info straight from the document
249
- const { versions, currentVersion } = getVersionsFromDocument(existingDoc);
250
- if (versions.length > 0) {
251
- // Use the current version ID to get the file
252
- const versionId = versions[currentVersion]?.id || versions[0]?.id;
253
- if (!versionId || !existingDoc._files[versionId]) {
254
- throw new Error(`Version ${versionId} not found in document files`);
255
- }
256
- const imageFile = existingDoc._files[versionId];
257
- let fileObj;
258
- // Handle different file access methods
259
- if ('file' in imageFile && typeof imageFile.file === 'function') {
260
- // DocFileMeta interface from Fireproof
261
- fileObj = await imageFile.file();
262
- }
263
- else {
264
- // Direct File object
265
- fileObj = imageFile;
266
- }
267
- // Read the file as base64
268
- const reader = new FileReader();
269
- const base64Promise = new Promise((resolve, reject) => {
270
- reader.onload = () => {
271
- const base64 = reader.result;
272
- // Strip the data URL prefix if present
273
- const base64Data = base64.split(',')[1] || base64;
274
- resolve(base64Data);
275
- };
276
- reader.onerror = reject;
277
- });
278
- reader.readAsDataURL(fileObj);
279
- const base64Data = await base64Promise;
280
- setImageData(base64Data);
281
- }
282
- else {
283
- // Handle legacy files structure
284
- if (existingDoc._files.image) {
285
- const imageFile = existingDoc._files.image;
286
- let fileObj;
287
- if ('file' in imageFile && typeof imageFile.file === 'function') {
288
- fileObj = await imageFile.file();
289
- }
290
- else {
291
- fileObj = imageFile;
292
- }
293
- // Read the file as base64
294
- const reader = new FileReader();
295
- const base64Promise = new Promise((resolve, reject) => {
296
- reader.onload = () => {
297
- const base64 = reader.result;
298
- // Strip the data URL prefix if present
299
- const base64Data = base64.split(',')[1] || base64;
300
- resolve(base64Data);
301
- };
302
- reader.onerror = reject;
303
- });
304
- reader.readAsDataURL(fileObj);
305
- const base64Data = await base64Promise;
306
- setImageData(base64Data);
307
- }
308
- }
309
- }
310
- catch (err) {
311
- console.error('Error loading image file:', err);
312
- throw new Error(`Failed to load image from document: ${err instanceof Error ? err.message : String(err)}`);
313
- }
314
- }
315
- else if (existingDoc) {
316
- // Document exists but has no files - check if it has a prompt field we can use
317
- if ('prompt' in existingDoc &&
318
- typeof existingDoc.prompt === 'string' &&
319
- existingDoc.prompt) {
320
- // Use the document's prompt to generate an image
321
- console.log(`Document ${_id} has no files but has prompt, generating image`);
322
- // Extended debug info
323
- if (options?.debug) {
324
- console.log('[ImgGen Debug] Document found with prompt but no files:', {
325
- docId: existingDoc._id,
326
- docPrompt: existingDoc.prompt,
327
- settingLoading: true,
328
- settingDocument: true,
329
- loadingValue: loading,
330
- });
331
- }
332
- const docPrompt = existingDoc.prompt;
333
- // Set loading state to true to show progress indicator
334
- setLoading(true);
335
- // Set the document immediately so UI knows we have a document with a prompt
336
- // This prevents "Waiting for prompt" message
337
- setDocument(existingDoc);
338
- // Generate image using the document's prompt
339
- data = await callImageGeneration(docPrompt, options);
340
- if (data?.data?.[0]?.b64_json) {
341
- setImageData(data.data[0].b64_json);
342
- // Create a file object from the base64 data
343
- const blob = base64ToFile(data.data[0].b64_json, 'image/png');
344
- const newImageFile = new File([blob], generateSafeFilename(docPrompt), {
345
- type: 'image/png',
346
- });
347
- // Add the file to the document
348
- // Use type assertion to help TypeScript understand this is an ImageDocument
349
- const typedDoc = existingDoc;
350
- const updatedDoc = addNewVersion(typedDoc, newImageFile, docPrompt);
351
- await db.put(updatedDoc);
352
- setDocument(updatedDoc);
353
- return; // Exit early since we've handled this case
354
- }
355
- }
356
- else {
357
- // Document has no files and no usable prompt
358
- throw new Error(`Document exists but has no files: ${_id}`);
359
- }
360
- }
361
- }
362
- else if (prompt && !hasBothPromptAndId) {
363
- // No document ID provided but we have a prompt - generate a new image
364
- // Skip this section if we have both prompt and ID - in that case we use the document path above
365
- // If we have a document in memory and a generationId, we should add a version
366
- // to the existing document instead of creating a new one
367
- if (document?._id && generationId) {
368
- let currentPromptText = '';
369
- if (editedPrompt) {
370
- // Use the edited prompt provided from the UI
371
- currentPromptText = editedPrompt;
372
- }
373
- else {
374
- // Otherwise extract the prompt from the document
375
- const { prompts, currentPromptKey } = getPromptsFromDocument(document);
376
- currentPromptText =
377
- (currentPromptKey && prompts[currentPromptKey]?.text) ||
378
- document.prompt ||
379
- '' ||
380
- prompt ||
381
- ''; // Fall back to provided prompt if document has none, ensure string type
382
- }
383
- // Create regeneration options with unique ID to force new API call
384
- const regenerationOptions = {
385
- ...options,
386
- _regenerationId: Date.now(),
387
- };
388
- // Clear any cached promise for the original prompt+options
389
- const requestKey = `${currentPromptText}-${JSON.stringify(getRelevantOptions(options))}`;
390
- cleanupRequestKey(requestKey);
391
- // Generate a new image
392
- data = await callImageGeneration(currentPromptText, regenerationOptions);
393
- if (data?.data?.[0]?.b64_json) {
394
- // Create a File object from the base64 data
395
- const filename = generateSafeFilename(currentPromptText);
396
- const newImageFile = base64ToFile(data.data[0].b64_json, filename);
397
- // Ensure we preserve the original document ID
398
- const originalDocId = document._id;
399
- // Add the new version to the document
400
- const updatedDoc = addNewVersion(document, newImageFile, currentPromptText);
401
- // Make sure the _id is preserved
402
- updatedDoc._id = originalDocId;
403
- // Save the updated document
404
- await db.put(updatedDoc);
405
- // Get the updated document with the new version
406
- const refreshedDoc = await db.get(originalDocId);
407
- setDocument(refreshedDoc);
408
- // Set the image data from the new version
409
- const reader = new FileReader();
410
- reader.readAsDataURL(newImageFile);
411
- await new Promise((resolve) => {
412
- reader.onloadend = () => {
413
- if (typeof reader.result === 'string') {
414
- setImageData(reader.result);
415
- }
416
- resolve();
417
- };
418
- });
419
- // Set progress to 100%
420
- setProgress(100);
421
- return;
422
- }
423
- }
424
- else {
425
- // Regular prompt-only generation (no document in memory)
426
- // Build generation options; if this is a regeneration request (generationId present)
427
- // attach a unique _regenerationId to force a fresh network call and clear cached key.
428
- let generationOptions = options;
429
- if (generationId) {
430
- generationOptions = {
431
- ...options,
432
- _regenerationId: Date.now(),
433
- };
434
- // Clear any cached promise for the original prompt+options so that
435
- // imageGen will not re-use the previous response.
436
- const requestKey = `${prompt}-${JSON.stringify(getRelevantOptions(options))}`;
437
- cleanupRequestKey(requestKey);
438
- }
439
- // Generate the image
440
- data = await callImageGeneration(prompt, generationOptions);
441
- }
442
- // Process the data response
443
- if (data?.data?.[0]?.b64_json) {
444
- // Create a File object from the base64 data
445
- // Generate a filename based on the prompt text
446
- const filename = generateSafeFilename(prompt);
447
- const imageFile = base64ToFile(data.data[0].b64_json, filename);
448
- // Define a stable key for deduplication based on all relevant parameters.
449
- // Include _id (if present) and current time for regeneration requests
450
- // to ensure each regeneration gets a unique key
451
- // Create a unique stable key for this request that changes with generationId
452
- // When generationId changes, we'll generate a new image
453
- const regenPart = generationId ? `gen-${generationId}` : '0';
454
- const stableKey = [
455
- prompt || '',
456
- _id || '',
457
- // For generation requests, use the generationId to ensure uniqueness
458
- // When there's no _id but there is a prompt, we still want regeneration to work
459
- regenPart,
460
- // Stringify only relevant options to avoid spurious cache misses
461
- JSON.stringify(getRelevantOptions(options)),
462
- ].join('|');
463
- // Schedule cleanup of this request from the cache maps
464
- // to ensure future requests don't reuse this one
465
- setTimeout(() => {
466
- cleanupRequestKey(stableKey);
467
- });
468
- // Schedule cleanup of this request from the cache maps
469
- // to ensure future requests don't reuse this one
470
- setTimeout(() => {
471
- cleanupRequestKey(stableKey);
472
- }, 100); // Clear after a short delay
473
- try {
474
- // First check if there's already a document ID for this request
475
- const existingDocId = MODULE_STATE.createdDocuments.get(stableKey);
476
- if (_id) {
477
- try {
478
- // Try to get the document from the database
479
- // If it exists, we'll use its internal prompts for display
480
- const existingDoc = await db.get(_id);
481
- // If the document has a prompt, use it as the prompt text
482
- // Need to check for prompt using 'in' operator to avoid TypeScript errors
483
- if ('prompt' in existingDoc &&
484
- typeof existingDoc.prompt === 'string' &&
485
- existingDoc.prompt) {
486
- prompt = existingDoc.prompt;
487
- }
488
- setDocument(existingDoc);
489
- }
490
- catch {
491
- // Error fetching existing document, ignore silently
492
- }
493
- }
494
- if (existingDocId) {
495
- try {
496
- // Try to get the existing document
497
- const existingDoc = await db.get(existingDocId);
498
- setDocument(existingDoc);
499
- setImageData(data.data[0].b64_json);
500
- return; // Exit early, we're using the existing document
501
- }
502
- catch {
503
- // Error fetching existing document, ignore silently
504
- // Will continue to document creation below
505
- }
506
- }
507
- // Check if there's already a document creation in progress
508
- let documentCreationPromise = MODULE_STATE.pendingDocumentCreations.get(stableKey);
509
- if (!documentCreationPromise) {
510
- // No document creation in progress, start a new one
511
- // This promise will be shared by all subscribers requesting the same document
512
- documentCreationPromise = (async () => {
513
- // Create a new document with initial version and prompt
514
- const imgDoc = {
515
- _id: '', // Will be assigned by Fireproof
516
- type: 'image',
517
- created: Date.now(),
518
- currentVersion: 0, // 0-based indexing for versions array
519
- versions: [
520
- {
521
- id: 'v1',
522
- created: Date.now(),
523
- promptKey: 'p1',
524
- },
525
- ],
526
- prompts: {
527
- p1: {
528
- text: prompt,
529
- created: Date.now(),
530
- },
531
- },
532
- currentPromptKey: 'p1',
533
- _files: {
534
- v1: imageFile,
535
- },
536
- };
537
- // Save the new document to Fireproof
538
- const result = await db.put(imgDoc);
539
- // Store the document ID in our tracking map to prevent duplicates
540
- MODULE_STATE.createdDocuments.set(stableKey, result.id);
541
- // Get the document with the file attached
542
- const doc = (await db.get(result.id));
543
- return { id: result.id, doc };
544
- })();
545
- // Store the promise for other subscribers
546
- MODULE_STATE.pendingDocumentCreations.set(stableKey, documentCreationPromise);
547
- }
548
- else {
549
- // Reusing existing document creation promise
550
- // No additional action needed
551
- }
552
- try {
553
- // Wait for the document creation to complete
554
- const { doc } = await documentCreationPromise;
555
- setDocument(doc);
556
- setImageData(data.data[0].b64_json);
557
- }
558
- catch (e) {
559
- console.error('Error in document creation:', e);
560
- // Still show the image even if document creation fails
561
- setImageData(data.data[0].b64_json);
562
- // Clean up the failed promise so future requests can try again
563
- MODULE_STATE.pendingDocumentCreations.delete(stableKey);
564
- }
565
- // Empty block - all document creation logic is now handled by the Promise
566
- }
567
- catch (e) {
568
- console.error('Error saving to Fireproof:', e);
569
- // Even if we fail to save to Fireproof, we still have the image data
570
- setImageData(data.data[0].b64_json);
571
- }
572
- finally {
573
- // Clean up processing flag
574
- MODULE_STATE.processingRequests.delete(stableKey);
575
- // Clean up the document creation promise if successful
576
- // This prevents memory leaks while preserving the document ID in createdDocuments
577
- if (MODULE_STATE.createdDocuments.has(stableKey)) {
578
- MODULE_STATE.pendingDocumentCreations.delete(stableKey);
579
- MODULE_STATE.requestTimestamps.delete(stableKey);
580
- }
581
- }
582
- }
583
- }
584
- else {
585
- console.error('[loadOrGenerateImage] Document loading failed - requirements not met:', {
586
- _id,
587
- hasPrompt: !!prompt,
588
- promptLength: prompt?.length,
589
- generationId,
590
- });
591
- throw new Error('Document not found and no prompt provided for generation');
592
- }
593
- }
594
- catch (error) {
595
- // Log the error
596
- console.error('Error retrieving from Fireproof:', error);
597
- // Only try image generation as fallback for document load failures when we have a prompt
598
- if (prompt && !data && _id) {
599
- try {
600
- data = await callImageGeneration(prompt, options);
601
- if (data?.data?.[0]?.b64_json) {
602
- setImageData(data.data[0].b64_json);
603
- }
604
- }
605
- catch (genError) {
606
- console.error('Fallback generation also failed:', genError);
607
- throw genError;
608
- }
609
- }
610
- else {
611
- throw error;
612
- }
613
- }
614
- finally {
615
- // Always reset loading state and progress indicators
616
- // This ensures UI progress bars are stopped even if an error occurs
617
- if (isMounted) {
618
- setLoading(false);
619
- setProgress(0); // Reset progress to 0 instead of null
620
- }
621
- // Clear progress timer
622
- if (progressTimerRef.current) {
623
- clearInterval(progressTimerRef.current);
624
- progressTimerRef.current = null;
625
- }
626
- }
627
- // Update state with the image data
628
- if (isMounted && data) {
629
- setProgress(100);
630
- // Clear progress timer
631
- if (progressTimerRef.current) {
632
- clearInterval(progressTimerRef.current);
633
- progressTimerRef.current = null;
634
- }
635
- // Log completion time
636
- }
637
- }
638
- catch (err) {
639
- if (isMounted) {
640
- setError(err instanceof Error ? err : new Error(String(err)));
641
- }
642
- }
643
- finally {
644
- if (isMounted) {
645
- // Clear progress timer if it's still running
646
- if (progressTimerRef.current) {
647
- clearInterval(progressTimerRef.current);
648
- progressTimerRef.current = null;
649
- }
650
- setLoading(false);
651
- }
652
- }
653
- };
654
- const loadImageTimer = setTimeout(() => {
655
- if (isMounted) {
656
- loadOrGenerateImage();
657
- }
658
- }, 10); // Small delay to allow React to batch updates
659
- return () => {
660
- isMounted = false;
661
- if (loadImageTimer) {
662
- clearTimeout(loadImageTimer);
663
- }
664
- };
665
- }, [prompt, _id, memoizedOptions, requestId, database, skip, generationId]); // Dependencies that trigger image loading/generation
666
- return {
667
- imageData,
668
- loading,
669
- progress,
670
- error,
671
- size: { width, height },
672
- document,
673
- };
674
- }
675
- //# sourceMappingURL=use-image-gen.js.map