@umituz/react-native-ai-gemini-provider 2.0.9 → 2.0.11
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 -3
- package/src/domain/entities/gemini.types.ts +1 -32
- package/src/domain/entities/models.ts +1 -44
- package/src/index.ts +0 -7
- package/src/infrastructure/content/ContentBuilder.ts +3 -37
- package/src/infrastructure/job/JobManager.ts +0 -4
- package/src/infrastructure/services/gemini-client-core.service.ts +0 -6
- package/src/infrastructure/services/gemini-provider.ts +1 -18
- package/src/infrastructure/services/gemini-streaming.service.ts +1 -14
- package/src/infrastructure/services/gemini-structured-text.service.ts +0 -45
- package/src/infrastructure/services/gemini-text-generation.service.ts +1 -56
- package/src/infrastructure/services/generation-executor.ts +0 -30
- package/src/infrastructure/services/job-processor.ts +1 -5
- package/src/infrastructure/services/provider-initializer.ts +0 -7
- package/src/infrastructure/utils/async-state.util.ts +1 -1
- package/src/infrastructure/utils/gemini-data-transformer.util.ts +0 -10
- package/src/infrastructure/utils/index.ts +5 -45
- package/src/infrastructure/utils/rate-limiter.util.ts +1 -3
- package/src/presentation/hooks/use-gemini.ts +3 -20
- package/src/providers/ProviderFactory.ts +0 -1
- package/src/infrastructure/utils/DATA_TRANSFORMER_UTILS.md +0 -175
- package/src/infrastructure/utils/ERROR_MAPPER.md +0 -170
- package/src/infrastructure/utils/ERROR_UTILITIES.md +0 -208
- package/src/infrastructure/utils/IMAGE_PREPARER_UTILS.md +0 -185
- package/src/infrastructure/utils/MODEL_VALIDATION_UTILS.md +0 -189
- package/src/infrastructure/utils/PERFORMANCE_UTILITIES.md +0 -477
- package/src/infrastructure/utils/PERFORMANCE_UTILS.md +0 -219
- package/src/infrastructure/utils/model-validation.util.ts +0 -81
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-gemini-provider",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
4
4
|
"description": "Google Gemini AI text generation provider for React Native applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
"typecheck": "tsc --noEmit",
|
|
9
9
|
"lint": "eslint src --ext .ts,.tsx --max-warnings 0",
|
|
10
10
|
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
11
|
-
"content-check": "node scripts/content-check.js",
|
|
12
|
-
"prepublishOnly": "npm run content-check",
|
|
13
11
|
"version:patch": "npm version patch -m 'chore: release v%s'",
|
|
14
12
|
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
15
13
|
"version:major": "npm version major -m 'chore: release v%s'"
|
|
@@ -14,12 +14,6 @@ export interface GeminiConfig {
|
|
|
14
14
|
defaultTimeoutMs?: number;
|
|
15
15
|
/** Model used for text generation (default: gemini-2.5-flash-lite) */
|
|
16
16
|
textModel?: string;
|
|
17
|
-
/** Model used for text-to-image generation (default: imagen-4.0-generate-001) */
|
|
18
|
-
textToImageModel?: string;
|
|
19
|
-
/** Model used for image editing/transformation (default: gemini-2.5-flash-image) */
|
|
20
|
-
imageEditModel?: string;
|
|
21
|
-
/** Model used for video generation (default: veo-3.1-fast-generate-preview) */
|
|
22
|
-
videoGenerationModel?: string;
|
|
23
17
|
}
|
|
24
18
|
|
|
25
19
|
export type GeminiGenerationConfig = Omit<GenerationConfig, "responseSchema"> & {
|
|
@@ -50,9 +44,7 @@ export interface GeminiContent {
|
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
export type GeminiPart =
|
|
53
|
-
| { text: string }
|
|
54
|
-
| { inlineData: { mimeType: string; data: string } }
|
|
55
|
-
| { fileData: { mimeType: string; fileUri: string } };
|
|
47
|
+
| { text: string };
|
|
56
48
|
|
|
57
49
|
export interface GeminiRequest {
|
|
58
50
|
contents: GeminiContent[];
|
|
@@ -108,26 +100,3 @@ export interface GeminiModel {
|
|
|
108
100
|
supportedCapabilities?: string[];
|
|
109
101
|
}
|
|
110
102
|
|
|
111
|
-
/**
|
|
112
|
-
* Result from image generation
|
|
113
|
-
*/
|
|
114
|
-
export interface GeminiImageGenerationResult {
|
|
115
|
-
/** Generated text (story, caption, etc.) */
|
|
116
|
-
text?: string;
|
|
117
|
-
/** Data URL of the generated image (data:image/png;base64,...) */
|
|
118
|
-
imageUrl?: string;
|
|
119
|
-
/** Raw base64 image data */
|
|
120
|
-
imageBase64?: string;
|
|
121
|
-
/** MIME type of the generated image */
|
|
122
|
-
mimeType?: string;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Input for image generation
|
|
127
|
-
*/
|
|
128
|
-
export interface GeminiImageInput {
|
|
129
|
-
/** Base64 encoded image data (with or without data URL prefix) */
|
|
130
|
-
base64: string;
|
|
131
|
-
/** MIME type (e.g., "image/png", "image/jpeg") */
|
|
132
|
-
mimeType: string;
|
|
133
|
-
}
|
|
@@ -8,44 +8,12 @@
|
|
|
8
8
|
* Available Gemini models
|
|
9
9
|
* Pricing (per 1M tokens):
|
|
10
10
|
* - Flash-Lite: $0.10 input / $0.40 output (FREE: 1000 req/day)
|
|
11
|
-
* - Flash: $0.15 input / $0.60 output (FREE: 20 req/day)
|
|
12
|
-
* - Pro: $1.25 input / $10.00 output (FREE: 25 req/day)
|
|
13
11
|
*/
|
|
14
12
|
export const GEMINI_MODELS = {
|
|
15
13
|
// Text generation models (ordered by cost: cheapest first)
|
|
16
14
|
TEXT: {
|
|
17
15
|
/** Most cost-effective, 1000 free requests/day */
|
|
18
16
|
FLASH_LITE: "gemini-2.5-flash-lite",
|
|
19
|
-
/** Good balance of speed and quality */
|
|
20
|
-
FLASH: "gemini-2.5-flash",
|
|
21
|
-
/** Highest quality, best for complex reasoning */
|
|
22
|
-
PRO: "gemini-2.5-pro",
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
// Text-to-Image models (Imagen 4.0) - generates images from text only
|
|
26
|
-
TEXT_TO_IMAGE: {
|
|
27
|
-
DEFAULT: "imagen-4.0-generate-001",
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
// Image editing models - transforms/edits images with input image + prompt
|
|
31
|
-
// gemini-2.5-flash-image is the most cost-effective (500 images/day free tier)
|
|
32
|
-
IMAGE_EDIT: {
|
|
33
|
-
DEFAULT: "gemini-2.5-flash-image",
|
|
34
|
-
HIGH_QUALITY: "gemini-3-pro-image-preview",
|
|
35
|
-
LEGACY: "gemini-2.0-flash-preview-image-generation",
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
// Video understanding models (analysis only)
|
|
39
|
-
VIDEO: {
|
|
40
|
-
FLASH: "gemini-2.5-flash",
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
// Video generation models (Google Veo)
|
|
44
|
-
// See: https://ai.google.dev/gemini-api/docs/video
|
|
45
|
-
VIDEO_GENERATION: {
|
|
46
|
-
DEFAULT: "veo-3.1-generate-preview",
|
|
47
|
-
VEO_3: "veo-3-generate-preview",
|
|
48
|
-
VEO_2: "veo-2-generate",
|
|
49
17
|
},
|
|
50
18
|
} as const;
|
|
51
19
|
|
|
@@ -57,10 +25,6 @@ export const GEMINI_MODELS = {
|
|
|
57
25
|
export const DEFAULT_MODELS = {
|
|
58
26
|
/** Flash-Lite: Cheapest & highest free tier (1000/day) */
|
|
59
27
|
TEXT: GEMINI_MODELS.TEXT.FLASH_LITE,
|
|
60
|
-
TEXT_TO_IMAGE: GEMINI_MODELS.TEXT_TO_IMAGE.DEFAULT,
|
|
61
|
-
IMAGE_EDIT: GEMINI_MODELS.IMAGE_EDIT.DEFAULT,
|
|
62
|
-
VIDEO: GEMINI_MODELS.VIDEO.FLASH,
|
|
63
|
-
VIDEO_GENERATION: GEMINI_MODELS.VIDEO_GENERATION.DEFAULT,
|
|
64
28
|
} as const;
|
|
65
29
|
|
|
66
30
|
/**
|
|
@@ -68,8 +32,6 @@ export const DEFAULT_MODELS = {
|
|
|
68
32
|
*/
|
|
69
33
|
export const MODEL_PRICING = {
|
|
70
34
|
[GEMINI_MODELS.TEXT.FLASH_LITE]: { input: 0.10, output: 0.40, freePerDay: 1000 },
|
|
71
|
-
[GEMINI_MODELS.TEXT.FLASH]: { input: 0.15, output: 0.60, freePerDay: 20 },
|
|
72
|
-
[GEMINI_MODELS.TEXT.PRO]: { input: 1.25, output: 10.00, freePerDay: 25 },
|
|
73
35
|
} as const;
|
|
74
36
|
|
|
75
37
|
/**
|
|
@@ -77,11 +39,6 @@ export const MODEL_PRICING = {
|
|
|
77
39
|
*/
|
|
78
40
|
export const RESPONSE_MODALITIES = {
|
|
79
41
|
TEXT_ONLY: ["TEXT"] as const,
|
|
80
|
-
IMAGE_ONLY: ["IMAGE"] as const,
|
|
81
|
-
VIDEO_ONLY: ["VIDEO"] as const,
|
|
82
|
-
TEXT_AND_IMAGE: ["TEXT", "IMAGE"] as const,
|
|
83
42
|
} as const;
|
|
84
43
|
|
|
85
|
-
export type ResponseModality = "TEXT"
|
|
86
|
-
|
|
87
|
-
|
|
44
|
+
export type ResponseModality = "TEXT";
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,6 @@ export type {
|
|
|
21
21
|
GeminiPromptFeedback,
|
|
22
22
|
GeminiUsageMetadata,
|
|
23
23
|
GeminiModel,
|
|
24
|
-
GeminiImageInput,
|
|
25
24
|
GeminiErrorInfo,
|
|
26
25
|
GeminiApiError,
|
|
27
26
|
ResponseModality,
|
|
@@ -56,12 +55,6 @@ export {
|
|
|
56
55
|
isGeminiErrorRetryable,
|
|
57
56
|
categorizeGeminiError,
|
|
58
57
|
createGeminiError,
|
|
59
|
-
isValidModel,
|
|
60
|
-
validateModel,
|
|
61
|
-
getSafeModel,
|
|
62
|
-
isTextModel,
|
|
63
|
-
getModelCategory,
|
|
64
|
-
getAllValidModels,
|
|
65
58
|
measureAsync,
|
|
66
59
|
measureSync,
|
|
67
60
|
debounce,
|
|
@@ -3,36 +3,15 @@
|
|
|
3
3
|
* Constructs Gemini API content from various input formats
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GeminiContent
|
|
7
|
-
import { extractBase64Data } from "../utils/gemini-data-transformer.util";
|
|
6
|
+
import type { GeminiContent } from "../../domain/entities";
|
|
8
7
|
|
|
9
8
|
export class ContentBuilder {
|
|
10
9
|
buildContents(input: Record<string, unknown>): GeminiContent[] {
|
|
11
10
|
const contents: GeminiContent[] = [];
|
|
12
11
|
|
|
13
12
|
if (typeof input.prompt === "string") {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// Handle single image
|
|
17
|
-
if (input.image_url && typeof input.image_url === "string") {
|
|
18
|
-
const imageData = this.parseImageUrl(input.image_url);
|
|
19
|
-
if (imageData) {
|
|
20
|
-
parts.push({ inlineData: imageData });
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Handle multiple images
|
|
25
|
-
if (Array.isArray(input.images)) {
|
|
26
|
-
for (const img of input.images as GeminiImageInput[]) {
|
|
27
|
-
parts.push({
|
|
28
|
-
inlineData: {
|
|
29
|
-
mimeType: img.mimeType,
|
|
30
|
-
data: extractBase64Data(img.base64),
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
13
|
+
// Create parts array properly typed as GeminiPart[]
|
|
14
|
+
const parts = [{ text: input.prompt }];
|
|
36
15
|
contents.push({ parts, role: "user" });
|
|
37
16
|
}
|
|
38
17
|
|
|
@@ -42,17 +21,4 @@ export class ContentBuilder {
|
|
|
42
21
|
|
|
43
22
|
return contents;
|
|
44
23
|
}
|
|
45
|
-
|
|
46
|
-
private parseImageUrl(
|
|
47
|
-
imageUrl: string,
|
|
48
|
-
): { mimeType: string; data: string } | null {
|
|
49
|
-
const base64Match = imageUrl.match(/^data:([^;]+);base64,(.+)$/);
|
|
50
|
-
if (base64Match) {
|
|
51
|
-
return {
|
|
52
|
-
mimeType: base64Match[1],
|
|
53
|
-
data: base64Match[2],
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
24
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Handles async job submission, tracking, and status management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
declare const __DEV__: boolean;
|
|
7
6
|
|
|
8
7
|
export type AIJobStatusType = "IN_QUEUE" | "IN_PROGRESS" | "COMPLETED" | "FAILED";
|
|
9
8
|
|
|
@@ -38,9 +37,6 @@ export class JobManager {
|
|
|
38
37
|
status: "IN_QUEUE",
|
|
39
38
|
});
|
|
40
39
|
|
|
41
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
42
|
-
console.log("[JobManager] Job submitted:", { requestId, model });
|
|
43
|
-
}
|
|
44
40
|
|
|
45
41
|
return {
|
|
46
42
|
requestId,
|
|
@@ -15,8 +15,6 @@ const DEFAULT_CONFIG: Partial<GeminiConfig> = {
|
|
|
15
15
|
maxDelay: 10000,
|
|
16
16
|
defaultTimeoutMs: 60000,
|
|
17
17
|
textModel: DEFAULT_MODELS.TEXT,
|
|
18
|
-
textToImageModel: DEFAULT_MODELS.TEXT_TO_IMAGE,
|
|
19
|
-
imageEditModel: DEFAULT_MODELS.IMAGE_EDIT,
|
|
20
18
|
};
|
|
21
19
|
|
|
22
20
|
class GeminiClientCoreService {
|
|
@@ -38,8 +36,6 @@ class GeminiClientCoreService {
|
|
|
38
36
|
console.log("[GeminiClient] initialize() called", {
|
|
39
37
|
hasApiKey: !!config.apiKey,
|
|
40
38
|
textModel: config.textModel,
|
|
41
|
-
textToImageModel: config.textToImageModel,
|
|
42
|
-
imageEditModel: config.imageEditModel,
|
|
43
39
|
});
|
|
44
40
|
}
|
|
45
41
|
|
|
@@ -51,8 +47,6 @@ class GeminiClientCoreService {
|
|
|
51
47
|
// eslint-disable-next-line no-console
|
|
52
48
|
console.log("[GeminiClient] initialized successfully", {
|
|
53
49
|
textModel: this.config.textModel,
|
|
54
|
-
textToImageModel: this.config.textToImageModel,
|
|
55
|
-
imageEditModel: this.config.imageEditModel,
|
|
56
50
|
maxRetries: this.config.maxRetries,
|
|
57
51
|
});
|
|
58
52
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Text-only AI provider for Google Gemini
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GeminiConfig
|
|
6
|
+
import type { GeminiConfig } from "../../domain/entities";
|
|
7
7
|
import { providerInitializer } from "./provider-initializer";
|
|
8
8
|
import { generationExecutor } from "./generation-executor";
|
|
9
9
|
|
|
@@ -36,23 +36,6 @@ export class GeminiProvider {
|
|
|
36
36
|
return generationExecutor.executeTextGeneration(prompt, model);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* Generate text with images (multimodal)
|
|
41
|
-
* Useful for "describe this image" scenarios
|
|
42
|
-
*/
|
|
43
|
-
async generateTextWithImages(
|
|
44
|
-
prompt: string,
|
|
45
|
-
images: GeminiImageInput[],
|
|
46
|
-
model: string,
|
|
47
|
-
): Promise<string> {
|
|
48
|
-
const result = await generationExecutor.generateWithImages(
|
|
49
|
-
model,
|
|
50
|
-
prompt,
|
|
51
|
-
images,
|
|
52
|
-
);
|
|
53
|
-
return result.text;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
39
|
/**
|
|
57
40
|
* Generate structured JSON response
|
|
58
41
|
*/
|
|
@@ -23,20 +23,7 @@ class GeminiStreamingService {
|
|
|
23
23
|
|
|
24
24
|
const sdkContents = contents.map((content) => ({
|
|
25
25
|
role: content.role || "user",
|
|
26
|
-
parts: content.parts.map((part) => {
|
|
27
|
-
if ("text" in part) {
|
|
28
|
-
return { text: part.text };
|
|
29
|
-
}
|
|
30
|
-
if ("inlineData" in part) {
|
|
31
|
-
return {
|
|
32
|
-
inlineData: {
|
|
33
|
-
mimeType: part.inlineData.mimeType,
|
|
34
|
-
data: part.inlineData.data,
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
return part;
|
|
39
|
-
}),
|
|
26
|
+
parts: content.parts.map((part) => ({ text: part.text })),
|
|
40
27
|
}));
|
|
41
28
|
|
|
42
29
|
const result = await genModel.generateContentStream({
|
|
@@ -40,44 +40,6 @@ class GeminiStructuredTextService {
|
|
|
40
40
|
return this.parseJSONResponse<T>(response);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/**
|
|
44
|
-
* Generate structured JSON response with images and schema
|
|
45
|
-
*/
|
|
46
|
-
async generateStructuredTextWithImages<T>(
|
|
47
|
-
model: string,
|
|
48
|
-
prompt: string,
|
|
49
|
-
images: Array<{ base64: string; mimeType: string }>,
|
|
50
|
-
schema: Record<string, unknown>,
|
|
51
|
-
config?: Omit<GeminiGenerationConfig, "responseMimeType" | "responseSchema">,
|
|
52
|
-
): Promise<T> {
|
|
53
|
-
const generationConfig: GeminiGenerationConfig = {
|
|
54
|
-
...config,
|
|
55
|
-
responseMimeType: "application/json",
|
|
56
|
-
responseSchema: schema as unknown as undefined,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const parts: GeminiContent["parts"] = [{ text: prompt }];
|
|
60
|
-
|
|
61
|
-
for (const image of images) {
|
|
62
|
-
parts.push({
|
|
63
|
-
inlineData: {
|
|
64
|
-
mimeType: image.mimeType,
|
|
65
|
-
data: image.base64,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const contents: GeminiContent[] = [{ parts, role: "user" }];
|
|
71
|
-
|
|
72
|
-
const response = await geminiTextGenerationService.generateContent(
|
|
73
|
-
model,
|
|
74
|
-
contents,
|
|
75
|
-
generationConfig,
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
return this.parseJSONResponse<T>(response);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
43
|
/**
|
|
82
44
|
* Parse JSON response from Gemini
|
|
83
45
|
*/
|
|
@@ -98,13 +60,6 @@ class GeminiStructuredTextService {
|
|
|
98
60
|
try {
|
|
99
61
|
return JSON.parse(cleanedText) as T;
|
|
100
62
|
} catch (error) {
|
|
101
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
102
|
-
// eslint-disable-next-line no-console
|
|
103
|
-
console.error("[Gemini] Failed to parse structured response:", {
|
|
104
|
-
text: cleanedText.substring(0, 200),
|
|
105
|
-
error: error instanceof Error ? error.message : String(error),
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
63
|
throw new Error(`Failed to parse structured response: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
64
|
}
|
|
110
65
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { geminiClientCoreService } from "./gemini-client-core.service";
|
|
7
7
|
import { geminiRetryService } from "./gemini-retry.service";
|
|
8
|
-
import {
|
|
8
|
+
import { extractTextFromResponse } from "../utils/gemini-data-transformer.util";
|
|
9
9
|
import type {
|
|
10
10
|
GeminiContent,
|
|
11
11
|
GeminiGenerationConfig,
|
|
@@ -34,17 +34,6 @@ class GeminiTextGenerationService {
|
|
|
34
34
|
const sdkContents = contents.map((content) => ({
|
|
35
35
|
role: content.role || "user",
|
|
36
36
|
parts: content.parts.map((part) => {
|
|
37
|
-
if ("text" in part) {
|
|
38
|
-
return { text: part.text };
|
|
39
|
-
}
|
|
40
|
-
if ("inlineData" in part) {
|
|
41
|
-
return {
|
|
42
|
-
inlineData: {
|
|
43
|
-
mimeType: part.inlineData.mimeType,
|
|
44
|
-
data: part.inlineData.data,
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
37
|
return part;
|
|
49
38
|
}),
|
|
50
39
|
}));
|
|
@@ -75,14 +64,6 @@ class GeminiTextGenerationService {
|
|
|
75
64
|
if ("text" in part && part.text !== undefined) {
|
|
76
65
|
return { text: part.text };
|
|
77
66
|
}
|
|
78
|
-
if ("inlineData" in part && part.inlineData) {
|
|
79
|
-
return {
|
|
80
|
-
inlineData: {
|
|
81
|
-
mimeType: part.inlineData.mimeType,
|
|
82
|
-
data: part.inlineData.data,
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
67
|
return null;
|
|
87
68
|
})
|
|
88
69
|
.filter((p): p is GeminiPart => p !== null),
|
|
@@ -122,42 +103,6 @@ class GeminiTextGenerationService {
|
|
|
122
103
|
/**
|
|
123
104
|
* Generate content with images (multimodal)
|
|
124
105
|
*/
|
|
125
|
-
async generateWithImages(
|
|
126
|
-
model: string,
|
|
127
|
-
prompt: string,
|
|
128
|
-
images: Array<{ base64: string; mimeType: string }>,
|
|
129
|
-
config?: GeminiGenerationConfig,
|
|
130
|
-
): Promise<GeminiResponse> {
|
|
131
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
132
|
-
// eslint-disable-next-line no-console
|
|
133
|
-
console.log("[GeminiClient] generateWithImages() called", {
|
|
134
|
-
model,
|
|
135
|
-
promptLength: prompt.length,
|
|
136
|
-
imagesCount: images.length,
|
|
137
|
-
imageMimeTypes: images.map(i => i.mimeType),
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const parts: GeminiContent["parts"] = [{ text: prompt }];
|
|
142
|
-
|
|
143
|
-
for (const image of images) {
|
|
144
|
-
parts.push({
|
|
145
|
-
inlineData: {
|
|
146
|
-
mimeType: image.mimeType,
|
|
147
|
-
data: extractBase64Data(image.base64),
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const contents: GeminiContent[] = [{ parts, role: "user" }];
|
|
153
|
-
|
|
154
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
155
|
-
// eslint-disable-next-line no-console
|
|
156
|
-
console.log("[GeminiClient] generateWithImages() → calling generateContent()");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return this.generateContent(model, contents, config);
|
|
160
|
-
}
|
|
161
106
|
}
|
|
162
107
|
|
|
163
108
|
export const geminiTextGenerationService = new GeminiTextGenerationService();
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
* Handles execution of text generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GeminiImageInput } from "../../domain/entities";
|
|
7
6
|
import { geminiTextGenerationService } from "./gemini-text-generation.service";
|
|
8
7
|
import { geminiStructuredTextService } from "./gemini-structured-text.service";
|
|
9
8
|
|
|
10
|
-
declare const __DEV__: boolean;
|
|
11
9
|
|
|
12
10
|
export interface ExecutionOptions {
|
|
13
11
|
onProgress?: (progress: number) => void;
|
|
@@ -15,7 +13,6 @@ export interface ExecutionOptions {
|
|
|
15
13
|
|
|
16
14
|
export interface GenerationInput {
|
|
17
15
|
prompt?: string;
|
|
18
|
-
images?: GeminiImageInput[];
|
|
19
16
|
generationConfig?: unknown;
|
|
20
17
|
}
|
|
21
18
|
|
|
@@ -26,9 +23,6 @@ export class GenerationExecutor {
|
|
|
26
23
|
* Execute text generation
|
|
27
24
|
*/
|
|
28
25
|
async executeTextGeneration(prompt: string, model: string): Promise<string> {
|
|
29
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
30
|
-
console.log("[GenerationExecutor] executeTextGeneration() called", { model });
|
|
31
|
-
}
|
|
32
26
|
|
|
33
27
|
const response = await geminiTextGenerationService.generateContent(
|
|
34
28
|
model,
|
|
@@ -46,9 +40,6 @@ export class GenerationExecutor {
|
|
|
46
40
|
schema: Record<string, unknown>,
|
|
47
41
|
model: string,
|
|
48
42
|
): Promise<T> {
|
|
49
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
50
|
-
console.log("[GenerationExecutor] executeStructuredGeneration() called", { model });
|
|
51
|
-
}
|
|
52
43
|
|
|
53
44
|
return geminiStructuredTextService.generateStructuredText<T>(
|
|
54
45
|
model,
|
|
@@ -57,27 +48,6 @@ export class GenerationExecutor {
|
|
|
57
48
|
);
|
|
58
49
|
}
|
|
59
50
|
|
|
60
|
-
/**
|
|
61
|
-
* Generate text with images (multimodal)
|
|
62
|
-
*/
|
|
63
|
-
async generateWithImages(
|
|
64
|
-
model: string,
|
|
65
|
-
prompt: string,
|
|
66
|
-
images: GeminiImageInput[],
|
|
67
|
-
): Promise<{ text: string; response: unknown }> {
|
|
68
|
-
const response = await geminiTextGenerationService.generateWithImages(
|
|
69
|
-
model,
|
|
70
|
-
prompt,
|
|
71
|
-
images,
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
const text = response.candidates?.[0]?.content.parts
|
|
75
|
-
.filter((p): p is { text: string } => "text" in p)
|
|
76
|
-
.map((p) => p.text)
|
|
77
|
-
.join("") || "";
|
|
78
|
-
|
|
79
|
-
return { text, response };
|
|
80
|
-
}
|
|
81
51
|
|
|
82
52
|
/**
|
|
83
53
|
* Extract text from Gemini response
|
|
@@ -7,7 +7,6 @@ import { JobManager } from "../job/JobManager";
|
|
|
7
7
|
import type { JobSubmission, JobStatus } from "../job/JobManager";
|
|
8
8
|
import { generationExecutor } from "./generation-executor";
|
|
9
9
|
|
|
10
|
-
declare const __DEV__: boolean;
|
|
11
10
|
|
|
12
11
|
export class JobProcessor {
|
|
13
12
|
private jobManager = new JobManager();
|
|
@@ -15,10 +14,7 @@ export class JobProcessor {
|
|
|
15
14
|
submitJob(model: string, input: Record<string, unknown>): Promise<JobSubmission> {
|
|
16
15
|
const submission = this.jobManager.submitJob(model, input);
|
|
17
16
|
|
|
18
|
-
this.processJobAsync(submission.requestId).catch((
|
|
19
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
20
|
-
console.error("[GeminiProvider] Job failed:", error);
|
|
21
|
-
}
|
|
17
|
+
this.processJobAsync(submission.requestId).catch(() => {
|
|
22
18
|
});
|
|
23
19
|
|
|
24
20
|
return Promise.resolve(submission);
|
|
@@ -6,22 +6,15 @@
|
|
|
6
6
|
import type { GeminiConfig } from "../../domain/entities";
|
|
7
7
|
import { geminiClientCoreService } from "./gemini-client-core.service";
|
|
8
8
|
|
|
9
|
-
declare const __DEV__: boolean;
|
|
10
9
|
|
|
11
10
|
export type GeminiProviderConfig = GeminiConfig;
|
|
12
11
|
|
|
13
12
|
export class ProviderInitializer {
|
|
14
13
|
initialize(config: GeminiProviderConfig): void {
|
|
15
14
|
if (geminiClientCoreService.isInitialized()) {
|
|
16
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
17
|
-
console.log("[GeminiProvider] Already initialized, skipping");
|
|
18
|
-
}
|
|
19
15
|
return;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
23
|
-
console.log("[GeminiProvider] Initializing...");
|
|
24
|
-
}
|
|
25
18
|
|
|
26
19
|
geminiClientCoreService.initialize(config);
|
|
27
20
|
}
|
|
@@ -12,7 +12,7 @@ export interface AsyncStateSetters {
|
|
|
12
12
|
setIsGenerating: (value: boolean) => void;
|
|
13
13
|
setError: (value: string | null) => void;
|
|
14
14
|
setResult: (value: string | null) => void;
|
|
15
|
-
setJsonResult: (value: unknown
|
|
15
|
+
setJsonResult: (value: unknown) => void;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -5,16 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import type { GeminiResponse } from "../../domain/entities";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Extract base64 data from data URL or return as-is
|
|
10
|
-
*/
|
|
11
|
-
export function extractBase64Data(base64String: string): string {
|
|
12
|
-
if (!base64String.includes(",")) {
|
|
13
|
-
return base64String;
|
|
14
|
-
}
|
|
15
|
-
const parts = base64String.split(",");
|
|
16
|
-
return parts[1] ?? parts[0] ?? base64String;
|
|
17
|
-
}
|
|
18
8
|
|
|
19
9
|
/**
|
|
20
10
|
* Extract text from Gemini response
|
|
@@ -1,45 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
mapGeminiError,
|
|
8
|
-
isGeminiErrorRetryable,
|
|
9
|
-
categorizeGeminiError,
|
|
10
|
-
createGeminiError,
|
|
11
|
-
} from "./error-mapper.util";
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
extractBase64Data,
|
|
15
|
-
extractTextFromResponse,
|
|
16
|
-
} from "./gemini-data-transformer.util";
|
|
17
|
-
|
|
18
|
-
export {
|
|
19
|
-
isValidModel,
|
|
20
|
-
validateModel,
|
|
21
|
-
getSafeModel,
|
|
22
|
-
isTextModel,
|
|
23
|
-
getModelCategory,
|
|
24
|
-
getAllValidModels,
|
|
25
|
-
} from "./model-validation.util";
|
|
26
|
-
|
|
27
|
-
export {
|
|
28
|
-
measureAsync,
|
|
29
|
-
measureSync,
|
|
30
|
-
debounce,
|
|
31
|
-
throttle,
|
|
32
|
-
PerformanceTimer,
|
|
33
|
-
PerformanceTracker,
|
|
34
|
-
performanceTracker,
|
|
35
|
-
} from "./performance.util";
|
|
36
|
-
export type { PerformanceMetrics } from "./performance.util";
|
|
37
|
-
|
|
38
|
-
export {
|
|
39
|
-
RateLimiter,
|
|
40
|
-
rateLimiter,
|
|
41
|
-
} from "./rate-limiter.util";
|
|
42
|
-
export type { RateLimiterOptions } from "./rate-limiter.util";
|
|
43
|
-
|
|
44
|
-
export { executeWithState } from "./async-state.util";
|
|
45
|
-
export type { AsyncStateCallbacks, AsyncStateSetters } from "./async-state.util";
|
|
1
|
+
export * from "./async-state.util";
|
|
2
|
+
export * from "./error-mapper.util";
|
|
3
|
+
export * from "./gemini-data-transformer.util";
|
|
4
|
+
export * from "./performance.util";
|
|
5
|
+
export * from "./rate-limiter.util";
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
* Prevents API rate limit errors by controlling request frequency
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
declare const __DEV__: boolean;
|
|
7
|
-
|
|
8
6
|
export interface RateLimiterOptions {
|
|
9
7
|
minInterval?: number; // Minimum milliseconds between requests
|
|
10
8
|
maxQueueSize?: number; // Maximum number of pending requests
|
|
@@ -36,7 +34,7 @@ export class RateLimiter {
|
|
|
36
34
|
reject(error);
|
|
37
35
|
}
|
|
38
36
|
});
|
|
39
|
-
this.processQueue();
|
|
37
|
+
void this.processQueue();
|
|
40
38
|
});
|
|
41
39
|
}
|
|
42
40
|
|