@umituz/react-native-ai-gemini-provider 1.14.22 → 1.14.23
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/infrastructure/services/feature-input-builder.ts +85 -0
- package/src/infrastructure/services/feature-model-selector.ts +31 -0
- package/src/infrastructure/services/gemini-provider.ts +6 -59
- package/src/infrastructure/services/gemini-structured-text.service.ts +113 -0
- package/src/infrastructure/services/gemini-text-generation.service.ts +5 -94
- package/src/infrastructure/services/gemini-video-generation.service.ts +25 -129
- package/src/infrastructure/services/veo-http-client.service.ts +70 -0
- package/src/infrastructure/services/veo-polling.service.ts +119 -0
- package/src/infrastructure/utils/base-input-builders.util.ts +49 -0
- package/src/infrastructure/utils/image-feature-builders.util.ts +123 -0
- package/src/infrastructure/utils/input-builder.types.ts +44 -0
- package/src/infrastructure/utils/input-builders.util.ts +40 -263
- package/src/infrastructure/utils/video-feature-builders.util.ts +57 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Veo HTTP Client
|
|
3
|
+
* Handles HTTP requests to Google Veo API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createVideoError } from "./gemini-video-error";
|
|
7
|
+
import type { VeoOperation } from "../../domain/entities";
|
|
8
|
+
|
|
9
|
+
const VEO_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
10
|
+
|
|
11
|
+
class VeoHttpClient {
|
|
12
|
+
/**
|
|
13
|
+
* Start a text-to-video operation
|
|
14
|
+
*/
|
|
15
|
+
async startOperation(
|
|
16
|
+
model: string,
|
|
17
|
+
apiKey: string,
|
|
18
|
+
instances: Record<string, unknown>[],
|
|
19
|
+
parameters: Record<string, unknown>,
|
|
20
|
+
): Promise<VeoOperation> {
|
|
21
|
+
const url = `${VEO_API_BASE}/models/${model}:predictLongRunning`;
|
|
22
|
+
const body = { instances, parameters };
|
|
23
|
+
|
|
24
|
+
return this.postRequest(url, body, apiKey);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Fetch operation status
|
|
29
|
+
*/
|
|
30
|
+
async fetchOperationStatus(operationName: string, apiKey: string): Promise<VeoOperation> {
|
|
31
|
+
const url = `${VEO_API_BASE}/${operationName}`;
|
|
32
|
+
|
|
33
|
+
const res = await fetch(url, {
|
|
34
|
+
method: "GET",
|
|
35
|
+
headers: { "x-goog-api-key": apiKey },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
throw createVideoError("NETWORK", `Polling error: ${await res.text()}`, res.status);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return res.json() as Promise<VeoOperation>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generic POST request
|
|
47
|
+
*/
|
|
48
|
+
private async postRequest(
|
|
49
|
+
url: string,
|
|
50
|
+
body: Record<string, unknown>,
|
|
51
|
+
apiKey: string,
|
|
52
|
+
): Promise<VeoOperation> {
|
|
53
|
+
const res = await fetch(url, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: {
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
"x-goog-api-key": apiKey,
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify(body),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
throw createVideoError("OPERATION_FAILED", `Veo API error: ${await res.text()}`, res.status);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return res.json() as Promise<VeoOperation>;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const veoHttpClient = new VeoHttpClient();
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Veo Polling Service
|
|
3
|
+
* Handles polling logic for long-running video generation operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { veoHttpClient } from "./veo-http-client.service";
|
|
7
|
+
import { downloadVideoFromVeo } from "./gemini-video-downloader";
|
|
8
|
+
import { extractVideoUrl } from "./gemini-video-url-extractor";
|
|
9
|
+
import { createVideoError } from "./gemini-video-error";
|
|
10
|
+
import type {
|
|
11
|
+
VideoGenerationResult,
|
|
12
|
+
VideoGenerationProgress,
|
|
13
|
+
VeoOperation,
|
|
14
|
+
} from "../../domain/entities";
|
|
15
|
+
|
|
16
|
+
declare const __DEV__: boolean;
|
|
17
|
+
|
|
18
|
+
const POLL_INTERVAL = 10000;
|
|
19
|
+
const MAX_POLL_DURATION = 300000;
|
|
20
|
+
const MAX_POLL_ATTEMPTS = Math.floor(MAX_POLL_DURATION / POLL_INTERVAL);
|
|
21
|
+
|
|
22
|
+
/** Calculate polling progress (10-95% range) */
|
|
23
|
+
function calculateProgress(attempt: number, maxAttempts: number): number {
|
|
24
|
+
return Math.round(10 + (attempt / maxAttempts) * 85);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class VeoPollingService {
|
|
28
|
+
/**
|
|
29
|
+
* Poll an operation until completion
|
|
30
|
+
*/
|
|
31
|
+
async pollOperation(
|
|
32
|
+
operationName: string,
|
|
33
|
+
apiKey: string,
|
|
34
|
+
model: string,
|
|
35
|
+
onProgress?: (progress: VideoGenerationProgress) => void,
|
|
36
|
+
): Promise<VideoGenerationResult> {
|
|
37
|
+
let attempts = 0;
|
|
38
|
+
onProgress?.({ status: "processing", progress: 10 });
|
|
39
|
+
|
|
40
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.log("[VeoPolling] Starting polling...", { operationName, maxAttempts: MAX_POLL_ATTEMPTS });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
while (attempts < MAX_POLL_ATTEMPTS) {
|
|
46
|
+
await this.delay(POLL_INTERVAL);
|
|
47
|
+
attempts++;
|
|
48
|
+
const progress = calculateProgress(attempts, MAX_POLL_ATTEMPTS);
|
|
49
|
+
onProgress?.({ status: "processing", progress });
|
|
50
|
+
|
|
51
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.log("[VeoPolling] Poll attempt:", { attempts, progress });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const operation = await veoHttpClient.fetchOperationStatus(operationName, apiKey);
|
|
57
|
+
|
|
58
|
+
if (operation.error) {
|
|
59
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
60
|
+
// eslint-disable-next-line no-console
|
|
61
|
+
console.error("[VeoPolling] Operation error:", operation.error);
|
|
62
|
+
}
|
|
63
|
+
throw createVideoError("OPERATION_FAILED", operation.error.message, operation.error.code);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (operation.done) {
|
|
67
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
68
|
+
// eslint-disable-next-line no-console
|
|
69
|
+
console.log("[VeoPolling] Operation completed!");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return this.handleCompletedOperation(operation, apiKey, model, onProgress);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw createVideoError("TIMEOUT", `Operation timed out after ${MAX_POLL_DURATION / 1000}s`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Handle completed operation and download video
|
|
81
|
+
*/
|
|
82
|
+
private async handleCompletedOperation(
|
|
83
|
+
operation: VeoOperation,
|
|
84
|
+
apiKey: string,
|
|
85
|
+
model: string,
|
|
86
|
+
onProgress?: (progress: VideoGenerationProgress) => void,
|
|
87
|
+
): Promise<VideoGenerationResult> {
|
|
88
|
+
const rawVideoUrl = extractVideoUrl(operation);
|
|
89
|
+
|
|
90
|
+
if (!rawVideoUrl) {
|
|
91
|
+
throw createVideoError("OPERATION_FAILED", "No video URL in response");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.log("[VeoPolling] Downloading video...");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const result = await downloadVideoFromVeo(rawVideoUrl, apiKey);
|
|
100
|
+
onProgress?.({ status: "completed", progress: 100 });
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
videoUrl: result.base64DataUri,
|
|
104
|
+
metadata: {
|
|
105
|
+
duration: 8,
|
|
106
|
+
resolution: "720p",
|
|
107
|
+
aspectRatio: "16:9",
|
|
108
|
+
model,
|
|
109
|
+
operationName: operation.name,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private delay(ms: number): Promise<void> {
|
|
115
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const veoPollingService = new VeoPollingService();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Input Builders
|
|
3
|
+
* Provides core input construction functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Build Gemini single image input format
|
|
8
|
+
*/
|
|
9
|
+
export function buildSingleImageInput(
|
|
10
|
+
base64: string,
|
|
11
|
+
prompt: string,
|
|
12
|
+
): Record<string, unknown> {
|
|
13
|
+
const cleanBase64 = base64.replace(/^data:image\/\w+;base64,/, "");
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
contents: [
|
|
17
|
+
{
|
|
18
|
+
parts: [
|
|
19
|
+
{ text: prompt },
|
|
20
|
+
{ inlineData: { mimeType: "image/jpeg", data: cleanBase64 } },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build Gemini dual image input format
|
|
29
|
+
*/
|
|
30
|
+
export function buildDualImageInput(
|
|
31
|
+
sourceBase64: string,
|
|
32
|
+
targetBase64: string,
|
|
33
|
+
prompt: string,
|
|
34
|
+
): Record<string, unknown> {
|
|
35
|
+
const cleanSource = sourceBase64.replace(/^data:image\/\w+;base64,/, "");
|
|
36
|
+
const cleanTarget = targetBase64.replace(/^data:image\/\w+;base64,/, "");
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
contents: [
|
|
40
|
+
{
|
|
41
|
+
parts: [
|
|
42
|
+
{ text: prompt },
|
|
43
|
+
{ inlineData: { mimeType: "image/jpeg", data: cleanSource } },
|
|
44
|
+
{ inlineData: { mimeType: "image/jpeg", data: cleanTarget } },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Feature Input Builders
|
|
3
|
+
* Constructs Gemini API inputs for image processing features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { buildSingleImageInput, buildDualImageInput } from "./base-input-builders.util";
|
|
7
|
+
import type {
|
|
8
|
+
UpscaleOptions,
|
|
9
|
+
PhotoRestoreOptions,
|
|
10
|
+
FaceSwapOptions,
|
|
11
|
+
AnimeSelfieOptions,
|
|
12
|
+
RemoveBackgroundOptions,
|
|
13
|
+
RemoveObjectOptions,
|
|
14
|
+
ReplaceBackgroundOptions,
|
|
15
|
+
} from "./input-builder.types";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build upscale input for Gemini
|
|
19
|
+
*/
|
|
20
|
+
export function buildUpscaleInput(
|
|
21
|
+
base64: string,
|
|
22
|
+
options?: UpscaleOptions,
|
|
23
|
+
): Record<string, unknown> {
|
|
24
|
+
const scale = options?.scaleFactor || 2;
|
|
25
|
+
const faceEnhance = options?.enhanceFaces
|
|
26
|
+
? " Enhance facial features."
|
|
27
|
+
: "";
|
|
28
|
+
|
|
29
|
+
const prompt = `Upscale this image by ${scale}x. Preserve all details, colors and enhance clarity.${faceEnhance}`;
|
|
30
|
+
|
|
31
|
+
return buildSingleImageInput(base64, prompt);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build photo restore input for Gemini
|
|
36
|
+
*/
|
|
37
|
+
export function buildPhotoRestoreInput(
|
|
38
|
+
base64: string,
|
|
39
|
+
options?: PhotoRestoreOptions,
|
|
40
|
+
): Record<string, unknown> {
|
|
41
|
+
const faceEnhance = options?.enhanceFaces !== false
|
|
42
|
+
? " Enhance facial features and expressions."
|
|
43
|
+
: "";
|
|
44
|
+
|
|
45
|
+
const prompt = `Restore this photo. Remove noise, scratches, and damage while preserving original content.${faceEnhance}`;
|
|
46
|
+
|
|
47
|
+
return buildSingleImageInput(base64, prompt);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build face swap input for Gemini
|
|
52
|
+
*/
|
|
53
|
+
export function buildFaceSwapInput(
|
|
54
|
+
sourceBase64: string,
|
|
55
|
+
targetBase64: string,
|
|
56
|
+
_options?: FaceSwapOptions,
|
|
57
|
+
): Record<string, unknown> {
|
|
58
|
+
const prompt = "Swap the face from the first image onto the person in the second image. Preserve lighting, expression, and natural appearance.";
|
|
59
|
+
|
|
60
|
+
return buildDualImageInput(sourceBase64, targetBase64, prompt);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build anime selfie input for Gemini
|
|
65
|
+
*/
|
|
66
|
+
export function buildAnimeSelfieInput(
|
|
67
|
+
base64: string,
|
|
68
|
+
options?: AnimeSelfieOptions,
|
|
69
|
+
): Record<string, unknown> {
|
|
70
|
+
const style = options?.style || "anime";
|
|
71
|
+
|
|
72
|
+
const prompt = `Convert this photo into ${style} style. Preserve facial features and expression while applying artistic transformation.`;
|
|
73
|
+
|
|
74
|
+
return buildSingleImageInput(base64, prompt);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build remove background input for Gemini
|
|
79
|
+
*/
|
|
80
|
+
export function buildRemoveBackgroundInput(
|
|
81
|
+
base64: string,
|
|
82
|
+
_options?: RemoveBackgroundOptions,
|
|
83
|
+
): Record<string, unknown> {
|
|
84
|
+
const prompt = "Remove the background from this image. Keep only the main subject with transparent background.";
|
|
85
|
+
|
|
86
|
+
return buildSingleImageInput(base64, prompt);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build remove object (inpaint) input for Gemini
|
|
91
|
+
*/
|
|
92
|
+
export function buildRemoveObjectInput(
|
|
93
|
+
base64: string,
|
|
94
|
+
options?: RemoveObjectOptions,
|
|
95
|
+
): Record<string, unknown> {
|
|
96
|
+
const objectDescription = options?.prompt || "the unwanted object";
|
|
97
|
+
|
|
98
|
+
const prompt = `Remove ${objectDescription} from this image and fill the area naturally with the surrounding background.`;
|
|
99
|
+
|
|
100
|
+
return buildSingleImageInput(base64, prompt);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build replace background input for Gemini
|
|
105
|
+
*/
|
|
106
|
+
export function buildReplaceBackgroundInput(
|
|
107
|
+
base64: string,
|
|
108
|
+
options: ReplaceBackgroundOptions,
|
|
109
|
+
): Record<string, unknown> {
|
|
110
|
+
const prompt = `Replace the background with: ${options.prompt}. Keep the main subject intact and blend naturally.`;
|
|
111
|
+
|
|
112
|
+
return buildSingleImageInput(base64, prompt);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Build HD touch up input (same as upscale with face enhancement)
|
|
117
|
+
*/
|
|
118
|
+
export function buildHDTouchUpInput(
|
|
119
|
+
base64: string,
|
|
120
|
+
options?: UpscaleOptions,
|
|
121
|
+
): Record<string, unknown> {
|
|
122
|
+
return buildUpscaleInput(base64, { ...options, enhanceFaces: true });
|
|
123
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Builder Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface UpscaleOptions {
|
|
6
|
+
scaleFactor?: number;
|
|
7
|
+
enhanceFaces?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PhotoRestoreOptions {
|
|
11
|
+
enhanceFaces?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface FaceSwapOptions {
|
|
15
|
+
// No additional options
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AnimeSelfieOptions {
|
|
19
|
+
style?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RemoveBackgroundOptions {
|
|
23
|
+
// No additional options
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RemoveObjectOptions {
|
|
27
|
+
mask?: string;
|
|
28
|
+
prompt?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ReplaceBackgroundOptions {
|
|
32
|
+
prompt: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface VideoFromImageOptions {
|
|
36
|
+
motion_prompt?: string;
|
|
37
|
+
duration?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface VideoFromDualImageOptions {
|
|
41
|
+
target_image?: string;
|
|
42
|
+
motion_prompt?: string;
|
|
43
|
+
duration?: number;
|
|
44
|
+
}
|