@umituz/react-native-ai-generation-content 1.17.143 → 1.17.145

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 (43) hide show
  1. package/package.json +1 -1
  2. package/src/domains/creations/presentation/hooks/index.ts +2 -2
  3. package/src/domains/creations/presentation/hooks/{useMediaFilter.ts → useFilter.ts} +12 -7
  4. package/src/domains/creations/presentation/hooks/useGalleryFilters.ts +5 -6
  5. package/src/features/ai-hug/presentation/hooks/useAIHugFeature.ts +9 -113
  6. package/src/features/ai-kiss/presentation/hooks/useAIKissFeature.ts +9 -113
  7. package/src/features/image-to-video/infrastructure/services/image-to-video-executor.ts +5 -9
  8. package/src/features/image-to-video/presentation/hooks/useImageToVideoForm.ts +5 -0
  9. package/src/features/replace-background/index.ts +5 -50
  10. package/src/features/replace-background/presentation/components/index.ts +0 -12
  11. package/src/features/replace-background/presentation/hooks/index.ts +0 -3
  12. package/src/features/shared/dual-image-video/domain/types/dual-image-video.types.ts +67 -0
  13. package/src/features/shared/dual-image-video/domain/types/index.ts +13 -0
  14. package/src/features/shared/dual-image-video/index.ts +16 -0
  15. package/src/features/shared/dual-image-video/presentation/hooks/index.ts +5 -0
  16. package/src/features/shared/dual-image-video/presentation/hooks/useDualImageVideoFeature.ts +124 -0
  17. package/src/features/shared/index.ts +6 -0
  18. package/src/infrastructure/utils/index.ts +1 -0
  19. package/src/infrastructure/utils/progress-calculator.util.ts +31 -0
  20. package/src/infrastructure/utils/result-validator.util.ts +0 -214
  21. package/src/infrastructure/utils/url-extractor.util.ts +209 -0
  22. package/src/presentation/hooks/flow-state.utils.ts +101 -0
  23. package/src/presentation/hooks/useGenerationFlow.ts +47 -178
  24. package/src/presentation/types/flow-config.types.ts +5 -99
  25. package/src/presentation/types/flow-default-configs.ts +106 -0
  26. package/src/domains/creations/presentation/hooks/useStatusFilter.ts +0 -54
  27. package/src/features/replace-background/domain/entities/background.types.ts +0 -77
  28. package/src/features/replace-background/domain/entities/component.types.ts +0 -87
  29. package/src/features/replace-background/domain/entities/config.types.ts +0 -41
  30. package/src/features/replace-background/domain/entities/index.ts +0 -30
  31. package/src/features/replace-background/infrastructure/constants/index.ts +0 -5
  32. package/src/features/replace-background/infrastructure/constants/prompts.constants.ts +0 -15
  33. package/src/features/replace-background/infrastructure/index.ts +0 -5
  34. package/src/features/replace-background/presentation/components/BackgroundFeature.tsx +0 -143
  35. package/src/features/replace-background/presentation/components/ComparisonSlider.tsx +0 -187
  36. package/src/features/replace-background/presentation/components/ErrorDisplay.tsx +0 -60
  37. package/src/features/replace-background/presentation/components/FeatureHeader.tsx +0 -80
  38. package/src/features/replace-background/presentation/components/GenerateButton.tsx +0 -85
  39. package/src/features/replace-background/presentation/components/ImagePicker.tsx +0 -136
  40. package/src/features/replace-background/presentation/components/ModeSelector.tsx +0 -78
  41. package/src/features/replace-background/presentation/components/PromptInput.tsx +0 -142
  42. package/src/features/replace-background/presentation/components/ResultDisplay.tsx +0 -122
  43. package/src/features/replace-background/presentation/hooks/useBackgroundFeature.ts +0 -119
