@umituz/react-native-ai-gemini-provider 2.0.13 → 2.0.15
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/domain/entities/error.types.ts +0 -17
- package/src/domain/entities/gemini.types.ts +1 -37
- package/src/domain/entities/models.ts +0 -31
- package/src/index.ts +8 -24
- package/src/infrastructure/interceptors/RequestInterceptors.ts +20 -6
- package/src/infrastructure/interceptors/ResponseInterceptors.ts +20 -6
- package/src/infrastructure/services/gemini-client-core.service.ts +25 -32
- package/src/infrastructure/services/gemini-provider.ts +31 -15
- package/src/infrastructure/services/gemini-streaming.service.ts +0 -4
- package/src/infrastructure/services/gemini-structured-text.service.ts +8 -5
- package/src/infrastructure/services/gemini-text-generation.service.ts +20 -51
- package/src/infrastructure/services/index.ts +2 -16
- package/src/infrastructure/telemetry/TelemetryHooks.ts +23 -10
- package/src/infrastructure/utils/async-state.util.ts +0 -7
- package/src/infrastructure/utils/error-mapper.util.ts +13 -8
- package/src/infrastructure/utils/gemini-data-transformer.util.ts +12 -9
- package/src/infrastructure/utils/performance.util.ts +4 -54
- package/src/infrastructure/utils/rate-limiter.util.ts +12 -8
- package/src/presentation/hooks/use-gemini.ts +91 -24
- package/src/providers/ProviderConfig.ts +0 -32
- package/src/providers/ProviderFactory.ts +24 -37
- package/src/providers/index.ts +2 -7
- package/src/domain/README.md +0 -232
- package/src/domain/constants/index.ts +0 -5
- package/src/domain/entities/README.md +0 -238
- package/src/infrastructure/README.md +0 -252
- package/src/infrastructure/cache/CACHE_SYSTEM.md +0 -213
- package/src/infrastructure/cache/README.md +0 -213
- package/src/infrastructure/cache/SimpleCache.ts +0 -173
- package/src/infrastructure/cache/index.ts +0 -7
- package/src/infrastructure/content/ContentBuilder.ts +0 -24
- package/src/infrastructure/content/README.md +0 -175
- package/src/infrastructure/interceptors/README.md +0 -226
- package/src/infrastructure/interceptors/REQUEST_INTERCEPTORS.md +0 -171
- package/src/infrastructure/job/JOB_MANAGER.md +0 -174
- package/src/infrastructure/job/JobManager.ts +0 -114
- package/src/infrastructure/job/README.md +0 -194
- package/src/infrastructure/response/README.md +0 -187
- package/src/infrastructure/response/RESPONSE_FORMATTER.md +0 -185
- package/src/infrastructure/response/ResponseFormatter.ts +0 -58
- package/src/infrastructure/services/generation-executor.ts +0 -71
- package/src/infrastructure/services/job-processor.ts +0 -58
- package/src/infrastructure/services/provider-initializer.ts +0 -31
- package/src/infrastructure/telemetry/README.md +0 -203
- package/src/infrastructure/telemetry/TELEMETRY_SYSTEM.md +0 -200
- package/src/presentation/README.md +0 -187
- package/src/presentation/hooks/README.md +0 -188
- package/src/presentation/hooks/USE_GEMINI_HOOK.md +0 -226
- package/src/providers/README.md +0 -247
|
@@ -3,31 +3,17 @@
|
|
|
3
3
|
* Text-only Gemini services
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
// Core services
|
|
6
|
+
// Core services
|
|
7
7
|
export { geminiClientCoreService } from "./gemini-client-core.service";
|
|
8
8
|
export { geminiTextGenerationService } from "./gemini-text-generation.service";
|
|
9
|
-
export { geminiTextService } from "./gemini-text-generation.service";
|
|
10
9
|
export { geminiStructuredTextService } from "./gemini-structured-text.service";
|
|
11
10
|
export { geminiStreamingService } from "./gemini-streaming.service";
|
|
12
11
|
|
|
13
|
-
//
|
|
14
|
-
export { providerInitializer } from "./provider-initializer";
|
|
15
|
-
export { jobProcessor } from "./job-processor";
|
|
16
|
-
export { generationExecutor } from "./generation-executor";
|
|
17
|
-
|
|
18
|
-
// Public provider API
|
|
12
|
+
// Provider
|
|
19
13
|
export {
|
|
20
14
|
geminiProviderService,
|
|
21
15
|
createGeminiProvider,
|
|
22
16
|
GeminiProvider,
|
|
23
17
|
} from "./gemini-provider";
|
|
24
|
-
|
|
25
18
|
export type { GeminiProviderConfig } from "./gemini-provider";
|
|
26
19
|
|
|
27
|
-
// Generation executor types
|
|
28
|
-
export type {
|
|
29
|
-
GenerationInput,
|
|
30
|
-
GenerationResult,
|
|
31
|
-
ExecutionOptions,
|
|
32
|
-
} from "./generation-executor";
|
|
33
|
-
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Telemetry Hooks
|
|
3
|
-
* Allows applications to monitor and log AI operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
declare const __DEV__: boolean;
|
|
7
1
|
|
|
8
2
|
export interface TelemetryEvent {
|
|
9
3
|
type: "request" | "response" | "error" | "retry";
|
|
@@ -18,18 +12,25 @@ export type TelemetryListener = (event: TelemetryEvent) => void;
|
|
|
18
12
|
|
|
19
13
|
class TelemetryHooks {
|
|
20
14
|
private listeners: TelemetryListener[] = [];
|
|
15
|
+
private failedListeners: Set<TelemetryListener> = new Set();
|
|
16
|
+
private readonly MAX_FAILURES = 3;
|
|
17
|
+
private listenerFailureCounts = new Map<TelemetryListener, number>();
|
|
21
18
|
|
|
22
19
|
/**
|
|
23
20
|
* Register a telemetry listener
|
|
24
21
|
*/
|
|
25
22
|
subscribe(listener: TelemetryListener): () => void {
|
|
26
23
|
this.listeners.push(listener);
|
|
24
|
+
this.failedListeners.delete(listener);
|
|
25
|
+
this.listenerFailureCounts.set(listener, 0);
|
|
27
26
|
|
|
28
27
|
return () => {
|
|
29
28
|
const index = this.listeners.indexOf(listener);
|
|
30
29
|
if (index > -1) {
|
|
31
30
|
this.listeners.splice(index, 1);
|
|
32
31
|
}
|
|
32
|
+
this.failedListeners.delete(listener);
|
|
33
|
+
this.listenerFailureCounts.delete(listener);
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -38,13 +39,23 @@ class TelemetryHooks {
|
|
|
38
39
|
*/
|
|
39
40
|
emit(event: TelemetryEvent): void {
|
|
40
41
|
for (const listener of this.listeners) {
|
|
42
|
+
// Skip listeners that have failed too many times
|
|
43
|
+
if (this.failedListeners.has(listener)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
try {
|
|
42
48
|
listener(event);
|
|
49
|
+
// Reset failure count on success
|
|
50
|
+
this.listenerFailureCounts.set(listener, 0);
|
|
43
51
|
} catch (error) {
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
// Track failures
|
|
53
|
+
const failureCount = (this.listenerFailureCounts.get(listener) || 0) + 1;
|
|
54
|
+
this.listenerFailureCounts.set(listener, failureCount);
|
|
55
|
+
|
|
56
|
+
// If listener fails too many times, blacklist it
|
|
57
|
+
if (failureCount >= this.MAX_FAILURES) {
|
|
58
|
+
this.failedListeners.add(listener);
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
61
|
}
|
|
@@ -112,6 +123,8 @@ class TelemetryHooks {
|
|
|
112
123
|
*/
|
|
113
124
|
clear(): void {
|
|
114
125
|
this.listeners = [];
|
|
126
|
+
this.failedListeners.clear();
|
|
127
|
+
this.listenerFailureCounts.clear();
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
/**
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Async State Utility
|
|
3
|
-
* Common async execution pattern with state management
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
export interface AsyncStateCallbacks {
|
|
7
3
|
onSuccess?: (result: string) => void;
|
|
@@ -15,9 +11,6 @@ export interface AsyncStateSetters {
|
|
|
15
11
|
setJsonResult: (value: unknown) => void;
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
/**
|
|
19
|
-
* Execute an async operation with common state management
|
|
20
|
-
*/
|
|
21
14
|
export async function executeWithState<T>(
|
|
22
15
|
abortRef: React.MutableRefObject<boolean>,
|
|
23
16
|
setters: AsyncStateSetters,
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gemini Error Mapper
|
|
3
|
-
* Maps Gemini API errors to standardized format
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import {
|
|
7
3
|
GeminiErrorType,
|
|
@@ -76,7 +72,19 @@ function getStatusCode(error: unknown): number | undefined {
|
|
|
76
72
|
|
|
77
73
|
function matchesPattern(message: string, patterns: string[]): boolean {
|
|
78
74
|
const lower = message.toLowerCase();
|
|
79
|
-
|
|
75
|
+
|
|
76
|
+
return patterns.some((pattern) => {
|
|
77
|
+
const lowerPattern = pattern.toLowerCase();
|
|
78
|
+
|
|
79
|
+
// Use word boundary matching for better accuracy
|
|
80
|
+
// This prevents "invalid" from matching "valid"
|
|
81
|
+
const words = lowerPattern.split(/\s+/);
|
|
82
|
+
return words.every((word) => {
|
|
83
|
+
// Check if the word appears as a whole word or with common punctuation
|
|
84
|
+
const regex = new RegExp(`\\b${word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i');
|
|
85
|
+
return regex.test(lower) || lower.includes(word);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
export function mapGeminiError(error: unknown): GeminiErrorInfo {
|
|
@@ -114,9 +122,6 @@ export function categorizeGeminiError(error: unknown): GeminiErrorType {
|
|
|
114
122
|
return mapGeminiError(error).type;
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
/**
|
|
118
|
-
* Create a GeminiError instance from an unknown error
|
|
119
|
-
*/
|
|
120
125
|
export function createGeminiError(error: unknown): GeminiError {
|
|
121
126
|
const errorInfo = mapGeminiError(error);
|
|
122
127
|
return GeminiError.fromError(error, errorInfo);
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gemini Data Transformer Utility
|
|
3
|
-
* Handles data extraction and response parsing
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import type { GeminiResponse } from "../../domain/entities";
|
|
7
3
|
|
|
8
4
|
|
|
9
|
-
/**
|
|
10
|
-
* Extract text from Gemini response
|
|
11
|
-
*/
|
|
12
5
|
export function extractTextFromResponse(response: GeminiResponse): string {
|
|
13
6
|
const candidate = response.candidates?.[0];
|
|
14
7
|
|
|
@@ -16,8 +9,18 @@ export function extractTextFromResponse(response: GeminiResponse): string {
|
|
|
16
9
|
throw new Error("No response candidates");
|
|
17
10
|
}
|
|
18
11
|
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
// Handle all finish reasons appropriately
|
|
13
|
+
switch (candidate.finishReason) {
|
|
14
|
+
case "SAFETY":
|
|
15
|
+
throw new Error("Content blocked by safety filters");
|
|
16
|
+
case "RECITATION":
|
|
17
|
+
throw new Error("Content blocked due to recitation concerns");
|
|
18
|
+
case "MAX_TOKENS":
|
|
19
|
+
case "FINISH_REASON_UNSPECIFIED":
|
|
20
|
+
case "OTHER":
|
|
21
|
+
case "STOP":
|
|
22
|
+
// Continue to extract text
|
|
23
|
+
break;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
const textPart = candidate.content.parts.find(
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Performance Utilities
|
|
3
|
-
* Tools for measuring and optimizing performance
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
declare const __DEV__: boolean;
|
|
7
1
|
|
|
8
2
|
export interface PerformanceMetrics {
|
|
9
3
|
duration: number;
|
|
@@ -47,12 +41,8 @@ export async function measureAsync<T>(
|
|
|
47
41
|
const timer = new PerformanceTimer(metadata);
|
|
48
42
|
try {
|
|
49
43
|
const result = await operation();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// eslint-disable-next-line no-console
|
|
53
|
-
console.log("[Performance] Operation completed:", { duration: `${duration}ms`, metadata });
|
|
54
|
-
}
|
|
55
|
-
return { result, duration };
|
|
44
|
+
timer.stop();
|
|
45
|
+
return { result, duration: timer.duration };
|
|
56
46
|
} catch (error) {
|
|
57
47
|
timer.stop();
|
|
58
48
|
throw error;
|
|
@@ -66,12 +56,8 @@ export function measureSync<T>(
|
|
|
66
56
|
const timer = new PerformanceTimer(metadata);
|
|
67
57
|
try {
|
|
68
58
|
const result = operation();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// eslint-disable-next-line no-console
|
|
72
|
-
console.log("[Performance] Operation completed:", { duration: `${duration}ms`, metadata });
|
|
73
|
-
}
|
|
74
|
-
return { result, duration };
|
|
59
|
+
timer.stop();
|
|
60
|
+
return { result, duration: timer.duration };
|
|
75
61
|
} catch (error) {
|
|
76
62
|
timer.stop();
|
|
77
63
|
throw error;
|
|
@@ -107,39 +93,3 @@ export function throttle<T extends (...args: never[]) => unknown>(
|
|
|
107
93
|
};
|
|
108
94
|
}
|
|
109
95
|
|
|
110
|
-
export class PerformanceTracker {
|
|
111
|
-
private metrics = new Map<string, number[]>();
|
|
112
|
-
|
|
113
|
-
record(operation: string, duration: number): void {
|
|
114
|
-
if (!this.metrics.has(operation)) {
|
|
115
|
-
this.metrics.set(operation, []);
|
|
116
|
-
}
|
|
117
|
-
this.metrics.get(operation)!.push(duration);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
getStats(operation: string): { count: number; avg: number; min: number; max: number } | null {
|
|
121
|
-
const durations = this.metrics.get(operation);
|
|
122
|
-
if (!durations || durations.length === 0) return null;
|
|
123
|
-
return {
|
|
124
|
-
count: durations.length,
|
|
125
|
-
avg: durations.reduce((a, b) => a + b, 0) / durations.length,
|
|
126
|
-
min: Math.min(...durations),
|
|
127
|
-
max: Math.max(...durations),
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
getAllStats(): Record<string, ReturnType<PerformanceTracker["getStats"]>> {
|
|
132
|
-
const stats: Record<string, ReturnType<PerformanceTracker["getStats"]>> = {};
|
|
133
|
-
for (const operation of this.metrics.keys()) {
|
|
134
|
-
stats[operation] = this.getStats(operation);
|
|
135
|
-
}
|
|
136
|
-
return stats;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
clear(): void {
|
|
140
|
-
this.metrics.clear();
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export const performanceTracker = new PerformanceTracker();
|
|
145
|
-
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rate Limiter
|
|
3
|
-
* Prevents API rate limit errors by controlling request frequency
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
export interface RateLimiterOptions {
|
|
7
3
|
minInterval?: number; // Minimum milliseconds between requests
|
|
@@ -14,6 +10,7 @@ export class RateLimiter {
|
|
|
14
10
|
private lastRequest = 0;
|
|
15
11
|
private minInterval: number;
|
|
16
12
|
private maxQueueSize: number;
|
|
13
|
+
private processQueuePromise: Promise<void> | null = null;
|
|
17
14
|
|
|
18
15
|
constructor(options: RateLimiterOptions = {}) {
|
|
19
16
|
this.minInterval = options.minInterval ?? 100; // 100ms minimum interval
|
|
@@ -22,7 +19,7 @@ export class RateLimiter {
|
|
|
22
19
|
|
|
23
20
|
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
|
24
21
|
if (this.queue.length >= this.maxQueueSize) {
|
|
25
|
-
throw new Error(
|
|
22
|
+
throw new Error(`Rate limiter queue is full (${this.maxQueueSize} requests pending). Please wait before retrying.`);
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
return new Promise((resolve, reject) => {
|
|
@@ -34,7 +31,16 @@ export class RateLimiter {
|
|
|
34
31
|
reject(error);
|
|
35
32
|
}
|
|
36
33
|
});
|
|
37
|
-
|
|
34
|
+
|
|
35
|
+
// Ensure queue processing is running (wait for it to avoid race condition)
|
|
36
|
+
if (!this.processQueuePromise) {
|
|
37
|
+
this.processQueuePromise = this.processQueue();
|
|
38
|
+
this.processQueuePromise.then(() => {
|
|
39
|
+
this.processQueuePromise = null;
|
|
40
|
+
}).catch(() => {
|
|
41
|
+
this.processQueuePromise = null;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
38
44
|
});
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -72,5 +78,3 @@ export class RateLimiter {
|
|
|
72
78
|
this.lastRequest = 0;
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
|
-
|
|
76
|
-
export const rateLimiter = new RateLimiter();
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useGemini Hook
|
|
3
|
-
* React hook for Gemini AI generation
|
|
4
|
-
* Supports text, structured JSON, and multimodal generation
|
|
5
|
-
*/
|
|
6
1
|
|
|
7
2
|
import { useState, useCallback, useRef, useMemo, useEffect } from "react";
|
|
8
3
|
import type { GeminiGenerationConfig } from "../../domain/entities";
|
|
@@ -37,39 +32,105 @@ export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
|
|
|
37
32
|
const [jsonResult, setJsonResult] = useState<unknown>(null);
|
|
38
33
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
39
34
|
const [error, setError] = useState<string | null>(null);
|
|
40
|
-
|
|
35
|
+
// Use a ref to store the abort controller for the current operation
|
|
36
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
37
|
+
const operationIdRef = useRef(0);
|
|
41
38
|
|
|
42
39
|
const setters = useMemo(() => ({ setIsGenerating, setError, setResult, setJsonResult }), []);
|
|
43
40
|
const callbacks = useMemo(() => ({ onSuccess: options.onSuccess, onError: options.onError }), [options.onSuccess, options.onError]);
|
|
44
41
|
const model = options.model ?? DEFAULT_MODELS.TEXT;
|
|
45
42
|
|
|
46
43
|
const generate = useCallback(async (prompt: string) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
// Create new abort controller for this operation
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
abortControllerRef.current = controller;
|
|
47
|
+
const currentOpId = ++operationIdRef.current;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await executeWithState(
|
|
51
|
+
{ current: false }, // We'll use operation ID instead
|
|
52
|
+
setters,
|
|
53
|
+
callbacks,
|
|
54
|
+
async () => {
|
|
55
|
+
// Check if this operation is still the latest one
|
|
56
|
+
if (currentOpId !== operationIdRef.current) {
|
|
57
|
+
throw new Error("Operation cancelled by newer request");
|
|
58
|
+
}
|
|
59
|
+
return geminiTextGenerationService.generateText(model, prompt, options.generationConfig);
|
|
60
|
+
},
|
|
61
|
+
(text) => {
|
|
62
|
+
// Only update if this is still the latest operation
|
|
63
|
+
if (currentOpId === operationIdRef.current) {
|
|
64
|
+
setResult(text);
|
|
65
|
+
options.onSuccess?.(text);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
} finally {
|
|
70
|
+
// Clean up abort controller if this was the latest operation
|
|
71
|
+
if (currentOpId === operationIdRef.current) {
|
|
72
|
+
abortControllerRef.current = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
51
75
|
}, [model, options.generationConfig, setters, callbacks, options.onSuccess]);
|
|
52
76
|
|
|
53
77
|
const generateJSON = useCallback(async <T>(prompt: string, schema?: Record<string, unknown>): Promise<T | null> => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
// Create new abort controller for this operation
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
abortControllerRef.current = controller;
|
|
81
|
+
const currentOpId = ++operationIdRef.current;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
return await executeWithState(
|
|
85
|
+
{ current: false }, // We'll use operation ID instead
|
|
86
|
+
setters,
|
|
87
|
+
callbacks,
|
|
88
|
+
async () => {
|
|
89
|
+
// Check if this operation is still the latest one
|
|
90
|
+
if (currentOpId !== operationIdRef.current) {
|
|
91
|
+
throw new Error("Operation cancelled by newer request");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (schema) {
|
|
95
|
+
return geminiStructuredTextService.generateStructuredText<T>(model, prompt, schema, options.generationConfig);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const text = await geminiTextGenerationService.generateText(model, prompt, { ...options.generationConfig, responseMimeType: "application/json" });
|
|
99
|
+
const cleanedText = cleanJsonResponse(text);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(cleanedText) as T;
|
|
103
|
+
} catch (parseError) {
|
|
104
|
+
throw new Error(`Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : String(parseError)}. Response: ${cleanedText.substring(0, 200)}...`);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
(parsed) => {
|
|
108
|
+
// Only update if this is still the latest operation
|
|
109
|
+
if (currentOpId === operationIdRef.current) {
|
|
110
|
+
setJsonResult(parsed);
|
|
111
|
+
setResult(JSON.stringify(parsed, null, 2));
|
|
112
|
+
options.onSuccess?.(JSON.stringify(parsed));
|
|
113
|
+
}
|
|
58
114
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
(
|
|
63
|
-
|
|
64
|
-
setResult(JSON.stringify(parsed, null, 2));
|
|
65
|
-
options.onSuccess?.(JSON.stringify(parsed));
|
|
115
|
+
);
|
|
116
|
+
} finally {
|
|
117
|
+
// Clean up abort controller if this was the latest operation
|
|
118
|
+
if (currentOpId === operationIdRef.current) {
|
|
119
|
+
abortControllerRef.current = null;
|
|
66
120
|
}
|
|
67
|
-
|
|
121
|
+
}
|
|
68
122
|
}, [model, options.generationConfig, setters, callbacks, options.onSuccess]);
|
|
69
123
|
|
|
70
124
|
|
|
71
125
|
const reset = useCallback(() => {
|
|
72
|
-
|
|
126
|
+
// Abort any ongoing operation
|
|
127
|
+
if (abortControllerRef.current) {
|
|
128
|
+
abortControllerRef.current.abort();
|
|
129
|
+
abortControllerRef.current = null;
|
|
130
|
+
}
|
|
131
|
+
// Increment operation ID to cancel any pending operations
|
|
132
|
+
operationIdRef.current++;
|
|
133
|
+
|
|
73
134
|
setResult(null);
|
|
74
135
|
setJsonResult(null);
|
|
75
136
|
setIsGenerating(false);
|
|
@@ -77,7 +138,13 @@ export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
|
|
|
77
138
|
}, []);
|
|
78
139
|
|
|
79
140
|
useEffect(() => {
|
|
80
|
-
return () => {
|
|
141
|
+
return () => {
|
|
142
|
+
// Cleanup on unmount
|
|
143
|
+
if (abortControllerRef.current) {
|
|
144
|
+
abortControllerRef.current.abort();
|
|
145
|
+
}
|
|
146
|
+
operationIdRef.current++;
|
|
147
|
+
};
|
|
81
148
|
}, []);
|
|
82
149
|
|
|
83
150
|
return { generate, generateJSON, result, jsonResult, isGenerating, error, reset };
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Provider Configuration
|
|
3
|
-
* Centralized configuration for AI provider with simplified settings
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import { DEFAULT_MODELS } from "../domain/entities";
|
|
7
3
|
|
|
@@ -27,16 +23,10 @@ export interface ResolvedProviderConfig {
|
|
|
27
23
|
timeout: number;
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
/**
|
|
31
|
-
* Default configuration values
|
|
32
|
-
*/
|
|
33
26
|
const DEFAULTS = {
|
|
34
27
|
timeout: 30000,
|
|
35
28
|
};
|
|
36
29
|
|
|
37
|
-
/**
|
|
38
|
-
* Resolve provider configuration based on preferences
|
|
39
|
-
*/
|
|
40
30
|
export function resolveProviderConfig(
|
|
41
31
|
input: ProviderConfigInput,
|
|
42
32
|
): ResolvedProviderConfig {
|
|
@@ -48,25 +38,3 @@ export function resolveProviderConfig(
|
|
|
48
38
|
timeout: preferences.timeout ?? DEFAULTS.timeout,
|
|
49
39
|
};
|
|
50
40
|
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get cost-optimized config
|
|
54
|
-
*/
|
|
55
|
-
export function getCostOptimizedConfig(
|
|
56
|
-
input: ProviderConfigInput,
|
|
57
|
-
): ResolvedProviderConfig {
|
|
58
|
-
return resolveProviderConfig(input);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get quality-optimized config
|
|
63
|
-
*/
|
|
64
|
-
export function getQualityOptimizedConfig(
|
|
65
|
-
input: ProviderConfigInput,
|
|
66
|
-
): ResolvedProviderConfig {
|
|
67
|
-
const resolved = resolveProviderConfig(input);
|
|
68
|
-
return {
|
|
69
|
-
...resolved,
|
|
70
|
-
timeout: 60000, // Longer timeout
|
|
71
|
-
};
|
|
72
|
-
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Provider Factory
|
|
3
|
-
* Creates and configures AI provider instances with simplified settings
|
|
4
|
-
*/
|
|
5
1
|
|
|
6
2
|
import { geminiClientCoreService } from "../infrastructure/services/gemini-client-core.service";
|
|
7
3
|
import type { GeminiConfig } from "../domain/entities";
|
|
@@ -9,17 +5,11 @@ import type {
|
|
|
9
5
|
ProviderConfigInput,
|
|
10
6
|
ResolvedProviderConfig,
|
|
11
7
|
} from "./ProviderConfig";
|
|
12
|
-
import {
|
|
13
|
-
resolveProviderConfig,
|
|
14
|
-
getCostOptimizedConfig,
|
|
15
|
-
getQualityOptimizedConfig,
|
|
16
|
-
} from "./ProviderConfig";
|
|
17
|
-
|
|
18
|
-
export type OptimizationStrategy = "cost" | "quality" | "balanced";
|
|
8
|
+
import { resolveProviderConfig } from "./ProviderConfig";
|
|
19
9
|
|
|
20
10
|
export interface ProviderFactoryOptions extends ProviderConfigInput {
|
|
21
|
-
/**
|
|
22
|
-
strategy?:
|
|
11
|
+
/** Quality preference strategy */
|
|
12
|
+
strategy?: "cost" | "quality";
|
|
23
13
|
}
|
|
24
14
|
|
|
25
15
|
class ProviderFactory {
|
|
@@ -29,20 +19,11 @@ class ProviderFactory {
|
|
|
29
19
|
* Initialize provider with configuration
|
|
30
20
|
*/
|
|
31
21
|
initialize(options: ProviderFactoryOptions): void {
|
|
32
|
-
|
|
22
|
+
const config = resolveProviderConfig(options);
|
|
33
23
|
|
|
34
|
-
// Apply
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
config = getCostOptimizedConfig(options);
|
|
38
|
-
break;
|
|
39
|
-
case "quality":
|
|
40
|
-
config = getQualityOptimizedConfig(options);
|
|
41
|
-
break;
|
|
42
|
-
case "balanced":
|
|
43
|
-
default:
|
|
44
|
-
config = resolveProviderConfig(options);
|
|
45
|
-
break;
|
|
24
|
+
// Apply strategy-based adjustments
|
|
25
|
+
if (options.strategy === "quality") {
|
|
26
|
+
config.timeout = 60000; // Longer timeout for quality
|
|
46
27
|
}
|
|
47
28
|
|
|
48
29
|
this.currentConfig = config;
|
|
@@ -73,23 +54,29 @@ class ProviderFactory {
|
|
|
73
54
|
|
|
74
55
|
/**
|
|
75
56
|
* Update configuration without re-initializing
|
|
76
|
-
*
|
|
57
|
+
* Note: Changing apiKey requires full re-initialization
|
|
77
58
|
*/
|
|
78
59
|
updateConfig(updates: Partial<ProviderConfigInput>): void {
|
|
79
60
|
if (!this.currentConfig) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
"Provider not initialized. Call initialize() first.",
|
|
82
|
-
);
|
|
61
|
+
throw new Error("Provider not initialized. Call initialize() first.");
|
|
83
62
|
}
|
|
84
63
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
64
|
+
// If API key is changing, we need to re-initialize
|
|
65
|
+
if (updates.apiKey && updates.apiKey !== this.currentConfig.apiKey) {
|
|
66
|
+
const newInput: ProviderConfigInput = {
|
|
67
|
+
apiKey: updates.apiKey,
|
|
68
|
+
preferences: updates.preferences || {},
|
|
69
|
+
};
|
|
70
|
+
this.initialize(newInput);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
91
73
|
|
|
92
|
-
|
|
74
|
+
// For other updates, merge with current config
|
|
75
|
+
this.currentConfig = {
|
|
76
|
+
...this.currentConfig,
|
|
77
|
+
...updates.preferences,
|
|
78
|
+
timeout: updates.preferences?.timeout ?? this.currentConfig.timeout,
|
|
79
|
+
};
|
|
93
80
|
}
|
|
94
81
|
}
|
|
95
82
|
|
package/src/providers/index.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider Configuration & Factory
|
|
3
|
-
* Centralized configuration system for
|
|
3
|
+
* Centralized configuration system for AI provider setup
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export {
|
|
7
|
-
resolveProviderConfig,
|
|
8
|
-
getCostOptimizedConfig,
|
|
9
|
-
getQualityOptimizedConfig,
|
|
10
|
-
} from "./ProviderConfig";
|
|
6
|
+
export { resolveProviderConfig } from "./ProviderConfig";
|
|
11
7
|
|
|
12
8
|
export type {
|
|
13
9
|
QualityPreference,
|
|
@@ -19,6 +15,5 @@ export type {
|
|
|
19
15
|
export { providerFactory } from "./ProviderFactory";
|
|
20
16
|
|
|
21
17
|
export type {
|
|
22
|
-
OptimizationStrategy,
|
|
23
18
|
ProviderFactoryOptions,
|
|
24
19
|
} from "./ProviderFactory";
|