@umituz/react-native-ai-generation-content 1.17.221 → 1.17.223
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/features/image-to-video/presentation/hooks/useGenerationExecution.ts +7 -6
- package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +5 -1
- package/src/features/image-to-video/presentation/hooks/useImageToVideoValidation.ts +1 -1
- package/src/infrastructure/services/job-poller-factory.ts +16 -0
- package/src/infrastructure/services/job-poller.service.ts +11 -69
- package/src/infrastructure/services/job-poller.types.ts +24 -0
- package/src/infrastructure/services/video-feature-executor.service.ts +36 -99
- package/src/infrastructure/services/video-feature-executor.types.ts +36 -0
- package/src/infrastructure/utils/progress-calculator.util.ts +26 -0
- package/src/infrastructure/utils/video-result-extractor.util.ts +59 -0
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
ImageToVideoFeatureCallbacks,
|
|
11
11
|
ImageToVideoResult,
|
|
12
12
|
ImageToVideoGenerateParams,
|
|
13
|
+
ImageToVideoFeatureState,
|
|
13
14
|
} from "../../domain/types";
|
|
14
15
|
|
|
15
16
|
declare const __DEV__: boolean;
|
|
@@ -18,7 +19,7 @@ interface UseGenerationExecutionParams {
|
|
|
18
19
|
userId: string;
|
|
19
20
|
config: ImageToVideoFeatureConfig;
|
|
20
21
|
callbacks?: ImageToVideoFeatureCallbacks;
|
|
21
|
-
setState: React.Dispatch<React.SetStateAction<
|
|
22
|
+
setState: React.Dispatch<React.SetStateAction<ImageToVideoFeatureState>>;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function useGenerationExecution({
|
|
@@ -35,7 +36,7 @@ export function useGenerationExecution({
|
|
|
35
36
|
): Promise<ImageToVideoResult> => {
|
|
36
37
|
const creationId = `image-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
37
38
|
|
|
38
|
-
setState((prev
|
|
39
|
+
setState((prev) => ({
|
|
39
40
|
...prev,
|
|
40
41
|
imageUri,
|
|
41
42
|
isProcessing: true,
|
|
@@ -82,14 +83,14 @@ export function useGenerationExecution({
|
|
|
82
83
|
buildInput: config.buildInput,
|
|
83
84
|
extractResult: config.extractResult,
|
|
84
85
|
onProgress: (progress) => {
|
|
85
|
-
setState((prev
|
|
86
|
+
setState((prev) => ({ ...prev, progress }));
|
|
86
87
|
callbacks?.onProgress?.(progress);
|
|
87
88
|
},
|
|
88
89
|
},
|
|
89
90
|
);
|
|
90
91
|
|
|
91
92
|
if (result.success && result.videoUrl) {
|
|
92
|
-
setState((prev
|
|
93
|
+
setState((prev) => ({
|
|
93
94
|
...prev,
|
|
94
95
|
videoUrl: result.videoUrl ?? null,
|
|
95
96
|
thumbnailUrl: result.thumbnailUrl ?? null,
|
|
@@ -115,7 +116,7 @@ export function useGenerationExecution({
|
|
|
115
116
|
callbacks?.onGenerate?.(result);
|
|
116
117
|
} else {
|
|
117
118
|
const error = result.error || "Generation failed";
|
|
118
|
-
setState((prev
|
|
119
|
+
setState((prev) => ({ ...prev, isProcessing: false, error }));
|
|
119
120
|
config.onError?.(error);
|
|
120
121
|
callbacks?.onError?.(error);
|
|
121
122
|
}
|
|
@@ -127,7 +128,7 @@ export function useGenerationExecution({
|
|
|
127
128
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
128
129
|
console.error("[ImageToVideoFeature] Generation error:", errorMessage);
|
|
129
130
|
}
|
|
130
|
-
setState((prev
|
|
131
|
+
setState((prev) => ({
|
|
131
132
|
...prev,
|
|
132
133
|
isProcessing: false,
|
|
133
134
|
error: errorMessage,
|
|
@@ -68,7 +68,11 @@ export function useImageToVideoFeature(
|
|
|
68
68
|
return validation;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
return executeGeneration(
|
|
71
|
+
return executeGeneration(
|
|
72
|
+
effectiveImageUri!,
|
|
73
|
+
effectiveMotionPrompt,
|
|
74
|
+
options
|
|
75
|
+
);
|
|
72
76
|
},
|
|
73
77
|
[state.imageUri, state.motionPrompt, callbacks, config.creditCost, executeGeneration],
|
|
74
78
|
);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Image-to-Video Validation Utilities
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { ImageToVideoFeatureCallbacks,
|
|
5
|
+
import type { ImageToVideoFeatureCallbacks, ImageToVideoResult } from "../../domain/types";
|
|
6
6
|
|
|
7
7
|
declare const __DEV__: boolean;
|
|
8
8
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Job Poller Factory
|
|
3
|
+
* Creates pre-configured job poller instances
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PollingConfig } from "../../domain/entities";
|
|
7
|
+
import type { PollJobOptions } from "./job-poller.types";
|
|
8
|
+
import { pollJob } from "./job-poller.service";
|
|
9
|
+
|
|
10
|
+
export function createJobPoller(defaultConfig?: Partial<PollingConfig>) {
|
|
11
|
+
return {
|
|
12
|
+
poll<T = unknown>(options: Omit<PollJobOptions, "config">) {
|
|
13
|
+
return pollJob<T>({ ...options, config: defaultConfig });
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -3,33 +3,21 @@
|
|
|
3
3
|
* Provider-agnostic job polling with exponential backoff
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { IAIProvider
|
|
7
|
-
import type { PollingConfig } from "../../domain/entities";
|
|
6
|
+
import type { IAIProvider } from "../../domain/interfaces"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
7
|
+
import type { PollingConfig } from "../../domain/entities"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
8
8
|
import { DEFAULT_POLLING_CONFIG } from "../../domain/entities";
|
|
9
9
|
import { calculatePollingInterval } from "../utils/polling-interval.util";
|
|
10
10
|
import { checkStatusForErrors, isJobComplete } from "../utils/status-checker.util";
|
|
11
11
|
import { validateResult } from "../utils/result-validator.util";
|
|
12
12
|
import { isTransientError } from "../utils/error-classifier.util";
|
|
13
|
+
import { calculateProgressFromJobStatus } from "../utils/progress-calculator.util";
|
|
14
|
+
import type { PollJobOptions, PollJobResult } from "./job-poller.types";
|
|
15
|
+
import { createJobPoller } from "./job-poller-factory"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export interface PollJobOptions {
|
|
17
|
-
provider: IAIProvider;
|
|
18
|
-
model: string;
|
|
19
|
-
requestId: string;
|
|
20
|
-
config?: Partial<PollingConfig>;
|
|
21
|
-
onProgress?: (progress: number) => void;
|
|
22
|
-
onStatusChange?: (status: JobStatus) => void;
|
|
23
|
-
signal?: AbortSignal;
|
|
24
|
-
}
|
|
17
|
+
// IAIProvider and PollingConfig are used indirectly through provider methods
|
|
18
|
+
// createJobPoller is re-exported
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
success: boolean;
|
|
28
|
-
data?: T;
|
|
29
|
-
error?: Error;
|
|
30
|
-
attempts: number;
|
|
31
|
-
elapsedMs: number;
|
|
32
|
-
}
|
|
20
|
+
declare const __DEV__: boolean;
|
|
33
21
|
|
|
34
22
|
const MAX_CONSECUTIVE_TRANSIENT_ERRORS = 5;
|
|
35
23
|
|
|
@@ -57,7 +45,6 @@ export async function pollJob<T = unknown>(
|
|
|
57
45
|
let lastProgress = 0;
|
|
58
46
|
|
|
59
47
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
60
|
-
// Check for abort
|
|
61
48
|
if (signal?.aborted) {
|
|
62
49
|
return {
|
|
63
50
|
success: false,
|
|
@@ -67,18 +54,15 @@ export async function pollJob<T = unknown>(
|
|
|
67
54
|
};
|
|
68
55
|
}
|
|
69
56
|
|
|
70
|
-
// Wait for polling interval (skip first attempt)
|
|
71
57
|
if (attempt > 0) {
|
|
72
58
|
const interval = calculatePollingInterval({ attempt, config: pollingConfig });
|
|
73
59
|
await new Promise<void>((resolve) => setTimeout(() => resolve(), interval));
|
|
74
60
|
}
|
|
75
61
|
|
|
76
62
|
try {
|
|
77
|
-
// Get job status
|
|
78
63
|
const status = await provider.getJobStatus(model, requestId);
|
|
79
64
|
onStatusChange?.(status);
|
|
80
65
|
|
|
81
|
-
// Check for errors in status
|
|
82
66
|
const statusCheck = checkStatusForErrors(status);
|
|
83
67
|
|
|
84
68
|
if (statusCheck.shouldStop && statusCheck.hasError) {
|
|
@@ -90,24 +74,19 @@ export async function pollJob<T = unknown>(
|
|
|
90
74
|
};
|
|
91
75
|
}
|
|
92
76
|
|
|
93
|
-
// Reset transient error counter on success
|
|
94
77
|
consecutiveTransientErrors = 0;
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
const progress = calculateProgressFromStatus(status, attempt, maxAttempts);
|
|
79
|
+
const progress = calculateProgressFromJobStatus(status, attempt, maxAttempts);
|
|
98
80
|
if (progress > lastProgress) {
|
|
99
81
|
lastProgress = progress;
|
|
100
82
|
onProgress?.(progress);
|
|
101
83
|
}
|
|
102
84
|
|
|
103
|
-
// Check if complete
|
|
104
85
|
if (isJobComplete(status)) {
|
|
105
86
|
onProgress?.(90);
|
|
106
87
|
|
|
107
|
-
// Fetch result
|
|
108
88
|
const result = await provider.getJobResult<T>(model, requestId);
|
|
109
89
|
|
|
110
|
-
// Validate result
|
|
111
90
|
const validation = validateResult(result);
|
|
112
91
|
if (!validation.isValid) {
|
|
113
92
|
return {
|
|
@@ -131,7 +110,6 @@ export async function pollJob<T = unknown>(
|
|
|
131
110
|
if (isTransientError(error)) {
|
|
132
111
|
consecutiveTransientErrors++;
|
|
133
112
|
|
|
134
|
-
// Too many consecutive transient errors
|
|
135
113
|
if (consecutiveTransientErrors >= MAX_CONSECUTIVE_TRANSIENT_ERRORS) {
|
|
136
114
|
return {
|
|
137
115
|
success: false,
|
|
@@ -141,10 +119,8 @@ export async function pollJob<T = unknown>(
|
|
|
141
119
|
};
|
|
142
120
|
}
|
|
143
121
|
|
|
144
|
-
// Continue retrying
|
|
145
122
|
if (attempt < maxAttempts - 1) {
|
|
146
123
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
147
|
-
|
|
148
124
|
console.log(
|
|
149
125
|
`[JobPoller] Transient error, retrying (${attempt + 1}/${maxAttempts})`,
|
|
150
126
|
);
|
|
@@ -153,7 +129,6 @@ export async function pollJob<T = unknown>(
|
|
|
153
129
|
}
|
|
154
130
|
}
|
|
155
131
|
|
|
156
|
-
// Permanent error or max retries reached
|
|
157
132
|
return {
|
|
158
133
|
success: false,
|
|
159
134
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
@@ -163,7 +138,6 @@ export async function pollJob<T = unknown>(
|
|
|
163
138
|
}
|
|
164
139
|
}
|
|
165
140
|
|
|
166
|
-
// Max attempts reached
|
|
167
141
|
return {
|
|
168
142
|
success: false,
|
|
169
143
|
error: new Error(`Polling timeout after ${maxAttempts} attempts`),
|
|
@@ -172,37 +146,5 @@ export async function pollJob<T = unknown>(
|
|
|
172
146
|
};
|
|
173
147
|
}
|
|
174
148
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
*/
|
|
178
|
-
function calculateProgressFromStatus(
|
|
179
|
-
status: JobStatus,
|
|
180
|
-
attempt: number,
|
|
181
|
-
maxAttempts: number,
|
|
182
|
-
): number {
|
|
183
|
-
const statusString = String(status.status).toUpperCase();
|
|
184
|
-
|
|
185
|
-
switch (statusString) {
|
|
186
|
-
case "IN_QUEUE":
|
|
187
|
-
return 30 + Math.min(attempt * 2, 10);
|
|
188
|
-
case "IN_PROGRESS":
|
|
189
|
-
return 50 + Math.min(attempt * 3, 30);
|
|
190
|
-
case "COMPLETED":
|
|
191
|
-
return 90;
|
|
192
|
-
case "FAILED":
|
|
193
|
-
return 0;
|
|
194
|
-
default:
|
|
195
|
-
return 20 + Math.min((attempt / maxAttempts) * 30, 30);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Create a job poller with preset configuration
|
|
201
|
-
*/
|
|
202
|
-
export function createJobPoller(defaultConfig?: Partial<PollingConfig>) {
|
|
203
|
-
return {
|
|
204
|
-
poll<T = unknown>(options: Omit<PollJobOptions, "config">) {
|
|
205
|
-
return pollJob<T>({ ...options, config: defaultConfig });
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
}
|
|
149
|
+
export { createJobPoller } from "./job-poller-factory";
|
|
150
|
+
export type { PollJobOptions, PollJobResult } from "./job-poller.types";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Job Poller Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { IAIProvider, JobStatus } from "../../domain/interfaces";
|
|
6
|
+
import type { PollingConfig } from "../../domain/entities";
|
|
7
|
+
|
|
8
|
+
export interface PollJobOptions {
|
|
9
|
+
provider: IAIProvider;
|
|
10
|
+
model: string;
|
|
11
|
+
requestId: string;
|
|
12
|
+
config?: Partial<PollingConfig>;
|
|
13
|
+
onProgress?: (progress: number) => void;
|
|
14
|
+
onStatusChange?: (status: JobStatus) => void;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PollJobResult<T = unknown> {
|
|
19
|
+
success: boolean;
|
|
20
|
+
data?: T;
|
|
21
|
+
error?: Error;
|
|
22
|
+
attempts: number;
|
|
23
|
+
elapsedMs: number;
|
|
24
|
+
}
|
|
@@ -6,94 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
import { providerRegistry } from "./provider-registry.service";
|
|
8
8
|
import { cleanBase64 } from "../utils";
|
|
9
|
+
import { defaultExtractVideoResult } from "../utils/video-result-extractor.util";
|
|
9
10
|
import type { VideoFeatureType, VideoFeatureInputData } from "../../domain/interfaces";
|
|
11
|
+
import type {
|
|
12
|
+
ExecuteVideoFeatureOptions,
|
|
13
|
+
VideoFeatureResult,
|
|
14
|
+
VideoFeatureRequest,
|
|
15
|
+
} from "./video-feature-executor.types";
|
|
10
16
|
|
|
11
17
|
declare const __DEV__: boolean;
|
|
12
18
|
|
|
13
|
-
/**
|
|
14
|
-
* Result extractor function type
|
|
15
|
-
*/
|
|
16
|
-
export type VideoResultExtractor = (result: unknown) => string | undefined;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Execution options
|
|
20
|
-
*/
|
|
21
|
-
export interface ExecuteVideoFeatureOptions {
|
|
22
|
-
extractResult?: VideoResultExtractor;
|
|
23
|
-
onProgress?: (progress: number) => void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Execution result
|
|
28
|
-
*/
|
|
29
|
-
export interface VideoFeatureResult {
|
|
30
|
-
success: boolean;
|
|
31
|
-
videoUrl?: string;
|
|
32
|
-
error?: string;
|
|
33
|
-
requestId?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Request data for video features
|
|
38
|
-
*/
|
|
39
|
-
export interface VideoFeatureRequest {
|
|
40
|
-
sourceImageBase64: string;
|
|
41
|
-
targetImageBase64: string;
|
|
42
|
-
prompt?: string;
|
|
43
|
-
options?: Record<string, unknown>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Default result extractor - handles common response formats
|
|
48
|
-
* Supports FAL data wrapper and nested object formats
|
|
49
|
-
*/
|
|
50
|
-
function defaultExtractVideoResult(result: unknown): string | undefined {
|
|
51
|
-
if (typeof result !== "object" || result === null) return undefined;
|
|
52
|
-
|
|
53
|
-
const r = result as Record<string, unknown>;
|
|
54
|
-
|
|
55
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
56
|
-
console.log("[VideoExtractor] Result keys:", Object.keys(r));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Handle fal.ai data wrapper
|
|
60
|
-
const data = (r.data as Record<string, unknown>) ?? r;
|
|
61
|
-
|
|
62
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
63
|
-
console.log("[VideoExtractor] Data keys:", Object.keys(data));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Direct string values
|
|
67
|
-
if (typeof data.video === "string") return data.video;
|
|
68
|
-
if (typeof data.videoUrl === "string") return data.videoUrl;
|
|
69
|
-
if (typeof data.video_url === "string") return data.video_url;
|
|
70
|
-
if (typeof data.output === "string") return data.output;
|
|
71
|
-
if (typeof data.url === "string") return data.url;
|
|
72
|
-
|
|
73
|
-
// Object with url property (e.g., { video: { url: "..." } })
|
|
74
|
-
const videoObj = data.video as Record<string, unknown> | undefined;
|
|
75
|
-
if (videoObj && typeof videoObj === "object" && typeof videoObj.url === "string") {
|
|
76
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
77
|
-
console.log("[VideoExtractor] Found data.video.url:", videoObj.url);
|
|
78
|
-
}
|
|
79
|
-
return videoObj.url;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Array format (e.g., { videos: [{ url: "..." }] })
|
|
83
|
-
if (Array.isArray(data.videos) && typeof data.videos[0]?.url === "string") {
|
|
84
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
85
|
-
console.log("[VideoExtractor] Found videos[0].url:", data.videos[0].url);
|
|
86
|
-
}
|
|
87
|
-
return data.videos[0].url;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
91
|
-
console.log("[VideoExtractor] No video URL found in result");
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
19
|
/**
|
|
98
20
|
* Execute any video feature using the active provider
|
|
99
21
|
* Uses subscribe for video features to handle long-running generation with progress updates
|
|
@@ -173,24 +95,33 @@ export async function executeVideoFeature(
|
|
|
173
95
|
requestId: (result as { requestId?: string })?.requestId,
|
|
174
96
|
};
|
|
175
97
|
} catch (error) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
} else if (typeof error === "object" && error !== null) {
|
|
181
|
-
const errObj = error as Record<string, unknown>;
|
|
182
|
-
if (errObj.detail) {
|
|
183
|
-
message = JSON.stringify(errObj.detail);
|
|
184
|
-
} else if (errObj.message) {
|
|
185
|
-
message = String(errObj.message);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
98
|
+
const message = extractErrorMessage(error, featureType);
|
|
99
|
+
return { success: false, error: message };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
188
102
|
|
|
189
|
-
|
|
190
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Extract error message from various error formats
|
|
105
|
+
*/
|
|
106
|
+
function extractErrorMessage(error: unknown, featureType: VideoFeatureType): string {
|
|
107
|
+
let message = "Processing failed";
|
|
108
|
+
|
|
109
|
+
if (error instanceof Error) {
|
|
110
|
+
message = error.message;
|
|
111
|
+
} else if (typeof error === "object" && error !== null) {
|
|
112
|
+
const errObj = error as Record<string, unknown>;
|
|
113
|
+
if (errObj.detail) {
|
|
114
|
+
message = JSON.stringify(errObj.detail);
|
|
115
|
+
} else if (errObj.message) {
|
|
116
|
+
message = String(errObj.message);
|
|
191
117
|
}
|
|
192
|
-
return { success: false, error: message };
|
|
193
118
|
}
|
|
119
|
+
|
|
120
|
+
if (__DEV__) {
|
|
121
|
+
console.error(`[Video:${featureType}] Error:`, message, error);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return message;
|
|
194
125
|
}
|
|
195
126
|
|
|
196
127
|
/**
|
|
@@ -200,3 +131,9 @@ export function hasVideoFeatureSupport(): boolean {
|
|
|
200
131
|
const provider = providerRegistry.getActiveProvider();
|
|
201
132
|
return provider !== null && provider.isInitialized();
|
|
202
133
|
}
|
|
134
|
+
|
|
135
|
+
export type {
|
|
136
|
+
ExecuteVideoFeatureOptions,
|
|
137
|
+
VideoFeatureResult,
|
|
138
|
+
VideoFeatureRequest,
|
|
139
|
+
} from "./video-feature-executor.types";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video Feature Executor Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Result extractor function type
|
|
7
|
+
*/
|
|
8
|
+
export type VideoResultExtractor = (result: unknown) => string | undefined;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Execution options
|
|
12
|
+
*/
|
|
13
|
+
export interface ExecuteVideoFeatureOptions {
|
|
14
|
+
extractResult?: VideoResultExtractor;
|
|
15
|
+
onProgress?: (progress: number) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execution result
|
|
20
|
+
*/
|
|
21
|
+
export interface VideoFeatureResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
videoUrl?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
requestId?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Request data for video features
|
|
30
|
+
*/
|
|
31
|
+
export interface VideoFeatureRequest {
|
|
32
|
+
sourceImageBase64: string;
|
|
33
|
+
targetImageBase64: string;
|
|
34
|
+
prompt?: string;
|
|
35
|
+
options?: Record<string, unknown>;
|
|
36
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ProgressStageConfig,
|
|
11
11
|
} from "../../domain/entities";
|
|
12
12
|
import type { AIJobStatusType } from "../../domain/interfaces/ai-provider.interface";
|
|
13
|
+
import type { JobStatus } from "../../domain/interfaces";
|
|
13
14
|
|
|
14
15
|
export interface ProgressOptions {
|
|
15
16
|
status: GenerationStatus;
|
|
@@ -126,3 +127,28 @@ export function getProgressFromJobStatus(
|
|
|
126
127
|
const generationStatus = mapJobStatusToGenerationStatus(jobStatus);
|
|
127
128
|
return getProgressForStatus({ status: generationStatus, stages });
|
|
128
129
|
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Calculate progress percentage from job status and attempt number
|
|
133
|
+
* Used by job poller for granular progress reporting during polling
|
|
134
|
+
*/
|
|
135
|
+
export function calculateProgressFromJobStatus(
|
|
136
|
+
status: JobStatus,
|
|
137
|
+
attempt: number,
|
|
138
|
+
maxAttempts: number,
|
|
139
|
+
): number {
|
|
140
|
+
const statusString = String(status.status).toUpperCase();
|
|
141
|
+
|
|
142
|
+
switch (statusString) {
|
|
143
|
+
case "IN_QUEUE":
|
|
144
|
+
return 30 + Math.min(attempt * 2, 10);
|
|
145
|
+
case "IN_PROGRESS":
|
|
146
|
+
return 50 + Math.min(attempt * 3, 30);
|
|
147
|
+
case "COMPLETED":
|
|
148
|
+
return 90;
|
|
149
|
+
case "FAILED":
|
|
150
|
+
return 0;
|
|
151
|
+
default:
|
|
152
|
+
return 20 + Math.min((attempt / maxAttempts) * 30, 30);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video Result Extractor Utility
|
|
3
|
+
* Extracts video URL from various provider response formats
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { VideoResultExtractor } from "../services/video-feature-executor.types";
|
|
7
|
+
|
|
8
|
+
declare const __DEV__: boolean;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default result extractor - handles common response formats
|
|
12
|
+
* Supports FAL data wrapper and nested object formats
|
|
13
|
+
*/
|
|
14
|
+
export const defaultExtractVideoResult: VideoResultExtractor = (result) => {
|
|
15
|
+
if (typeof result !== "object" || result === null) return undefined;
|
|
16
|
+
|
|
17
|
+
const r = result as Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
20
|
+
console.log("[VideoExtractor] Result keys:", Object.keys(r));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Handle fal.ai data wrapper
|
|
24
|
+
const data = (r.data as Record<string, unknown>) ?? r;
|
|
25
|
+
|
|
26
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
27
|
+
console.log("[VideoExtractor] Data keys:", Object.keys(data));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Direct string values
|
|
31
|
+
if (typeof data.video === "string") return data.video;
|
|
32
|
+
if (typeof data.videoUrl === "string") return data.videoUrl;
|
|
33
|
+
if (typeof data.video_url === "string") return data.video_url;
|
|
34
|
+
if (typeof data.output === "string") return data.output;
|
|
35
|
+
if (typeof data.url === "string") return data.url;
|
|
36
|
+
|
|
37
|
+
// Object with url property (e.g., { video: { url: "..." } })
|
|
38
|
+
const videoObj = data.video as Record<string, unknown> | undefined;
|
|
39
|
+
if (videoObj && typeof videoObj === "object" && typeof videoObj.url === "string") {
|
|
40
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
41
|
+
console.log("[VideoExtractor] Found data.video.url:", videoObj.url);
|
|
42
|
+
}
|
|
43
|
+
return videoObj.url;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Array format (e.g., { videos: [{ url: "..." }] })
|
|
47
|
+
if (Array.isArray(data.videos) && typeof data.videos[0]?.url === "string") {
|
|
48
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
49
|
+
console.log("[VideoExtractor] Found videos[0].url:", data.videos[0].url);
|
|
50
|
+
}
|
|
51
|
+
return data.videos[0].url;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
55
|
+
console.log("[VideoExtractor] No video URL found in result");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return undefined;
|
|
59
|
+
};
|