@umituz/react-native-ai-generation-content 1.90.2 → 1.90.3
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.
- package/package.json +1 -1
- package/src/domain/interfaces/app-services-auth.interface.ts +27 -0
- package/src/domain/interfaces/app-services-composite.interface.ts +29 -0
- package/src/domain/interfaces/app-services-optional.interface.ts +42 -0
- package/src/domain/interfaces/app-services.interface.ts +0 -79
- package/src/domains/background/infrastructure/services/job-poller-index.ts +7 -0
- package/src/domains/background/infrastructure/services/job-poller-utils.ts +130 -0
- package/src/domains/background/infrastructure/utils/polling-interval.util.ts +1 -1
- package/src/domains/background/presentation/hooks/use-background-generation.ts +1 -1
- package/src/domains/content-moderation/infrastructure/services/content-moderation.service.ts +1 -1
- package/src/domains/content-moderation/infrastructure/services/moderators/image.moderator.ts +34 -8
- package/src/domains/content-moderation/infrastructure/services/moderators/text.moderator.ts +15 -4
- package/src/domains/content-moderation/infrastructure/services/moderators/video.moderator.ts +34 -8
- package/src/domains/content-moderation/infrastructure/services/moderators/voice.moderator.ts +19 -8
- package/src/domains/creations/domain/types/creation-categories.constants.ts +57 -0
- package/src/domains/creations/domain/types/creation-categories.helpers.ts +67 -0
- package/src/domains/creations/domain/types/creation-categories.ts +5 -111
- package/src/domains/creations/presentation/hooks/creation-validators.ts +31 -29
- package/src/domains/creations/presentation/hooks/job-poller-index.ts +10 -0
- package/src/domains/creations/presentation/hooks/job-poller-utils.filters.ts +34 -0
- package/src/domains/creations/presentation/hooks/job-poller-utils.logger.ts +76 -0
- package/src/domains/creations/presentation/hooks/job-poller-utils.stale-handlers.ts +52 -0
- package/src/domains/creations/presentation/hooks/job-poller-utils.ts +8 -0
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +1 -1
- package/src/domains/creations/presentation-exports.ts +2 -2
- package/src/domains/face-detection/domain/entities/FaceDetection.ts +4 -3
- package/src/domains/face-detection/presentation/hooks/useFaceDetection.ts +24 -21
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple-preparation.ts +58 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple-prompt.ts +69 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple-resolution.ts +77 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple.ts +54 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-index.ts +8 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-scenario.ts +113 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/builder.ts +7 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/index.ts +20 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/types.ts +44 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/builder-end-logger.ts +18 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/builder-start-logger.ts +57 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/builder-step-logger.ts +106 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/index.ts +8 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils/types.ts +49 -0
- package/src/domains/generation/infrastructure/couple-generation-builder/utils.ts +8 -0
- package/src/domains/generation/infrastructure/flow/flow-store-actions.ts +105 -0
- package/src/domains/generation/infrastructure/flow/flow-store-initial-state.ts +26 -0
- package/src/domains/generation/infrastructure/flow/useFlowStore.ts +4 -116
- package/src/domains/generation/presentation/useAIGeneration.hook.ts +1 -1
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation-strategy-index.ts +7 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.ts +2 -12
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.types.ts +11 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.executor.utils.ts +12 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts +1 -220
- package/src/domains/generation/wizard/infrastructure/strategies/image-input-builder.ts +66 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-input-extraction.ts +88 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-input-prompt-builder.ts +75 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-input-style-enhancements.ts +35 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-strategy-factory.ts +42 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-executor-index.ts +11 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-executor.ts +76 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-input-builder.ts +46 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-result-types.ts +17 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation-submission.ts +62 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.audio-extractor.ts +27 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.executor.ts +2 -175
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.input-builder.ts +90 -0
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts +3 -108
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.types.ts +0 -129
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.validation.ts +136 -0
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/index.ts +39 -0
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/types.ts +37 -0
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/usePhotoUploadStateLogic.ts +142 -0
- package/src/domains/generation/wizard/presentation/hooks/use-video-queue-utils.ts +103 -0
- package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.handlers.ts +97 -0
- package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.saver.ts +54 -0
- package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.ts +22 -87
- package/src/domains/generation/wizard/presentation/hooks/usePhotoUploadState.ts +8 -177
- package/src/domains/generation/wizard/presentation/hooks/useVideoQueueGeneration.ts +1 -295
- package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +1 -1
- package/src/domains/generation/wizard/presentation/hooks/video-queue/index.ts +82 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationCallbacks.ts +120 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationPolling.ts +76 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationRefs.ts +65 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationStart.ts +123 -0
- package/src/domains/generation/wizard/presentation/hooks/video-queue-index.ts +9 -0
- package/src/domains/image-to-video/domain/types/image-to-video-state.types.ts +11 -4
- package/src/domains/text-to-image/domain/types/text-to-image.types.ts +44 -22
- package/src/domains/text-to-video/domain/types/request.types.ts +33 -9
- package/src/domains/text-to-video/domain/types/state.types.ts +29 -9
- package/src/domains/text-to-video/presentation/hooks/useTextToVideoForm.handlers.ts +44 -0
- package/src/domains/text-to-video/presentation/hooks/useTextToVideoForm.ts +5 -51
- package/src/domains/text-to-video/presentation/hooks/useTextToVideoForm.types.ts +33 -0
- package/src/infrastructure/services/generation-orchestrator.service.ts +2 -2
- package/src/infrastructure/utils/couple-input-context.ts +13 -0
- package/src/infrastructure/utils/couple-input-index.ts +8 -0
- package/src/infrastructure/utils/couple-input-refiner.ts +101 -0
- package/src/infrastructure/utils/couple-input-resolver.ts +71 -0
- package/src/infrastructure/utils/couple-input-types.ts +14 -0
- package/src/infrastructure/utils/couple-input.util.ts +3 -176
- package/src/infrastructure/utils/photo-generation/photo-preparation.util.ts +1 -1
- package/src/infrastructure/validation/base-validator.ts +1 -27
- package/src/infrastructure/validation/base-validator.types.ts +32 -0
- package/src/presentation/hooks/generation/index.ts +1 -1
- package/src/presentation/hooks/generation/orchestrator-abort-logs.ts +48 -0
- package/src/presentation/hooks/generation/orchestrator-execution-logs.ts +67 -0
- package/src/presentation/hooks/generation/orchestrator-index.ts +14 -0
- package/src/presentation/hooks/generation/orchestrator-start-logs.ts +65 -0
- package/src/presentation/hooks/generation/orchestrator-state-utils.ts +17 -0
- package/src/presentation/hooks/generation/orchestrator-types.ts +55 -0
- package/src/presentation/hooks/generation/orchestrator-utils-index.ts +29 -0
- package/src/presentation/hooks/generation/orchestrator-utils.ts +25 -0
- package/src/presentation/hooks/generation/useDualImageGeneration.ts +1 -1
- package/src/presentation/hooks/generation/useImageGeneration.ts +1 -1
- package/src/presentation/hooks/generation/useVideoGeneration.ts +1 -1
- package/src/shared/hooks/factories/generation-hook-index.ts +12 -0
- package/src/shared/hooks/factories/generation-hook-types.ts +47 -0
- package/src/shared/hooks/factories/generation-hook-utils.ts +94 -0
- package/src/shared/hooks/factories/index.ts +1 -1
- package/src/shared/index.ts +1 -1
- package/src/shared/utils/calculations/aspect-ratio-calculations.ts +30 -0
- package/src/shared/utils/calculations/base64-calculations.ts +26 -0
- package/src/shared/utils/calculations/confidence-calculations.ts +21 -0
- package/src/shared/utils/calculations/cost-calculations-index.ts +43 -0
- package/src/shared/utils/calculations/cost-calculations.ts +25 -0
- package/src/shared/utils/calculations/credit-calculations.ts +37 -0
- package/src/shared/utils/calculations/index.ts +46 -0
- package/src/shared/utils/calculations/math-utilities.ts +32 -0
- package/src/shared/utils/calculations/memory-calculations.ts +33 -0
- package/src/shared/utils/calculations/pagination-calculations.ts +38 -0
- package/src/shared/utils/calculations/percentage-calculations.ts +33 -0
- package/src/shared/utils/calculations/time-calculations.ts +99 -0
- package/src/shared/utils/credit.ts +1 -1
- package/src/shared-kernel/application/hooks/index.ts +8 -0
- package/src/shared-kernel/application/hooks/use-feature-state.ts +107 -0
- package/src/shared-kernel/application/hooks/use-generation-handler.ts +110 -0
- package/src/shared-kernel/base-types/base-callbacks.types.ts +73 -0
- package/src/shared-kernel/base-types/base-feature-state.types.ts +77 -0
- package/src/shared-kernel/base-types/base-generation.types.ts +69 -0
- package/src/shared-kernel/base-types/index.ts +30 -0
- package/src/shared-kernel/domain/base-generation-strategy.ts +146 -0
- package/src/shared-kernel/domain/index.ts +7 -0
- package/src/shared-kernel/index.ts +17 -0
- package/src/shared-kernel/infrastructure/validation/common-validators.ts +126 -0
- package/src/shared-kernel/infrastructure/validation/common-validators.types.ts +33 -0
- package/src/shared-kernel/infrastructure/validation/error-handler.ts +52 -0
- package/src/shared-kernel/infrastructure/validation/error-handler.types.ts +38 -0
- package/src/shared-kernel/infrastructure/validation/error-handler.utils.ts +79 -0
- package/src/shared-kernel/infrastructure/validation/index.ts +28 -0
- package/src/domains/background/infrastructure/services/job-poller.service.ts +0 -234
- package/src/domains/creations/presentation/hooks/useProcessingJobsPoller.ts +0 -256
- package/src/domains/generation/infrastructure/couple-generation-builder.ts +0 -374
- package/src/presentation/hooks/generation/orchestrator.ts +0 -276
- package/src/shared/hooks/factories/createGenerationHook.ts +0 -253
- package/src/shared/utils/calculations.util.ts +0 -366
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Poller Service
|
|
3
|
-
* Provider-agnostic job polling with exponential backoff
|
|
4
|
-
* Reports only real status - no fake progress
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { DEFAULT_POLLING_CONFIG } from "../../../../domain/entities/polling.types";
|
|
8
|
-
import { calculatePollingInterval } from "../utils/polling-interval.util";
|
|
9
|
-
import { checkStatusForErrors, isJobComplete } from "../utils/status-checker.util";
|
|
10
|
-
import { validateResult } from "../utils/result-validator.util";
|
|
11
|
-
import type { PollJobOptions, PollJobResult } from "./job-poller.types";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Wraps a promise with abort signal support
|
|
16
|
-
* Rejects if signal is aborted before promise resolves
|
|
17
|
-
*/
|
|
18
|
-
function withAbortSignal<T>(
|
|
19
|
-
promise: Promise<T>,
|
|
20
|
-
signal: AbortSignal | undefined,
|
|
21
|
-
timeoutMs?: number,
|
|
22
|
-
): Promise<T> {
|
|
23
|
-
if (!signal && !timeoutMs) {
|
|
24
|
-
return promise;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return new Promise<T>((resolve, reject) => {
|
|
28
|
-
// Handle abort signal
|
|
29
|
-
if (signal?.aborted) {
|
|
30
|
-
reject(new Error("Operation aborted"));
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let isResolved = false;
|
|
35
|
-
const abortHandler = () => {
|
|
36
|
-
if (isResolved) return;
|
|
37
|
-
isResolved = true;
|
|
38
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
39
|
-
reject(new Error("Operation aborted"));
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
signal?.addEventListener("abort", abortHandler, { once: true });
|
|
43
|
-
|
|
44
|
-
// Handle timeout
|
|
45
|
-
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
46
|
-
if (timeoutMs) {
|
|
47
|
-
timeoutId = setTimeout(() => {
|
|
48
|
-
if (isResolved) return;
|
|
49
|
-
isResolved = true;
|
|
50
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
51
|
-
reject(new Error(`Operation timeout after ${timeoutMs}ms`));
|
|
52
|
-
}, timeoutMs);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
promise
|
|
56
|
-
.then((result) => {
|
|
57
|
-
if (isResolved) return;
|
|
58
|
-
isResolved = true;
|
|
59
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
60
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
61
|
-
resolve(result);
|
|
62
|
-
})
|
|
63
|
-
.catch((error) => {
|
|
64
|
-
if (isResolved) return;
|
|
65
|
-
isResolved = true;
|
|
66
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
67
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
68
|
-
reject(error);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Poll job until completion with exponential backoff
|
|
75
|
-
* Only reports 100% on actual completion
|
|
76
|
-
*/
|
|
77
|
-
export async function pollJob<T = unknown>(
|
|
78
|
-
options: PollJobOptions,
|
|
79
|
-
): Promise<PollJobResult<T>> {
|
|
80
|
-
const {
|
|
81
|
-
provider,
|
|
82
|
-
model,
|
|
83
|
-
requestId,
|
|
84
|
-
config,
|
|
85
|
-
onProgress,
|
|
86
|
-
onStatusChange,
|
|
87
|
-
signal,
|
|
88
|
-
} = options;
|
|
89
|
-
|
|
90
|
-
// Validate requestId early
|
|
91
|
-
if (!requestId || typeof requestId !== "string" || requestId.trim() === "") {
|
|
92
|
-
return {
|
|
93
|
-
success: false,
|
|
94
|
-
error: new Error("Invalid requestId provided"),
|
|
95
|
-
attempts: 0,
|
|
96
|
-
elapsedMs: 0,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Validate model early
|
|
101
|
-
if (!model || typeof model !== "string" || model.trim() === "") {
|
|
102
|
-
return {
|
|
103
|
-
success: false,
|
|
104
|
-
error: new Error("Invalid model provided"),
|
|
105
|
-
attempts: 0,
|
|
106
|
-
elapsedMs: 0,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const pollingConfig = { ...DEFAULT_POLLING_CONFIG, ...config };
|
|
111
|
-
const { maxAttempts, maxTotalTimeMs, maxConsecutiveErrors } = pollingConfig;
|
|
112
|
-
|
|
113
|
-
const startTime = Date.now();
|
|
114
|
-
let consecutiveTransientErrors = 0;
|
|
115
|
-
|
|
116
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
117
|
-
// Check total time limit
|
|
118
|
-
const elapsedMs = Date.now() - startTime;
|
|
119
|
-
if (maxTotalTimeMs && elapsedMs >= maxTotalTimeMs) {
|
|
120
|
-
return {
|
|
121
|
-
success: false,
|
|
122
|
-
error: new Error(`Polling timeout after ${elapsedMs}ms`),
|
|
123
|
-
attempts: attempt + 1,
|
|
124
|
-
elapsedMs,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (signal?.aborted) {
|
|
129
|
-
return {
|
|
130
|
-
success: false,
|
|
131
|
-
error: new Error("Polling aborted"),
|
|
132
|
-
attempts: attempt + 1,
|
|
133
|
-
elapsedMs,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (attempt > 0) {
|
|
138
|
-
const interval = calculatePollingInterval({ attempt, config: pollingConfig });
|
|
139
|
-
await new Promise<void>((resolve) => setTimeout(() => resolve(), interval));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
// Wrap provider calls with abort signal support and timeout (30s default)
|
|
144
|
-
const status = await withAbortSignal(
|
|
145
|
-
provider.getJobStatus(model, requestId),
|
|
146
|
-
signal,
|
|
147
|
-
30000,
|
|
148
|
-
);
|
|
149
|
-
onStatusChange?.(status);
|
|
150
|
-
|
|
151
|
-
const statusCheck = checkStatusForErrors(status);
|
|
152
|
-
|
|
153
|
-
if (statusCheck.shouldStop && statusCheck.hasError) {
|
|
154
|
-
return {
|
|
155
|
-
success: false,
|
|
156
|
-
error: new Error(statusCheck.errorMessage || "Job failed"),
|
|
157
|
-
attempts: attempt + 1,
|
|
158
|
-
elapsedMs: Date.now() - startTime,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
consecutiveTransientErrors = 0;
|
|
163
|
-
|
|
164
|
-
if (isJobComplete(status)) {
|
|
165
|
-
// Wrap result retrieval with abort signal support and timeout (60s for larger results)
|
|
166
|
-
const result = await withAbortSignal(
|
|
167
|
-
provider.getJobResult<T>(model, requestId),
|
|
168
|
-
signal,
|
|
169
|
-
60000,
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
const validation = validateResult(result);
|
|
173
|
-
if (!validation.isValid) {
|
|
174
|
-
return {
|
|
175
|
-
success: false,
|
|
176
|
-
error: new Error(validation.errorMessage || "Invalid result"),
|
|
177
|
-
attempts: attempt + 1,
|
|
178
|
-
elapsedMs: Date.now() - startTime,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
onProgress?.(100);
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
success: true,
|
|
186
|
-
data: result,
|
|
187
|
-
attempts: attempt + 1,
|
|
188
|
-
elapsedMs: Date.now() - startTime,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
} catch (error) {
|
|
192
|
-
consecutiveTransientErrors++;
|
|
193
|
-
|
|
194
|
-
if (__DEV__) {
|
|
195
|
-
console.warn("[JobPoller] Transient error during polling", {
|
|
196
|
-
attempt: attempt + 1,
|
|
197
|
-
requestId,
|
|
198
|
-
model,
|
|
199
|
-
consecutiveErrors: consecutiveTransientErrors,
|
|
200
|
-
error: error instanceof Error ? error.message : String(error),
|
|
201
|
-
code: (error as { code?: string })?.code,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Check if we've hit max consecutive transient errors
|
|
206
|
-
if (maxConsecutiveErrors && consecutiveTransientErrors >= maxConsecutiveErrors) {
|
|
207
|
-
if (__DEV__) {
|
|
208
|
-
console.error("[JobPoller] Max consecutive errors reached", {
|
|
209
|
-
maxConsecutiveErrors,
|
|
210
|
-
requestId,
|
|
211
|
-
model,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
return {
|
|
215
|
-
success: false,
|
|
216
|
-
error: new Error(`Too many consecutive errors (${consecutiveTransientErrors})`),
|
|
217
|
-
attempts: attempt + 1,
|
|
218
|
-
elapsedMs: Date.now() - startTime,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Continue polling on transient errors
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
success: false,
|
|
229
|
-
error: new Error(`Polling timeout after ${maxAttempts} attempts`),
|
|
230
|
-
attempts: maxAttempts,
|
|
231
|
-
elapsedMs: Date.now() - startTime,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useProcessingJobsPoller Hook
|
|
3
|
-
* Polls queue status for "processing" creations and updates Firestore when complete
|
|
4
|
-
* Enables true background generation - works even after wizard is dismissed
|
|
5
|
-
* Uses provider registry internally - no need to pass provider functions
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useEffect, useRef, useMemo } from "react";
|
|
9
|
-
import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
|
|
10
|
-
import { QUEUE_STATUS, CREATION_STATUS } from "../../../../domain/constants/queue-status.constants";
|
|
11
|
-
import { DEFAULT_POLL_INTERVAL_MS, DEFAULT_MAX_POLL_TIME_MS } from "../../../../infrastructure/constants/polling.constants";
|
|
12
|
-
import {
|
|
13
|
-
extractResultUrl,
|
|
14
|
-
type GenerationResult,
|
|
15
|
-
} from "../../../generation/wizard/presentation/hooks/generation-result.utils";
|
|
16
|
-
import type { Creation } from "../../domain/entities/Creation";
|
|
17
|
-
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
18
|
-
import { isOlderThan, calculateAgeMs } from "../../../../shared/utils/calculations.util";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export interface UseProcessingJobsPollerConfig {
|
|
22
|
-
readonly userId?: string | null;
|
|
23
|
-
readonly creations: Creation[];
|
|
24
|
-
readonly repository: ICreationsRepository;
|
|
25
|
-
readonly enabled?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface UseProcessingJobsPollerReturn {
|
|
29
|
-
readonly processingCount: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function useProcessingJobsPoller(
|
|
33
|
-
config: UseProcessingJobsPollerConfig,
|
|
34
|
-
): UseProcessingJobsPollerReturn {
|
|
35
|
-
const {
|
|
36
|
-
userId,
|
|
37
|
-
creations,
|
|
38
|
-
repository,
|
|
39
|
-
enabled = true,
|
|
40
|
-
} = config;
|
|
41
|
-
|
|
42
|
-
const pollingRef = useRef<Set<string>>(new Set());
|
|
43
|
-
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
44
|
-
|
|
45
|
-
// Convert to IDs to prevent re-creating array on every render
|
|
46
|
-
const processingJobIds = useMemo(
|
|
47
|
-
() => creations
|
|
48
|
-
.filter((c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model)
|
|
49
|
-
.map((c) => c.id),
|
|
50
|
-
[creations],
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const processingJobs = useMemo(
|
|
54
|
-
() => creations.filter(
|
|
55
|
-
(c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model,
|
|
56
|
-
),
|
|
57
|
-
[creations],
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// Orphan jobs: processing but no requestId/model (e.g. blocking image jobs that got stuck)
|
|
61
|
-
const orphanJobs = useMemo(
|
|
62
|
-
() => creations.filter(
|
|
63
|
-
(c) => c.status === CREATION_STATUS.PROCESSING && !c.requestId && !c.model,
|
|
64
|
-
),
|
|
65
|
-
[creations],
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
// Use ref for stable function reference to prevent effect re-runs
|
|
69
|
-
const pollJobRef = useRef<((creation: Creation) => Promise<void>) | undefined>(undefined);
|
|
70
|
-
|
|
71
|
-
// Use mounted ref to prevent operations after unmount
|
|
72
|
-
const isMountedRef = useRef(true);
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
isMountedRef.current = true;
|
|
76
|
-
return () => {
|
|
77
|
-
isMountedRef.current = false;
|
|
78
|
-
};
|
|
79
|
-
}, []);
|
|
80
|
-
|
|
81
|
-
pollJobRef.current = async (creation: Creation) => {
|
|
82
|
-
if (!isMountedRef.current || !userId || !creation.requestId || !creation.model) return;
|
|
83
|
-
|
|
84
|
-
if (pollingRef.current.has(creation.id)) return;
|
|
85
|
-
pollingRef.current.add(creation.id);
|
|
86
|
-
|
|
87
|
-
// Stale detection: if creation is older than max poll time, mark as failed
|
|
88
|
-
if (isOlderThan(creation.createdAt, DEFAULT_MAX_POLL_TIME_MS)) {
|
|
89
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
90
|
-
console.log("[ProcessingJobsPoller] Stale job detected, marking as failed:", creation.id, {
|
|
91
|
-
ageMs: calculateAgeMs(creation.createdAt),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
try {
|
|
95
|
-
await repository.update(userId, creation.id, {
|
|
96
|
-
status: CREATION_STATUS.FAILED,
|
|
97
|
-
metadata: { ...creation.metadata, error: "Generation timed out" },
|
|
98
|
-
completedAt: new Date(),
|
|
99
|
-
});
|
|
100
|
-
} catch (e) {
|
|
101
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
102
|
-
console.error("[ProcessingJobsPoller] Failed to mark stale job:", e);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
pollingRef.current.delete(creation.id);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const provider = providerRegistry.getActiveProvider();
|
|
110
|
-
if (!provider || !provider.isInitialized()) {
|
|
111
|
-
pollingRef.current.delete(creation.id);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
117
|
-
console.log("[ProcessingJobsPoller] Checking status:", creation.id);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const status = await provider.getJobStatus(creation.model, creation.requestId);
|
|
121
|
-
|
|
122
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
123
|
-
console.log("[ProcessingJobsPoller] Status:", creation.id, status.status);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (status.status === QUEUE_STATUS.COMPLETED) {
|
|
127
|
-
const result = await provider.getJobResult<GenerationResult>(creation.model, creation.requestId);
|
|
128
|
-
const urls = extractResultUrl(result);
|
|
129
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[ProcessingJobsPoller] Completed:", creation.id, urls);
|
|
130
|
-
|
|
131
|
-
if (!isMountedRef.current) return;
|
|
132
|
-
|
|
133
|
-
const uri = urls.videoUrl || urls.imageUrl || "";
|
|
134
|
-
|
|
135
|
-
// Validate that we have a valid URI before marking as completed
|
|
136
|
-
if (!uri || uri.trim() === "") {
|
|
137
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
138
|
-
console.error("[ProcessingJobsPoller] No valid URI in result:", creation.id);
|
|
139
|
-
}
|
|
140
|
-
await repository.update(userId, creation.id, {
|
|
141
|
-
status: CREATION_STATUS.FAILED,
|
|
142
|
-
metadata: { ...creation.metadata, error: "No valid result URL received" },
|
|
143
|
-
completedAt: new Date(),
|
|
144
|
-
});
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const output: Record<string, string | undefined> = {};
|
|
149
|
-
if (urls.imageUrl) output.imageUrl = urls.imageUrl;
|
|
150
|
-
if (urls.videoUrl) output.videoUrl = urls.videoUrl;
|
|
151
|
-
if (urls.thumbnailUrl) output.thumbnailUrl = urls.thumbnailUrl;
|
|
152
|
-
|
|
153
|
-
await repository.update(userId, creation.id, {
|
|
154
|
-
status: CREATION_STATUS.COMPLETED,
|
|
155
|
-
uri,
|
|
156
|
-
output,
|
|
157
|
-
completedAt: new Date(),
|
|
158
|
-
});
|
|
159
|
-
} else if (status.status === QUEUE_STATUS.FAILED) {
|
|
160
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[ProcessingJobsPoller] Failed:", creation.id);
|
|
161
|
-
|
|
162
|
-
if (!isMountedRef.current) return;
|
|
163
|
-
|
|
164
|
-
await repository.update(userId, creation.id, {
|
|
165
|
-
status: CREATION_STATUS.FAILED,
|
|
166
|
-
metadata: { ...creation.metadata, error: "Generation failed" },
|
|
167
|
-
completedAt: new Date(),
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
} catch (error) {
|
|
171
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
172
|
-
console.error("[ProcessingJobsPoller] Poll error:", creation.id, error);
|
|
173
|
-
}
|
|
174
|
-
} finally {
|
|
175
|
-
pollingRef.current.delete(creation.id);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// Clean up orphan processing creations (no requestId/model) older than max poll time
|
|
180
|
-
useEffect(() => {
|
|
181
|
-
if (!enabled || !userId || orphanJobs.length === 0) return;
|
|
182
|
-
|
|
183
|
-
const cleanupOrphans = async () => {
|
|
184
|
-
const staleOrphans = orphanJobs.filter((creation) =>
|
|
185
|
-
isOlderThan(creation.createdAt, DEFAULT_MAX_POLL_TIME_MS)
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
if (staleOrphans.length === 0) return;
|
|
189
|
-
|
|
190
|
-
await Promise.allSettled(
|
|
191
|
-
staleOrphans.map(async (creation) => {
|
|
192
|
-
if (!isMountedRef.current) return;
|
|
193
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
194
|
-
console.log("[ProcessingJobsPoller] Orphan job timed out, marking as failed:", creation.id);
|
|
195
|
-
}
|
|
196
|
-
await repository.update(userId, creation.id, {
|
|
197
|
-
status: CREATION_STATUS.FAILED,
|
|
198
|
-
metadata: { ...creation.metadata, error: "Generation timed out" },
|
|
199
|
-
completedAt: new Date(),
|
|
200
|
-
});
|
|
201
|
-
}),
|
|
202
|
-
);
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
void cleanupOrphans().catch((e) => {
|
|
206
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
207
|
-
console.error("[ProcessingJobsPoller] Failed to clean up orphan jobs:", e);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
}, [enabled, userId, orphanJobs, repository]);
|
|
211
|
-
|
|
212
|
-
// Use ref to always get latest creations
|
|
213
|
-
const creationsRef = useRef(creations);
|
|
214
|
-
useEffect(() => {
|
|
215
|
-
creationsRef.current = creations;
|
|
216
|
-
}, [creations]);
|
|
217
|
-
|
|
218
|
-
useEffect(() => {
|
|
219
|
-
if (!enabled || !userId || processingJobIds.length === 0) {
|
|
220
|
-
if (intervalRef.current) {
|
|
221
|
-
clearInterval(intervalRef.current);
|
|
222
|
-
intervalRef.current = null;
|
|
223
|
-
}
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Get current jobs at poll time from ref to avoid stale closures
|
|
228
|
-
const pollCurrentJobs = () => {
|
|
229
|
-
const currentJobs = creationsRef.current.filter(
|
|
230
|
-
(c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model,
|
|
231
|
-
);
|
|
232
|
-
currentJobs.forEach((job) => pollJobRef.current?.(job));
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// Initial poll
|
|
236
|
-
pollCurrentJobs();
|
|
237
|
-
|
|
238
|
-
// Set up interval polling
|
|
239
|
-
intervalRef.current = setInterval(pollCurrentJobs, DEFAULT_POLL_INTERVAL_MS);
|
|
240
|
-
|
|
241
|
-
return () => {
|
|
242
|
-
// Clear polling set first to prevent new operations
|
|
243
|
-
pollingRef.current.clear();
|
|
244
|
-
|
|
245
|
-
// Then clear interval
|
|
246
|
-
if (intervalRef.current) {
|
|
247
|
-
clearInterval(intervalRef.current);
|
|
248
|
-
intervalRef.current = null;
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
}, [enabled, userId, processingJobIds]);
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
processingCount: processingJobs.length,
|
|
255
|
-
};
|
|
256
|
-
}
|