@@ -0,0 +1,124 @@
1
+ /**
2
+ * useDualImageVideoFeature Hook
3
+ * Base hook for video features that take two images (ai-hug, ai-kiss, etc.)
4
+ * DRY: Consolidates common logic from useAIHugFeature and useAIKissFeature
5
+ */
6
+
7
+ import { useState, useCallback } from "react";
8
+ import { executeVideoFeature } from "../../../../../infrastructure/services";
9
+ import type {
10
+ DualImageVideoFeatureState,
11
+ UseDualImageVideoFeatureProps,
12
+ UseDualImageVideoFeatureReturn,
13
+ } from "../../domain/types/dual-image-video.types";
14
+
15
+ const initialState: DualImageVideoFeatureState = {
16
+ sourceImageUri: null,
17
+ targetImageUri: null,
18
+ processedVideoUrl: null,
19
+ isProcessing: false,
20
+ progress: 0,
21
+ error: null,
22
+ };
23
+
24
+ export function useDualImageVideoFeature(
25
+ props: UseDualImageVideoFeatureProps,
26
+ ): UseDualImageVideoFeatureReturn {
27
+ const { featureType, config, onSelectSourceImage, onSelectTargetImage, onSaveVideo } = props;
28
+ const [state, setState] = useState<DualImageVideoFeatureState>(initialState);
29
+
30
+ const selectSourceImage = useCallback(async () => {
31
+ try {
32
+ const uri = await onSelectSourceImage();
33
+ if (uri) {
34
+ setState((prev) => ({ ...prev, sourceImageUri: uri, error: null }));
35
+ config.onSourceImageSelect?.(uri);
36
+ }
37
+ } catch (error) {
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ setState((prev) => ({ ...prev, error: message }));
40
+ }
41
+ }, [onSelectSourceImage, config]);
42
+
43
+ const selectTargetImage = useCallback(async () => {
44
+ try {
45
+ const uri = await onSelectTargetImage();
46
+ if (uri) {
47
+ setState((prev) => ({ ...prev, targetImageUri: uri, error: null }));
48
+ config.onTargetImageSelect?.(uri);
49
+ }
50
+ } catch (error) {
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ setState((prev) => ({ ...prev, error: message }));
53
+ }
54
+ }, [onSelectTargetImage, config]);
55
+
56
+ const handleProgress = useCallback((progress: number) => {
57
+ setState((prev) => ({ ...prev, progress }));
58
+ }, []);
59
+
60
+ const process = useCallback(async () => {
61
+ if (!state.sourceImageUri || !state.targetImageUri) return;
62
+
63
+ setState((prev) => ({
64
+ ...prev,
65
+ isProcessing: true,
66
+ progress: 0,
67
+ error: null,
68
+ }));
69
+
70
+ config.onProcessingStart?.();
71
+
72
+ const sourceImageBase64 = await config.prepareImage(state.sourceImageUri);
73
+ const targetImageBase64 = await config.prepareImage(state.targetImageUri);
74
+
75
+ const result = await executeVideoFeature(
76
+ featureType,
77
+ { sourceImageBase64, targetImageBase64 },
78
+ { extractResult: config.extractResult, onProgress: handleProgress },
79
+ );
80
+
81
+ if (result.success && result.videoUrl) {
82
+ setState((prev) => ({
83
+ ...prev,
84
+ isProcessing: false,
85
+ processedVideoUrl: result.videoUrl!,
86
+ progress: 100,
87
+ }));
88
+ config.onProcessingComplete?.({ success: true, videoUrl: result.videoUrl });
89
+ } else {
90
+ const errorMessage = result.error || "Processing failed";
91
+ setState((prev) => ({
92
+ ...prev,
93
+ isProcessing: false,
94
+ error: errorMessage,
95
+ progress: 0,
96
+ }));
97
+ config.onError?.(errorMessage);
98
+ }
99
+ }, [state.sourceImageUri, state.targetImageUri, featureType, config, handleProgress]);
100
+
101
+ const save = useCallback(async () => {
102
+ if (!state.processedVideoUrl) return;
103
+
104
+ try {
105
+ await onSaveVideo(state.processedVideoUrl);
106
+ } catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ setState((prev) => ({ ...prev, error: message }));
109
+ }
110
+ }, [state.processedVideoUrl, onSaveVideo]);
111
+
112
+ const reset = useCallback(() => {
113
+ setState(initialState);
114
+ }, []);
115
+
116
+ return {
117
+ ...state,
118
+ selectSourceImage,
119
+ selectTargetImage,
120
+ process,
121
+ save,
122
+ reset,
123
+ };
124
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared Features
3
+ * Common infrastructure for multiple feature types
4
+ */
5
+
6
+ export * from "./dual-image-video";
@@ -7,6 +7,7 @@ export * from "./polling-interval.util";
7
7
  export * from "./progress-calculator.util";
