MIF_E31222656/lib/services/gemini_disease_service.dart

331 lines
15 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:tugas_akhir_supabase/data/models/diagnosis_result_model.dart';
/// Supported Gemini models for multimodal (image+text) as per official docs
const List<String> supportedGeminiModels = [
'gemini-1.5-pro',
'gemini-2.0-pro',
'gemini-2.5-pro',
];
// Fungsi helper untuk isolate
DiagnosisResultModel _createDiagnosisModel(Map<String, dynamic> diagnosisData) {
return DiagnosisResultModel(
plantSpecies: diagnosisData['plant_species'] ?? 'Unknown Plant',
isHealthy: diagnosisData['is_healthy'] ?? true,
diseaseName: diagnosisData['disease_name'] ?? '',
scientificName: diagnosisData['scientific_name'] ?? '',
confidenceValue: (diagnosisData['confidence_value'] ?? 0.0).toDouble(),
symptoms: (diagnosisData['symptoms'] is List)
? (diagnosisData['symptoms'] as List).join(', ')
: (diagnosisData['symptoms'] ?? 'Tidak ada gejala terdeteksi'),
causes: (diagnosisData['causes'] is List)
? (diagnosisData['causes'] as List).join(', ')
: (diagnosisData['causes'] ?? 'Tidak ada penyebab teridentifikasi'),
preventionMeasures: (diagnosisData['prevention_measures'] is List)
? List<String>.from(diagnosisData['prevention_measures'])
: (diagnosisData['prevention_measures'] is String)
? [diagnosisData['prevention_measures']]
: [],
organicTreatment: (diagnosisData['organic_treatment'] is List)
? (diagnosisData['organic_treatment'] as List).join(', ')
: (diagnosisData['organic_treatment'] ?? 'Tidak ada pengobatan organik'),
chemicalTreatment: (diagnosisData['chemical_treatment'] is List)
? (diagnosisData['chemical_treatment'] as List).join(', ')
: (diagnosisData['chemical_treatment'] ?? 'Tidak ada pengobatan kimia'),
additionalInfo: AdditionalInfoModel(
severity: diagnosisData['additional_info']?['severity'] ?? 'Tidak diketahui',
spreadRate: diagnosisData['additional_info']?['spread_rate'] ?? 'Tidak diketahui',
affectedParts: (diagnosisData['additional_info']?['affected_parts'] is List)
? List<String>.from(diagnosisData['additional_info']?['affected_parts'])
: (diagnosisData['additional_info']?['affected_parts'] is String)
? [diagnosisData['additional_info']?['affected_parts']]
: [],
environmentalConditions: diagnosisData['additional_info']?['environmental_conditions'] ?? 'Tidak diketahui',
),
environmentalData: diagnosisData['environmental_data'] ?? {},
plantData: diagnosisData['plant_data'] ?? {},
treatmentSchedule: diagnosisData['treatment_schedule'] ?? {},
economicImpact: diagnosisData['economic_impact'] ?? {},
alternativeVarieties: (diagnosisData['alternative_varieties'] is List)
? List<Map<String, dynamic>>.from(diagnosisData['alternative_varieties'])
: [],
);
}
class GeminiDiseaseDiagnosisService {
final String apiKey;
final SupabaseClient supabaseClient;
final String baseUrl = 'https://generativelanguage.googleapis.com/v1beta/models';
final String model = 'gemini-1.5-flash'; // Reverting to gemini-1.5-flash
GeminiDiseaseDiagnosisService({
required this.apiKey,
required this.supabaseClient,
});
/// (Optional) Fetch available models for this API key
Future<List<String>> fetchAvailableModels() async {
final response = await http.get(
Uri.parse('$baseUrl?key=$apiKey'),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
final models = (data['models'] as List?)?.map((m) => m['name'] as String).toList() ?? [];
return models;
} else {
throw Exception('Failed to fetch models: ${response.statusCode}');
}
}
/// Main diagnosis function using Gemini API (multimodal: image + text)
Future<DiagnosisResultModel> diagnosePlant(String imagePath, {String? cropName}) async {
try {
// Read and encode the image
String base64Image;
if (kIsWeb) {
// For web platform, imagePath might be a data URL
if (imagePath.startsWith('data:image')) {
// Extract base64 data from data URL
base64Image = imagePath.split(',')[1];
} else {
// For web, we need to handle this differently
throw Exception('Web platform requires a data URL for images');
}
} else {
// For mobile platforms, read from file
final imageBytes = await File(imagePath).readAsBytes();
base64Image = base64Encode(imageBytes);
}
// Prepare the prompt for Gemini (instruct to answer in Bahasa Indonesia)
final prompt = '''
Analisis gambar tanaman ini dan berikan diagnosis penyakit secara detail dalam format JSON berikut (jawab seluruhnya dalam Bahasa Indonesia!):
{
"plant_species": "Nama tanaman",
"is_healthy": true/false,
"disease_name": "Nama penyakit",
"scientific_name": "Nama ilmiah",
"confidence_value": 0.0-1.0,
"symptoms": "Gejala yang terlihat",
"causes": "Penyebab penyakit",
"prevention_measures": ["Langkah pencegahan 1", "Langkah pencegahan 2", "Langkah pencegahan 3", "Langkah pencegahan 4"],
"organic_treatment": "Pengobatan organik",
"chemical_treatment": "Pengobatan kimia",
"additional_info": {
"severity": "Tinggi/Sedang/Rendah",
"spread_rate": "Tingkat penyebaran",
"affected_parts": ["Daun", "Batang", "Akar", "Buah"],
"environmental_conditions": "Kondisi lingkungan yang mendukung perkembangan penyakit"
},
"environmental_data": {
"temperature": 0.0,
"humidity": 0.0,
"lightIntensity": 0.0,
"soilPh": 0.0
},
"plant_data": {
"growthStage": "Fase pertumbuhan",
"age": "Umur tanaman",
"diseaseSeverity": 0.0,
"infectedArea": 0.0
},
"treatment_schedule": {
"wateringSchedule": "Penyiraman harus disesuaikan dengan kebutuhan tanaman dan kondisi lingkungan. Hindari penyiraman yang berlebihan.",
"fertilizingSchedule": "Pemupukan yang seimbang dan tepat dapat meningkatkan ketahanan tanaman terhadap penyakit. Gunakan pupuk sesuai rekomendasi.",
"pesticideSchedule": "Penggunaan pestisida (baik organik maupun kimia) harus dilakukan sesuai dengan petunjuk dan rekomendasi, dengan memperhatikan interval waktu aplikasi."
},
"economic_impact": {
"estimatedLoss": "Estimasi kerugian",
"recoveryTime": "Waktu pemulihan"
},
"alternative_varieties": [
{
"name": "Nama varietas",
"description": "Deskripsi varietas",
"resistanceLevel": "Tingkat ketahanan"
}
]
}
Penting: Berikan informasi yang lengkap dan akurat untuk semua bidang. Untuk tanaman padi, berikan informasi spesifik tentang penyakit bercak daun bakteri jika terdeteksi. Sertakan rekomendasi pengendalian gulma untuk mengurangi penyebaran penyakit.
''';
final requestBody = {
'contents': [
{
'parts': [
{'text': prompt},
{
'inline_data': {
'mime_type': 'image/jpeg',
'data': base64Image
}
}
]
}
],
'generationConfig': {
'temperature': 0.2,
'topK': 32,
'topP': 1,
'maxOutputTokens': 4096,
}
};
final response = await http.post(
Uri.parse('$baseUrl/$model:generateContent?key=$apiKey'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(requestBody),
);
if (response.statusCode != 200) {
String msg = response.body;
if (response.statusCode == 404 && msg.contains('not found')) {
msg += '\nModel $model might not be available for your API key. For free users, only gemini-1.5-flash is guaranteed.\nSee: https://ai.google.dev/gemini-api/docs/models';
}
throw Exception('Failed to get diagnosis: ${response.statusCode} - $msg');
}
final responseData = jsonDecode(response.body);
if (responseData['candidates'] == null || responseData['candidates'].isEmpty) {
throw Exception('No response from Gemini API');
}
final generatedText = responseData['candidates'][0]['content']['parts'][0]['text'];
final jsonMatch = RegExp(r'\{[\s\S]*\}').firstMatch(generatedText);
if (jsonMatch == null) {
throw Exception('Gagal memproses respons dari server. Data tidak dapat dibaca.');
}
final diagnosisData = jsonDecode(jsonMatch.group(0)!);
// Ensure all required fields have default values if missing
final processedData = _ensureCompleteData(diagnosisData);
// Create the diagnosis model
return DiagnosisResultModel(
plantSpecies: processedData['plant_species'] ?? 'Unknown Plant',
isHealthy: processedData['is_healthy'] ?? true,
diseaseName: processedData['disease_name'] ?? '',
scientificName: processedData['scientific_name'] ?? '',
confidenceValue: (processedData['confidence_value'] ?? 0.5).toDouble(),
symptoms: (processedData['symptoms'] is List)
? (processedData['symptoms'] as List).join(', ')
: (processedData['symptoms'] ?? 'Tidak ada gejala terdeteksi'),
causes: (processedData['causes'] is List)
? (processedData['causes'] as List).join(', ')
: (processedData['causes'] ?? 'Tidak ada penyebab teridentifikasi'),
preventionMeasures: (processedData['prevention_measures'] is List)
? List<String>.from(processedData['prevention_measures'])
: (processedData['prevention_measures'] is String)
? [processedData['prevention_measures']]
: [],
organicTreatment: (processedData['organic_treatment'] is List)
? (processedData['organic_treatment'] as List).join(', ')
: (processedData['organic_treatment'] ?? 'Tidak ada pengobatan organik'),
chemicalTreatment: (processedData['chemical_treatment'] is List)
? (processedData['chemical_treatment'] as List).join(', ')
: (processedData['chemical_treatment'] ?? 'Tidak ada pengobatan kimia'),
additionalInfo: AdditionalInfoModel(
severity: processedData['additional_info']?['severity'] ?? 'Tidak diketahui',
spreadRate: processedData['additional_info']?['spread_rate'] ?? 'Tidak diketahui',
affectedParts: (processedData['additional_info']?['affected_parts'] is List)
? List<String>.from(processedData['additional_info']?['affected_parts'])
: (processedData['additional_info']?['affected_parts'] is String)
? [processedData['additional_info']?['affected_parts']]
: ['Daun'],
environmentalConditions: processedData['additional_info']?['environmental_conditions'] ?? 'Tidak diketahui',
),
environmentalData: processedData['environmental_data'] ?? {},
plantData: processedData['plant_data'] ?? {},
treatmentSchedule: processedData['treatment_schedule'] ?? {},
economicImpact: processedData['economic_impact'] ?? {},
alternativeVarieties: (processedData['alternative_varieties'] is List)
? List<Map<String, dynamic>>.from(processedData['alternative_varieties'])
: [],
);
} catch (e) {
print('Error in diagnosePlant: $e');
rethrow;
}
}
// Helper method to ensure all required fields have default values
Map<String, dynamic> _ensureCompleteData(Map<String, dynamic> diagnosisData) {
// Deep copy the original data
final Map<String, dynamic> processedData = Map.from(diagnosisData);
// Ensure plant_species exists
processedData['plant_species'] ??= 'Padi';
// Ensure is_healthy exists
processedData['is_healthy'] ??= false;
// For rice plants, if disease is detected but not named, default to bacterial leaf blight
if (processedData['plant_species'].toString().toLowerCase().contains('padi') &&
processedData['is_healthy'] == false &&
(processedData['disease_name'] == null || processedData['disease_name'].toString().isEmpty)) {
processedData['disease_name'] = 'Bercak Daun Bakteri';
processedData['scientific_name'] = 'Xanthomonas oryzae pv. oryzae';
}
// Ensure additional_info exists
processedData['additional_info'] ??= {};
// Ensure severity exists in additional_info
if (processedData['additional_info'] is Map) {
(processedData['additional_info'] as Map)['severity'] ??= 'Sedang';
(processedData['additional_info'] as Map)['affected_parts'] ??= ['Daun'];
(processedData['additional_info'] as Map)['environmental_conditions'] ??=
'Kondisi lingkungan yang lembab dan hangat (suhu 25-30°C dan kelembapan tinggi) sangat mendukung perkembangan penyakit ini.';
}
// Ensure plant_data exists
processedData['plant_data'] ??= {};
// Add default values for plant_data
if (processedData['plant_data'] is Map) {
(processedData['plant_data'] as Map)['growthStage'] ??= 'Tidak dapat ditentukan dari gambar';
(processedData['plant_data'] as Map)['infectedArea'] ??= 0;
}
// Ensure treatment_schedule exists
processedData['treatment_schedule'] ??= {
'wateringSchedule': 'Penyiraman harus disesuaikan dengan kebutuhan tanaman dan kondisi lingkungan. Hindari penyiraman yang berlebihan.',
'fertilizingSchedule': 'Pemupukan yang seimbang dan tepat dapat meningkatkan ketahanan tanaman terhadap penyakit. Gunakan pupuk sesuai rekomendasi.',
'pesticideSchedule': 'Penggunaan pestisida (baik organik maupun kimia) harus dilakukan sesuai dengan petunjuk dan rekomendasi, dengan memperhatikan interval waktu aplikasi.'
};
// Ensure economic_impact exists
processedData['economic_impact'] ??= {};
// Add default values for economic_impact
if (processedData['economic_impact'] is Map) {
(processedData['economic_impact'] as Map)['estimatedLoss'] ??= 'Tidak dapat ditentukan dari gambar. Kerugian bergantung pada luas area yang terinfeksi.';
}
// Ensure prevention_measures exists
if (processedData['prevention_measures'] == null ||
(processedData['prevention_measures'] is List && (processedData['prevention_measures'] as List).isEmpty)) {
processedData['prevention_measures'] = [
'Penggunaan benih yang sehat dan bersertifikat bebas penyakit.',
'Sanitasi lahan pertanian yang baik, termasuk pembersihan sisa-sisa tanaman setelah panen.',
'Penggunaan varietas padi yang tahan terhadap penyakit bercak daun bakteri.',
'Pengendalian gulma untuk mengurangi penyebaran penyakit.'
];
}
// Ensure organic_treatment exists
processedData['organic_treatment'] ??= 'Penggunaan pestisida nabati seperti ekstrak nimba atau ekstrak tembakau dapat membantu mengendalikan penyebaran penyakit. Namun, efikasi pengobatan organik terbatas dan mungkin perlu dikombinasikan dengan metode pengendalian lainnya.';
// Ensure chemical_treatment exists
processedData['chemical_treatment'] ??= 'Penggunaan bakterisida seperti kasugamycin atau oxytetracycline dapat efektif dalam mengendalikan penyakit bercak daun bakteri. Ikuti petunjuk penggunaan dengan cermat dan perhatikan dosis yang tepat untuk menghindari dampak negatif terhadap lingkungan dan kesehatan manusia.';
return processedData;
}
}