Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

234 lines
9.0KB

  1. // lib/services/ollama_service.dart
  2. import 'dart:convert';
  3. import 'package:http/http.dart' as http;
  4. import '../domain/catalogs/filter_catalog.dart'; // <-- 1. IMPORTER LE CATALOGUE DE FILTRES
  5. // =========================================================================
  6. // SERVICE 1: ANALYSE D'IMAGE (POUR PROMPT ET SUGGESTIONS DE FILTRES)
  7. // =========================================================================
  8. typedef ImageAnalysisResult = ({String prompt, List<String> filterIds});
  9. /// Définit un contrat pour tout service capable d'analyser une image.
  10. abstract class ImageAnalysisService {
  11. /// Prend une image en base64 et retourne une chaîne JSON contenant la description et les filtres suggérés.
  12. Future<String> analyzeImage(String base64Image);
  13. }
  14. /// L'implémentation Ollama de ce service.
  15. class OllamaImageAnalysisService implements ImageAnalysisService {
  16. final String _apiUrl = 'http://192.168.20.200:11434/api/generate';
  17. final String _visionModel = 'llava:7b';
  18. // --- MÉTHODE MISE À JOUR ---
  19. @override
  20. Future<String> analyzeImage(String base64Image) async {
  21. print("[OllamaImageAnalysisService] 🚀 Lancement de l'analyse pour description ET suggestions de filtres...");
  22. // 2. Préparer un texte décrivant nos filtres pour que Llava puisse choisir
  23. final filtersForPrompt = availableFilters
  24. .map((f) => 'Filtre "${f.id}": ${f.description}')
  25. .join('\n');
  26. // 3. NOUVEAU PROMPT qui demande une sortie JSON avec description et filtres
  27. final requestPrompt = '''
  28. SYSTEM: Tu es un retoucheur photo expert. Ta mission est double :
  29. 1. Décris l'image fournie en une seule phrase courte et factuelle. et donne les axes d'amélioration pour l'IA comme un prompt pour des posts sur Instagram.
  30. 2. Analyse l'image et suggère les 2 filtres les plus pertinents depuis la liste ci-dessous.
  31. LISTE DES FILTRES DISPONIBLES :
  32. $filtersForPrompt
  33. Tu dois répondre dans un format JSON strict, sans AUCUN autre texte avant ou après.
  34. Le format doit être :
  35. {
  36. "description": "Ta description de l'image ici ainsi que les axes d'améliorations.",
  37. "filters": ["ID du Filtre 1", "ID du Filtre 2"]
  38. }
  39. USER: Voici l'image.
  40. ''';
  41. final requestBody = {
  42. 'model': _visionModel,
  43. 'format': 'json', // 4. FORCER LA SORTIE EN JSON (très important !)
  44. 'prompt': requestPrompt,
  45. 'images': [base64Image],
  46. 'stream': false,
  47. };
  48. try {
  49. final response = await http.post(
  50. Uri.parse(_apiUrl),
  51. headers: {'Content-Type': 'application/json'},
  52. body: jsonEncode(requestBody),
  53. ).timeout(const Duration(minutes: 2));
  54. if (response.statusCode == 200) {
  55. final body = jsonDecode(response.body);
  56. // 5. La réponse de l'IA est une chaîne de caractères qui contient notre JSON.
  57. final jsonString = (body['response'] as String? ?? '').trim();
  58. print('[OllamaImageAnalysisService] ✅ Réponse JSON brute reçue : $jsonString');
  59. // On valide que le JSON n'est pas vide avant de le retourner
  60. if (jsonString.isEmpty) {
  61. throw Exception("La réponse d'Ollama est vide.");
  62. }
  63. // On retourne la chaîne JSON complète pour que l'appelant puisse la parser.
  64. return jsonString;
  65. } else {
  66. throw Exception('Erreur Ollama (analyzeImage) ${response.statusCode}: ${response.body}');
  67. }
  68. } catch (e) {
  69. print('[OllamaImageAnalysisService] ❌ Exception : ${e.toString()}');
  70. rethrow;
  71. }
  72. }
  73. }
  74. // =========================================================================
  75. // SERVICE 2: CRÉATION DE POSTS SOCIAUX (INCHANGÉ)
  76. // =========================================================================
  77. /// Définit un contrat pour tout service capable de générer des idées de posts.
  78. abstract class PostCreationService {
  79. Future<List<String>> generatePostIdeas({
  80. required String base64Image,
  81. required String profession,
  82. required String tone,
  83. });
  84. }
  85. /// L'implémentation Ollama de ce service.
  86. class OllamaPostCreationService implements PostCreationService {
  87. final String _apiUrl = 'http://192.168.20.200:11434/api/generate';
  88. final String _visionModel = 'llava:7b';
  89. @override
  90. Future<List<String>> generatePostIdeas({
  91. required String base64Image,
  92. required String profession,
  93. required String tone,
  94. }) async {
  95. // ... (VOTRE CODE INCHANGÉ ICI) ...
  96. final requestPrompt = """
  97. You are a social media expert helping a client.
  98. Client is a professional of "$profession".
  99. Analyze the image.
  100. Génère 3 propositions de texte distinctes pour une publication Instagram, chacune avec un angle marketing différent avec le ton "$tone" :
  101. 1. **Angle Éducatif/Informatif :** Rédige un texte qui apporte de la valeur, une astuce, ou une information clé en lien avec l'image. Utilise des emojis pertinents pour structurer le texte. Termine par une question ouverte pour encourager les commentaires. Inclus 3-5 hashtags de niche.
  102. 2. **Angle Inspirationnel/Storytelling :** Rédige un texte court qui raconte une histoire ou partage une réflexion personnelle inspirée par l'image. L'objectif est de créer une connexion émotionnelle. Termine par un appel à l'action simple (ex: "Double-tape si tu es d'accord !"). Inclus 3 hashtags plus larges et aspirationnels.
  103. 3. **Angle Promotionnel/Commercial :** Rédige un texte qui met en avant un produit, un service ou une offre spéciale lié à l'image. Le ton doit être engageant et persuasif, pas trop vendeur. Met en évidence le bénéfice principal pour le client. Termine par un appel à l'action clair (ex: "Clique sur le lien en bio pour en savoir plus !"). Inclus 3 hashtags liés au produit ou au service.
  104. Your output MUST be a valid JSON array of strings.
  105. Example:
  106. ["Texte de la proposition 1...", "Texte de la proposition 2...", "Texte de la proposition 3..."]
  107. """;
  108. final requestBody = {
  109. 'model': _visionModel,
  110. 'prompt': requestPrompt,
  111. 'images': [base64Image],
  112. 'stream': false,
  113. };
  114. try {
  115. print('[OllamaPostCreationService] 🚀 Appel pour générer des idées...');
  116. final response = await http.post(
  117. Uri.parse(_apiUrl),
  118. headers: {'Content-Type': 'application/json'},
  119. body: jsonEncode(requestBody),
  120. ).timeout(const Duration(minutes: 3));
  121. if (response.statusCode == 200) {
  122. final responseData = jsonDecode(response.body);
  123. final jsonString = (responseData['response'] as String? ?? '').trim();
  124. if (jsonString.isEmpty) return [];
  125. try {
  126. final ideasList = jsonDecode(jsonString) as List;
  127. return ideasList.map((idea) => idea.toString()).toList();
  128. } catch (e) {
  129. print('[OllamaPostCreationService] ❌ Erreur de parsing JSON. Réponse : $jsonString');
  130. return [jsonString]; // Retourne la réponse brute comme une seule idée
  131. }
  132. } else {
  133. throw Exception('Erreur Ollama (generatePostIdeas) ${response.statusCode}: ${response.body}');
  134. }
  135. } catch (e) {
  136. print('[OllamaPostCreationService] ❌ Exception : ${e.toString()}');
  137. rethrow;
  138. }
  139. }
  140. }
  141. // =========================================================================
  142. // SERVICE 3: AMÉLIORATION DE TEXTE (INCHANGÉ)
  143. // =========================================================================
  144. /// Définit un contrat pour tout service capable d'améliorer un texte.
  145. abstract class TextImprovementService {
  146. Future<String> improveText({
  147. required String originalText,
  148. required String userInstruction,
  149. });
  150. }
  151. /// L'implémentation Ollama de ce service.
  152. class OllamaTextImprovementService implements TextImprovementService {
  153. // ... (VOTRE CODE INCHANGÉ ICI) ...
  154. final String _apiUrl = 'http://192.168.20.200:11434/api/generate';
  155. final String _textModel = 'gpt-oss:20b';
  156. @override
  157. Future<String> improveText({
  158. required String originalText,
  159. required String userInstruction,
  160. }) async {
  161. print('[OllamaTextImprovementService] 🚀 Appel pour améliorer le texte...');
  162. final requestPrompt = """
  163. You are a social media writing assistant.
  164. A user wants to improve the following text:
  165. --- TEXT TO IMPROVE ---
  166. $originalText
  167. -----------------------
  168. The user's instruction is: "$userInstruction".
  169. Rewrite the text based on the instruction.
  170. Your output MUST be ONLY the improved text, without any extra commentary or explanations.
  171. """;
  172. final requestBody = {
  173. 'model': _textModel,
  174. 'prompt': requestPrompt,
  175. 'stream': false,
  176. };
  177. try {
  178. final response = await http.post(
  179. Uri.parse(_apiUrl),
  180. headers: {'Content-Type': 'application/json'},
  181. body: jsonEncode(requestBody),
  182. ).timeout(const Duration(minutes: 2));
  183. if (response.statusCode == 200) {
  184. final responseData = jsonDecode(response.body);
  185. return (responseData['response'] as String? ?? '').trim();
  186. } else {
  187. throw Exception('Erreur Ollama (improveText) ${response.statusCode}: ${response.body}');
  188. }
  189. } catch (e) {
  190. print('[OllamaTextImprovementService] ❌ Exception : ${e.toString()}');
  191. rethrow;
  192. }
  193. }
  194. }