8
8
  export * from "./status-checker.util";
9
9
  export * from "./result-validator.util";
10
+ export * from "./url-extractor.util";
10
11
  export * from "./photo-generation";
11
12
  export * from "./feature-utils";
12
13
  export * from "./video-helpers";
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Progress Calculator Utility
3
3
  * Calculates progress based on generation stage
4
+ * Maps provider status to generation progress
4
5
  */
5
6
 
6
7
  import type { GenerationStatus } from "../../domain/entities";
@@ -8,6 +9,7 @@ import {
8
9
  DEFAULT_PROGRESS_STAGES,
9
10
  type ProgressStageConfig,
10
11
  } from "../../domain/entities";
12
+ import type { AIJobStatusType } from "../../domain/interfaces/ai-provider.interface";
11
13
 
12
14
  export interface ProgressOptions {
13
15
  status: GenerationStatus;
@@ -95,3 +97,32 @@ export function createProgressTracker(stages?: ProgressStageConfig[]) {
95
97
  },
96
98
  };
97
99
  }
100
+
101
+ /**
102
+ * Maps provider job status to generation status
103
+ * Provider: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED
104
+ * Generation: idle, preparing, submitting, generating, polling, finalizing, completed, failed
105
+ */
106
+ export function mapJobStatusToGenerationStatus(
107
+ jobStatus: AIJobStatusType
108
+ ): GenerationStatus {
109
+ const statusMap: Record<AIJobStatusType, GenerationStatus> = {
110
+ IN_QUEUE: "submitting",
111
+ IN_PROGRESS: "generating",
112
+ COMPLETED: "completed",
113
+ FAILED: "failed",
114
+ };
115
+ return statusMap[jobStatus] ?? "generating";
116
+ }
117
+
118
+ /**
119
+ * Calculate progress from provider job status
120
+ * Uses default progress stages for consistent progress reporting
121
+ */
122
+ export function getProgressFromJobStatus(
123
+ jobStatus: AIJobStatusType,
124
+ stages: ProgressStageConfig[] = DEFAULT_PROGRESS_STAGES
125
+ ): number {
126
+ const generationStatus = mapJobStatusToGenerationStatus(jobStatus);
127
+ return getProgressForStatus({ status: generationStatus, stages });
128
+ }
@@ -13,9 +13,7 @@ export interface ResultValidation {
13
13
  }
14
14
 
15
15
  export interface ValidateResultOptions {
16
- /** Custom output field names to check */
17
16
  outputFields?: string[];
18
- /** Whether empty results are allowed */
19
17
  allowEmpty?: boolean;
20
18
  }
21
19
 
