153 lines
4.6 KiB
Dart
153 lines
4.6 KiB
Dart
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<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
|
|
- 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<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 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;
|
|
}
|
|
}
|