@umituz/react-native-ai-gemini-provider 2.0.0 → 2.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-gemini-provider",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
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",
@@ -50,7 +50,7 @@ export class GenerationExecutor {
50
50
  console.log("[GenerationExecutor] executeStructuredGeneration() called", { model });
51
51
  }
52
52
 
53
- return geminiStructuredTextService.generateStructuredContent<T>(
53
+ return geminiStructuredTextService.generateStructuredText<T>(
54
54
  model ?? "gemini-2.0-flash",
55
55
  prompt,
56
56
  schema,
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Async State Utility
3
+ * Common async execution pattern with state management
4
+ */
5
+
6
+ export interface AsyncStateCallbacks {
7
+ onSuccess?: (result: string) => void;
8
+ onError?: (error: string) => void;
9
+ }
10
+
11
+ export interface AsyncStateSetters {
12
+ setIsGenerating: (value: boolean) => void;
13
+ setError: (value: string | null) => void;
14
+ setResult: (value: string | null) => void;
15
+ setJsonResult: (value: unknown | null) => void;
16
+ }
17
+
18
+ /**
19
+ * Execute an async operation with common state management
20
+ */
21
+ export async function executeWithState<T>(
22
+ abortRef: React.MutableRefObject<boolean>,
23
+ setters: AsyncStateSetters,
24
+ callbacks: AsyncStateCallbacks,
25
+ execute: () => Promise<T>,
26
+ onResult: (result: T) => void,
27
+ ): Promise<T | null> {
28
+ abortRef.current = false;
29
+ setters.setIsGenerating(true);
30
+ setters.setError(null);
31
+ setters.setResult(null);
32
+ setters.setJsonResult(null);
33
+
34
+ try {
35
+ const result = await execute();
36
+ if (abortRef.current) return null;
37
+
38
+ onResult(result);
39
+ return result;
40
+ } catch (err) {
41
+ if (abortRef.current) return null;
42
+
43
+ const errorMessage = err instanceof Error ? err.message : "Generation failed";
44
+ setters.setError(errorMessage);
45
+ callbacks.onError?.(errorMessage);
46
+ return null;
47
+ } finally {
48
+ if (!abortRef.current) {
49
+ setters.setIsGenerating(false);
50
+ }
51
+ }
52
+ }
@@ -40,3 +40,6 @@ export {
40
40
  rateLimiter,
41
41
  } from "./rate-limiter.util";
42
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";
@@ -4,196 +4,86 @@
4
4
  * Supports text, structured JSON, and multimodal generation
5
5
  */
6
6
 
7
- import { useState, useCallback, useRef } from "react";
7
+ import { useState, useCallback, useRef, useMemo } from "react";
8
8
  import type { GeminiGenerationConfig } from "../../domain/entities";
9
9
  import { DEFAULT_MODELS } from "../../domain/entities";
10
- import { geminiTextGenerationService } from "../../infrastructure/services";
11
- import { geminiStructuredTextService } from "../../infrastructure/services";
10
+ import { geminiTextGenerationService, geminiStructuredTextService } from "../../infrastructure/services";
11
+ import { executeWithState } from "../../infrastructure/utils";
12
12
 
13
13
  export interface UseGeminiOptions {
14
- /** Model to use (default: gemini-2.5-flash-lite) */
15
14
  model?: string;
16
- /** Generation configuration */
17
15
  generationConfig?: GeminiGenerationConfig;
18
- /** Called on successful generation */
19
16
  onSuccess?: (result: string) => void;
20
- /** Called on error */
21
17
  onError?: (error: string) => void;
22
18
  }
23
19
 
24
20
  export interface UseGeminiReturn {
25
- /** Generate text from prompt */
26
21
  generate: (prompt: string) => Promise<void>;
27
- /** Generate text with image input */
28
- generateWithImage: (
29
- prompt: string,
30
- imageBase64: string,
31
- mimeType: string,
32
- ) => Promise<void>;
33
- /** Generate structured JSON response */
22
+ generateWithImage: (prompt: string, imageBase64: string, mimeType: string) => Promise<void>;
34
23
  generateJSON: <T>(prompt: string, schema?: Record<string, unknown>) => Promise<T | null>;
35
- /** Current result */
36
24
  result: string | null;
37
- /** JSON result (when using generateJSON) */
38
25
  jsonResult: unknown | null;
39
- /** Loading state */
40
26
  isGenerating: boolean;
41
- /** Error message */
42
27
  error: string | null;
43
- /** Reset state */
44
28
  reset: () => void;
45
29
  }
46
30
 
31
+ function cleanJsonResponse(text: string): string {
32
+ return text.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
33
+ }
34
+
35
+ function extractTextFromParts(parts: unknown[]): string {
36
+ return parts
37
+ .filter((p): p is { text: string } => typeof p === "object" && p !== null && "text" in p)
38
+ .map((p) => p.text)
39
+ .join("");
40
+ }
41
+
47
42
  export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
48
43
  const [result, setResult] = useState<string | null>(null);
49
44
  const [jsonResult, setJsonResult] = useState<unknown | null>(null);
50
45
  const [isGenerating, setIsGenerating] = useState(false);
51
46
  const [error, setError] = useState<string | null>(null);
52
-
53
47
  const abortRef = useRef(false);
54
48
 
55
- const generate = useCallback(
56
- async (prompt: string) => {
57
- abortRef.current = false;
58
- setIsGenerating(true);
59
- setError(null);
60
- setResult(null);
61
- setJsonResult(null);
62
-
63
- try {
64
- const model = options.model ?? DEFAULT_MODELS.TEXT;
65
- const text = await geminiTextGenerationService.generateText(
66
- model,
67
- prompt,
68
- options.generationConfig,
69
- );
70
-
71
- if (abortRef.current) return;
72
-
73
- setResult(text);
74
- options.onSuccess?.(text);
75
- } catch (err) {
76
- if (abortRef.current) return;
77
-
78
- const errorMessage =
79
- err instanceof Error ? err.message : "Generation failed";
80
- setError(errorMessage);
81
- options.onError?.(errorMessage);
82
- } finally {
83
- if (!abortRef.current) {
84
- setIsGenerating(false);
85
- }
86
- }
87
- },
88
- [options],
89
- );
90
-
91
- const generateJSON = useCallback(
92
- async <T>(prompt: string, schema?: Record<string, unknown>): Promise<T | null> => {
93
- abortRef.current = false;
94
- setIsGenerating(true);
95
- setError(null);
96
- setResult(null);
97
- setJsonResult(null);
49
+ const setters = useMemo(() => ({ setIsGenerating, setError, setResult, setJsonResult }), []);
50
+ const callbacks = useMemo(() => ({ onSuccess: options.onSuccess, onError: options.onError }), [options.onSuccess, options.onError]);
51
+ const model = options.model ?? DEFAULT_MODELS.TEXT;
98
52
 
99
- try {
100
- const model = options.model ?? DEFAULT_MODELS.TEXT;
101
-
102
- let parsed: T;
53
+ const generate = useCallback(async (prompt: string) => {
54
+ await executeWithState(abortRef, setters, callbacks,
55
+ () => geminiTextGenerationService.generateText(model, prompt, options.generationConfig),
56
+ (text) => { setResult(text); options.onSuccess?.(text); }
57
+ );
58
+ }, [model, options.generationConfig, setters, callbacks, options.onSuccess]);
103
59
 
60
+ const generateJSON = useCallback(async <T>(prompt: string, schema?: Record<string, unknown>): Promise<T | null> => {
61
+ return executeWithState(abortRef, setters, callbacks,
62
+ async () => {
104
63
  if (schema) {
105
- // Use structured text service with schema
106
- parsed = await geminiStructuredTextService.generateStructuredText<T>(
107
- model,
108
- prompt,
109
- schema,
110
- options.generationConfig,
111
- );
112
- } else {
113
- // Generate text and parse JSON manually
114
- const text = await geminiTextGenerationService.generateText(
115
- model,
116
- prompt,
117
- {
118
- ...options.generationConfig,
119
- responseMimeType: "application/json",
120
- },
121
- );
122
-
123
- // Clean and parse JSON
124
- const cleanedText = text
125
- .replace(/```json\n?/g, "")
126
- .replace(/```\n?/g, "")
127
- .trim();
128
- parsed = JSON.parse(cleanedText) as T;
64
+ return geminiStructuredTextService.generateStructuredText<T>(model, prompt, schema, options.generationConfig);
129
65
  }
130
-
131
- if (abortRef.current) return null;
132
-
66
+ const text = await geminiTextGenerationService.generateText(model, prompt, { ...options.generationConfig, responseMimeType: "application/json" });
67
+ return JSON.parse(cleanJsonResponse(text)) as T;
68
+ },
69
+ (parsed) => {
133
70
  setJsonResult(parsed);
134
71
  setResult(JSON.stringify(parsed, null, 2));
135
72
  options.onSuccess?.(JSON.stringify(parsed));
136
- return parsed;
137
- } catch (err) {
138
- if (abortRef.current) return null;
139
-
140
- const errorMessage =
141
- err instanceof Error ? err.message : "JSON generation failed";
142
- setError(errorMessage);
143
- options.onError?.(errorMessage);
144
- return null;
145
- } finally {
146
- if (!abortRef.current) {
147
- setIsGenerating(false);
148
- }
149
73
  }
150
- },
151
- [options],
152
- );
153
-
154
- const generateWithImage = useCallback(
155
- async (prompt: string, imageBase64: string, mimeType: string) => {
156
- abortRef.current = false;
157
- setIsGenerating(true);
158
- setError(null);
159
- setResult(null);
160
- setJsonResult(null);
161
-
162
- try {
163
- const model = options.model ?? DEFAULT_MODELS.TEXT;
164
- const response = await geminiTextGenerationService.generateWithImages(
165
- model,
166
- prompt,
167
- [{ base64: imageBase64, mimeType }],
168
- options.generationConfig,
169
- );
170
-
171
- if (abortRef.current) return;
172
-
173
- // Extract text from response
174
- const text =
175
- response.candidates?.[0]?.content.parts
176
- .filter((p): p is { text: string } => "text" in p)
177
- .map((p: { text: string }) => p.text)
178
- .join("") || "";
179
-
74
+ );
75
+ }, [model, options.generationConfig, setters, callbacks, options.onSuccess]);
76
+
77
+ const generateWithImage = useCallback(async (prompt: string, imageBase64: string, mimeType: string) => {
78
+ await executeWithState(abortRef, setters, callbacks,
79
+ () => geminiTextGenerationService.generateWithImages(model, prompt, [{ base64: imageBase64, mimeType }], options.generationConfig),
80
+ (response) => {
81
+ const text = extractTextFromParts(response.candidates?.[0]?.content.parts ?? []);
180
82
  setResult(text);
181
83
  options.onSuccess?.(text);
182
- } catch (err) {
183
- if (abortRef.current) return;
184
-
185
- const errorMessage =
186
- err instanceof Error ? err.message : "Generation failed";
187
- setError(errorMessage);
188
- options.onError?.(errorMessage);
189
- } finally {
190
- if (!abortRef.current) {
191
- setIsGenerating(false);
192
- }
193
84
  }
194
- },
195
- [options],
196
- );
85
+ );
86
+ }, [model, options.generationConfig, setters, callbacks, options.onSuccess]);
197
87
 
198
88
  const reset = useCallback(() => {
199
89
  abortRef.current = true;
@@ -203,14 +93,5 @@ export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
203
93
  setError(null);
204
94
  }, []);
205
95
 
206
- return {
207
- generate,
208
- generateWithImage,
209
- generateJSON,
210
- result,
211
- jsonResult,
212
- isGenerating,
213
- error,
214
- reset,
215
- };
96
+ return { generate, generateWithImage, generateJSON, result, jsonResult, isGenerating, error, reset };
216
97
  }