@umituz/react-native-ai-gemini-provider 1.8.3 → 1.9.1

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.
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Gemini Text Generation Service
3
+ * Handles text and multimodal content generation
4
+ */
5
+
6
+ import { geminiClientCoreService } from "./gemini-client-core.service";
7
+ import { geminiRetryService } from "./gemini-retry.service";
8
+ import { extractBase64Data, extractTextFromResponse } from "../utils/gemini-data-transformer.util";
9
+ import type {
10
+ GeminiContent,
11
+ GeminiGenerationConfig,
12
+ GeminiResponse,
13
+ GeminiPart,
14
+ GeminiFinishReason,
15
+ } from "../../domain/entities";
16
+
17
+ declare const __DEV__: boolean;
18
+
19
+ class GeminiTextGenerationService {
20
+ /**
21
+ * Generate content (text, with optional images)
22
+ */
23
+ async generateContent(
24
+ model: string,
25
+ contents: GeminiContent[],
26
+ generationConfig?: GeminiGenerationConfig,
27
+ ): Promise<GeminiResponse> {
28
+ const genModel = geminiClientCoreService.getModel(model);
29
+
30
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
31
+ // eslint-disable-next-line no-console
32
+ console.log("[Gemini] Generate content:", { model });
33
+ }
34
+
35
+ const sdkContents = contents.map((content) => ({
36
+ role: content.role || "user",
37
+ parts: content.parts.map((part) => {
38
+ if ("text" in part) {
39
+ return { text: part.text };
40
+ }
41
+ if ("inlineData" in part) {
42
+ return {
43
+ inlineData: {
44
+ mimeType: part.inlineData.mimeType,
45
+ data: part.inlineData.data,
46
+ },
47
+ };
48
+ }
49
+ return part;
50
+ }),
51
+ }));
52
+
53
+ try {
54
+ const result = await geminiRetryService.executeWithRetry(() =>
55
+ genModel.generateContent({
56
+ contents: sdkContents as Parameters<typeof genModel.generateContent>[0] extends { contents: infer C } ? C : never,
57
+ generationConfig,
58
+ }),
59
+ );
60
+
61
+ const response = result.response;
62
+
63
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
64
+ // eslint-disable-next-line no-console
65
+ console.log("[Gemini] Content generated:", {
66
+ candidatesCount: response.candidates?.length ?? 0,
67
+ finishReason: response.candidates?.[0]?.finishReason,
68
+ });
69
+ }
70
+
71
+ return {
72
+ candidates: response.candidates?.map((candidate) => ({
73
+ content: {
74
+ parts: candidate.content.parts
75
+ .map((part): GeminiPart | null => {
76
+ if ("text" in part && part.text !== undefined) {
77
+ return { text: part.text };
78
+ }
79
+ if ("inlineData" in part && part.inlineData) {
80
+ return {
81
+ inlineData: {
82
+ mimeType: part.inlineData.mimeType,
83
+ data: part.inlineData.data,
84
+ },
85
+ };
86
+ }
87
+ return null;
88
+ })
89
+ .filter((p): p is GeminiPart => p !== null),
90
+ role: (candidate.content.role || "model") as "user" | "model",
91
+ },
92
+ finishReason: candidate.finishReason as GeminiFinishReason | undefined,
93
+ })),
94
+ };
95
+ } catch (error) {
96
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
97
+ // eslint-disable-next-line no-console
98
+ console.error("[Gemini] Content generation failed:", {
99
+ model,
100
+ error: error instanceof Error ? error.message : String(error),
101
+ });
102
+ }
103
+ throw error;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Generate text from prompt
109
+ */
110
+ async generateText(
111
+ model: string,
112
+ prompt: string,
113
+ config?: GeminiGenerationConfig,
114
+ ): Promise<string> {
115
+ const contents: GeminiContent[] = [
116
+ { parts: [{ text: prompt }], role: "user" },
117
+ ];
118
+
119
+ const response = await this.generateContent(model, contents, config);
120
+ return extractTextFromResponse(response);
121
+ }
122
+
123
+ /**
124
+ * Generate content with images (multimodal)
125
+ */
126
+ async generateWithImages(
127
+ model: string,
128
+ prompt: string,
129
+ images: Array<{ base64: string; mimeType: string }>,
130
+ config?: GeminiGenerationConfig,
131
+ ): Promise<GeminiResponse> {
132
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
133
+ // eslint-disable-next-line no-console
134
+ console.log("[GeminiClient] generateWithImages() called", {
135
+ model,
136
+ promptLength: prompt.length,
137
+ imagesCount: images.length,
138
+ imageMimeTypes: images.map(i => i.mimeType),
139
+ });
140
+ }
141
+
142
+ const parts: GeminiContent["parts"] = [{ text: prompt }];
143
+
144
+ for (const image of images) {
145
+ parts.push({
146
+ inlineData: {
147
+ mimeType: image.mimeType,
148
+ data: extractBase64Data(image.base64),
149
+ },
150
+ });
151
+ }
152
+
153
+ const contents: GeminiContent[] = [{ parts, role: "user" }];
154
+
155
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
156
+ // eslint-disable-next-line no-console
157
+ console.log("[GeminiClient] generateWithImages() → calling generateContent()");
158
+ }
159
+
160
+ return this.generateContent(model, contents, config);
161
+ }
162
+ }
163
+
164
+ export const geminiTextGenerationService = new GeminiTextGenerationService();
@@ -2,7 +2,15 @@
2
2
  * Infrastructure Services
