116 lines
3.6 KiB
Dart
116 lines
3.6 KiB
Dart
import 'dart:io';
|
|
import 'dart:convert';
|
|
import 'package:google_generative_ai/google_generative_ai.dart';
|
|
import '../models/models.dart';
|
|
|
|
class GeminiService {
|
|
final String apiKey;
|
|
late final GenerativeModel _model;
|
|
|
|
GeminiService({required this.apiKey}) {
|
|
_model = GenerativeModel(model: 'gemini-1.5-flash', apiKey: apiKey);
|
|
}
|
|
|
|
Future<Book?> analyzeBookCover(String imagePath) async {
|
|
try {
|
|
// Read the image file
|
|
final imageFile = File(imagePath);
|
|
final imageBytes = await imageFile.readAsBytes();
|
|
final base64Image = base64Encode(imageBytes);
|
|
|
|
// Create the prompt for book analysis
|
|
const prompt = '''
|
|
Analyze this book cover image and extract the following information in JSON format:
|
|
{
|
|
"title": "book title (required)",
|
|
"author": "author name (required)",
|
|
"genre": "fiction/fantasy/science/detective/biography/other",
|
|
"annotation": "brief description or summary if visible, otherwise generate a generic one"
|
|
}
|
|
|
|
Rules:
|
|
- Extract exact text from the cover
|
|
- If genre is unclear, choose the most appropriate one
|
|
- If annotation is not visible, create a brief generic description
|
|
- Return ONLY valid JSON, no additional text
|
|
- Ensure all required fields are present
|
|
''';
|
|
|
|
// Create the image part for the model
|
|
final imagePart = Content.data('image/jpeg', imageBytes);
|
|
|
|
// Generate content with both text and image
|
|
final response = await _model.generateContent([
|
|
Content.text(prompt),
|
|
imagePart,
|
|
]);
|
|
|
|
final responseText = response.text?.trim();
|
|
|
|
if (responseText == null || responseText.isEmpty) {
|
|
print('Empty response from Gemini');
|
|
return null;
|
|
}
|
|
|
|
// Extract JSON from response (handle potential markdown formatting)
|
|
String jsonString = responseText;
|
|
if (jsonString.contains('```json')) {
|
|
jsonString = jsonString.split('```json')[1].split('```')[0].trim();
|
|
} else if (jsonString.contains('```')) {
|
|
jsonString = jsonString.split('```')[1].split('```')[0].trim();
|
|
}
|
|
|
|
// Parse JSON response
|
|
final Map<String, dynamic> jsonData = json.decode(jsonString);
|
|
|
|
// Create Book object with extracted data
|
|
final Book book = (
|
|
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
|
title: jsonData['title']?.toString() ?? 'Неизвестная книга',
|
|
author: jsonData['author']?.toString() ?? 'Неизвестный автор',
|
|
genre: _normalizeGenre(jsonData['genre']?.toString()),
|
|
annotation: jsonData['annotation']?.toString() ?? 'Нет описания',
|
|
coverUrl: null, // Will be set by the caller
|
|
pages: null,
|
|
language: 'Russian',
|
|
publishedYear: DateTime.now().year,
|
|
rating: 5.0,
|
|
status: 'want_to_read',
|
|
progress: null,
|
|
isFavorite: false,
|
|
);
|
|
|
|
return book;
|
|
} catch (e) {
|
|
print('Error analyzing book cover: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
String _normalizeGenre(String? genre) {
|
|
if (genre == null || genre.isEmpty) return 'other';
|
|
|
|
final normalized = genre.toLowerCase().trim();
|
|
|
|
// Map various genre names to our standard genres
|
|
final genreMap = {
|
|
'фантастика': 'fiction',
|
|
'fantasy': 'fantasy',
|
|
'фэнтези': 'fantasy',
|
|
'science': 'science',
|
|
'научпоп': 'science',
|
|
'научная': 'science',
|
|
'biography': 'biography',
|
|
'биография': 'biography',
|
|
'detective': 'detective',
|
|
'детектив': 'detective',
|
|
'роман': 'other',
|
|
'novel': 'other',
|
|
'poetry': 'other',
|
|
'поэзия': 'other',
|
|
};
|
|
|
|
return genreMap[normalized] ?? normalized;
|
|
}
|
|
}
|