Repository navigation
Expand file tree
/
Copy pathgemini_api_service.dart
More file actions
87 lines (76 loc) · 2.64 KB
/
Copy pathgemini_api_service.dart
File metadata and controls
87 lines (76 loc) · 2.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import 'package:dio/dio.dart';
import '../../../core/constants/api_constants.dart';
import '../../../core/network/api_endpoints.dart';
/// Transport contract for Gemini-compatible chat-completion providers.
/// Alternative model providers can implement this without changing data sources.
abstract interface class GeminiApiService {
Future<String> createChatCompletion({
required String model,
required List<Map<String, String>> messages,
required double temperature,
required int maxTokens,
});
}
class GeminiRestApiService implements GeminiApiService {
final Dio _dio;
GeminiRestApiService({required Dio dio}) : _dio = dio;
@override
Future<String> createChatCompletion({
required String model,
required List<Map<String, String>> messages,
required double temperature,
required int maxTokens,
}) async {
final apiKey = ApiConstants.geminiApiKey;
if (apiKey.isEmpty) throw StateError('Gemini API key is missing.');
// Convert OpenAI-style messages to native Gemini contents format
final contents = <Map<String, dynamic>>[];
for (final msg in messages) {
final role = msg['role'] == 'assistant' ? 'model' : 'user';
contents.add({
'role': role,
'parts': [
{'text': msg['content'] ?? ''}
],
});
}
final url = ApiEndpoints.geminiGenerateContent(model);
final response = await _dio.post(
url,
queryParameters: {'key': apiKey},
options: Options(
receiveTimeout: const Duration(seconds: 90),
sendTimeout: const Duration(seconds: 30),
),
data: {
'contents': contents,
'generationConfig': {
'temperature': temperature,
'maxOutputTokens': maxTokens,
},
},
);
final data = response.data;
if (data is! Map<String, dynamic>) {
throw const FormatException('Invalid Gemini response.');
}
final candidates = data['candidates'];
if (candidates is! List || candidates.isEmpty) {
throw const FormatException('Gemini returned no candidates.');
}
final firstCandidate = candidates.first as Map<String, dynamic>;
final content = firstCandidate['content'];
if (content is! Map<String, dynamic>) {
throw const FormatException('Gemini returned no content.');
}
final parts = content['parts'];
if (parts is! List || parts.isEmpty) {
throw const FormatException('Gemini returned no parts.');
}
final text = (parts.first as Map<String, dynamic>)['text'];
if (text is! String || text.trim().isEmpty) {
throw const FormatException('Gemini returned an empty response.');
}
return text;
}
}