This commit is contained in:
2026-05-21 11:39:30 +01:00
parent 98dcd621c7
commit 2f411d08a4
3 changed files with 220 additions and 80 deletions

View File

@@ -8,6 +8,7 @@ import '../../../../core/services/chat_memory_service.dart';
import '../../../../core/services/materials_rag_service.dart';
import '../../../../core/services/rag_ai_service.dart';
import '../../../../core/utils/logger.dart';
import '../../../materials/presentation/pages/pdf_viewer_page.dart';
/// Simple AI Tutor chat interface page (for testing)
class TutorChatPageSimple extends StatefulWidget {
@@ -21,6 +22,7 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
with TickerProviderStateMixin {
final TextEditingController _messageController = TextEditingController();
final ScrollController _scrollController = ScrollController();
final FocusNode _messageFocusNode = FocusNode();
bool _isLoading = false;
bool _materialsConfirmed = false;
@@ -68,6 +70,7 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
void dispose() {
_messageController.dispose();
_scrollController.dispose();
_messageFocusNode.dispose();
super.dispose();
}
@@ -674,12 +677,12 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
),
if (_selectedMaterialIds.isNotEmpty)
..._selectedMaterialIds.map((id) {
final name =
_availableMaterials.firstWhere(
(m) => m['id'] == id,
orElse: () => {'id': id, 'name': id},
)['name'] ??
id;
final material = _availableMaterials.firstWhere(
(m) => m['id'] == id,
orElse: () => {'id': id, 'name': id},
);
final name = material['name'] ?? id;
final url = material['url'];
final cleanName = name
.replaceAll('.pdf', '')
.replaceAll('_', ' ');
@@ -690,76 +693,98 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
final isLast = _selectedMaterialIds.length == 1;
return Padding(
padding: const EdgeInsets.only(left: 6),
child: Chip(
label: Text(
short,
style: const TextStyle(
fontSize: 11,
color: Colors.white,
fontWeight: FontWeight.w600,
child: InkWell(
onTap: url != null
? () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
PdfViewerPage(
url: url,
fileName: name,
),
),
);
}
: null,
borderRadius: BorderRadius.circular(20),
child: Chip(
label: Text(
short,
style: const TextStyle(
fontSize: 11,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
backgroundColor: chipBg,
deleteIconColor: Colors.white.withValues(
alpha: 0.85,
),
deleteIcon: const Icon(Icons.close, size: 14),
onDeleted: () {
if (isLast) {
ScaffoldMessenger.of(context)
..clearSnackBars()
..showSnackBar(
SnackBar(
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
backgroundColor: const Color(
0xFFF68D2D,
),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(12),
),
duration: const Duration(
seconds: 2,
),
content: const Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: Colors.white,
size: 20,
),
SizedBox(width: 10),
Expanded(
child: Text(
'Tens de manter pelo menos um material selecionado.',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight:
FontWeight.w500,
backgroundColor: chipBg,
deleteIconColor: Colors.white.withValues(
alpha: 0.85,
),
deleteIcon: const Icon(
Icons.close,
size: 14,
),
onDeleted: () {
if (isLast) {
ScaffoldMessenger.of(context)
..clearSnackBars()
..showSnackBar(
SnackBar(
behavior:
SnackBarBehavior.floating,
margin:
const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
backgroundColor: const Color(
0xFFF68D2D,
),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(12),
),
duration: const Duration(
seconds: 2,
),
content: const Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: Colors.white,
size: 20,
),
SizedBox(width: 10),
Expanded(
child: Text(
'Tens de manter pelo menos um material selecionado.',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight:
FontWeight.w500,
),
),
),
),
],
],
),
),
),
);
} else {
setState(
() => _selectedMaterialIds.remove(id),
);
} else {
setState(
() => _selectedMaterialIds.remove(id),
);
}
},
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
padding: const EdgeInsets.symmetric(
horizontal: 4,
}
},
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
padding: const EdgeInsets.symmetric(
horizontal: 4,
),
visualDensity: VisualDensity.compact,
),
visualDensity: VisualDensity.compact,
),
);
}).toList(),
@@ -785,6 +810,8 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
Expanded(
child: TextField(
controller: _messageController,
focusNode: _messageFocusNode,
autofocus: false,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
@@ -1184,6 +1211,7 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
RAGAIService.clearLastContext();
}
Navigator.of(dialogContext).pop();
_messageFocusNode.unfocus();
},
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(