open ai service
This commit is contained in:
90
books_flutter/lib/services/camera_service.dart
Normal file
90
books_flutter/lib/services/camera_service.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class CameraService {
|
||||
CameraController? _controller;
|
||||
List<CameraDescription>? _cameras;
|
||||
bool _isInitialized = false;
|
||||
|
||||
bool get isInitialized => _isInitialized;
|
||||
CameraController? get controller => _controller;
|
||||
|
||||
Future<bool> requestPermissions() async {
|
||||
final cameraStatus = await Permission.camera.request();
|
||||
return cameraStatus.isGranted;
|
||||
}
|
||||
|
||||
Future<bool> initializeCamera() async {
|
||||
try {
|
||||
// Request camera permissions
|
||||
final hasPermission = await requestPermissions();
|
||||
if (!hasPermission) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get available cameras
|
||||
_cameras = await availableCameras();
|
||||
if (_cameras == null || _cameras!.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize the back camera (first camera is usually the back one)
|
||||
_controller = CameraController(
|
||||
_cameras!.first,
|
||||
ResolutionPreset.high,
|
||||
enableAudio: false,
|
||||
);
|
||||
|
||||
await _controller!.initialize();
|
||||
_isInitialized = true;
|
||||
return true;
|
||||
} catch (e) {
|
||||
print('Error initializing camera: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> captureImage() async {
|
||||
if (_controller == null || !_isInitialized) {
|
||||
print('Camera not initialized');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final image = await _controller!.takePicture();
|
||||
return image.path;
|
||||
} catch (e) {
|
||||
print('Error capturing image: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _controller?.dispose();
|
||||
_controller = null;
|
||||
_isInitialized = false;
|
||||
}
|
||||
|
||||
Future<void> switchCamera() async {
|
||||
if (_cameras == null || _cameras!.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final currentCameraIndex = _cameras!.indexOf(_controller!.description);
|
||||
final nextCameraIndex = (currentCameraIndex + 1) % _cameras!.length;
|
||||
|
||||
await _controller?.dispose();
|
||||
|
||||
_controller = CameraController(
|
||||
_cameras![nextCameraIndex],
|
||||
ResolutionPreset.high,
|
||||
enableAudio: false,
|
||||
);
|
||||
|
||||
await _controller!.initialize();
|
||||
} catch (e) {
|
||||
print('Error switching camera: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
152
books_flutter/lib/services/openai_service.dart
Normal file
152
books_flutter/lib/services/openai_service.dart
Normal file
@@ -0,0 +1,152 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user