import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:supabase_flutter/supabase_flutter.dart'; class AiRecommendationService { static const String _apiUrl = 'https://apichat.epvc.pt/api/chat'; static const String _model = 'llama3.2:3b'; static const String _systemPrompt = 'es um assistente que ajuda a montar outfits e escolher o que levar para o dia ou viagem. usa linguagem simples e curta, sem emojis. baseia-te nas tags e notas dos itens do utilizador. responde sempre em portugues e se breve.'; final List> _history = []; Future _itemsContext() async { try { final user = Supabase.instance.client.auth.currentUser; if (user == null) return ''; final rows = await Supabase.instance.client .from('items') .select() .eq('user_id', user.id); if (rows.isEmpty) return ''; final buf = StringBuffer( 'Itens disponiveis no inventario do utilizador:\n', ); for (final it in rows) { final nome = it['nome'] ?? ''; final cat = it['categoria'] ?? ''; final tags = (it['tags'] as List?)?.join(', ') ?? ''; final nota = it['nota'] ?? it['notes'] ?? ''; buf.write('- $nome'); if (cat.toString().isNotEmpty) buf.write(' (categoria: $cat)'); if (tags.isNotEmpty) buf.write(' [tags: $tags]'); if (nota.toString().isNotEmpty) buf.write(' {nota: $nota}'); buf.writeln(); } return buf.toString(); } catch (_) { return ''; } } Future sendMessage(String userMessage, {bool silent = false}) async { final ctx = await _itemsContext(); final systemContent = ctx.isNotEmpty ? '$_systemPrompt\n\n$ctx' : _systemPrompt; final messages = >[ {'role': 'system', 'content': systemContent}, ..._history, ]; final userContent = silent ? '$userMessage\n\n[Instrucao: responde APENAS com os nomes exatos dos itens do meu inventario que sugeres, um por linha, sem numeracao, sem explicacao, sem comentarios.]' : userMessage; messages.add({'role': 'user', 'content': userContent}); try { final response = await http .post( Uri.parse(_apiUrl), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'model': _model, 'messages': messages, 'stream': false, }), ) .timeout(const Duration(seconds: 60)); if (response.statusCode != 200) { return 'Erro a contactar a IA (${response.statusCode}). Tenta de novo.'; } final data = jsonDecode(utf8.decode(response.bodyBytes)); final aiText = _extract(data); if (aiText.isEmpty) { return 'Nao recebi resposta da IA. Tenta de novo.'; } // guardar no historico (mensagem do user "limpa", sem instrucoes silenciosas) _history.add({'role': 'user', 'content': userMessage}); _history.add({'role': 'assistant', 'content': aiText}); return aiText; } catch (_) { return 'Nao consegui ligar ao servidor. Verifica a tua internet e tenta de novo.'; } } String _extract(dynamic data) { if (data is Map) { // Ollama /api/chat -> { message: { role, content }, done, ... } final msg = data['message']; if (msg is Map && msg['content'] != null) { return msg['content'].toString().trim(); } // Ollama /api/generate -> { response: "..." } if (data['response'] != null) return data['response'].toString().trim(); // OpenAI-style fallback final choices = data['choices']; if (choices is List && choices.isNotEmpty) { final m = choices[0]['message']; if (m is Map && m['content'] != null) { return m['content'].toString().trim(); } } if (data['content'] != null) return data['content'].toString().trim(); } if (data is String) return data.trim(); return ''; } void clearHistory() => _history.clear(); Future>> getItemsWithImages() async { try { final user = Supabase.instance.client.auth.currentUser; if (user == null) return []; final rows = await Supabase.instance.client .from('items') .select('*, item_images(image_url)') .eq('user_id', user.id); return List>.from(rows); } catch (_) { return []; } } }