3
3
  */
4
4
 
5
- export { geminiClientService } from "./gemini-client.service";
5
+ // Core services
6
+ export { geminiClientCoreService } from "./gemini-client-core.service";
7
+ export { geminiRetryService } from "./gemini-retry.service";
8
+ export { geminiTextGenerationService } from "./gemini-text-generation.service";
9
+ export { geminiImageGenerationService } from "./gemini-image-generation.service";
10
+ export { geminiImageEditService } from "./gemini-image-edit.service";
11
+ export { geminiStreamingService } from "./gemini-streaming.service";
12
+
13
+ // Public provider API
6
14
  export {
7
15
  geminiProviderService,
8
16
  createGeminiProvider,
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Gemini Data Transformer Utility
3
+ * Handles data extraction and response parsing
4
+ */
5
+
6
+ import type { GeminiResponse } from "../../domain/entities";
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
+
19
+ /**
20
+ * Extract text from Gemini response
21
+ */
22
+ export function extractTextFromResponse(response: GeminiResponse): string {
23
+ const candidate = response.candidates?.[0];
24
+
25
+ if (!candidate) {
26
+ throw new Error("No response candidates");
27
+ }
28
+
29
+ if (candidate.finishReason === "SAFETY") {
30
+ throw new Error("Content blocked by safety filters");
31
+ }
32
+
33
+ const textPart = candidate.content.parts.find(
34
+ (p): p is { text: string } => "text" in p && typeof p.text === "string",
35
+ );
36
+
37
+ if (!textPart) {
38
+ throw new Error("No text in response");
39
+ }
40
+
41
+ return textPart.text;
42
+ }
@@ -7,3 +7,8 @@ export {
7
7
  isGeminiErrorRetryable,
8
8
  categorizeGeminiError,
9
9
  } from "./error-mapper.util";
10
+
11
+ export {
12
+ extractBase64Data,
13
+ extractTextFromResponse,
14
+ } from "./gemini-data-transformer.util";
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { useState, useCallback, useRef } from "react";
7
7
  import type { GeminiGenerationConfig } from "../../domain/entities";
8
- import { geminiClientService } from "../../infrastructure/services";
8
+ import { geminiTextGenerationService } from "../../infrastructure/services";
9
9
 
10
10
  export interface UseGeminiOptions {
11
11
  model?: string;
@@ -43,7 +43,7 @@ export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
43
43
 
44
44
  try {
45
45
  const model = options.model ?? "gemini-1.5-flash";
46
- const text = await geminiClientService.generateText(
46
+ const text = await geminiTextGenerationService.generateText(
47
47
  model,
48
48
  prompt,
49
49
  options.generationConfig,
@@ -78,7 +78,7 @@ export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
78
78
 
79
79
  try {
80
80
  const model = options.model ?? "gemini-1.5-flash";
81
- const response = await geminiClientService.generateWithImages(
81
+ const response = await geminiTextGenerationService.generateWithImages(
82
82
  model,
83
83
  prompt,
84
84
  [{ base64: imageBase64, mimeType }],
@@ -91,7 +91,7 @@ export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
91
91
  const text =
92
92
  response.candidates?.[0]?.content.parts
93
93
  .filter((p): p is { text: string } => "text" in p)
94
- .map((p) => p.text)
94
+ .map((p: { text: string }) => p.text)
95
95
  .join("") || "";
96
96
 
97
97
  setResult(text);