@umituz/react-native-ai-gemini-provider 3.0.41 → 3.0.42
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/application/builders/config-builder.ts +102 -0
- package/src/application/builders/index.ts +8 -0
- package/src/application/dtos/generation-request.dto.ts +89 -0
- package/src/application/dtos/index.ts +8 -0
- package/src/application/index.ts +16 -0
- package/src/application/providers/gemini-provider.ts +135 -0
- package/src/application/providers/index.ts +6 -0
- package/src/application/use-cases/generate-json.use-case.ts +73 -0
- package/src/application/use-cases/generate-text.use-case.ts +81 -0
- package/src/application/use-cases/index.ts +20 -0
- package/src/application/use-cases/stream-content.use-case.ts +46 -0
- package/src/domain/entities/error.types.ts +0 -5
- package/src/domain/entities/gemini.types.ts +3 -1
- package/src/domain/index.ts +16 -0
- package/src/domain/repositories/index.ts +19 -0
- package/src/domain/repositories/streaming.repository.ts +41 -0
- package/src/domain/repositories/structured-text.repository.ts +41 -0
- package/src/domain/repositories/text-generation.repository.ts +38 -0
- package/src/domain/services/validation.service.ts +157 -0
- package/src/domain/value-objects/api-key.vo.ts +55 -0
- package/src/domain/value-objects/index.ts +8 -0
- package/src/domain/value-objects/model-name.vo.ts +66 -0
- package/src/domain/value-objects/timeout.vo.ts +69 -0
- package/src/index.ts +110 -25
- package/src/infrastructure/external/gemini-client.singleton.ts +49 -0
- package/src/infrastructure/external/gemini-sdk.adapter.ts +143 -0
- package/src/infrastructure/external/index.ts +7 -0
- package/src/infrastructure/index.ts +16 -0
- package/src/infrastructure/mappers/content.mapper.ts +80 -0
- package/src/infrastructure/mappers/error.mapper.ts +152 -0
- package/src/infrastructure/mappers/index.ts +7 -0
- package/src/infrastructure/mappers/response.mapper.ts +165 -0
- package/src/infrastructure/repositories/base-gemini.repository.ts +94 -0
- package/src/infrastructure/repositories/gemini-streaming.repository.impl.ts +119 -0
- package/src/infrastructure/repositories/gemini-structured-text.repository.impl.ts +108 -0
- package/src/infrastructure/repositories/gemini-text.repository.impl.ts +76 -0
- package/src/infrastructure/repositories/index.ts +10 -0
- package/src/infrastructure/utils/index.ts +6 -0
- package/src/presentation/hooks/index.ts +8 -0
- package/src/presentation/hooks/use-gemini.hook.ts +181 -0
- package/src/presentation/hooks/use-operation-manager.hook.ts +67 -0
- package/src/presentation/index.ts +10 -0
- package/src/presentation/providers/gemini-provider.tsx +93 -0
- package/src/presentation/providers/index.ts +10 -0
- package/dist/domain/entities/error.types.d.ts +0 -96
- package/dist/domain/entities/gemini.types.d.ts +0 -128
- package/dist/domain/entities/index.d.ts +0 -6
- package/dist/domain/entities/models.d.ts +0 -23
- package/dist/index.d.ts +0 -15
- package/dist/infrastructure/services/BaseService.d.ts +0 -29
- package/dist/infrastructure/services/ChatSession.d.ts +0 -63
- package/dist/infrastructure/services/GeminiClient.d.ts +0 -16
- package/dist/infrastructure/services/GeminiProvider.d.ts +0 -10
- package/dist/infrastructure/services/Streaming.d.ts +0 -7
- package/dist/infrastructure/services/StructuredText.d.ts +0 -6
- package/dist/infrastructure/services/TextGeneration.d.ts +0 -8
- package/dist/infrastructure/services/index.d.ts +0 -6
- package/dist/infrastructure/telemetry/TelemetryHooks.d.ts +0 -41
- package/dist/infrastructure/telemetry/index.d.ts +0 -4
- package/dist/infrastructure/utils/async/execute-state.util.d.ts +0 -49
- package/dist/infrastructure/utils/async/index.d.ts +0 -4
- package/dist/infrastructure/utils/content-mapper.util.d.ts +0 -45
- package/dist/infrastructure/utils/error-mapper.util.d.ts +0 -2
- package/dist/infrastructure/utils/gemini-data-transformer.util.d.ts +0 -2
- package/dist/infrastructure/utils/json-parser.util.d.ts +0 -9
- package/dist/infrastructure/utils/stream-processor.util.d.ts +0 -14
- package/dist/presentation/hooks/index.d.ts +0 -1
- package/dist/presentation/hooks/useGemini.d.ts +0 -17
- package/dist/presentation/hooks/useOperationManager.d.ts +0 -23
- package/dist/providers/ConfigBuilder.d.ts +0 -46
- package/dist/providers/ProviderFactory.d.ts +0 -25
- package/dist/providers/index.d.ts +0 -7
- package/src/infrastructure/services/BaseService.ts +0 -53
- package/src/infrastructure/services/ChatSession.ts +0 -199
- package/src/infrastructure/services/GeminiClient.ts +0 -112
- package/src/infrastructure/services/Streaming.ts +0 -56
- package/src/infrastructure/services/StructuredText.ts +0 -57
- package/src/infrastructure/services/TextGeneration.ts +0 -57
- package/src/infrastructure/telemetry/TelemetryHooks.ts +0 -110
- package/src/infrastructure/utils/async/execute-state.util.ts +0 -93
- package/src/infrastructure/utils/content-mapper.util.ts +0 -175
- package/src/infrastructure/utils/error-mapper.util.ts +0 -145
- package/src/infrastructure/utils/gemini-data-transformer.util.ts +0 -40
- package/src/infrastructure/utils/text-calculations.util.ts +0 -51
- package/src/presentation/hooks/useGemini.ts +0 -125
- package/src/presentation/hooks/useOperationManager.ts +0 -88
- package/src/providers/ConfigBuilder.ts +0 -112
- package/src/providers/ProviderFactory.ts +0 -65
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Streaming Repository Implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { GeminiGenerationConfig } from "../../domain/entities";
|
|
6
|
+
import type {
|
|
7
|
+
IStreamingRepository,
|
|
8
|
+
StreamingRequest,
|
|
9
|
+
} from "../../domain/repositories/streaming.repository";
|
|
10
|
+
import { ValidationService } from "../../domain/services/validation.service";
|
|
11
|
+
import { ContentMapper } from "../mappers/content.mapper";
|
|
12
|
+
import { BaseGeminiRepository } from "./base-gemini.repository";
|
|
13
|
+
|
|
14
|
+
interface StreamChunk {
|
|
15
|
+
text(): string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class GeminiStreamingRepository
|
|
19
|
+
extends BaseGeminiRepository
|
|
20
|
+
implements IStreamingRepository
|
|
21
|
+
{
|
|
22
|
+
constructor(
|
|
23
|
+
getModel: (name: string) => unknown,
|
|
24
|
+
validator: ValidationService,
|
|
25
|
+
contentMapper: ContentMapper
|
|
26
|
+
) {
|
|
27
|
+
super(getModel as (name: string) => any, validator, contentMapper);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Stream text content
|
|
32
|
+
*/
|
|
33
|
+
async stream(request: StreamingRequest): Promise<string> {
|
|
34
|
+
this.validator.validateCallback(request.onChunk, "onChunk");
|
|
35
|
+
|
|
36
|
+
return this.executeWithErrorHandling(async () => {
|
|
37
|
+
const { genModel, sdkContents, config, signal } =
|
|
38
|
+
this.prepareRequest(request);
|
|
39
|
+
|
|
40
|
+
const requestOptions = this.createRequestOptions(
|
|
41
|
+
sdkContents,
|
|
42
|
+
config
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const result = signal
|
|
46
|
+
? await (genModel as any).generateContentStream(
|
|
47
|
+
requestOptions,
|
|
48
|
+
{ signal }
|
|
49
|
+
)
|
|
50
|
+
: await (genModel as any).generateContentStream(requestOptions);
|
|
51
|
+
|
|
52
|
+
return await this.processStream(
|
|
53
|
+
result.stream,
|
|
54
|
+
request.onChunk
|
|
55
|
+
);
|
|
56
|
+
}, "GeminiStreamingRepository.stream");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Stream simple text from prompt
|
|
61
|
+
*/
|
|
62
|
+
async streamText(
|
|
63
|
+
model: string,
|
|
64
|
+
prompt: string,
|
|
65
|
+
onChunk: (text: string) => void,
|
|
66
|
+
config?: GeminiGenerationConfig,
|
|
67
|
+
signal?: AbortSignal
|
|
68
|
+
): Promise<string> {
|
|
69
|
+
this.validator.validatePrompt(prompt);
|
|
70
|
+
|
|
71
|
+
const contents = [this.contentMapper.createTextContent(prompt, "user")];
|
|
72
|
+
return this.stream({
|
|
73
|
+
model,
|
|
74
|
+
contents,
|
|
75
|
+
onChunk,
|
|
76
|
+
generationConfig: config,
|
|
77
|
+
signal,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Process stream with chunk callback
|
|
83
|
+
*/
|
|
84
|
+
private async processStream(
|
|
85
|
+
stream: AsyncIterable<StreamChunk>,
|
|
86
|
+
onChunk: (text: string) => void
|
|
87
|
+
): Promise<string> {
|
|
88
|
+
let fullText = "";
|
|
89
|
+
|
|
90
|
+
for await (const chunk of stream) {
|
|
91
|
+
try {
|
|
92
|
+
const chunkText = chunk.text();
|
|
93
|
+
if (chunkText) {
|
|
94
|
+
fullText += chunkText;
|
|
95
|
+
this.safeCallChunk(onChunk, chunkText);
|
|
96
|
+
}
|
|
97
|
+
} catch (chunkError) {
|
|
98
|
+
// Chunk errors are critical (e.g., safety blocks)
|
|
99
|
+
throw chunkError;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return fullText;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Safely call chunk callback
|
|
108
|
+
*/
|
|
109
|
+
private safeCallChunk(
|
|
110
|
+
callback: (text: string) => void,
|
|
111
|
+
text: string
|
|
112
|
+
): void {
|
|
113
|
+
try {
|
|
114
|
+
callback(text);
|
|
115
|
+
} catch {
|
|
116
|
+
// Silently ignore callback errors
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Structured Text Repository Implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { GeminiContent, GeminiGenerationConfig } from "../../domain/entities";
|
|
6
|
+
import type {
|
|
7
|
+
IStructuredTextRepository,
|
|
8
|
+
StructuredGenerationRequest,
|
|
9
|
+
} from "../../domain/repositories/structured-text.repository";
|
|
10
|
+
import { ValidationService } from "../../domain/services/validation.service";
|
|
11
|
+
import { ContentMapper } from "../mappers/content.mapper";
|
|
12
|
+
import { ResponseMapper } from "../mappers/response.mapper";
|
|
13
|
+
import { parseJsonResponse } from "../utils/json-parser.util";
|
|
14
|
+
import { BaseGeminiRepository } from "./base-gemini.repository";
|
|
15
|
+
|
|
16
|
+
export class GeminiStructuredTextRepository
|
|
17
|
+
extends BaseGeminiRepository
|
|
18
|
+
implements IStructuredTextRepository
|
|
19
|
+
{
|
|
20
|
+
constructor(
|
|
21
|
+
getModel: (name: string) => unknown,
|
|
22
|
+
validator: ValidationService,
|
|
23
|
+
contentMapper: ContentMapper,
|
|
24
|
+
private readonly responseMapper: ResponseMapper
|
|
25
|
+
) {
|
|
26
|
+
super(getModel as (name: string) => any, validator, contentMapper);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate structured JSON from prompt
|
|
31
|
+
*/
|
|
32
|
+
async generateStructured<T>(
|
|
33
|
+
request: StructuredGenerationRequest
|
|
34
|
+
): Promise<T> {
|
|
35
|
+
this.validator.validatePrompt(request.prompt);
|
|
36
|
+
this.validator.validateSchema(request.schema);
|
|
37
|
+
|
|
38
|
+
const generationConfig: GeminiGenerationConfig = {
|
|
39
|
+
...request.config,
|
|
40
|
+
responseMimeType: "application/json",
|
|
41
|
+
responseSchema: request.schema as any,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const contents = [
|
|
45
|
+
this.contentMapper.createTextContent(request.prompt, "user"),
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
return this.generateStructuredFromContents<T>(
|
|
49
|
+
request.model,
|
|
50
|
+
contents,
|
|
51
|
+
request.schema,
|
|
52
|
+
generationConfig,
|
|
53
|
+
request.signal
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate structured JSON from contents
|
|
59
|
+
*/
|
|
60
|
+
async generateStructuredFromContents<T>(
|
|
61
|
+
model: string,
|
|
62
|
+
contents: GeminiContent[],
|
|
63
|
+
schema: Record<string, unknown>,
|
|
64
|
+
config?: GeminiGenerationConfig,
|
|
65
|
+
signal?: AbortSignal
|
|
66
|
+
): Promise<T> {
|
|
67
|
+
const generationConfig: GeminiGenerationConfig = {
|
|
68
|
+
...config,
|
|
69
|
+
responseMimeType: "application/json",
|
|
70
|
+
responseSchema: schema as any,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return this.executeWithErrorHandling(async () => {
|
|
74
|
+
const { genModel, sdkContents, config: finalConfig } =
|
|
75
|
+
this.prepareRequest({
|
|
76
|
+
model,
|
|
77
|
+
contents,
|
|
78
|
+
generationConfig: generationConfig,
|
|
79
|
+
signal,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const requestOptions = this.createRequestOptions(
|
|
83
|
+
sdkContents,
|
|
84
|
+
finalConfig
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const result = signal
|
|
88
|
+
? await (genModel as any).generateContent(requestOptions, {
|
|
89
|
+
signal,
|
|
90
|
+
})
|
|
91
|
+
: await (genModel as any).generateContent(requestOptions);
|
|
92
|
+
|
|
93
|
+
if (!result.response) {
|
|
94
|
+
throw new Error("No response received from Gemini API");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const response = this.responseMapper.toDomain(result.response);
|
|
98
|
+
const candidates = response.candidates;
|
|
99
|
+
|
|
100
|
+
if (!candidates || candidates.length === 0) {
|
|
101
|
+
throw new Error("No candidates in response");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const text = this.responseMapper.extractText(response);
|
|
105
|
+
return parseJsonResponse<T>(text);
|
|
106
|
+
}, "GeminiStructuredTextRepository.generateStructuredFromContents");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Text Generation Repository Implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { GeminiGenerationConfig, GeminiResponse } from "../../domain/entities";
|
|
6
|
+
import type {
|
|
7
|
+
ITextGenerationRepository,
|
|
8
|
+
TextGenerationRequest,
|
|
9
|
+
} from "../../domain/repositories/text-generation.repository";
|
|
10
|
+
import { ValidationService } from "../../domain/services/validation.service";
|
|
11
|
+
import { ContentMapper } from "../mappers/content.mapper";
|
|
12
|
+
import { ResponseMapper } from "../mappers/response.mapper";
|
|
13
|
+
import { BaseGeminiRepository } from "./base-gemini.repository";
|
|
14
|
+
|
|
15
|
+
export class GeminiTextRepository
|
|
16
|
+
extends BaseGeminiRepository
|
|
17
|
+
implements ITextGenerationRepository
|
|
18
|
+
{
|
|
19
|
+
constructor(
|
|
20
|
+
getModel: (name: string) => unknown,
|
|
21
|
+
validator: ValidationService,
|
|
22
|
+
contentMapper: ContentMapper,
|
|
23
|
+
private readonly responseMapper: ResponseMapper
|
|
24
|
+
) {
|
|
25
|
+
super(getModel as (name: string) => any, validator, contentMapper);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generate text content
|
|
30
|
+
*/
|
|
31
|
+
async generate(request: TextGenerationRequest): Promise<GeminiResponse> {
|
|
32
|
+
return this.executeWithErrorHandling(async () => {
|
|
33
|
+
const { genModel, sdkContents, config, signal } =
|
|
34
|
+
this.prepareRequest(request);
|
|
35
|
+
|
|
36
|
+
const requestOptions = this.createRequestOptions(
|
|
37
|
+
sdkContents,
|
|
38
|
+
config
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const result = signal
|
|
42
|
+
? await (genModel as any).generateContent(requestOptions, {
|
|
43
|
+
signal,
|
|
44
|
+
})
|
|
45
|
+
: await (genModel as any).generateContent(requestOptions);
|
|
46
|
+
|
|
47
|
+
if (!result.response) {
|
|
48
|
+
throw new Error("No response received from Gemini API");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return this.responseMapper.toDomain(result.response);
|
|
52
|
+
}, "GeminiTextRepository.generate");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate simple text from prompt
|
|
57
|
+
*/
|
|
58
|
+
async generateText(
|
|
59
|
+
model: string,
|
|
60
|
+
prompt: string,
|
|
61
|
+
config?: GeminiGenerationConfig,
|
|
62
|
+
signal?: AbortSignal
|
|
63
|
+
): Promise<string> {
|
|
64
|
+
this.validator.validatePrompt(prompt);
|
|
65
|
+
|
|
66
|
+
const contents = [this.contentMapper.createTextContent(prompt, "user")];
|
|
67
|
+
const response = await this.generate({
|
|
68
|
+
model,
|
|
69
|
+
contents,
|
|
70
|
+
generationConfig: config,
|
|
71
|
+
signal,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return this.responseMapper.extractText(response);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure Repository Implementations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { BaseGeminiRepository } from "./base-gemini.repository";
|
|
6
|
+
export { GeminiTextRepository } from "./gemini-text.repository.impl";
|
|
7
|
+
export { GeminiStreamingRepository } from "./gemini-streaming.repository.impl";
|
|
8
|
+
export {
|
|
9
|
+
GeminiStructuredTextRepository,
|
|
10
|
+
} from "./gemini-structured-text.repository.impl";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentation Hooks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { useGemini } from "./use-gemini.hook";
|
|
6
|
+
export { useOperationManager } from "./use-operation-manager.hook";
|
|
7
|
+
export type { UseGeminiOptions, UseGeminiReturn } from "./use-gemini.hook";
|
|
8
|
+
export type { OperationManager } from "./use-operation-manager.hook";
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useGemini Hook
|
|
3
|
+
* React hook for Gemini operations using DDD architecture
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback, useMemo } from "react";
|
|
7
|
+
import type { GeminiGenerationConfig } from "../../domain/entities";
|
|
8
|
+
import { DEFAULT_MODELS } from "../../domain/entities";
|
|
9
|
+
import { GenerateTextUseCase } from "../../application/use-cases/generate-text.use-case";
|
|
10
|
+
import { StreamContentUseCase } from "../../application/use-cases/stream-content.use-case";
|
|
11
|
+
import { GenerateJSONUseCase } from "../../application/use-cases/generate-json.use-case";
|
|
12
|
+
import { geminiProvider } from "../../application/providers/gemini-provider";
|
|
13
|
+
import { useOperationManager } from "./use-operation-manager.hook";
|
|
14
|
+
|
|
15
|
+
export interface UseGeminiOptions {
|
|
16
|
+
model?: string;
|
|
17
|
+
generationConfig?: GeminiGenerationConfig;
|
|
18
|
+
onSuccess?: (result: string) => void;
|
|
19
|
+
onError?: (error: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UseGeminiReturn {
|
|
23
|
+
// Text generation
|
|
24
|
+
generate: (prompt: string) => Promise<void>;
|
|
25
|
+
// JSON generation
|
|
26
|
+
generateJSON: <T>(prompt: string, schema: Record<string, unknown>) => Promise<T | null>;
|
|
27
|
+
// Streaming
|
|
28
|
+
stream: (prompt: string, onChunk: (text: string) => void) => Promise<void>;
|
|
29
|
+
// State
|
|
30
|
+
result: string | null;
|
|
31
|
+
jsonResult: unknown;
|
|
32
|
+
isGenerating: boolean;
|
|
33
|
+
error: string | null;
|
|
34
|
+
// Actions
|
|
35
|
+
reset: () => void;
|
|
36
|
+
abort: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
|
|
40
|
+
const [result, setResult] = useState<string | null>(null);
|
|
41
|
+
const [jsonResult, setJsonResult] = useState<unknown>(null);
|
|
42
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
43
|
+
const [error, setError] = useState<string | null>(null);
|
|
44
|
+
|
|
45
|
+
const { executeOperation, abort } = useOperationManager();
|
|
46
|
+
|
|
47
|
+
const model = options.model ?? DEFAULT_MODELS.TEXT;
|
|
48
|
+
|
|
49
|
+
// Generate text
|
|
50
|
+
const generate = useCallback(
|
|
51
|
+
async (prompt: string) => {
|
|
52
|
+
setIsGenerating(true);
|
|
53
|
+
setError(null);
|
|
54
|
+
setResult(null);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const useCase = new GenerateTextUseCase(
|
|
58
|
+
geminiProvider.getTextRepository(),
|
|
59
|
+
geminiProvider.getValidator(),
|
|
60
|
+
geminiProvider.getContentMapper()
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const text = await executeOperation(async (signal) => {
|
|
64
|
+
return useCase.executeSimple({
|
|
65
|
+
model,
|
|
66
|
+
prompt,
|
|
67
|
+
config: options.generationConfig,
|
|
68
|
+
signal,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
setResult(text);
|
|
73
|
+
options.onSuccess?.(text);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
const errorMessage = err instanceof Error ? err.message : "Generation failed";
|
|
76
|
+
setError(errorMessage);
|
|
77
|
+
options.onError?.(errorMessage);
|
|
78
|
+
} finally {
|
|
79
|
+
setIsGenerating(false);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
[model, options.generationConfig, options.onSuccess, options.onError, executeOperation]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Generate JSON
|
|
86
|
+
const generateJSON = useCallback(
|
|
87
|
+
async <T>(prompt: string, schema: Record<string, unknown>): Promise<T | null> => {
|
|
88
|
+
setIsGenerating(true);
|
|
89
|
+
setError(null);
|
|
90
|
+
setJsonResult(null);
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const useCase = new GenerateJSONUseCase(
|
|
94
|
+
geminiProvider.getStructuredTextRepository(),
|
|
95
|
+
geminiProvider.getValidator()
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const data = await executeOperation(async (signal) => {
|
|
99
|
+
return useCase.executeSimple<T>({
|
|
100
|
+
model,
|
|
101
|
+
prompt,
|
|
102
|
+
schema,
|
|
103
|
+
config: options.generationConfig,
|
|
104
|
+
signal,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
setJsonResult(data);
|
|
109
|
+
setResult(JSON.stringify(data, null, 2));
|
|
110
|
+
options.onSuccess?.(JSON.stringify(data, null, 2));
|
|
111
|
+
|
|
112
|
+
return data;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
const errorMessage = err instanceof Error ? err.message : "Generation failed";
|
|
115
|
+
setError(errorMessage);
|
|
116
|
+
options.onError?.(errorMessage);
|
|
117
|
+
return null;
|
|
118
|
+
} finally {
|
|
119
|
+
setIsGenerating(false);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
[model, options.generationConfig, options.onSuccess, options.onError, executeOperation]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Stream content
|
|
126
|
+
const stream = useCallback(
|
|
127
|
+
async (prompt: string, onChunk: (text: string) => void) => {
|
|
128
|
+
setIsGenerating(true);
|
|
129
|
+
setError(null);
|
|
130
|
+
setResult(null);
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const useCase = new StreamContentUseCase(
|
|
134
|
+
geminiProvider.getStreamingRepository(),
|
|
135
|
+
geminiProvider.getValidator(),
|
|
136
|
+
geminiProvider.getContentMapper()
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const text = await executeOperation(async (signal) => {
|
|
140
|
+
return useCase.execute({
|
|
141
|
+
model,
|
|
142
|
+
prompt,
|
|
143
|
+
onChunk,
|
|
144
|
+
config: options.generationConfig,
|
|
145
|
+
signal,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
setResult(text);
|
|
150
|
+
options.onSuccess?.(text);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const errorMessage = err instanceof Error ? err.message : "Streaming failed";
|
|
153
|
+
setError(errorMessage);
|
|
154
|
+
options.onError?.(errorMessage);
|
|
155
|
+
} finally {
|
|
156
|
+
setIsGenerating(false);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
[model, options.generationConfig, options.onSuccess, options.onError, executeOperation]
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const reset = useCallback(() => {
|
|
163
|
+
abort();
|
|
164
|
+
setResult(null);
|
|
165
|
+
setJsonResult(null);
|
|
166
|
+
setIsGenerating(false);
|
|
167
|
+
setError(null);
|
|
168
|
+
}, [abort]);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
generate,
|
|
172
|
+
generateJSON,
|
|
173
|
+
stream,
|
|
174
|
+
result,
|
|
175
|
+
jsonResult,
|
|
176
|
+
isGenerating,
|
|
177
|
+
error,
|
|
178
|
+
reset,
|
|
179
|
+
abort,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation Manager Hook
|
|
3
|
+
* Manages abort controllers and operation tracking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useRef, useCallback, useEffect } from "react";
|
|
7
|
+
|
|
8
|
+
export interface OperationManager {
|
|
9
|
+
executeOperation: <T>(
|
|
10
|
+
operation: (signal: AbortSignal) => Promise<T>
|
|
11
|
+
) => Promise<T>;
|
|
12
|
+
abort: () => void;
|
|
13
|
+
isOperationActive: () => boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useOperationManager(): OperationManager {
|
|
17
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
18
|
+
const isActiveRef = useRef(false);
|
|
19
|
+
|
|
20
|
+
const abort = useCallback(() => {
|
|
21
|
+
if (abortControllerRef.current) {
|
|
22
|
+
abortControllerRef.current.abort();
|
|
23
|
+
abortControllerRef.current = null;
|
|
24
|
+
}
|
|
25
|
+
isActiveRef.current = false;
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const executeOperation = useCallback(
|
|
29
|
+
async <T>(operation: (signal: AbortSignal) => Promise<T>): Promise<T> => {
|
|
30
|
+
// Abort any existing operation
|
|
31
|
+
abort();
|
|
32
|
+
|
|
33
|
+
// Create new controller
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
abortControllerRef.current = controller;
|
|
36
|
+
isActiveRef.current = true;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
return await operation(controller.signal);
|
|
40
|
+
} finally {
|
|
41
|
+
// Clean up only if still active
|
|
42
|
+
if (abortControllerRef.current === controller) {
|
|
43
|
+
abortControllerRef.current = null;
|
|
44
|
+
isActiveRef.current = false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
[abort]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const isOperationActive = useCallback((): boolean => {
|
|
52
|
+
return isActiveRef.current;
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
// Cleanup on unmount
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
return () => {
|
|
58
|
+
abort();
|
|
59
|
+
};
|
|
60
|
+
}, [abort]);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
executeOperation,
|
|
64
|
+
abort,
|
|
65
|
+
isOperationActive,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Provider Component
|
|
3
|
+
* React context provider for Gemini services
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
7
|
+
import { GeminiConfigBuilder } from "../../application/builders/config-builder";
|
|
8
|
+
import { geminiProvider } from "../../application/providers/gemini-provider";
|
|
9
|
+
import type { GeminiConfig } from "../../domain/entities";
|
|
10
|
+
|
|
11
|
+
interface GeminiContextValue {
|
|
12
|
+
isInitialized: boolean;
|
|
13
|
+
initialize: (config: GeminiConfig) => void;
|
|
14
|
+
reset: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const GeminiContext = createContext<GeminiContextValue | undefined>(undefined);
|
|
18
|
+
|
|
19
|
+
export interface GeminiProviderProps {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
config?: GeminiConfig;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function GeminiProviderComponent({
|
|
25
|
+
children,
|
|
26
|
+
config: initialConfig,
|
|
27
|
+
}: GeminiProviderProps) {
|
|
28
|
+
const [isInitialized, setIsInitialized] = React.useState(false);
|
|
29
|
+
|
|
30
|
+
const initialize = React.useCallback((config: GeminiConfig) => {
|
|
31
|
+
geminiProvider.initialize(config);
|
|
32
|
+
setIsInitialized(true);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const reset = React.useCallback(() => {
|
|
36
|
+
// Reset using the class directly
|
|
37
|
+
const { GeminiProviderClass } = require("../../application/providers");
|
|
38
|
+
GeminiProviderClass.reset();
|
|
39
|
+
setIsInitialized(false);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
// Initialize with config if provided
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (initialConfig && !isInitialized) {
|
|
45
|
+
initialize(initialConfig);
|
|
46
|
+
}
|
|
47
|
+
}, [initialConfig, isInitialized, initialize]);
|
|
48
|
+
|
|
49
|
+
const value = useMemo(
|
|
50
|
+
() => ({
|
|
51
|
+
isInitialized,
|
|
52
|
+
initialize,
|
|
53
|
+
reset,
|
|
54
|
+
}),
|
|
55
|
+
[isInitialized, initialize, reset]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return <GeminiContext.Provider value={value}>{children}</GeminiContext.Provider>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Hook to access Gemini context
|
|
63
|
+
*/
|
|
64
|
+
export function useGeminiContext(): GeminiContextValue {
|
|
65
|
+
const context = useContext(GeminiContext);
|
|
66
|
+
if (!context) {
|
|
67
|
+
throw new Error("useGeminiContext must be used within GeminiProvider");
|
|
68
|
+
}
|
|
69
|
+
return context;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Convenience hook to initialize with builder
|
|
74
|
+
*/
|
|
75
|
+
export function useGeminiInitializer() {
|
|
76
|
+
const { initialize, isInitialized } = useGeminiContext();
|
|
77
|
+
|
|
78
|
+
const initWithBuilder = React.useCallback(
|
|
79
|
+
(builderOrConfig: GeminiConfigBuilder | GeminiConfig) => {
|
|
80
|
+
const config =
|
|
81
|
+
builderOrConfig instanceof GeminiConfigBuilder
|
|
82
|
+
? builderOrConfig.build()
|
|
83
|
+
: builderOrConfig;
|
|
84
|
+
initialize(config);
|
|
85
|
+
},
|
|
86
|
+
[initialize]
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
initialize: initWithBuilder,
|
|
91
|
+
isInitialized,
|
|
92
|
+
};
|
|
93
|
+
}
|