Fixed texts, added auto scroll

This commit is contained in:
Dawid Pietrykowski 2024-08-09 01:03:08 +02:00
parent 55325b260d
commit fc9ab973d2
9 changed files with 255 additions and 63 deletions

View File

@ -12,6 +12,7 @@ const String systemPrmpt =
At the start you will be provided a script with a lesson to cover. At the start you will be provided a script with a lesson to cover.
Keep the analysis short but the lesson can be as long as needed. Keep the analysis short but the lesson can be as long as needed.
Student is 15 years old. You can only interact using text, no videos, images, or audio. Student is 15 years old. You can only interact using text, no videos, images, or audio.
Make the lesson more in the style of a lecture, with you explaining the topic and the student asking questions.
After completing the theoretical part there's a quiz, you can start it yourself at the appropriate time or react to users' request by including <QUIZ_START_TOKEN> at the start of your response After completing the theoretical part there's a quiz, you can start it yourself at the appropriate time or react to users' request by including <QUIZ_START_TOKEN> at the start of your response
@ -81,11 +82,6 @@ class Message {
} }
Content toGeminiContent() { Content toGeminiContent() {
// if (source == MessageSource.user || type == MessageType.lessonScript) {
// return Content.text(text);
// } else {
// return Content.model([TextPart(text)]);
// }
switch (type) { switch (type) {
case MessageType.text: case MessageType.text:
if (source == MessageSource.user) { if (source == MessageSource.user) {
@ -187,7 +183,7 @@ class GeminiCubit extends Cubit<GeminiState> {
// final String prompt = // final String prompt =
// "Jesteś nauczycielem/chatbotem prowadzącym zajęcia z jednym uczniem. Uczeń ma możliwość zadawania pytań w trakcie, natomiast jesteś odpowiedzialny za prowadzenie lekcji i przedstawienie tematu. Zacznij prowadzić lekcje dla jednego ucznia na podstawie poniszego skryptu:\n$rjp"; // "Jesteś nauczycielem/chatbotem prowadzącym zajęcia z jednym uczniem. Uczeń ma możliwość zadawania pytań w trakcie, natomiast jesteś odpowiedzialny za prowadzenie lekcji i przedstawienie tematu. Zacznij prowadzić lekcje dla jednego ucznia na podstawie poniszego skryptu:\n$rjp";
final String prompt = final String prompt =
"You are a teacher/chatbot conducting a class with one student. The student has the ability to ask questions during the lesson, while you are responsible for leading the class and presenting the topic. Start conducting the lesson for one student based on the script below:\n$lessonScript"; "You are a teacher/chatbot conducting a class with one student. The student has the ability to ask questions during the lesson, while you are responsible for leading the class and presenting the topic. Start conducting the lecture for one student based on the script below:\n$lessonScript";
final safetySettings = [ final safetySettings = [
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.none), SafetySetting(HarmCategory.harassment, HarmBlockThreshold.none),

View File

@ -1 +1 @@
const bool isDebug = true; const bool isDebug = false;

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:gemini_app/eeg/eeg_service.dart'; import 'package:gemini_app/eeg/eeg_service.dart';
import 'package:gemini_app/screens/eeg_calibration_screen.dart'; import 'package:gemini_app/screens/eeg_calibration_screen.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:google_fonts/google_fonts.dart';
void main() { void main() {
GetIt.I.registerSingleton<EegService>(EegService()); GetIt.I.registerSingleton<EegService>(EegService());
@ -16,8 +17,11 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
title: 'MindEasy', title: 'MindEasy',
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), brightness: Brightness.dark,
useMaterial3: true, primarySwatch: Colors.teal,
textTheme: GoogleFonts.openSansTextTheme(
Theme.of(context).textTheme,
),
), ),
home: const MindWanderScreen(), home: const MindWanderScreen(),
); );

View File

@ -70,19 +70,22 @@ class MindWanderScreenState extends State<MindWanderScreen>
AnimatedBuilder( AnimatedBuilder(
animation: _animation, animation: _animation,
builder: (context, child) { builder: (context, child) {
return Opacity( return Container(
opacity: _animation.value, color: Colors.white,
child: Container( child: Opacity(
decoration: BoxDecoration( opacity: _animation.value,
gradient: LinearGradient( child: Container(
begin: Alignment.topLeft, decoration: BoxDecoration(
end: Alignment.bottomRight, gradient: LinearGradient(
colors: [ begin: Alignment.topLeft,
Colors.blue.withOpacity(0.5), end: Alignment.bottomRight,
Colors.green.withOpacity(0.5), colors: [
], Colors.blue.withOpacity(0.5),
transform: Colors.green.withOpacity(0.5),
GradientRotation(_animation.value * 2 * 3.1415926535), ],
transform: GradientRotation(
_animation.value * 2 * 3.1415926535),
),
), ),
), ),
), ),

View File

@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gemini_app/config.dart'; import 'package:gemini_app/config.dart';
import 'package:gemini_app/eeg/eeg_service.dart'; import 'package:gemini_app/eeg/eeg_service.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:google_fonts/google_fonts.dart';
class GeminiScreen extends StatelessWidget { class GeminiScreen extends StatelessWidget {
const GeminiScreen({super.key, required this.lessonId}); const GeminiScreen({super.key, required this.lessonId});
@ -35,8 +36,9 @@ class GeminiChat extends StatefulWidget {
class GeminiChatState extends State<GeminiChat> { class GeminiChatState extends State<GeminiChat> {
final _textController = TextEditingController(); final _textController = TextEditingController();
bool _quizMode = false; // Add this line bool _quizMode = false;
final EegService _eegService = GetIt.instance<EegService>(); final EegService _eegService = GetIt.instance<EegService>();
final ScrollController _scrollController = ScrollController();
@override @override
void initState() { void initState() {
@ -57,6 +59,7 @@ class GeminiChatState extends State<GeminiChat> {
@override @override
void dispose() { void dispose() {
_eegService.stopPolling(); _eegService.stopPolling();
_scrollController.dispose(); // Add this line
super.dispose(); super.dispose();
} }
@ -67,6 +70,20 @@ class GeminiChatState extends State<GeminiChat> {
void _sendMessage() async { void _sendMessage() async {
context.read<GeminiCubit>().sendMessage(_textController.text); context.read<GeminiCubit>().sendMessage(_textController.text);
_textController.clear(); _textController.clear();
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
}
void _scrollToBottom() {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
} }
void _toggleEegState() { void _toggleEegState() {
@ -108,9 +125,21 @@ class GeminiChatState extends State<GeminiChat> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Mind Wandering: ${_eegService.state.mindWandering.toStringAsFixed(2)}'), 'Mind Wandering: ${_eegService.state.mindWandering.toStringAsFixed(2)}',
style: GoogleFonts.roboto(
textStyle: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white),
)),
Text( Text(
'Focus: ${_eegService.state.focus.toStringAsFixed(2)}'), 'Focus: ${_eegService.state.focus.toStringAsFixed(2)}',
style: GoogleFonts.roboto(
textStyle: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white),
)),
], ],
), ),
), ),
@ -120,6 +149,10 @@ class GeminiChatState extends State<GeminiChat> {
Expanded( Expanded(
child: BlocBuilder<GeminiCubit, GeminiState>( child: BlocBuilder<GeminiCubit, GeminiState>(
builder: (context, state) { builder: (context, state) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
if (state.status == GeminiStatus.loading) { if (state.status == GeminiStatus.loading) {
return buildChatList(state, loading: true); return buildChatList(state, loading: true);
} else if (state.status == GeminiStatus.error) { } else if (state.status == GeminiStatus.error) {
@ -137,15 +170,18 @@ class GeminiChatState extends State<GeminiChat> {
? Container() ? Container()
: TextField( : TextField(
controller: _textController, controller: _textController,
style: TextStyle(color: Colors.white),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Enter your message', hintText: 'Enter your message',
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(20.0),
borderSide: BorderSide.none,
), ),
filled: true, filled: true,
fillColor: Colors.grey[200], // fillColor: Colors.grey[200],
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 12.0), horizontal: 20.0, vertical: 14.0),
), ),
onSubmitted: (_) => _sendMessage(), onSubmitted: (_) => _sendMessage(),
); );
@ -153,6 +189,7 @@ class GeminiChatState extends State<GeminiChat> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: _resetConversation, onPressed: _resetConversation,
@ -176,19 +213,18 @@ class GeminiChatState extends State<GeminiChat> {
], ],
), ),
), ),
isDebug if (isDebug)
? ElevatedButton( ElevatedButton(
onPressed: _toggleEegState, onPressed: _toggleEegState,
child: const Row( child: const Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.toggle_on), Icon(Icons.toggle_on),
SizedBox(width: 3), SizedBox(width: 3),
Text('Toggle State'), Text('Toggle State'),
], ],
), ),
) )
: Container(),
], ],
), ),
], ],
@ -200,6 +236,7 @@ class GeminiChatState extends State<GeminiChat> {
ListView buildChatList(GeminiState state, {bool loading = false}) { ListView buildChatList(GeminiState state, {bool loading = false}) {
return ListView.builder( return ListView.builder(
itemCount: state.messages.length + (loading ? 1 : 0), itemCount: state.messages.length + (loading ? 1 : 0),
controller: _scrollController,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == state.messages.length && loading) { if (index == state.messages.length && loading) {
return const Card( return const Card(
@ -230,7 +267,16 @@ class GeminiChatState extends State<GeminiChat> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
MarkdownBody(data: text), MarkdownBody(
data: text,
styleSheet: MarkdownStyleSheet(
p: GoogleFonts.roboto(
textStyle: const TextStyle(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.w300)),
)),
const SizedBox(height: 16),
...message.quizOptions!.asMap().entries.map((entry) { ...message.quizOptions!.asMap().entries.map((entry) {
return ElevatedButton( return ElevatedButton(
onPressed: () => {_checkAnswer(entry.key)}, onPressed: () => {_checkAnswer(entry.key)},
@ -271,7 +317,17 @@ class GeminiChatState extends State<GeminiChat> {
crossAxisAlignment: message.source == MessageSource.agent crossAxisAlignment: message.source == MessageSource.agent
? CrossAxisAlignment.start ? CrossAxisAlignment.start
: CrossAxisAlignment.end, : CrossAxisAlignment.end,
children: [MarkdownBody(data: text)], children: [
MarkdownBody(
data: text,
styleSheet: MarkdownStyleSheet(
p: GoogleFonts.roboto(
textStyle: const TextStyle(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.w300)),
))
],
), ),
), ),
); );

View File

@ -1,9 +1,11 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:gemini_app/lesson.dart'; import 'package:gemini_app/lesson.dart';
import 'package:gemini_app/main.dart'; import 'package:gemini_app/main.dart';
import 'package:gemini_app/screens/gemini_chat_screen.dart'; import 'package:gemini_app/screens/gemini_chat_screen.dart';
import 'package:google_fonts/google_fonts.dart';
class LessonListScreen extends StatefulWidget { class LessonListScreen extends StatefulWidget {
@override @override
@ -34,26 +36,49 @@ class _LessonListScreenState extends State<LessonListScreen> {
appBar: AppBar( appBar: AppBar(
title: const Text('Available Lessons'), title: const Text('Available Lessons'),
), ),
body: ListView.separated( body: AnimationLimiter(
itemCount: lessons.length, child: ListView.separated(
itemBuilder: (context, index) { itemCount: lessons.length,
final lesson = lessons[index]; separatorBuilder: (context, index) => const Divider(),
return ListTile( itemBuilder: (context, index) {
title: Text(lesson.title), final lesson = lessons[index];
subtitle: Text(lesson.content), return AnimationConfiguration.staggeredList(
onTap: () { position: index,
Navigator.push( duration: const Duration(milliseconds: 375),
context, child: SlideAnimation(
createSmoothRoute( verticalOffset: 50.0,
GeminiScreen(lessonId: lesson.id.toString()), child: FadeInAnimation(
child: Card(
elevation: 4,
margin:
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: ListTile(
title: Text(
lesson.title,
style: GoogleFonts.roboto(
textStyle: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
),
subtitle: Text(
lesson.content,
style: GoogleFonts.roboto(fontSize: 14),
),
onTap: () {
Navigator.push(
context,
createSmoothRoute(
GeminiScreen(lessonId: lesson.id.toString()),
),
);
},
),
),
), ),
); ),
}, );
); },
}, ),
separatorBuilder: (context, index) {
return Divider(); // or any other widget you want to use as a separator
},
), ),
); );
} }

View File

@ -5,6 +5,8 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
} }

View File

@ -57,6 +57,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -73,6 +81,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -102,6 +118,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.3" version: "0.7.3"
flutter_staggered_animations:
dependency: "direct main"
description:
name: flutter_staggered_animations
sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -115,6 +139,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.7.0" version: "7.7.0"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
url: "https://pub.dev"
source: hosted
version: "6.2.1"
google_generative_ai: google_generative_ai:
dependency: "direct main" dependency: "direct main"
description: description:
@ -219,6 +251,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev"
source: hosted
version: "2.1.4"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb"
url: "https://pub.dev"
source: hosted
version: "2.2.9"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev"
source: hosted
version: "2.4.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider: provider:
dependency: transitive dependency: transitive
description: description:
@ -312,6 +408,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.5.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.4"
sdks: sdks:
dart: ">=3.4.1 <4.0.0" dart: ">=3.4.1 <4.0.0"
flutter: ">=3.19.0" flutter: ">=3.22.0"

View File

@ -44,6 +44,8 @@ dependencies:
get_it: ^7.7.0 get_it: ^7.7.0
http: ^1.2.2 http: ^1.2.2
bloc: ^8.1.4 bloc: ^8.1.4
flutter_staggered_animations: ^1.1.1
google_fonts: ^6.2.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: