Coisas
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -2119,16 +2119,99 @@ class _QuizListPageState extends State<QuizListPage>
|
||||
);
|
||||
}
|
||||
|
||||
final groups = _groupHistoryByDiscipline();
|
||||
final filteredItems = items.where((item) {
|
||||
final cid = item['classId'] as String?;
|
||||
return groups.containsKey(cid) && cid != null;
|
||||
}).toList();
|
||||
// Handle selected discipline for AI-generated quizzes
|
||||
if (_selectedHistoryDisciplineId != null) {
|
||||
final groups = _groupHistoryByDiscipline();
|
||||
final disciplineItems = groups[_selectedHistoryDisciplineId] ?? [];
|
||||
// Filter only AI-generated quizzes (teacherQuizId == null)
|
||||
final aiDisciplineItems = disciplineItems
|
||||
.where((q) => q['teacherQuizId'] == null)
|
||||
.toList();
|
||||
final disciplineName =
|
||||
_historyClassNames[_selectedHistoryDisciplineId] ??
|
||||
_selectedHistoryDisciplineId!;
|
||||
|
||||
if (filteredItems.isEmpty) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.arrow_back, color: cs.onSurface),
|
||||
onPressed: () =>
|
||||
setState(() => _selectedHistoryDisciplineId = null),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
disciplineName,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: cs.onSurface,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${aiDisciplineItems.length} quiz${aiDisciplineItems.length != 1 ? 'zes' : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
_buildHistoryList(
|
||||
cs,
|
||||
aiDisciplineItems,
|
||||
isTeacherQuizzes: false,
|
||||
),
|
||||
if (_isSelectionMode && _selectedQuizIds.isNotEmpty)
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
child: FloatingActionButton.extended(
|
||||
onPressed: _deleteSelectedQuizzes,
|
||||
backgroundColor: Colors.red,
|
||||
icon: const Icon(Icons.delete),
|
||||
label: Text('Eliminar (${_selectedQuizIds.length})'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Group only AI-generated quizzes by discipline
|
||||
final aiItems = items.where((q) => q['teacherQuizId'] == null).toList();
|
||||
|
||||
// Filter groups to only include disciplines with AI-generated quizzes
|
||||
final aiGroups = <String, List<Map<String, dynamic>>>{};
|
||||
for (final item in aiItems) {
|
||||
final cid = item['classId'] as String?;
|
||||
final groupId = (cid != null && _historyClassNames.containsKey(cid))
|
||||
? cid
|
||||
: '__geral__';
|
||||
aiGroups.putIfAbsent(groupId, () => []).add(item);
|
||||
}
|
||||
|
||||
if (aiGroups.isEmpty) {
|
||||
return Stack(
|
||||
children: [
|
||||
_buildHistoryList(cs, items, isTeacherQuizzes: isTeacherQuizzes),
|
||||
_buildHistoryList(cs, aiItems, isTeacherQuizzes: isTeacherQuizzes),
|
||||
if (_isSelectionMode && _selectedQuizIds.isNotEmpty)
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
@@ -2144,7 +2227,7 @@ class _QuizListPageState extends State<QuizListPage>
|
||||
);
|
||||
}
|
||||
|
||||
final realDisciplineIds = groups.keys
|
||||
final realDisciplineIds = aiGroups.keys
|
||||
.where((k) => k != '__geral__' && _historyClassNames.containsKey(k))
|
||||
.toList();
|
||||
|
||||
@@ -2176,7 +2259,7 @@ class _QuizListPageState extends State<QuizListPage>
|
||||
itemBuilder: (context, i) {
|
||||
final dId = realDisciplineIds[i];
|
||||
final dName = _historyClassNames[dId] ?? dId;
|
||||
final count = groups[dId]!.length;
|
||||
final count = aiGroups[dId]!.length;
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: () => setState(() => _selectedHistoryDisciplineId = dId),
|
||||
|
||||
Reference in New Issue
Block a user