diff --git a/lib/core/services/rag_ai_service.dart b/lib/core/services/rag_ai_service.dart index 3a03d03..3920e07 100644 --- a/lib/core/services/rag_ai_service.dart +++ b/lib/core/services/rag_ai_service.dart @@ -492,6 +492,33 @@ Usas formatação clara e organizada.'''; } } + /// Cache do último contexto PDF enviado ao modelo — reutilizado em follow-ups + static String _lastPdfContext = ''; + + /// Limpar contexto cacheado — chamar ao mudar de material + static void clearLastContext() { + _lastPdfContext = ''; + Logger.info('Last PDF context cleared'); + } + + /// Detecta se a query é um follow-up (pergunta curta/vaga sem keywords de conteúdo) + static bool _isFollowUp(String query) { + final q = query.trim().toLowerCase(); + // Menos de 6 palavras E começa com pronome/advérbio de follow-up + final words = q.split(RegExp(r'\s+')); + if (words.length > 8) return false; + const followUpStarters = [ + 'e ', 'e o', 'e a', 'e os', 'e as', 'mas ', 'então ', + 'explica', 'explique', 'explica melhor', 'melhor', 'mais detalhes', + 'podes', 'pode ', 'consegues', 'e se ', 'e quando', + 'dá um exemplo', 'da um exemplo', 'um exemplo', 'exemplo', + 'como assim', 'o que significa', 'porquê', 'porque isso', + 'e o ponto', 'e a regra', 'continua', 'continua', + 'o que mais', 'mais algum', 'e depois', 'e agora', + ]; + return followUpStarters.any((s) => q.startsWith(s) || q == s.trim()); + } + /// Simple ask method for chat UI - uses conversation memory, teacher PDFs, and O GOAT identity /// [selectedMaterialIds] — se fornecido, limita o RAG apenas aos materiais escolhidos pelo aluno static Future ask( @@ -537,15 +564,24 @@ REGRAS CRÍTICAS SOBRE O CONTEXTO: } // PASSO 4 — BUSCAR PDFs DO PROFESSOR NO Firebase Storage (RAG CHUNK RETRIEVAL) - final pdfContext = await MaterialsRAGService.getRelevantChunks( - userQuery: userQuery, - maxMaterials: 5, - maxChunks: 5, - selectedMaterialIds: selectedMaterialIds, - ); - if (pdfContext.isNotEmpty) { - Logger.info('PDF context sent to model (${pdfContext.length} chars): ${pdfContext.length > 300 ? pdfContext.substring(0, 300) : pdfContext}'); - } else if (selectedMaterialIds != null && selectedMaterialIds.isNotEmpty) { + // Detectar follow-up e reutilizar contexto anterior se disponível + String pdfContext; + if (_isFollowUp(userQuery) && _lastPdfContext.isNotEmpty) { + pdfContext = _lastPdfContext; + Logger.info('Follow-up detected — reusing last PDF context (${pdfContext.length} chars)'); + } else { + pdfContext = await MaterialsRAGService.getRelevantChunks( + userQuery: userQuery, + maxMaterials: 5, + maxChunks: 5, + selectedMaterialIds: selectedMaterialIds, + ); + if (pdfContext.isNotEmpty) { + _lastPdfContext = pdfContext; + Logger.info('PDF context sent to model (${pdfContext.length} chars): ${pdfContext.length > 300 ? pdfContext.substring(0, 300) : pdfContext}'); + } + } + if (pdfContext.isEmpty && selectedMaterialIds != null && selectedMaterialIds.isNotEmpty) { // Contexto vazio com materiais seleccionados — retornar resposta local imediatamente const noContextReply = 'Neste momento não tenho acesso ao conteúdo do ficheiro selecionado. ' @@ -553,7 +589,8 @@ REGRAS CRÍTICAS SOBRE O CONTEXTO: await ChatMemoryService.saveMessage(role: 'user', content: userQuery); await ChatMemoryService.saveMessage(role: 'assistant', content: noContextReply); return noContextReply; - } else { + } + if (pdfContext.isEmpty && (selectedMaterialIds == null || selectedMaterialIds.isEmpty)) { // Sem material seleccionado — pedir ao utilizador para seleccionar um const noMaterialReply = 'Para responder a perguntas sobre conteúdo, preciso que selecciones um material primeiro. ' diff --git a/lib/features/ai_tutor/presentation/pages/tutor_chat_page_simple.dart b/lib/features/ai_tutor/presentation/pages/tutor_chat_page_simple.dart index 9b6cacf..b57afdf 100644 --- a/lib/features/ai_tutor/presentation/pages/tutor_chat_page_simple.dart +++ b/lib/features/ai_tutor/presentation/pages/tutor_chat_page_simple.dart @@ -621,6 +621,7 @@ class _TutorChatPageSimpleState extends State _messages.clear(); }); ChatMemoryService.clearHistory(); + RAGAIService.clearLastContext(); Navigator.of(dialogContext).pop(); }, child: const Text('Limpar'), @@ -632,6 +633,7 @@ class _TutorChatPageSimpleState extends State _messages.clear(); }); ChatMemoryService.clearHistory(); + RAGAIService.clearLastContext(); Navigator.of(dialogContext).pop(); }, style: ElevatedButton.styleFrom(