@@ -44,7 +42,6 @@ export function validateResult(
44
42
  const { outputFields = DEFAULT_OUTPUT_FIELDS, allowEmpty = false } =
45
43
  options ?? {};
46
44
 
47
- // Handle null/undefined
48
45
  if (result === null || result === undefined) {
49
46
  return {
50
47
  isValid: allowEmpty,
@@ -54,7 +51,6 @@ export function validateResult(
54
51
  };
55
52
  }
56
53
 
57
- // Handle non-object results
58
54
  if (typeof result !== "object") {
59
55
  return {
60
56
  isValid: true,
@@ -65,7 +61,6 @@ export function validateResult(
65
61
 
66
62
  const resultObj = result as Record<string, unknown>;
67
63
 
68
- // Check for error fields
69
64
  const errorValue = resultObj.error || resultObj.detail;
70
65
  const errorString = errorValue ? String(errorValue).toLowerCase() : "";
71
66
 
@@ -74,15 +69,12 @@ export function validateResult(
74
69
  errorString.includes("500") ||
75
70
  errorString === "internal server error";
76
71
 
77
- // Check for empty object
78
72
  const isEmpty = Object.keys(resultObj).length === 0;
79
73
 
80
- // Check for output in expected fields
81
74
  const hasOutput = outputFields.some((field) => {
82
75
  const value = resultObj[field];
83
76
  if (!value) return false;
84
77
 
85
- // Handle nested output structures
86
78
  if (typeof value === "object" && value !== null) {
87
79
  const nested = value as Record<string, unknown>;
88
80
  return !!(
@@ -96,7 +88,6 @@ export function validateResult(
96
88
  return true;
97
89
  });
98
90
 
99
- // Determine if result has error
100
91
  const hasError =
101
92
  hasInternalServerError || (isEmpty && !hasOutput && !allowEmpty);
102
93
 
@@ -113,213 +104,8 @@ export function validateResult(
113
104
  isValid: validation.isValid,
114
105
  hasOutput: validation.hasOutput,
115
106
  hasError: validation.hasError,
116
- checkedFields: outputFields.join(", "),
117
107
  });
118
108
  }
119
109
 
120
110
  return validation;
121
111
  }
122
-
123
- /**
124
- * Extract output URL from result
125
- * Supports various AI provider response formats
126
- */
127
- export function extractOutputUrl(
128
- result: unknown,
129
- urlFields?: string[],
130
- ): string | undefined {
131
- if (!result || typeof result !== "object") {
132
- return undefined;
133
- }
134
-
135
- const fields = urlFields ?? [
136
- "url",
137
- "image_url",
138
- "video_url",
139
- "output_url",
140
- "result_url",
141
- ];
142
-
143
- const resultObj = result as Record<string, unknown>;
144
-
145
- // Check top-level fields
146
- for (const field of fields) {
147
- const value = resultObj[field];
148
- if (typeof value === "string" && value.length > 0) {
149
- return value;
150
- }
151
- }
152
-
153
- // Check nested data/output objects
154
- const nested =
155
- (resultObj.data as Record<string, unknown>) ||
156
- (resultObj.output as Record<string, unknown>) ||
157
- (resultObj.result as Record<string, unknown>);
158
-
159
- if (nested && typeof nested === "object") {
160
- for (const field of fields) {
161
- const value = nested[field];
162
- if (typeof value === "string" && value.length > 0) {
163
- return value;
164
- }
165
- }
166
-
167
- // Check for nested image/video objects
168
- const media =
169
- (nested.image as Record<string, unknown>) ||
170
- (nested.video as Record<string, unknown>);
171
- if (media && typeof media === "object" && typeof media.url === "string") {
172
- return media.url;
173
- }
174
- }
175
-
176
- return undefined;
177
- }
178
-
179
- /**
180
- * Extract multiple output URLs from result
181
- */
182
- export function extractOutputUrls(
183
- result: unknown,
184
- urlFields?: string[],
185
- ): string[] {
186
- if (!result || typeof result !== "object") {
187
- return [];
188
- }
189
-
190
- const urls: string[] = [];
191
- const resultObj = result as Record<string, unknown>;
192
-
193
- // Check for arrays
194
- const arrayFields = ["images", "videos", "outputs", "results", "urls"];
195
- for (const field of arrayFields) {
196
- const arr = resultObj[field];
197
- if (Array.isArray(arr)) {
198
- for (const item of arr) {
199
- const url = extractOutputUrl(item, urlFields);
200
- if (url) {
201
- urls.push(url);
202
- }
203
- }
204
- }
205
- }
206
-
207
- // Check nested data/output
208
- const nested = resultObj.data || resultObj.output;
209
- if (nested && typeof nested === "object") {
210
- for (const field of arrayFields) {
211
- const arr = (nested as Record<string, unknown>)[field];
212
- if (Array.isArray(arr)) {
213
- for (const item of arr) {
214
- const url = extractOutputUrl(item, urlFields);
215
- if (url) {
216
- urls.push(url);
217
- }
218
- }
219
- }
220
- }
221
- }
222
-
223
- // If no array found, try single URL
224
- if (urls.length === 0) {
225
- const singleUrl = extractOutputUrl(result, urlFields);
226
- if (singleUrl) {
227
- urls.push(singleUrl);
228
- }
229
- }
230
-
231
- return urls;
232
- }
233
-
234
- /**
235
- * Extract video URL from AI generation result
236
- */
237
- export function extractVideoUrl(result: unknown): string | undefined {
238
- return extractOutputUrl(result, [
239
- "video_url",
240
- "videoUrl",
241
- "video",
242
- "url",
243
- ]);
244
- }
245
-
246
- /**
247
- * Extract thumbnail URL from AI generation result
248
- */
249
- export function extractThumbnailUrl(result: unknown): string | undefined {
250
- if (!result || typeof result !== "object") {
251
- return undefined;
252
- }
253
-
254
- const resultObj = result as Record<string, unknown>;
255
-
256
- // Check direct fields
257
- const fields = ["thumbnail_url", "thumbnailUrl", "thumbnail", "poster"];
258
- for (const field of fields) {
259
- const value = resultObj[field];
260
- if (typeof value === "string" && value.length > 0) {
261
- return value;
262
- }
263
- if (value && typeof value === "object") {
264
- const nested = value as Record<string, unknown>;
265
- if (typeof nested.url === "string") {
266
- return nested.url;
267
- }
268
- }
269
- }
270
-
271
- return undefined;
272
- }
273
-
274
- /**
275
- * Extract audio URL from AI generation result
276
- */
277
- export function extractAudioUrl(result: unknown): string | undefined {
278
- return extractOutputUrl(result, [
279
- "audio_url",
280
- "audioUrl",
281
- "audio",
282
- "url",
283
- ]);
284
- }
285
-
286
- /**
287
- * Extract image URLs from AI generation result
288
- */
289
- export function extractImageUrls(result: unknown): string[] {
290
- if (!result || typeof result !== "object") {
291
- return [];
292
- }
293
-
294
- const urls: string[] = [];
295
- const resultObj = result as Record<string, unknown>;
296
-
297
- // Check images array
298
- if (Array.isArray(resultObj.images)) {
299
- for (const img of resultObj.images) {
300
- if (typeof img === "string" && img.length > 0) {
301
- urls.push(img);
302
- } else if (img && typeof img === "object") {
303
- const imgObj = img as Record<string, unknown>;
304
- if (typeof imgObj.url === "string") {
305
- urls.push(imgObj.url);
306
- }
307
- }
308
- }
309
- }
310
-
311
- // Check single image
312
- if (urls.length === 0) {
313
- const singleUrl = extractOutputUrl(result, [
314
- "image_url",
315
- "imageUrl",
316
- "image",
317
- "url",
318
- ]);
319
- if (singleUrl) {
320
- urls.push(singleUrl);
321
- }
322
- }
323
-
324
- return urls;
325
- }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * URL Extractor Utility
3
+ * Extracts output URLs from AI generation results
4
+ * Supports various provider response formats
5
+ */
6
+
7
+ /**
8
+ * Extract output URL from result
9
+ * Supports various AI provider response formats
10
+ */
11
+ export function extractOutputUrl(
12
+ result: unknown,
13
+ urlFields?: string[],
14
+ ): string | undefined {
15
+ if (!result || typeof result !== "object") {
16
+ return undefined;
17
+ }
18
+
19
+ const fields = urlFields ?? [
20
+ "url",
21
+ "image_url",
22
+ "video_url",
23
+ "output_url",
24
+ "result_url",
25
+ ];
26
+
27
+ const resultObj = result as Record<string, unknown>;
28
+
29
+ // Check top-level fields
30
+ for (const field of fields) {
31
+ const value = resultObj[field];
32
+ if (typeof value === "string" && value.length > 0) {
33
+ return value;
34
+ }
35
+ }
36
+
37
+ // Check nested data/output objects
38
+ const nested =
39
+ (resultObj.data as Record<string, unknown>) ||
40
+ (resultObj.output as Record<string, unknown>) ||
41
+ (resultObj.result as Record<string, unknown>);
42
+
43
+ if (nested && typeof nested === "object") {
44
+ for (const field of fields) {
45
+ const value = nested[field];
46
+ if (typeof value === "string" && value.length > 0) {
47
+ return value;
48
+ }
49
+ }
50
+
51
+ // Check for nested image/video objects
52
+ const media =
53
+ (nested.image as Record<string, unknown>) ||
54
+ (nested.video as Record<string, unknown>);
55
+ if (media && typeof media === "object" && typeof media.url === "string") {
56
+ return media.url;
57
+ }
58
+ }
59
+
60
+ return undefined;
61
+ }
62
+
63
+ /**
64
+ * Extract multiple output URLs from result
65
+ */
66
+ export function extractOutputUrls(
67
+ result: unknown,
68
+ urlFields?: string[],
69
+ ): string[] {
70
+ if (!result || typeof result !== "object") {
71
+ return [];
72
+ }
73
+
74
+ const urls: string[] = [];
75
+ const resultObj = result as Record<string, unknown>;
76
+
77
+ // Check for arrays
78
+ const arrayFields = ["images", "videos", "outputs", "results", "urls"];
79
+ for (const field of arrayFields) {
80
+ const arr = resultObj[field];
81
+ if (Array.isArray(arr)) {
82
+ for (const item of arr) {
83
+ const url = extractOutputUrl(item, urlFields);
84
+ if (url) {
85
+ urls.push(url);
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ // Check nested data/output
92
+ const nested = resultObj.data || resultObj.output;
93
+ if (nested && typeof nested === "object") {
94
+ for (const field of arrayFields) {
95
+ const arr = (nested as Record<string, unknown>)[field];
96
+ if (Array.isArray(arr)) {
97
+ for (const item of arr) {
98
+ const url = extractOutputUrl(item, urlFields);
99
+ if (url) {
100
+ urls.push(url);
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ // If no array found, try single URL
108
+ if (urls.length === 0) {
109
+ const singleUrl = extractOutputUrl(result, urlFields);
110
+ if (singleUrl) {
111
+ urls.push(singleUrl);
112
+ }
113
+ }
114
+
115
+ return urls;
116
+ }
117
+
118
+ /**
119
+ * Extract video URL from AI generation result
120
+ */
121
+ export function extractVideoUrl(result: unknown): string | undefined {
122
+ return extractOutputUrl(result, [
123
+ "video_url",
124
+ "videoUrl",
125
+ "video",
126
+ "url",
127
+ ]);
128
+ }
129
+
130
+ /**
131
+ * Extract thumbnail URL from AI generation result
132
+ */
133
+ export function extractThumbnailUrl(result: unknown): string | undefined {
134
+ if (!result || typeof result !== "object") {
135
+ return undefined;
136
+ }
137
+
138
+ const resultObj = result as Record<string, unknown>;
139
+
140
+ // Check direct fields
141
+ const fields = ["thumbnail_url", "thumbnailUrl", "thumbnail", "poster"];
142
+ for (const field of fields) {
143
+ const value = resultObj[field];
144
+ if (typeof value === "string" && value.length > 0) {
145
+ return value;
146
+ }
147
+ if (value && typeof value === "object") {
148
+ const nested = value as Record<string, unknown>;
149
+ if (typeof nested.url === "string") {
150
+ return nested.url;
151
+ }
152
+ }
153
+ }
154
+
155
+ return undefined;
156
+ }
157
+
158
+ /**
159
+ * Extract audio URL from AI generation result
160
+ */
161
+ export function extractAudioUrl(result: unknown): string | undefined {
162
+ return extractOutputUrl(result, [
163
+ "audio_url",
164
+ "audioUrl",
165
+ "audio",
166
+ "url",
167
+ ]);
168
+ }
169
+
170
+ /**
171
+ * Extract image URLs from AI generation result
172
+ */
173
+ export function extractImageUrls(result: unknown): string[] {
174
+ if (!result || typeof result !== "object") {
175
+ return [];
176
+ }
177
+
178
+ const urls: string[] = [];
179
+ const resultObj = result as Record<string, unknown>;
180
+
181
+ // Check images array
182
+ if (Array.isArray(resultObj.images)) {
183
+ for (const img of resultObj.images) {
184
+ if (typeof img === "string" && img.length > 0) {
185
+ urls.push(img);
186
+ } else if (img && typeof img === "object") {
187
+ const imgObj = img as Record<string, unknown>;
188
+ if (typeof imgObj.url === "string") {
189
+ urls.push(imgObj.url);
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ // Check single image
196
+ if (urls.length === 0) {
197
+ const singleUrl = extractOutputUrl(result, [
198
+ "image_url",
199
+ "imageUrl",
200
+ "image",
201
+ "url",
202
+ ]);
203
+ if (singleUrl) {
204
+ urls.push(singleUrl);
205
+ }
206
+ }
207
+
208
+ return urls;
209
+ }