@umituz/react-native-ai-generation-content 1.26.30 → 1.26.31
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/generation/wizard/infrastructure/strategies/wizard-strategy.constants.ts +32 -0
- package/src/domains/generation/wizard/infrastructure/strategies/wizard-strategy.factory.ts +23 -23
- package/src/index.ts +6 -9
- package/src/infrastructure/services/generation-orchestrator.service.ts +5 -37
- package/src/infrastructure/services/index.ts +0 -5
- package/src/infrastructure/services/job-poller.service.ts +2 -16
- package/src/infrastructure/utils/index.ts +1 -0
- package/src/infrastructure/{wrappers/language.wrapper.ts → utils/language.util.ts} +1 -1
- package/src/infrastructure/utils/progress-calculator.util.ts +4 -133
- package/src/infrastructure/services/generation-wrapper.service.ts +0 -148
- package/src/infrastructure/services/progress-manager.ts +0 -58
- package/src/infrastructure/wrappers/README.md +0 -336
- package/src/infrastructure/wrappers/index.ts +0 -19
- package/src/infrastructure/wrappers/moderation.wrapper.ts +0 -37
- package/src/infrastructure/wrappers/synchronous-generation.wrapper.ts +0 -106
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.31",
|
|
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",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard Strategy Constants
|
|
3
|
+
* Centralized configuration values for wizard-based generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { VideoFeatureType } from "../../../../../domain/interfaces";
|
|
7
|
+
|
|
8
|
+
/** Generation timeout in milliseconds (2 minutes) */
|
|
9
|
+
export const GENERATION_TIMEOUT_MS = 120000;
|
|
10
|
+
|
|
11
|
+
/** Base64 image format prefix */
|
|
12
|
+
export const BASE64_IMAGE_PREFIX = "data:image/jpeg;base64,";
|
|
13
|
+
|
|
14
|
+
/** Photo key prefix in wizard data */
|
|
15
|
+
export const PHOTO_KEY_PREFIX = "photo_";
|
|
16
|
+
|
|
17
|
+
/** Default style value (no custom style applied) */
|
|
18
|
+
export const DEFAULT_STYLE_VALUE = "original";
|
|
19
|
+
|
|
20
|
+
/** Model input defaults */
|
|
21
|
+
export const MODEL_INPUT_DEFAULTS = {
|
|
22
|
+
aspectRatio: "1:1",
|
|
23
|
+
outputFormat: "jpeg",
|
|
24
|
+
numImages: 1,
|
|
25
|
+
enableSafetyChecker: false,
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
/** Video feature type patterns for scenario detection */
|
|
29
|
+
export const VIDEO_FEATURE_PATTERNS: Record<string, VideoFeatureType> = {
|
|
30
|
+
kiss: "ai-kiss",
|
|
31
|
+
hug: "ai-hug",
|
|
32
|
+
};
|
|
@@ -10,6 +10,14 @@ import type { VideoFeatureType } from "../../../../../domain/interfaces";
|
|
|
10
10
|
import { executeVideoFeature } from "../../../../../infrastructure/services/video-feature-executor.service";
|
|
11
11
|
import { createCreationsRepository } from "../../../../creations/infrastructure/adapters";
|
|
12
12
|
import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
|
|
13
|
+
import {
|
|
14
|
+
GENERATION_TIMEOUT_MS,
|
|
15
|
+
BASE64_IMAGE_PREFIX,
|
|
16
|
+
PHOTO_KEY_PREFIX,
|
|
17
|
+
DEFAULT_STYLE_VALUE,
|
|
18
|
+
MODEL_INPUT_DEFAULTS,
|
|
19
|
+
VIDEO_FEATURE_PATTERNS,
|
|
20
|
+
} from "./wizard-strategy.constants";
|
|
13
21
|
|
|
14
22
|
declare const __DEV__: boolean;
|
|
15
23
|
|
|
@@ -56,10 +64,8 @@ async function executeImageGeneration(
|
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
try {
|
|
59
|
-
onProgress?.(5);
|
|
60
|
-
|
|
61
67
|
const formatBase64 = (base64: string): string => {
|
|
62
|
-
return base64.startsWith("data:") ? base64 :
|
|
68
|
+
return base64.startsWith("data:") ? base64 : `${BASE64_IMAGE_PREFIX}${base64}`;
|
|
63
69
|
};
|
|
64
70
|
|
|
65
71
|
const imageUrls = input.photos.map(formatBase64);
|
|
@@ -68,17 +74,15 @@ async function executeImageGeneration(
|
|
|
68
74
|
return { success: false, error: "At least one image required" };
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
onProgress?.(10);
|
|
72
|
-
|
|
73
77
|
const enhancedPrompt = `Create a photorealistic image featuring the exact two people from the provided photos. Use the person from @image1 and the person from @image2 exactly as they appear in the reference images - maintain their facial features, expressions, and identity. ${input.prompt}. Professional photography, high quality, detailed, natural lighting, photorealistic rendering.`;
|
|
74
78
|
|
|
75
79
|
const modelInput = {
|
|
76
80
|
image_urls: imageUrls,
|
|
77
81
|
prompt: enhancedPrompt,
|
|
78
|
-
aspect_ratio:
|
|
79
|
-
output_format:
|
|
80
|
-
num_images:
|
|
81
|
-
enable_safety_checker:
|
|
82
|
+
aspect_ratio: MODEL_INPUT_DEFAULTS.aspectRatio,
|
|
83
|
+
output_format: MODEL_INPUT_DEFAULTS.outputFormat,
|
|
84
|
+
num_images: MODEL_INPUT_DEFAULTS.numImages,
|
|
85
|
+
enable_safety_checker: MODEL_INPUT_DEFAULTS.enableSafetyChecker,
|
|
82
86
|
};
|
|
83
87
|
|
|
84
88
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
@@ -92,17 +96,13 @@ async function executeImageGeneration(
|
|
|
92
96
|
|
|
93
97
|
let lastStatus = "";
|
|
94
98
|
const result = await provider.subscribe(model, modelInput, {
|
|
95
|
-
timeoutMs:
|
|
99
|
+
timeoutMs: GENERATION_TIMEOUT_MS,
|
|
96
100
|
onQueueUpdate: (status) => {
|
|
97
101
|
if (status.status === lastStatus) return;
|
|
98
102
|
lastStatus = status.status;
|
|
99
|
-
if (status.status === "IN_QUEUE") onProgress?.(20);
|
|
100
|
-
else if (status.status === "IN_PROGRESS") onProgress?.(50);
|
|
101
103
|
},
|
|
102
104
|
});
|
|
103
105
|
|
|
104
|
-
onProgress?.(90);
|
|
105
|
-
|
|
106
106
|
const rawResult = result as Record<string, unknown>;
|
|
107
107
|
const data = (rawResult?.data ?? rawResult) as { images?: Array<{ url: string }> };
|
|
108
108
|
const imageUrl = data?.images?.[0]?.url;
|
|
@@ -129,7 +129,7 @@ async function extractPhotosFromWizardData(
|
|
|
129
129
|
): Promise<string[] | null> {
|
|
130
130
|
// Find ALL photo keys dynamically (photo_1, photo_2, photo_3, ...)
|
|
131
131
|
const photoKeys = Object.keys(wizardData)
|
|
132
|
-
.filter((k) => k.includes(
|
|
132
|
+
.filter((k) => k.includes(PHOTO_KEY_PREFIX))
|
|
133
133
|
.sort(); // Sort to maintain order (photo_1, photo_2, ...)
|
|
134
134
|
|
|
135
135
|
if (photoKeys.length === 0) {
|
|
@@ -214,13 +214,13 @@ async function buildImageGenerationInput(
|
|
|
214
214
|
|
|
215
215
|
// Art style (single select)
|
|
216
216
|
const artStyle = wizardData.selection_art_style as string | undefined;
|
|
217
|
-
if (artStyle && artStyle !==
|
|
217
|
+
if (artStyle && artStyle !== DEFAULT_STYLE_VALUE) {
|
|
218
218
|
styleEnhancements.push(`Art style: ${artStyle}`);
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// Artist style (single select)
|
|
222
222
|
const artist = wizardData.selection_artist_style as string | undefined;
|
|
223
|
-
if (artist && artist !==
|
|
223
|
+
if (artist && artist !== DEFAULT_STYLE_VALUE) {
|
|
224
224
|
styleEnhancements.push(`Artist style: ${artist}`);
|
|
225
225
|
}
|
|
226
226
|
|
|
@@ -296,13 +296,13 @@ async function buildGenerationInput(
|
|
|
296
296
|
function getVideoFeatureType(scenarioId: string): VideoFeatureType {
|
|
297
297
|
const id = scenarioId.toLowerCase();
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
console.warn(`[WizardStrategy] Unknown scenario type "${scenarioId}", defaulting to ai-hug`);
|
|
299
|
+
for (const [pattern, featureType] of Object.entries(VIDEO_FEATURE_PATTERNS)) {
|
|
300
|
+
if (id.includes(pattern)) {
|
|
301
|
+
return featureType;
|
|
302
|
+
}
|
|
304
303
|
}
|
|
305
|
-
|
|
304
|
+
|
|
305
|
+
throw new Error(`Unknown video feature type for scenario "${scenarioId}". Add pattern to VIDEO_FEATURE_PATTERNS.`);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
// ============================================================================
|
package/src/index.ts
CHANGED
|
@@ -35,12 +35,12 @@ export {
|
|
|
35
35
|
} from "./infrastructure/config";
|
|
36
36
|
|
|
37
37
|
export {
|
|
38
|
-
providerRegistry, generationOrchestrator, pollJob, createJobPoller,
|
|
39
|
-
|
|
38
|
+
providerRegistry, generationOrchestrator, pollJob, createJobPoller,
|
|
39
|
+
executeImageFeature, hasImageFeatureSupport, executeVideoFeature, hasVideoFeatureSupport,
|
|
40
40
|
} from "./infrastructure/services";
|
|
41
41
|
|
|
42
42
|
export type {
|
|
43
|
-
OrchestratorConfig, PollJobOptions, PollJobResult,
|
|
43
|
+
OrchestratorConfig, PollJobOptions, PollJobResult, ImageResultExtractor,
|
|
44
44
|
ExecuteImageFeatureOptions, ImageFeatureResult, ImageFeatureRequest,
|
|
45
45
|
ExecuteVideoFeatureOptions, VideoFeatureResult, VideoFeatureRequest,
|
|
46
46
|
} from "./infrastructure/services";
|
|
@@ -50,26 +50,23 @@ export type { CreditCheckConfig, HistoryConfig, HistoryEntry } from "./infrastru
|
|
|
50
50
|
|
|
51
51
|
export {
|
|
52
52
|
classifyError, isTransientError, isPermanentError, isResultNotReady, calculatePollingInterval,
|
|
53
|
-
createPollingDelay,
|
|
54
|
-
calculatePollingProgress, checkStatusForErrors, isJobComplete, isJobProcessing, isJobFailed,
|
|
53
|
+
createPollingDelay, checkStatusForErrors, isJobComplete, isJobProcessing, isJobFailed,
|
|
55
54
|
validateResult, extractOutputUrl, extractOutputUrls, extractVideoUrl, extractThumbnailUrl,
|
|
56
55
|
extractAudioUrl, extractImageUrls, cleanBase64, addBase64Prefix, preparePhoto, preparePhotos,
|
|
57
56
|
isValidBase64, getBase64Size, getBase64SizeMB, prepareImage, createDevCallbacks, createFeatureUtils,
|
|
58
57
|
showVideoGenerationSuccess, handleGenerationError, showContentModerationWarning,
|
|
59
58
|
saveMediaToGallery, shareMedia, createSaveHandler, createShareHandler, createMediaHandlers,
|
|
59
|
+
mapJobStatusToGenerationStatus, enhancePromptWithLanguage, getSupportedLanguages, getLanguageName,
|
|
60
60
|
} from "./infrastructure/utils";
|
|
61
61
|
|
|
62
62
|
export { distinctBy } from "./utils/arrayUtils";
|
|
63
63
|
|
|
64
64
|
export type {
|
|
65
|
-
IntervalOptions,
|
|
65
|
+
IntervalOptions, StatusCheckResult, ResultValidation, ValidateResultOptions,
|
|
66
66
|
PhotoInput, PreparedImage, ImageSelector, VideoSaver, AlertFunction, FeatureUtilsConfig, VideoAlertFunction,
|
|
67
67
|
MediaActionResult, MediaActionTranslations, ToastConfig,
|
|
68
68
|
} from "./infrastructure/utils";
|
|
69
69
|
|
|
70
|
-
export { enhancePromptWithLanguage, getSupportedLanguages, getLanguageName, ModerationWrapper, generateSynchronously } from "./infrastructure/wrappers";
|
|
71
|
-
export type { ModerationResult, ModerationConfig, SynchronousGenerationInput, SynchronousGenerationConfig } from "./infrastructure/wrappers";
|
|
72
|
-
|
|
73
70
|
export {
|
|
74
71
|
useGeneration, usePendingJobs, useBackgroundGeneration,
|
|
75
72
|
useGenerationFlow, useAIFeatureCallbacks,
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generation Orchestrator Service
|
|
3
3
|
* Provider-agnostic AI generation workflow
|
|
4
|
+
* Reports only real status - no fake progress
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import type {
|
|
7
8
|
GenerationRequest,
|
|
8
9
|
GenerationResult,
|
|
9
|
-
GenerationProgress,
|
|
10
10
|
PollingConfig,
|
|
11
11
|
} from "../../domain/entities";
|
|
12
12
|
import { classifyError } from "../utils/error-classifier.util";
|
|
13
|
-
import { ProgressManager } from "./progress-manager";
|
|
14
13
|
import { pollJob } from "./job-poller.service";
|
|
15
14
|
import { ProviderValidator } from "./provider-validator";
|
|
16
15
|
|
|
@@ -22,7 +21,6 @@ export interface OrchestratorConfig {
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
class GenerationOrchestratorService {
|
|
25
|
-
private progressManager = new ProgressManager();
|
|
26
24
|
private providerValidator = new ProviderValidator();
|
|
27
25
|
private pollingConfig?: Partial<PollingConfig>;
|
|
28
26
|
private onStatusUpdateCallback?: (requestId: string, status: string) => Promise<void>;
|
|
@@ -53,20 +51,9 @@ class GenerationOrchestratorService {
|
|
|
53
51
|
});
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
const updateProgress = (
|
|
57
|
-
stage: GenerationProgress["stage"],
|
|
58
|
-
subProgress = 0,
|
|
59
|
-
) => {
|
|
60
|
-
this.progressManager.updateProgress(stage, subProgress, request.onProgress);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
54
|
try {
|
|
64
|
-
updateProgress("preparing");
|
|
65
|
-
|
|
66
55
|
const submission = await provider.submitJob(request.model, request.input);
|
|
67
56
|
|
|
68
|
-
updateProgress("submitting");
|
|
69
|
-
|
|
70
57
|
if (__DEV__) {
|
|
71
58
|
console.log("[Orchestrator] Job submitted:", {
|
|
72
59
|
requestId: submission.requestId,
|
|
@@ -74,33 +61,17 @@ class GenerationOrchestratorService {
|
|
|
74
61
|
});
|
|
75
62
|
}
|
|
76
63
|
|
|
77
|
-
updateProgress("generating");
|
|
78
|
-
|
|
79
64
|
const pollResult = await pollJob<T>({
|
|
80
65
|
provider,
|
|
81
66
|
model: request.model,
|
|
82
67
|
requestId: submission.requestId,
|
|
83
68
|
config: this.pollingConfig,
|
|
84
69
|
onStatusChange: async (status) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
70
|
+
if (this.onStatusUpdateCallback) {
|
|
71
|
+
await this.onStatusUpdateCallback(submission.requestId, status.status);
|
|
72
|
+
}
|
|
88
73
|
},
|
|
89
|
-
onProgress:
|
|
90
|
-
// We map polling progress (0-100) to our "generating" stage progress
|
|
91
|
-
// Since we can't easily access the internal 'status' and 'attempt' here nicely without changing pollJob signature to pass them to onProgress
|
|
92
|
-
// But pollJob onProgress passes a number.
|
|
93
|
-
// The original code used: this.progressManager.updateProgressFromStatus(status, attempt, config...)
|
|
94
|
-
// pollJob calculates progress internally.
|
|
95
|
-
// We can just use the numeric progress directly for the converting.
|
|
96
|
-
|
|
97
|
-
// Actually, the original code had access to `status` object inside the callback.
|
|
98
|
-
// pollJob abstracts that away.
|
|
99
|
-
// However, progressManager.updateProgress takes (stage, subProgress).
|
|
100
|
-
// So we can just pass 'generating' and the percentage.
|
|
101
|
-
|
|
102
|
-
this.progressManager.updateProgress("generating", progress, request.onProgress);
|
|
103
|
-
}
|
|
74
|
+
onProgress: request.onProgress,
|
|
104
75
|
});
|
|
105
76
|
|
|
106
77
|
if (!pollResult.success) {
|
|
@@ -108,9 +79,6 @@ class GenerationOrchestratorService {
|
|
|
108
79
|
}
|
|
109
80
|
|
|
110
81
|
const result = pollResult.data as T;
|
|
111
|
-
|
|
112
|
-
updateProgress("completed");
|
|
113
|
-
|
|
114
82
|
const duration = Date.now() - startTime;
|
|
115
83
|
|
|
116
84
|
if (__DEV__) {
|
|
@@ -7,11 +7,6 @@ export { generationOrchestrator } from "./generation-orchestrator.service";
|
|
|
7
7
|
export type { OrchestratorConfig } from "./generation-orchestrator.service";
|
|
8
8
|
export { pollJob, createJobPoller } from "./job-poller.service";
|
|
9
9
|
export type { PollJobOptions, PollJobResult } from "./job-poller.service";
|
|
10
|
-
export {
|
|
11
|
-
generationWrapper,
|
|
12
|
-
createGenerationWrapper,
|
|
13
|
-
} from "./generation-wrapper.service";
|
|
14
|
-
export type { WrapperConfig } from "./generation-wrapper.service";
|
|
15
10
|
export {
|
|
16
11
|
executeImageFeature,
|
|
17
12
|
hasImageFeatureSupport,
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Job Poller Service
|
|
3
3
|
* Provider-agnostic job polling with exponential backoff
|
|
4
|
+
* Reports only real status - no fake progress
|
|
4
5
|
*/
|
|
5
6
|
|
|
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
7
|
import { DEFAULT_POLLING_CONFIG } from "../../domain/entities";
|
|
9
8
|
import { calculatePollingInterval } from "../utils/polling-interval.util";
|
|
10
9
|
import { checkStatusForErrors, isJobComplete } from "../utils/status-checker.util";
|
|
11
10
|
import { validateResult } from "../utils/result-validator.util";
|
|
12
11
|
import { isTransientError } from "../utils/error-classifier.util";
|
|
13
|
-
import { calculateProgressFromJobStatus } from "../utils/progress-calculator.util";
|
|
14
12
|
import type { PollJobOptions, PollJobResult } from "./job-poller.types";
|
|
15
|
-
import { createJobPoller } from "./job-poller-factory"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
16
|
-
|
|
17
|
-
// IAIProvider and PollingConfig are used indirectly through provider methods
|
|
18
|
-
// createJobPoller is re-exported
|
|
19
13
|
|
|
20
14
|
declare const __DEV__: boolean;
|
|
21
15
|
|
|
@@ -23,6 +17,7 @@ const MAX_CONSECUTIVE_TRANSIENT_ERRORS = 5;
|
|
|
23
17
|
|
|
24
18
|
/**
|
|
25
19
|
* Poll job until completion with exponential backoff
|
|
20
|
+
* Only reports 100% on actual completion
|
|
26
21
|
*/
|
|
27
22
|
export async function pollJob<T = unknown>(
|
|
28
23
|
options: PollJobOptions,
|
|
@@ -42,7 +37,6 @@ export async function pollJob<T = unknown>(
|
|
|
42
37
|
|
|
43
38
|
const startTime = Date.now();
|
|
44
39
|
let consecutiveTransientErrors = 0;
|
|
45
|
-
let lastProgress = 0;
|
|
46
40
|
|
|
47
41
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
48
42
|
if (signal?.aborted) {
|
|
@@ -76,15 +70,7 @@ export async function pollJob<T = unknown>(
|
|
|
76
70
|
|
|
77
71
|
consecutiveTransientErrors = 0;
|
|
78
72
|
|
|
79
|
-
const progress = calculateProgressFromJobStatus(status, attempt, maxAttempts);
|
|
80
|
-
if (progress > lastProgress) {
|
|
81
|
-
lastProgress = progress;
|
|
82
|
-
onProgress?.(progress);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
73
|
if (isJobComplete(status)) {
|
|
86
|
-
onProgress?.(90);
|
|
87
|
-
|
|
88
74
|
const result = await provider.getJobResult<T>(model, requestId);
|
|
89
75
|
|
|
90
76
|
const validation = validateResult(result);
|
|
@@ -1,111 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Progress Calculator Utility
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Maps provider status to generation status
|
|
4
|
+
* Only reports real status - no fake progress calculations
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { GenerationStatus } from "../../domain/entities";
|
|
8
|
-
import {
|
|
9
|
-
DEFAULT_PROGRESS_STAGES,
|
|
10
|
-
type ProgressStageConfig,
|
|
11
|
-
} from "../../domain/entities";
|
|
12
8
|
import type { AIJobStatusType } from "../../domain/interfaces/ai-provider.interface";
|
|
13
|
-
import type { JobStatus } from "../../domain/interfaces";
|
|
14
|
-
|
|
15
|
-
export interface ProgressOptions {
|
|
16
|
-
status: GenerationStatus;
|
|
17
|
-
stages?: ProgressStageConfig[];
|
|
18
|
-
subProgress?: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function getProgressForStatus(options: ProgressOptions): number {
|
|
22
|
-
const { status, stages = DEFAULT_PROGRESS_STAGES, subProgress = 0 } = options;
|
|
23
|
-
|
|
24
|
-
const stage = stages.find((s) => s.status === status);
|
|
25
|
-
|
|
26
|
-
if (!stage) {
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const range = stage.maxProgress - stage.minProgress;
|
|
31
|
-
const adjustedProgress = stage.minProgress + range * (subProgress / 100);
|
|
32
|
-
|
|
33
|
-
return Math.round(adjustedProgress);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function interpolateProgress(
|
|
37
|
-
startTime: number,
|
|
38
|
-
estimatedDurationMs: number,
|
|
39
|
-
minProgress: number,
|
|
40
|
-
maxProgress: number,
|
|
41
|
-
): number {
|
|
42
|
-
const elapsed = Date.now() - startTime;
|
|
43
|
-
const ratio = Math.min(elapsed / estimatedDurationMs, 1);
|
|
44
|
-
|
|
45
|
-
const eased = 1 - Math.pow(1 - ratio, 2);
|
|
46
|
-
|
|
47
|
-
const progress = minProgress + (maxProgress - minProgress) * eased;
|
|
48
|
-
return Math.round(progress);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Calculate progress for polling-based operations
|
|
53
|
-
* Returns a rounded integer between minProgress and maxProgress
|
|
54
|
-
*
|
|
55
|
-
* @param currentAttempt - Current polling attempt (1-based)
|
|
56
|
-
* @param maxAttempts - Maximum number of polling attempts
|
|
57
|
-
* @param minProgress - Starting progress percentage (default: 10)
|
|
58
|
-
* @param maxProgress - Maximum progress percentage before completion (default: 95)
|
|
59
|
-
* @returns Rounded progress percentage
|
|
60
|
-
*/
|
|
61
|
-
export function calculatePollingProgress(
|
|
62
|
-
currentAttempt: number,
|
|
63
|
-
maxAttempts: number,
|
|
64
|
-
minProgress: number = 10,
|
|
65
|
-
maxProgress: number = 95,
|
|
66
|
-
): number {
|
|
67
|
-
const ratio = currentAttempt / maxAttempts;
|
|
68
|
-
const progress = minProgress + ratio * (maxProgress - minProgress);
|
|
69
|
-
return Math.round(Math.min(maxProgress, progress));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function createProgressTracker(stages?: ProgressStageConfig[]) {
|
|
73
|
-
const effectiveStages = stages ?? DEFAULT_PROGRESS_STAGES;
|
|
74
|
-
let currentStatus: GenerationStatus = "idle";
|
|
75
|
-
let stageStartTime = Date.now();
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
setStatus(status: GenerationStatus): number {
|
|
79
|
-
currentStatus = status;
|
|
80
|
-
stageStartTime = Date.now();
|
|
81
|
-
return getProgressForStatus({ status, stages: effectiveStages });
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
getProgress(subProgress = 0): number {
|
|
85
|
-
return getProgressForStatus({
|
|
86
|
-
status: currentStatus,
|
|
87
|
-
stages: effectiveStages,
|
|
88
|
-
subProgress,
|
|
89
|
-
});
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
getCurrentStatus(): GenerationStatus {
|
|
93
|
-
return currentStatus;
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
getElapsedTime(): number {
|
|
97
|
-
return Date.now() - stageStartTime;
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
9
|
|
|
102
10
|
/**
|
|
103
11
|
* Maps provider job status to generation status
|
|
104
12
|
* Provider: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED
|
|
105
|
-
* Generation: idle, preparing, submitting, generating,
|
|
13
|
+
* Generation: idle, preparing, submitting, generating, completed, failed
|
|
106
14
|
*/
|
|
107
15
|
export function mapJobStatusToGenerationStatus(
|
|
108
|
-
jobStatus: AIJobStatusType
|
|
16
|
+
jobStatus: AIJobStatusType,
|
|
109
17
|
): GenerationStatus {
|
|
110
18
|
const statusMap: Record<AIJobStatusType, GenerationStatus> = {
|
|
111
19
|
IN_QUEUE: "submitting",
|
|
@@ -115,40 +23,3 @@ export function mapJobStatusToGenerationStatus(
|
|
|
115
23
|
};
|
|
116
24
|
return statusMap[jobStatus] ?? "generating";
|
|
117
25
|
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Calculate progress from provider job status
|
|
121
|
-
* Uses default progress stages for consistent progress reporting
|
|
122
|
-
*/
|
|
123
|
-
export function getProgressFromJobStatus(
|
|
124
|
-
jobStatus: AIJobStatusType,
|
|
125
|
-
stages: ProgressStageConfig[] = DEFAULT_PROGRESS_STAGES
|
|
126
|
-
): number {
|
|
127
|
-
const generationStatus = mapJobStatusToGenerationStatus(jobStatus);
|
|
128
|
-
return getProgressForStatus({ status: generationStatus, stages });
|
|
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
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generation Wrapper Service
|
|
3
|
-
* Wraps orchestrator with middleware support
|
|
4
|
-
* Enables credit checking, moderation, history via middleware pattern
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type {
|
|
8
|
-
GenerationRequest,
|
|
9
|
-
GenerationResult,
|
|
10
|
-
GenerationMiddleware,
|
|
11
|
-
MiddlewareContext,
|
|
12
|
-
MiddlewareResultContext,
|
|
13
|
-
} from "../../domain/entities";
|
|
14
|
-
import { generationOrchestrator } from "./generation-orchestrator.service";
|
|
15
|
-
import type { OrchestratorConfig } from "./generation-orchestrator.service";
|
|
16
|
-
|
|
17
|
-
export interface WrapperConfig {
|
|
18
|
-
/** Middleware to execute before/after generation */
|
|
19
|
-
middleware?: GenerationMiddleware[];
|
|
20
|
-
/** Orchestrator configuration */
|
|
21
|
-
orchestratorConfig?: OrchestratorConfig;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
class GenerationWrapperService {
|
|
25
|
-
private middleware: GenerationMiddleware[] = [];
|
|
26
|
-
private orchestratorConfig?: OrchestratorConfig;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Configure wrapper with middleware
|
|
30
|
-
*/
|
|
31
|
-
configure(config: WrapperConfig): void {
|
|
32
|
-
this.middleware = config.middleware || [];
|
|
33
|
-
this.orchestratorConfig = config.orchestratorConfig;
|
|
34
|
-
|
|
35
|
-
if (this.orchestratorConfig) {
|
|
36
|
-
generationOrchestrator.configure(this.orchestratorConfig);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Add middleware to chain
|
|
42
|
-
*/
|
|
43
|
-
use(middleware: GenerationMiddleware): void {
|
|
44
|
-
this.middleware.push(middleware);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Clear all middleware
|
|
49
|
-
*/
|
|
50
|
-
clearMiddleware(): void {
|
|
51
|
-
this.middleware = [];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Generate with middleware support
|
|
56
|
-
*/
|
|
57
|
-
async generate<T = unknown>(
|
|
58
|
-
request: GenerationRequest,
|
|
59
|
-
userId?: string,
|
|
60
|
-
metadata?: Record<string, unknown>,
|
|
61
|
-
): Promise<GenerationResult<T>> {
|
|
62
|
-
const startTime = Date.now();
|
|
63
|
-
|
|
64
|
-
const context: MiddlewareContext = {
|
|
65
|
-
request,
|
|
66
|
-
userId,
|
|
67
|
-
metadata,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
await this.executeBeforeHooks(context);
|
|
72
|
-
|
|
73
|
-
const result = await generationOrchestrator.generate<T>(request);
|
|
74
|
-
|
|
75
|
-
const resultContext: MiddlewareResultContext<T> = {
|
|
76
|
-
...context,
|
|
77
|
-
result,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
await this.executeAfterHooks(resultContext);
|
|
81
|
-
|
|
82
|
-
return result;
|
|
83
|
-
} catch (error) {
|
|
84
|
-
const errorResult: GenerationResult<T> = {
|
|
85
|
-
success: false,
|
|
86
|
-
error: error instanceof Error ? error.message : String(error),
|
|
87
|
-
metadata: {
|
|
88
|
-
model: request.model || "unknown",
|
|
89
|
-
startTime,
|
|
90
|
-
endTime: Date.now(),
|
|
91
|
-
duration: Date.now() - startTime,
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const resultContext: MiddlewareResultContext<T> = {
|
|
96
|
-
...context,
|
|
97
|
-
result: errorResult,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
await this.executeAfterHooks(resultContext).catch(() => {
|
|
101
|
-
// Silent fail on afterHooks error
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return errorResult;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Execute all beforeGenerate hooks
|
|
110
|
-
*/
|
|
111
|
-
private async executeBeforeHooks(
|
|
112
|
-
context: MiddlewareContext,
|
|
113
|
-
): Promise<void> {
|
|
114
|
-
for (const mw of this.middleware) {
|
|
115
|
-
if (mw.beforeGenerate) {
|
|
116
|
-
await mw.beforeGenerate(context);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Execute all afterGenerate hooks
|
|
123
|
-
*/
|
|
124
|
-
private async executeAfterHooks<T>(
|
|
125
|
-
context: MiddlewareResultContext<T>,
|
|
126
|
-
): Promise<void> {
|
|
127
|
-
for (const mw of this.middleware) {
|
|
128
|
-
if (mw.afterGenerate) {
|
|
129
|
-
await mw.afterGenerate(context);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export const generationWrapper = new GenerationWrapperService();
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Create new wrapper instance
|
|
139
|
-
*/
|
|
140
|
-
export function createGenerationWrapper(
|
|
141
|
-
config?: WrapperConfig,
|
|
142
|
-
): GenerationWrapperService {
|
|
143
|
-
const wrapper = new GenerationWrapperService();
|
|
144
|
-
if (config) {
|
|
145
|
-
wrapper.configure(config);
|
|
146
|
-
}
|
|
147
|
-
return wrapper;
|
|
148
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Progress Manager
|
|
3
|
-
* Handles progress tracking and updates during generation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { GenerationProgress, PollingConfig } from "../../domain/entities";
|
|
7
|
-
import type { JobStatus } from "../../domain/interfaces";
|
|
8
|
-
import { createProgressTracker } from "../utils/progress-calculator.util";
|
|
9
|
-
|
|
10
|
-
export class ProgressManager {
|
|
11
|
-
private progressTracker = createProgressTracker();
|
|
12
|
-
|
|
13
|
-
updateProgress(
|
|
14
|
-
stage: GenerationProgress["stage"],
|
|
15
|
-
subProgress: number,
|
|
16
|
-
onProgress?: (progress: GenerationProgress) => void,
|
|
17
|
-
): void {
|
|
18
|
-
const progress = this.progressTracker.setStatus(stage);
|
|
19
|
-
onProgress?.({
|
|
20
|
-
stage,
|
|
21
|
-
progress: progress + subProgress,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
updateProgressFromStatus(
|
|
26
|
-
status: JobStatus,
|
|
27
|
-
attempt: number,
|
|
28
|
-
config: PollingConfig,
|
|
29
|
-
onProgress?: (progress: GenerationProgress) => void,
|
|
30
|
-
): void {
|
|
31
|
-
const baseProgress = 25;
|
|
32
|
-
const maxProgress = 85;
|
|
33
|
-
const range = maxProgress - baseProgress;
|
|
34
|
-
|
|
35
|
-
let progress: number;
|
|
36
|
-
|
|
37
|
-
if (status.status === "IN_QUEUE") {
|
|
38
|
-
progress = baseProgress + range * 0.2;
|
|
39
|
-
} else if (status.status === "IN_PROGRESS") {
|
|
40
|
-
const ratio = Math.min(attempt / (config.maxAttempts * 0.7), 1);
|
|
41
|
-
progress = baseProgress + range * (0.2 + 0.6 * ratio);
|
|
42
|
-
} else {
|
|
43
|
-
progress = baseProgress;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
onProgress?.({
|
|
47
|
-
stage: "generating",
|
|
48
|
-
progress: Math.round(progress),
|
|
49
|
-
eta: status.eta,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
reset(): void {
|
|
54
|
-
this.progressTracker = createProgressTracker();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const progressManager = new ProgressManager();
|
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
# Infrastructure Wrappers
|
|
2
|
-
|
|
3
|
-
Wrapper utilities and enhancements for AI generation operations.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The wrappers module provides wrapper utilities that add additional functionality to core AI generation operations, such as language enhancement, moderation, and synchronous execution.
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- Language enhancement for prompts
|
|
12
|
-
- Content moderation wrapper
|
|
13
|
-
- Synchronous generation support
|
|
14
|
-
- Prompt augmentation
|
|
15
|
-
- Result filtering
|
|
16
|
-
|
|
17
|
-
## Language Enhancement
|
|
18
|
-
|
|
19
|
-
### enhancePromptWithLanguage
|
|
20
|
-
|
|
21
|
-
Enhance prompts with language support:
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
import { enhancePromptWithLanguage } from '@umituz/react-native-ai-generation-content';
|
|
25
|
-
|
|
26
|
-
// Enhance prompt for better results
|
|
27
|
-
const enhanced = enhancePromptWithLanguage({
|
|
28
|
-
prompt: 'A beautiful sunset',
|
|
29
|
-
targetLanguage: 'en', // Translate to English first
|
|
30
|
-
enhance: true, // Add descriptive details
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
console.log('Enhanced prompt:', enhanced);
|
|
34
|
-
// "A beautiful sunset over the ocean with vibrant orange and pink colors,
|
|
35
|
-
// captured during golden hour with dramatic cloud formations"
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### getSupportedLanguages
|
|
39
|
-
|
|
40
|
-
Get list of supported languages:
|
|
41
|
-
|
|
42
|
-
```tsx
|
|
43
|
-
import { getSupportedLanguages } from '@umituz/react-native-ai-generation-content';
|
|
44
|
-
|
|
45
|
-
const languages = getSupportedLanguages();
|
|
46
|
-
console.log('Supported languages:', languages);
|
|
47
|
-
// [
|
|
48
|
-
// { code: 'en', name: 'English' },
|
|
49
|
-
// { code: 'es', name: 'Spanish' },
|
|
50
|
-
// { code: 'fr', name: 'French' },
|
|
51
|
-
// ...
|
|
52
|
-
// ]
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### getLanguageName
|
|
56
|
-
|
|
57
|
-
Get language name from code:
|
|
58
|
-
|
|
59
|
-
```tsx
|
|
60
|
-
import { getLanguageName } from '@umituz/react-native-ai-generation-content';
|
|
61
|
-
|
|
62
|
-
const name = getLanguageName('es');
|
|
63
|
-
console.log('Language:', name); // "Spanish"
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Content Moderation
|
|
67
|
-
|
|
68
|
-
### ModerationWrapper
|
|
69
|
-
|
|
70
|
-
Wrap generation with content moderation:
|
|
71
|
-
|
|
72
|
-
```tsx
|
|
73
|
-
import { ModerationWrapper } from '@umituz/react-native-ai-generation-content';
|
|
74
|
-
|
|
75
|
-
const wrapper = new ModerationWrapper({
|
|
76
|
-
enabled: true,
|
|
77
|
-
rules: [
|
|
78
|
-
{
|
|
79
|
-
id: 'no-violence',
|
|
80
|
-
category: 'violence',
|
|
81
|
-
severity: 'high',
|
|
82
|
-
enabled: true,
|
|
83
|
-
action: 'block',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
id: 'no-adult',
|
|
87
|
-
category: 'sexual',
|
|
88
|
-
severity: 'high',
|
|
89
|
-
enabled: true,
|
|
90
|
-
action: 'block',
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
onViolation: (result) => {
|
|
94
|
-
Alert.alert('Content Warning', result.warning, [
|
|
95
|
-
{ text: 'OK', onPress: () => console.log('Acknowledged') },
|
|
96
|
-
]);
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Wrap generation
|
|
101
|
-
const wrapped = wrapper.wrap({
|
|
102
|
-
type: 'text-to-image',
|
|
103
|
-
generate: async (input) => {
|
|
104
|
-
return await generateImage(input);
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// Execute with moderation
|
|
109
|
-
const result = await wrapped.generate({
|
|
110
|
-
prompt: 'Your prompt here',
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
if (result.isModerated) {
|
|
114
|
-
console.log('Content was moderated');
|
|
115
|
-
console.log('Violations:', result.violations);
|
|
116
|
-
} else {
|
|
117
|
-
console.log('Content is safe:', result.output);
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Moderation Result
|
|
122
|
-
|
|
123
|
-
```tsx
|
|
124
|
-
interface ModerationResult {
|
|
125
|
-
isSafe: boolean;
|
|
126
|
-
isModerated: boolean;
|
|
127
|
-
violations: {
|
|
128
|
-
category: string;
|
|
129
|
-
severity: 'low' | 'medium' | 'high';
|
|
130
|
-
confidence: number;
|
|
131
|
-
}[];
|
|
132
|
-
warning?: string;
|
|
133
|
-
output?: any;
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## Synchronous Generation
|
|
138
|
-
|
|
139
|
-
### generateSynchronously
|
|
140
|
-
|
|
141
|
-
Generate content synchronously (blocks until complete):
|
|
142
|
-
|
|
143
|
-
```tsx
|
|
144
|
-
import { generateSynchronously } from '@umituz/react-native-ai-generation-content';
|
|
145
|
-
|
|
146
|
-
// ⚠️ Warning: This blocks the thread
|
|
147
|
-
const result = await generateSynchronously({
|
|
148
|
-
featureType: 'text-to-image',
|
|
149
|
-
inputData: {
|
|
150
|
-
prompt: 'A sunset',
|
|
151
|
-
},
|
|
152
|
-
userId: 'user-123',
|
|
153
|
-
timeout: 30000, // 30 second timeout
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
console.log('Result:', result);
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
**⚠️ Use with caution**: Synchronous generation blocks the UI thread. Only use for:
|
|
160
|
-
- Testing
|
|
161
|
-
- Very fast operations
|
|
162
|
-
- Background workers
|
|
163
|
-
|
|
164
|
-
### SynchronousGenerationConfig
|
|
165
|
-
|
|
166
|
-
```tsx
|
|
167
|
-
interface SynchronousGenerationConfig {
|
|
168
|
-
featureType: string;
|
|
169
|
-
inputData: any;
|
|
170
|
-
userId: string;
|
|
171
|
-
providerId?: string;
|
|
172
|
-
timeout?: number;
|
|
173
|
-
onProgress?: (progress: number) => void;
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Prompt Augmentation
|
|
178
|
-
|
|
179
|
-
### Auto-Enhance Prompts
|
|
180
|
-
|
|
181
|
-
Automatically enhance prompts for better results:
|
|
182
|
-
|
|
183
|
-
```tsx
|
|
184
|
-
import { ModerationWrapper } from '@umituz/react-native-ai-generation-content';
|
|
185
|
-
|
|
186
|
-
const wrapper = new ModerationWrapper({
|
|
187
|
-
enabled: true,
|
|
188
|
-
autoEnhance: true,
|
|
189
|
-
enhancementOptions: {
|
|
190
|
-
addDetail: true,
|
|
191
|
-
addStyle: true,
|
|
192
|
-
improveGrammar: true,
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const enhanced = await wrapper.enhancePrompt('A cat');
|
|
197
|
-
// "A highly detailed, professional photograph of a beautiful cat,
|
|
198
|
-
// with perfect lighting and composition, captured with a DSLR camera"
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Style Prompts
|
|
202
|
-
|
|
203
|
-
Add style to prompts:
|
|
204
|
-
|
|
205
|
-
```tsx
|
|
206
|
-
import { enhancePromptWithLanguage } from '@umituz/react-native-ai-generation-content';
|
|
207
|
-
|
|
208
|
-
const styled = enhancePromptWithLanguage({
|
|
209
|
-
prompt: 'A sunset',
|
|
210
|
-
style: 'photorealistic',
|
|
211
|
-
quality: 'high',
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// "A photorealistic, high-quality image of a sunset over the ocean,
|
|
215
|
-
// with dramatic lighting and vivid colors"
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## Result Filtering
|
|
219
|
-
|
|
220
|
-
### FilterResults
|
|
221
|
-
|
|
222
|
-
Filter and clean generation results:
|
|
223
|
-
|
|
224
|
-
```tsx
|
|
225
|
-
import { ModerationWrapper } from '@umituz/react-native-ai-generation-content';
|
|
226
|
-
|
|
227
|
-
const wrapper = new ModerationWrapper({
|
|
228
|
-
enabled: true,
|
|
229
|
-
filterResults: true,
|
|
230
|
-
filterOptions: {
|
|
231
|
-
removeDuplicates: true,
|
|
232
|
-
qualityThreshold: 0.7,
|
|
233
|
-
maxResults: 4,
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const filtered = await wrapper.filterResults(results);
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
## Usage Examples
|
|
241
|
-
|
|
242
|
-
### Complete Moderation Setup
|
|
243
|
-
|
|
244
|
-
```tsx
|
|
245
|
-
import { ModerationWrapper } from '@umituz/react-native-ai-generation-content';
|
|
246
|
-
|
|
247
|
-
const wrapper = new ModerationWrapper({
|
|
248
|
-
enabled: true,
|
|
249
|
-
autoEnhance: true,
|
|
250
|
-
rules: [
|
|
251
|
-
{
|
|
252
|
-
id: 'safety',
|
|
253
|
-
category: 'violence',
|
|
254
|
-
severity: 'high',
|
|
255
|
-
enabled: true,
|
|
256
|
-
action: 'block',
|
|
257
|
-
},
|
|
258
|
-
],
|
|
259
|
-
onViolation: (result) => {
|
|
260
|
-
Alert.alert('Content Warning', result.warning);
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
const generateWithModeration = wrapper.wrap({
|
|
265
|
-
type: 'text-to-image',
|
|
266
|
-
generate: async (input) => {
|
|
267
|
-
return await generateImage(input);
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Use wrapped generation
|
|
272
|
-
const result = await generateWithModeration({
|
|
273
|
-
prompt: userInput,
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
if (result.isSafe) {
|
|
277
|
-
setImage(result.output.imageUrl);
|
|
278
|
-
} else {
|
|
279
|
-
showWarning(result.violations);
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### Multi-Language Support
|
|
284
|
-
|
|
285
|
-
```tsx
|
|
286
|
-
import { enhancePromptWithLanguage } from '@umituz/react-native-ai-generation-content';
|
|
287
|
-
|
|
288
|
-
// User enters prompt in Spanish
|
|
289
|
-
const userPrompt = 'Un hermoso atardecer';
|
|
290
|
-
|
|
291
|
-
// Enhance and translate to English for AI
|
|
292
|
-
const enhanced = enhancePromptWithLanguage({
|
|
293
|
-
prompt: userPrompt,
|
|
294
|
-
targetLanguage: 'en',
|
|
295
|
-
enhance: true,
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// Use enhanced prompt for generation
|
|
299
|
-
const result = await generateImage({
|
|
300
|
-
prompt: enhanced,
|
|
301
|
-
});
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
## Best Practices
|
|
305
|
-
|
|
306
|
-
1. **Moderation**: Always enable moderation in production
|
|
307
|
-
2. **Language Enhancement**: Use for non-English prompts
|
|
308
|
-
3. **Synchronous**: Avoid synchronous generation in production
|
|
309
|
-
4. **Rule Configuration**: Customize rules based on your use case
|
|
310
|
-
5. **Violation Handling**: Provide clear feedback to users
|
|
311
|
-
|
|
312
|
-
## Error Handling
|
|
313
|
-
|
|
314
|
-
```tsx
|
|
315
|
-
try {
|
|
316
|
-
const result = await wrapped.generate({ prompt: '...' });
|
|
317
|
-
} catch (error) {
|
|
318
|
-
if (error.type === 'MODERATION_ERROR') {
|
|
319
|
-
// Content was flagged
|
|
320
|
-
showModerationError(error.violations);
|
|
321
|
-
} else if (error.type === 'TIMEOUT_ERROR') {
|
|
322
|
-
// Generation timed out
|
|
323
|
-
showTimeoutError();
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
## Related
|
|
329
|
-
|
|
330
|
-
- [Middleware](../middleware/) - Request/response middleware
|
|
331
|
-
- [Services](../services/) - AI generation services
|
|
332
|
-
- [Utils](../utils/) - Utility functions
|
|
333
|
-
|
|
334
|
-
## License
|
|
335
|
-
|
|
336
|
-
MIT
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generation Wrappers
|
|
3
|
-
* High-level API wrappers for generation orchestration
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
enhancePromptWithLanguage,
|
|
8
|
-
getSupportedLanguages,
|
|
9
|
-
getLanguageName,
|
|
10
|
-
} from "./language.wrapper";
|
|
11
|
-
|
|
12
|
-
export { ModerationWrapper } from "./moderation.wrapper";
|
|
13
|
-
export type { ModerationResult, ModerationConfig } from "./moderation.wrapper";
|
|
14
|
-
|
|
15
|
-
export { generateSynchronously } from "./synchronous-generation.wrapper";
|
|
16
|
-
export type {
|
|
17
|
-
SynchronousGenerationInput,
|
|
18
|
-
SynchronousGenerationConfig,
|
|
19
|
-
} from "./synchronous-generation.wrapper";
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Moderation Wrapper
|
|
3
|
-
* Generic content moderation interface (app provides implementation)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface ModerationResult {
|
|
7
|
-
allowed: boolean;
|
|
8
|
-
error?: string;
|
|
9
|
-
violations?: Array<{
|
|
10
|
-
rule: string;
|
|
11
|
-
suggestion?: string;
|
|
12
|
-
}>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ModerationConfig {
|
|
16
|
-
check: (content: string) => Promise<ModerationResult>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class ModerationWrapper {
|
|
20
|
-
private static config: ModerationConfig | null = null;
|
|
21
|
-
|
|
22
|
-
static configure(config: ModerationConfig): void {
|
|
23
|
-
this.config = config;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
static async checkContent(content: string): Promise<ModerationResult> {
|
|
27
|
-
if (!this.config) {
|
|
28
|
-
return { allowed: true };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return this.config.check(content);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
static isConfigured(): boolean {
|
|
35
|
-
return this.config !== null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Synchronous Generation Wrapper
|
|
3
|
-
* Direct API execution for text/image generation (wait for result)
|
|
4
|
-
* For background job execution, use async generation instead
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { GenerationResult } from "../../domain/entities";
|
|
8
|
-
import { enhancePromptWithLanguage } from "./language.wrapper";
|
|
9
|
-
import { ModerationWrapper } from "./moderation.wrapper";
|
|
10
|
-
|
|
11
|
-
export interface SynchronousGenerationInput {
|
|
12
|
-
prompt: string;
|
|
13
|
-
userId?: string;
|
|
14
|
-
type?: string;
|
|
15
|
-
languageCode?: string;
|
|
16
|
-
metadata?: Record<string, unknown>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface SynchronousGenerationConfig<T = unknown> {
|
|
20
|
-
checkCredits?: (userId: string, type: string) => Promise<boolean>;
|
|
21
|
-
deductCredits?: (userId: string, type: string) => Promise<void>;
|
|
22
|
-
execute: (prompt: string, metadata?: Record<string, unknown>) => Promise<T>;
|
|
23
|
-
/** Model ID for metadata tracking */
|
|
24
|
-
model?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function generateSynchronously<T = string>(
|
|
28
|
-
input: SynchronousGenerationInput,
|
|
29
|
-
config: SynchronousGenerationConfig<T>,
|
|
30
|
-
): Promise<GenerationResult<T>> {
|
|
31
|
-
// Check user ID if required
|
|
32
|
-
if (config.checkCredits && !input.userId) {
|
|
33
|
-
return createErrorResult("user_id_required", config.model || "unknown");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Check credits if configured
|
|
37
|
-
if (config.checkCredits && input.userId) {
|
|
38
|
-
const hasCredits = await config.checkCredits(
|
|
39
|
-
input.userId,
|
|
40
|
-
input.type || "generation",
|
|
41
|
-
);
|
|
42
|
-
if (!hasCredits) {
|
|
43
|
-
return createErrorResult("insufficient_credits", config.model || "unknown");
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Check content moderation if configured
|
|
48
|
-
if (ModerationWrapper.isConfigured()) {
|
|
49
|
-
const moderationResult = await ModerationWrapper.checkContent(input.prompt);
|
|
50
|
-
if (!moderationResult.allowed) {
|
|
51
|
-
return createErrorResult(
|
|
52
|
-
moderationResult.error || "content_policy_violation",
|
|
53
|
-
config.model || "unknown",
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Enhance prompt with language
|
|
59
|
-
const enhancedPrompt = enhancePromptWithLanguage(
|
|
60
|
-
input.prompt,
|
|
61
|
-
input.languageCode,
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// Execute generation
|
|
65
|
-
try {
|
|
66
|
-
const result = await config.execute(enhancedPrompt, input.metadata);
|
|
67
|
-
|
|
68
|
-
// Deduct credits if configured
|
|
69
|
-
if (config.deductCredits && input.userId) {
|
|
70
|
-
await config.deductCredits(input.userId, input.type || "generation");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return createSuccessResult(result, config.model || "unknown");
|
|
74
|
-
} catch (error) {
|
|
75
|
-
return createErrorResult(
|
|
76
|
-
error instanceof Error ? error.message : "generation_failed",
|
|
77
|
-
config.model || "unknown",
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function createSuccessResult<T>(data: T, model: string): GenerationResult<T> {
|
|
83
|
-
return {
|
|
84
|
-
success: true,
|
|
85
|
-
data,
|
|
86
|
-
metadata: {
|
|
87
|
-
model,
|
|
88
|
-
startTime: Date.now(),
|
|
89
|
-
endTime: Date.now(),
|
|
90
|
-
duration: 0,
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function createErrorResult<T>(error: string, model: string): GenerationResult<T> {
|
|
96
|
-
return {
|
|
97
|
-
success: false,
|
|
98
|
-
error,
|
|
99
|
-
metadata: {
|
|
100
|
-
model,
|
|
101
|
-
startTime: Date.now(),
|
|
102
|
-
endTime: Date.now(),
|
|
103
|
-
duration: 0,
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
}
|