@umituz/react-native-ai-gemini-provider 2.0.0 → 2.0.2
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
|
@@ -50,7 +50,7 @@ export class GenerationExecutor {
|
|
|
50
50
|
console.log("[GenerationExecutor] executeStructuredGeneration() called", { model });
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
return geminiStructuredTextService.
|
|
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 {
|
|
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
|
-
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
}
|