import 'dart:io'; import 'dart:convert'; import 'package:books_flutter/config/api_config.dart'; import 'package:http/http.dart' as http; import '../models/models.dart'; class OpenAIService { final String apiKey; final String baseUrl; final String model; late final String _endpoint; OpenAIService({ required this.apiKey, this.baseUrl = ApiConfig.openaiApiKey, this.model = ApiConfig.openaiModel, }) { _endpoint = '$baseUrl/v1/chat/completions'; } Future 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 - Return result in russian language '''; // Create the request body for OpenAI API final requestBody = { 'model': model, // Use the configured model 'messages': [ { 'role': 'user', 'content': [ {'type': 'text', 'text': prompt}, { 'type': 'image_url', 'image_url': {'url': 'data:image/jpeg;base64,$base64Image'}, }, ], }, ], }; // Make the API request final response = await http.post( Uri.parse(_endpoint), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $apiKey', }, body: json.encode(requestBody), ); if (response.statusCode != 200) { print('OpenAI API error: ${response.statusCode}'); print('Response body: ${response.body}'); return null; } final responseData = json.decode(response.body); // Extract the message content final responseText = responseData['choices']?[0]?['message']?['content'] ?.toString() .trim(); if (responseText == null || responseText.isEmpty) { print('Empty response from OpenAI'); 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 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 with OpenAI: $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; } }