@umituz/react-native-ai-generation-content 1.61.56 → 1.61.58
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/domains/content-moderation/infrastructure/constants/moderation.constants.ts +4 -2
- package/src/domains/content-moderation/infrastructure/services/moderators/voice.moderator.ts +2 -3
- package/src/domains/content-moderation/infrastructure/services/pattern-matcher.service.ts +3 -2
- package/src/domains/generation/infrastructure/executors/image-executor.ts +2 -1
- package/src/domains/generation/infrastructure/executors/video-executor.ts +2 -1
- package/src/domains/generation/wizard/infrastructure/strategies/wizard-strategy.constants.ts +3 -2
- package/src/domains/generation/wizard/presentation/hooks/useVideoQueueGeneration.ts +9 -0
- package/src/domains/generation/wizard/presentation/hooks/useWizardFlowHandlers.ts +28 -1
- package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +21 -2
- package/src/features/image-to-video/infrastructure/services/image-to-video-executor.ts +2 -1
- package/src/infrastructure/config/env.config.ts +67 -0
- package/src/infrastructure/config/index.ts +2 -0
- package/src/infrastructure/constants/content.constants.ts +3 -1
- package/src/infrastructure/constants/polling.constants.ts +11 -9
- package/src/infrastructure/constants/storage.constants.ts +4 -2
- package/src/infrastructure/constants/validation.constants.ts +14 -12
- package/src/infrastructure/http/api-client.types.ts +17 -0
- package/src/infrastructure/http/http-client.util.ts +167 -0
- package/src/infrastructure/http/index.ts +16 -0
- package/src/infrastructure/http/query-string.util.ts +40 -0
- package/src/infrastructure/http/timeout.util.ts +21 -0
- package/src/infrastructure/services/multi-image-generation.executor.ts +3 -2
- package/src/infrastructure/utils/api-client.util.ts +15 -204
- package/src/infrastructure/utils/index.ts +1 -0
- package/src/infrastructure/utils/progress.utils.ts +60 -0
- package/src/presentation/components/GenerationProgressBar.tsx +3 -2
- package/src/presentation/components/PendingJobProgressBar.tsx +3 -1
- package/src/presentation/hooks/generation/orchestrator.ts +10 -3
- package/src/presentation/hooks/index.ts +3 -0
- package/src/presentation/hooks/useProgressDismiss.ts +42 -0
- package/src/presentation/layouts/DualImageFeatureLayout.tsx +5 -12
- package/src/presentation/layouts/DualImageVideoFeatureLayout.tsx +5 -12
- package/src/presentation/layouts/SingleImageFeatureLayout.tsx +5 -13
- package/src/presentation/layouts/SingleImageWithPromptFeatureLayout.tsx +5 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.61.
|
|
3
|
+
"version": "1.61.58",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* Shared constants for content moderation validators
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { env } from "../../../../infrastructure/config/env.config";
|
|
7
|
+
|
|
6
8
|
export const DEFAULT_PROTOCOLS = ["http:", "https:", "file:", "data:"] as const;
|
|
7
9
|
export const VIDEO_PROTOCOLS = ["http:", "https:", "file:"] as const;
|
|
8
|
-
export const DEFAULT_MAX_URI_LENGTH =
|
|
9
|
-
export const DEFAULT_MAX_TEXT_LENGTH =
|
|
10
|
+
export const DEFAULT_MAX_URI_LENGTH = env.moderationMaxUriLength;
|
|
11
|
+
export const DEFAULT_MAX_TEXT_LENGTH = env.moderationMaxTextLength;
|
package/src/domains/content-moderation/infrastructure/services/moderators/voice.moderator.ts
CHANGED
|
@@ -7,11 +7,10 @@ import type { Violation } from "../../../domain/entities/moderation.types";
|
|
|
7
7
|
import { patternMatcherService } from "../pattern-matcher.service";
|
|
8
8
|
import { rulesRegistry } from "../../rules/rules-registry";
|
|
9
9
|
import { BaseModerator, type ModerationResult } from "./base.moderator";
|
|
10
|
-
|
|
11
|
-
const DEFAULT_MAX_LENGTH = 5000;
|
|
10
|
+
import { env } from "../../../../../infrastructure/config/env.config";
|
|
12
11
|
|
|
13
12
|
class VoiceModerator extends BaseModerator {
|
|
14
|
-
private maxLength =
|
|
13
|
+
private maxLength = env.moderationVoiceMaxLength;
|
|
15
14
|
|
|
16
15
|
setMaxLength(length: number): void {
|
|
17
16
|
this.maxLength = length;
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Utility service for regex pattern matching with security validations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { env } from "../../../../infrastructure/config/env.config";
|
|
7
|
+
|
|
6
8
|
export interface PatternMatch {
|
|
7
9
|
pattern: string;
|
|
8
10
|
matched: boolean;
|
|
@@ -34,8 +36,7 @@ function isValidRegexPattern(pattern: string): boolean {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
// Limit pattern length to prevent potential attacks
|
|
37
|
-
|
|
38
|
-
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
39
|
+
if (pattern.length > env.validationMaxPatternLength) {
|
|
39
40
|
return false;
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
} from "../../domain/generation.types";
|
|
12
12
|
import type { GenerationResult } from "../../../../domain/entities/generation.types";
|
|
13
13
|
import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
|
|
14
|
+
import { env } from "../../../../infrastructure/config/env.config";
|
|
14
15
|
|
|
15
16
|
declare const __DEV__: boolean;
|
|
16
17
|
|
|
@@ -56,7 +57,7 @@ export class ImageExecutor
|
|
|
56
57
|
options?.onProgress?.(10);
|
|
57
58
|
|
|
58
59
|
const result = await provider.subscribe(model, modelInput, {
|
|
59
|
-
timeoutMs: options?.timeoutMs ??
|
|
60
|
+
timeoutMs: options?.timeoutMs ?? env.generationImageTimeoutMs,
|
|
60
61
|
onQueueUpdate: (status) => {
|
|
61
62
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
62
63
|
console.log("[ImageExecutor] Queue status:", status.status);
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
} from "../../domain/generation.types";
|
|
12
12
|
import type { GenerationResult } from "../../../../domain/entities/generation.types";
|
|
13
13
|
import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
|
|
14
|
+
import { env } from "../../../../infrastructure/config/env.config";
|
|
14
15
|
|
|
15
16
|
declare const __DEV__: boolean;
|
|
16
17
|
|
|
@@ -48,7 +49,7 @@ export class VideoExecutor
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const result = await provider.subscribe(model, modelInput, {
|
|
51
|
-
timeoutMs: options?.timeoutMs ??
|
|
52
|
+
timeoutMs: options?.timeoutMs ?? env.generationVideoTimeoutMs,
|
|
52
53
|
onQueueUpdate: (status) => {
|
|
53
54
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
54
55
|
console.log("[VideoExecutor] Queue status:", status.status);
|
package/src/domains/generation/wizard/infrastructure/strategies/wizard-strategy.constants.ts
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { VideoFeatureType } from "../../../../../domain/interfaces";
|
|
7
|
+
import { env } from "../../../../../infrastructure/config/env.config";
|
|
7
8
|
|
|
8
|
-
/** Generation timeout in milliseconds
|
|
9
|
-
export const GENERATION_TIMEOUT_MS =
|
|
9
|
+
/** Generation timeout in milliseconds */
|
|
10
|
+
export const GENERATION_TIMEOUT_MS = env.generationImageTimeoutMs;
|
|
10
11
|
|
|
11
12
|
/** Base64 image format prefix */
|
|
12
13
|
export const BASE64_IMAGE_PREFIX = "data:image/jpeg;base64,";
|
|
@@ -41,6 +41,7 @@ export function useVideoQueueGeneration(
|
|
|
41
41
|
const modelRef = useRef<string | null>(null);
|
|
42
42
|
const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
43
43
|
const isGeneratingRef = useRef(false);
|
|
44
|
+
const isPollingRef = useRef(false);
|
|
44
45
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
45
46
|
|
|
46
47
|
// Cleanup polling on unmount
|
|
@@ -58,6 +59,7 @@ export function useVideoQueueGeneration(
|
|
|
58
59
|
requestIdRef.current = null;
|
|
59
60
|
modelRef.current = null;
|
|
60
61
|
isGeneratingRef.current = false;
|
|
62
|
+
isPollingRef.current = false;
|
|
61
63
|
setIsGenerating(false);
|
|
62
64
|
}, []);
|
|
63
65
|
|
|
@@ -98,11 +100,15 @@ export function useVideoQueueGeneration(
|
|
|
98
100
|
);
|
|
99
101
|
|
|
100
102
|
const pollQueueStatus = useCallback(async () => {
|
|
103
|
+
// Guard against concurrent polls
|
|
104
|
+
if (isPollingRef.current) return;
|
|
105
|
+
|
|
101
106
|
const requestId = requestIdRef.current;
|
|
102
107
|
const model = modelRef.current;
|
|
103
108
|
const provider = providerRegistry.getActiveProvider();
|
|
104
109
|
if (!requestId || !model || !provider) return;
|
|
105
110
|
|
|
111
|
+
isPollingRef.current = true;
|
|
106
112
|
try {
|
|
107
113
|
const status = await provider.getJobStatus(model, requestId);
|
|
108
114
|
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[VideoQueueGeneration] Poll:", status.status);
|
|
@@ -133,6 +139,8 @@ export function useVideoQueueGeneration(
|
|
|
133
139
|
console.error("[VideoQueueGeneration] Poll error:", errorMessage);
|
|
134
140
|
}
|
|
135
141
|
await handleError(errorMessage);
|
|
142
|
+
} finally {
|
|
143
|
+
isPollingRef.current = false;
|
|
136
144
|
}
|
|
137
145
|
}, [handleComplete, handleError]);
|
|
138
146
|
|
|
@@ -179,6 +187,7 @@ export function useVideoQueueGeneration(
|
|
|
179
187
|
}
|
|
180
188
|
|
|
181
189
|
pollingRef.current = setInterval(() => void pollQueueStatus(), DEFAULT_POLL_INTERVAL_MS);
|
|
190
|
+
// Immediate poll to avoid waiting for first interval tick
|
|
182
191
|
void pollQueueStatus();
|
|
183
192
|
},
|
|
184
193
|
[userId, scenario, persistence, strategy, pollQueueStatus, onError],
|
|
@@ -11,6 +11,23 @@ import type { Creation } from "../../../../creations/domain/entities/Creation";
|
|
|
11
11
|
|
|
12
12
|
declare const __DEV__: boolean;
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Type guard to check if result is a valid Creation object
|
|
16
|
+
*/
|
|
17
|
+
function isCreation(result: unknown): result is Creation {
|
|
18
|
+
if (!result || typeof result !== "object") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const creation = result as Partial<Creation>;
|
|
22
|
+
return (
|
|
23
|
+
typeof creation.id === "string" &&
|
|
24
|
+
typeof creation.uri === "string" &&
|
|
25
|
+
typeof creation.type === "string" &&
|
|
26
|
+
creation.createdAt instanceof Date &&
|
|
27
|
+
typeof creation.isShared === "boolean"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
export interface UseWizardFlowHandlersProps {
|
|
15
32
|
readonly currentStepIndex: number;
|
|
16
33
|
readonly flowSteps: StepDefinition[];
|
|
@@ -64,7 +81,17 @@ export function useWizardFlowHandlers(props: UseWizardFlowHandlersProps) {
|
|
|
64
81
|
console.log("[WizardFlowHandlers] Generation completed");
|
|
65
82
|
}
|
|
66
83
|
setResult(result);
|
|
67
|
-
|
|
84
|
+
|
|
85
|
+
// Use type guard to safely check if result is a Creation
|
|
86
|
+
if (isCreation(result)) {
|
|
87
|
+
setCurrentCreation(result);
|
|
88
|
+
} else {
|
|
89
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
90
|
+
console.warn("[WizardFlowHandlers] Result is not a valid Creation object:", result);
|
|
91
|
+
}
|
|
92
|
+
setCurrentCreation(null);
|
|
93
|
+
}
|
|
94
|
+
|
|
68
95
|
onGenerationComplete?.(result);
|
|
69
96
|
if (!skipResultStep) nextStep();
|
|
70
97
|
},
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* States: IDLE → PREPARING → GENERATING → COMPLETED/ERROR → IDLE
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { useEffect, useReducer, useMemo } from "react";
|
|
11
|
+
import { useEffect, useReducer, useMemo, useRef } from "react";
|
|
12
12
|
import { createWizardStrategy, buildWizardInput } from "../../infrastructure/strategies";
|
|
13
13
|
import { createCreationPersistence } from "../../infrastructure/utils/creation-persistence.util";
|
|
14
14
|
import { useVideoQueueGeneration } from "./useVideoQueueGeneration";
|
|
@@ -96,6 +96,16 @@ export const useWizardGeneration = (
|
|
|
96
96
|
// State machine: replaces multiple useRef flags
|
|
97
97
|
const [state, dispatch] = useReducer(generationReducer, { status: "IDLE" });
|
|
98
98
|
|
|
99
|
+
// Mounted ref to prevent state updates after unmount
|
|
100
|
+
const isMountedRef = useRef(true);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
isMountedRef.current = true;
|
|
104
|
+
return () => {
|
|
105
|
+
isMountedRef.current = false;
|
|
106
|
+
};
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
99
109
|
const persistence = useMemo(() => createCreationPersistence(), []);
|
|
100
110
|
const strategy = useMemo(
|
|
101
111
|
() => createWizardStrategy({ scenario, creditCost }),
|
|
@@ -140,6 +150,9 @@ export const useWizardGeneration = (
|
|
|
140
150
|
|
|
141
151
|
buildWizardInput(wizardData, scenario)
|
|
142
152
|
.then(async (input) => {
|
|
153
|
+
// Check if component is still mounted
|
|
154
|
+
if (!isMountedRef.current) return;
|
|
155
|
+
|
|
143
156
|
if (!input) {
|
|
144
157
|
dispatch({ type: "ERROR", error: "Failed to build generation input" });
|
|
145
158
|
onError?.("Failed to build generation input");
|
|
@@ -161,9 +174,15 @@ export const useWizardGeneration = (
|
|
|
161
174
|
await photoGeneration.startGeneration(input, typedInput.prompt || "");
|
|
162
175
|
}
|
|
163
176
|
|
|
164
|
-
|
|
177
|
+
// Check again before final state update
|
|
178
|
+
if (isMountedRef.current) {
|
|
179
|
+
dispatch({ type: "COMPLETE" });
|
|
180
|
+
}
|
|
165
181
|
})
|
|
166
182
|
.catch((error) => {
|
|
183
|
+
// Check if component is still mounted before error handling
|
|
184
|
+
if (!isMountedRef.current) return;
|
|
185
|
+
|
|
167
186
|
const errorMsg = error?.message || "error.generation.unknown";
|
|
168
187
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
169
188
|
console.error("[WizardGeneration] Build input error:", errorMsg, error);
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
ImageToVideoInputBuilder,
|
|
18
18
|
ImageToVideoResultExtractor,
|
|
19
19
|
} from "../../domain/types";
|
|
20
|
+
import { env } from "../../../../infrastructure/config/env.config";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Options for image-to-video execution
|
|
@@ -89,7 +90,7 @@ class ImageToVideoExecutor extends BaseExecutor<
|
|
|
89
90
|
const progress = getProgressFromJobStatus(status.status);
|
|
90
91
|
onProgress?.(progress);
|
|
91
92
|
},
|
|
92
|
-
timeoutMs:
|
|
93
|
+
timeoutMs: env.generationVideoTimeoutMs,
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
this.logInfo(
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Configuration
|
|
3
|
+
* Centralized environment variable access with fallback defaults
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
type EnvValue = string | number | boolean;
|
|
7
|
+
|
|
8
|
+
function getEnvValue(key: string, defaultValue: EnvValue): EnvValue {
|
|
9
|
+
if (typeof process !== "undefined" && process.env) {
|
|
10
|
+
const value = process.env[key];
|
|
11
|
+
if (value !== undefined && value !== "") {
|
|
12
|
+
if (typeof defaultValue === "number") {
|
|
13
|
+
const parsed = Number(value);
|
|
14
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
15
|
+
}
|
|
16
|
+
if (typeof defaultValue === "boolean") {
|
|
17
|
+
return value === "true" || value === "1";
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return defaultValue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const env = {
|
|
26
|
+
// API Configuration
|
|
27
|
+
apiDefaultTimeoutMs: getEnvValue("API_DEFAULT_TIMEOUT_MS", 30000) as number,
|
|
28
|
+
apiRetryDelayMs: getEnvValue("API_RETRY_DELAY_MS", 1000) as number,
|
|
29
|
+
|
|
30
|
+
// Generation Timeouts
|
|
31
|
+
generationImageTimeoutMs: getEnvValue("GENERATION_IMAGE_TIMEOUT_MS", 120000) as number,
|
|
32
|
+
generationVideoTimeoutMs: getEnvValue("GENERATION_VIDEO_TIMEOUT_MS", 300000) as number,
|
|
33
|
+
generationMultiImageTimeoutMs: getEnvValue("GENERATION_MULTI_IMAGE_TIMEOUT_MS", 120000) as number,
|
|
34
|
+
|
|
35
|
+
// Polling Configuration
|
|
36
|
+
pollDefaultIntervalMs: getEnvValue("POLL_DEFAULT_INTERVAL_MS", 3000) as number,
|
|
37
|
+
pollGalleryIntervalMs: getEnvValue("POLL_GALLERY_INTERVAL_MS", 5000) as number,
|
|
38
|
+
pollMaxTimeMs: getEnvValue("POLL_MAX_TIME_MS", 300000) as number,
|
|
39
|
+
pollMaxAttempts: getEnvValue("POLL_MAX_ATTEMPTS", 100) as number,
|
|
40
|
+
pollMaxConsecutiveErrors: getEnvValue("POLL_MAX_CONSECUTIVE_ERRORS", 5) as number,
|
|
41
|
+
pollMinBackoffDelayMs: getEnvValue("POLL_MIN_BACKOFF_DELAY_MS", 1000) as number,
|
|
42
|
+
pollMaxBackoffDelayMs: getEnvValue("POLL_MAX_BACKOFF_DELAY_MS", 30000) as number,
|
|
43
|
+
pollBackoffMultiplier: getEnvValue("POLL_BACKOFF_MULTIPLIER", 1.5) as number,
|
|
44
|
+
|
|
45
|
+
// Validation Limits
|
|
46
|
+
validationMaxPromptLength: getEnvValue("VALIDATION_MAX_PROMPT_LENGTH", 10000) as number,
|
|
47
|
+
validationMinPromptLength: getEnvValue("VALIDATION_MIN_PROMPT_LENGTH", 1) as number,
|
|
48
|
+
validationMaxUserIdLength: getEnvValue("VALIDATION_MAX_USER_ID_LENGTH", 128) as number,
|
|
49
|
+
validationMaxCreationIdLength: getEnvValue("VALIDATION_MAX_CREATION_ID_LENGTH", 128) as number,
|
|
50
|
+
validationMaxUrlLength: getEnvValue("VALIDATION_MAX_URL_LENGTH", 2048) as number,
|
|
51
|
+
validationMaxBase64SizeMb: getEnvValue("VALIDATION_MAX_BASE64_SIZE_MB", 20) as number,
|
|
52
|
+
validationMaxPatternLength: getEnvValue("VALIDATION_MAX_PATTERN_LENGTH", 1000) as number,
|
|
53
|
+
validationMaxImageSizeMb: getEnvValue("VALIDATION_MAX_IMAGE_SIZE_MB", 10) as number,
|
|
54
|
+
validationMaxVideoDurationSeconds: getEnvValue("VALIDATION_MAX_VIDEO_DURATION_SECONDS", 60) as number,
|
|
55
|
+
validationMinVideoDurationSeconds: getEnvValue("VALIDATION_MIN_VIDEO_DURATION_SECONDS", 3) as number,
|
|
56
|
+
validationMaxMultiImageCount: getEnvValue("VALIDATION_MAX_MULTI_IMAGE_COUNT", 10) as number,
|
|
57
|
+
validationMinMultiImageCount: getEnvValue("VALIDATION_MIN_MULTI_IMAGE_COUNT", 2) as number,
|
|
58
|
+
|
|
59
|
+
// Content Moderation
|
|
60
|
+
moderationMaxTextLength: getEnvValue("MODERATION_MAX_TEXT_LENGTH", 5000) as number,
|
|
61
|
+
moderationMaxUriLength: getEnvValue("MODERATION_MAX_URI_LENGTH", 2048) as number,
|
|
62
|
+
moderationVoiceMaxLength: getEnvValue("MODERATION_VOICE_MAX_LENGTH", 5000) as number,
|
|
63
|
+
|
|
64
|
+
// Storage Configuration
|
|
65
|
+
storageDbOperationTimeoutMs: getEnvValue("STORAGE_DB_OPERATION_TIMEOUT_MS", 10000) as number,
|
|
66
|
+
storageMaxCacheSize: getEnvValue("STORAGE_MAX_CACHE_SIZE", 100) as number,
|
|
67
|
+
} as const;
|
|
@@ -3,29 +3,31 @@
|
|
|
3
3
|
* All timeout, interval, and retry related constants
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { env } from "../config/env.config";
|
|
7
|
+
|
|
6
8
|
/** Default polling interval for job status checks (milliseconds) */
|
|
7
|
-
export const DEFAULT_POLL_INTERVAL_MS =
|
|
9
|
+
export const DEFAULT_POLL_INTERVAL_MS = env.pollDefaultIntervalMs;
|
|
8
10
|
|
|
9
11
|
/** Gallery polling interval (slower than wizard) */
|
|
10
|
-
export const GALLERY_POLL_INTERVAL_MS =
|
|
12
|
+
export const GALLERY_POLL_INTERVAL_MS = env.pollGalleryIntervalMs;
|
|
11
13
|
|
|
12
14
|
/** Maximum number of polling attempts before timeout */
|
|
13
|
-
export const DEFAULT_MAX_POLL_ATTEMPTS =
|
|
15
|
+
export const DEFAULT_MAX_POLL_ATTEMPTS = env.pollMaxAttempts;
|
|
14
16
|
|
|
15
17
|
/** Maximum consecutive transient errors before aborting */
|
|
16
|
-
export const DEFAULT_MAX_CONSECUTIVE_ERRORS =
|
|
18
|
+
export const DEFAULT_MAX_CONSECUTIVE_ERRORS = env.pollMaxConsecutiveErrors;
|
|
17
19
|
|
|
18
|
-
/** Maximum total time for polling (milliseconds)
|
|
19
|
-
export const DEFAULT_MAX_POLL_TIME_MS =
|
|
20
|
+
/** Maximum total time for polling (milliseconds) */
|
|
21
|
+
export const DEFAULT_MAX_POLL_TIME_MS = env.pollMaxTimeMs;
|
|
20
22
|
|
|
21
23
|
/** Minimum backoff delay (milliseconds) */
|
|
22
|
-
export const MIN_BACKOFF_DELAY_MS =
|
|
24
|
+
export const MIN_BACKOFF_DELAY_MS = env.pollMinBackoffDelayMs;
|
|
23
25
|
|
|
24
26
|
/** Maximum backoff delay (milliseconds) */
|
|
25
|
-
export const MAX_BACKOFF_DELAY_MS =
|
|
27
|
+
export const MAX_BACKOFF_DELAY_MS = env.pollMaxBackoffDelayMs;
|
|
26
28
|
|
|
27
29
|
/** Exponential backoff base multiplier */
|
|
28
|
-
export const BACKOFF_MULTIPLIER =
|
|
30
|
+
export const BACKOFF_MULTIPLIER = env.pollBackoffMultiplier;
|
|
29
31
|
|
|
30
32
|
/** Progress percentage to report when job completes */
|
|
31
33
|
export const COMPLETION_PROGRESS = 100;
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Database and storage related constants
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { env } from "../config/env.config";
|
|
7
|
+
|
|
6
8
|
/** Maximum number of creations to fetch in one query */
|
|
7
9
|
export const MAX_CREATIONS_FETCH_LIMIT = 100;
|
|
8
10
|
|
|
@@ -13,10 +15,10 @@ export const DEFAULT_CREATIONS_PAGE_SIZE = 20;
|
|
|
13
15
|
export const MAX_LOG_ENTRIES = 1000;
|
|
14
16
|
|
|
15
17
|
/** Timeout for database operations (milliseconds) */
|
|
16
|
-
export const DB_OPERATION_TIMEOUT_MS =
|
|
18
|
+
export const DB_OPERATION_TIMEOUT_MS = env.storageDbOperationTimeoutMs;
|
|
17
19
|
|
|
18
20
|
/** Maximum cache size for frequently accessed data */
|
|
19
|
-
export const MAX_CACHE_SIZE =
|
|
21
|
+
export const MAX_CACHE_SIZE = env.storageMaxCacheSize;
|
|
20
22
|
|
|
21
23
|
/** Cache TTL for creations (milliseconds) - 5 minutes */
|
|
22
24
|
export const CREATIONS_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
@@ -3,38 +3,40 @@
|
|
|
3
3
|
* All validation limits and constraints
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { env } from "../config/env.config";
|
|
7
|
+
|
|
6
8
|
/** Maximum length for AI prompt text */
|
|
7
|
-
export const MAX_PROMPT_LENGTH =
|
|
9
|
+
export const MAX_PROMPT_LENGTH = env.validationMaxPromptLength;
|
|
8
10
|
|
|
9
11
|
/** Minimum length for AI prompt text */
|
|
10
|
-
export const MIN_PROMPT_LENGTH =
|
|
12
|
+
export const MIN_PROMPT_LENGTH = env.validationMinPromptLength;
|
|
11
13
|
|
|
12
14
|
/** Maximum length for user ID */
|
|
13
|
-
export const MAX_USER_ID_LENGTH =
|
|
15
|
+
export const MAX_USER_ID_LENGTH = env.validationMaxUserIdLength;
|
|
14
16
|
|
|
15
17
|
/** Maximum length for creation ID */
|
|
16
|
-
export const MAX_CREATION_ID_LENGTH =
|
|
18
|
+
export const MAX_CREATION_ID_LENGTH = env.validationMaxCreationIdLength;
|
|
17
19
|
|
|
18
20
|
/** Maximum base64 string length (before encoding) */
|
|
19
|
-
export const MAX_BASE64_SIZE_MB =
|
|
21
|
+
export const MAX_BASE64_SIZE_MB = env.validationMaxBase64SizeMb;
|
|
20
22
|
|
|
21
23
|
/** Maximum regex pattern length for validation */
|
|
22
|
-
export const MAX_PATTERN_LENGTH =
|
|
24
|
+
export const MAX_PATTERN_LENGTH = env.validationMaxPatternLength;
|
|
23
25
|
|
|
24
26
|
/** Maximum duration for video generation (seconds) */
|
|
25
|
-
export const MAX_VIDEO_DURATION_SECONDS =
|
|
27
|
+
export const MAX_VIDEO_DURATION_SECONDS = env.validationMaxVideoDurationSeconds;
|
|
26
28
|
|
|
27
29
|
/** Minimum duration for video generation (seconds) */
|
|
28
|
-
export const MIN_VIDEO_DURATION_SECONDS =
|
|
30
|
+
export const MIN_VIDEO_DURATION_SECONDS = env.validationMinVideoDurationSeconds;
|
|
29
31
|
|
|
30
32
|
/** Maximum image file size (MB) */
|
|
31
|
-
export const MAX_IMAGE_SIZE_MB =
|
|
33
|
+
export const MAX_IMAGE_SIZE_MB = env.validationMaxImageSizeMb;
|
|
32
34
|
|
|
33
35
|
/** Maximum URL length */
|
|
34
|
-
export const MAX_URL_LENGTH =
|
|
36
|
+
export const MAX_URL_LENGTH = env.validationMaxUrlLength;
|
|
35
37
|
|
|
36
38
|
/** Maximum number of images in multi-image generation */
|
|
37
|
-
export const MAX_MULTI_IMAGE_COUNT =
|
|
39
|
+
export const MAX_MULTI_IMAGE_COUNT = env.validationMaxMultiImageCount;
|
|
38
40
|
|
|
39
41
|
/** Minimum number of images for multi-image generation */
|
|
40
|
-
export const MIN_MULTI_IMAGE_COUNT =
|
|
42
|
+
export const MIN_MULTI_IMAGE_COUNT = env.validationMinMultiImageCount;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client Types
|
|
3
|
+
* Type definitions for HTTP client utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface RequestOptions extends RequestInit {
|
|
7
|
+
timeout?: number;
|
|
8
|
+
retries?: number;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ApiResponse<T> {
|
|
13
|
+
data: T | null;
|
|
14
|
+
error: string | null;
|
|
15
|
+
status: number;
|
|
16
|
+
headers: Headers;
|
|
17
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client Utilities
|
|
3
|
+
* Core HTTP request handling with error handling and retry logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { withErrorHandling, getErrorMessage, retryWithBackoff, isNetworkError } from "../utils/error-handling.util";
|
|
7
|
+
import { env } from "../config/env.config";
|
|
8
|
+
import type { RequestOptions, ApiResponse } from "./api-client.types";
|
|
9
|
+
import { createTimeoutController } from "./timeout.util";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Makes an HTTP request with error handling and retry logic
|
|
13
|
+
* @param url - Request URL
|
|
14
|
+
* @param options - Request options
|
|
15
|
+
* @returns API response with data or error
|
|
16
|
+
*/
|
|
17
|
+
export async function fetchWithTimeout<T>(
|
|
18
|
+
url: string,
|
|
19
|
+
options: RequestOptions = {}
|
|
20
|
+
): Promise<ApiResponse<T>> {
|
|
21
|
+
const {
|
|
22
|
+
timeout = env.apiDefaultTimeoutMs,
|
|
23
|
+
retries = 0,
|
|
24
|
+
headers = {},
|
|
25
|
+
...fetchOptions
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
const controller = createTimeoutController(timeout);
|
|
29
|
+
|
|
30
|
+
const operation = async (): Promise<T> => {
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
...fetchOptions,
|
|
33
|
+
headers: {
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
...headers,
|
|
36
|
+
},
|
|
37
|
+
signal: controller?.signal,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
42
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const contentType = response.headers.get("content-type");
|
|
46
|
+
if (contentType?.includes("application/json")) {
|
|
47
|
+
return response.json();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return response.text() as T;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const result = await withErrorHandling(
|
|
54
|
+
async () => {
|
|
55
|
+
if (retries > 0) {
|
|
56
|
+
return retryWithBackoff(operation, {
|
|
57
|
+
maxRetries: retries,
|
|
58
|
+
delayMs: env.apiRetryDelayMs,
|
|
59
|
+
shouldRetry: (error) => isNetworkError(error),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return operation();
|
|
63
|
+
},
|
|
64
|
+
(error) => {
|
|
65
|
+
console.error("[API Client] Request failed:", error);
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (result.error) {
|
|
70
|
+
return {
|
|
71
|
+
data: null,
|
|
72
|
+
error: getErrorMessage(result.error),
|
|
73
|
+
status: 0,
|
|
74
|
+
headers: new Headers(),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
data: result.data ?? null,
|
|
80
|
+
error: null,
|
|
81
|
+
status: 200,
|
|
82
|
+
headers: new Headers(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Makes a GET request
|
|
88
|
+
* @param url - Request URL
|
|
89
|
+
* @param options - Request options
|
|
90
|
+
* @returns API response
|
|
91
|
+
*/
|
|
92
|
+
export async function get<T>(
|
|
93
|
+
url: string,
|
|
94
|
+
options: Omit<RequestOptions, "method" | "body"> = {}
|
|
95
|
+
): Promise<ApiResponse<T>> {
|
|
96
|
+
return fetchWithTimeout<T>(url, { ...options, method: "GET" });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Makes a POST request
|
|
101
|
+
* @param url - Request URL
|
|
102
|
+
* @param data - Request body data
|
|
103
|
+
* @param options - Request options
|
|
104
|
+
* @returns API response
|
|
105
|
+
*/
|
|
106
|
+
export async function post<T>(
|
|
107
|
+
url: string,
|
|
108
|
+
data: unknown,
|
|
109
|
+
options: Omit<RequestOptions, "method"> = {}
|
|
110
|
+
): Promise<ApiResponse<T>> {
|
|
111
|
+
return fetchWithTimeout<T>(url, {
|
|
112
|
+
...options,
|
|
113
|
+
method: "POST",
|
|
114
|
+
body: JSON.stringify(data),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Makes a PUT request
|
|
120
|
+
* @param url - Request URL
|
|
121
|
+
* @param data - Request body data
|
|
122
|
+
* @param options - Request options
|
|
123
|
+
* @returns API response
|
|
124
|
+
*/
|
|
125
|
+
export async function put<T>(
|
|
126
|
+
url: string,
|
|
127
|
+
data: unknown,
|
|
128
|
+
options: Omit<RequestOptions, "method"> = {}
|
|
129
|
+
): Promise<ApiResponse<T>> {
|
|
130
|
+
return fetchWithTimeout<T>(url, {
|
|
131
|
+
...options,
|
|
132
|
+
method: "PUT",
|
|
133
|
+
body: JSON.stringify(data),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Makes a DELETE request
|
|
139
|
+
* @param url - Request URL
|
|
140
|
+
* @param options - Request options
|
|
141
|
+
* @returns API response
|
|
142
|
+
*/
|
|
143
|
+
export async function del<T>(
|
|
144
|
+
url: string,
|
|
145
|
+
options: Omit<RequestOptions, "method" | "body"> = {}
|
|
146
|
+
): Promise<ApiResponse<T>> {
|
|
147
|
+
return fetchWithTimeout<T>(url, { ...options, method: "DELETE" });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Makes a PATCH request
|
|
152
|
+
* @param url - Request URL
|
|
153
|
+
* @param data - Request body data
|
|
154
|
+
* @param options - Request options
|
|
155
|
+
* @returns API response
|
|
156
|
+
*/
|
|
157
|
+
export async function patch<T>(
|
|
158
|
+
url: string,
|
|
159
|
+
data: unknown,
|
|
160
|
+
options: Omit<RequestOptions, "method"> = {}
|
|
161
|
+
): Promise<ApiResponse<T>> {
|
|
162
|
+
return fetchWithTimeout<T>(url, {
|
|
163
|
+
...options,
|
|
164
|
+
method: "PATCH",
|
|
165
|
+
body: JSON.stringify(data),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client Module
|
|
3
|
+
* Exports all HTTP client utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type { RequestOptions, ApiResponse } from "./api-client.types";
|
|
7
|
+
export {
|
|
8
|
+
get,
|
|
9
|
+
post,
|
|
10
|
+
put,
|
|
11
|
+
del,
|
|
12
|
+
patch,
|
|
13
|
+
fetchWithTimeout,
|
|
14
|
+
} from "./http-client.util";
|
|
15
|
+
export { buildQueryString, appendQueryParams } from "./query-string.util";
|
|
16
|
+
export { createTimeoutController } from "./timeout.util";
|