From ebededacc1adb4ba046c03a6926c024f7b4e45fe Mon Sep 17 00:00:00 2001 From: Dawid Pietrykowski Date: Thu, 8 Aug 2024 18:18:01 +0200 Subject: [PATCH] Code fixes, style, analysis separation --- lib/api_key.dart | 1 + lib/bloc/eeg_state.dart | 1 + lib/bloc/gemini_state.dart | 113 +++++++++++++++---------- lib/config.dart | 2 +- lib/eeg/eeg_service.dart | 20 ++--- lib/screens/gemini_chat_screen.dart | 125 ++++++++++++++++++---------- pubspec.lock | 8 +- pubspec.yaml | 2 + 8 files changed, 169 insertions(+), 103 deletions(-) create mode 100644 lib/api_key.dart diff --git a/lib/api_key.dart b/lib/api_key.dart new file mode 100644 index 0000000..a98127c --- /dev/null +++ b/lib/api_key.dart @@ -0,0 +1 @@ +const String geminiApiKey = ''; diff --git a/lib/bloc/eeg_state.dart b/lib/bloc/eeg_state.dart index e69de29..8b13789 100644 --- a/lib/bloc/eeg_state.dart +++ b/lib/bloc/eeg_state.dart @@ -0,0 +1 @@ + diff --git a/lib/bloc/gemini_state.dart b/lib/bloc/gemini_state.dart index e944bd9..ffd344a 100644 --- a/lib/bloc/gemini_state.dart +++ b/lib/bloc/gemini_state.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:bloc/bloc.dart'; import 'package:flutter/services.dart'; +import 'package:gemini_app/api_key.dart'; import 'package:gemini_app/config.dart'; -import 'package:gemini_app/bloc/eeg_state.dart'; import 'package:gemini_app/eeg/eeg_service.dart'; import 'package:get_it/get_it.dart'; import 'package:google_generative_ai/google_generative_ai.dart'; @@ -23,6 +23,10 @@ here describe what is the state of the student and how to best approach them here continue with the lesson, respond to answers, etc """; +const String LESSON_START_TOKEN = ""; +const String ANALYSIS_START_TOKEN = ""; +const String QUIZ_START_TOKEN = ""; + enum GeminiStatus { initial, loading, success, error } // enum MessageType { text, image, audio, video } @@ -147,10 +151,11 @@ class GeminiCubit extends Cubit { void startLesson() async { final quizQuestions = await loadQuizQuestions(); - final String lessonScript = await rootBundle.loadString('assets/lessons/cells.md'); + final String lessonScript = + await rootBundle.loadString('assets/lessons/cells.md'); // 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"; - 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"; + 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"; final safetySettings = [ @@ -181,42 +186,51 @@ class GeminiCubit extends Cubit { model: model); emit(initialState); - try { - final chat = state.model!.startChat(history: [Content.text(prompt)]); - final stream = chat.sendMessageStream(Content.text( - "EEG DATA:\n${GetIt.instance().state.getJsonString()}\nMessage:\n$prompt")); + sendMessage(""); - String responseText = ''; + // try { + // final chat = state.model!.startChat(history: [Content.text(prompt)]); + // final stream = chat.sendMessageStream(Content.text( + // "EEG DATA:\n${GetIt.instance().state.getJsonString()}\nMessage:\n$prompt")); - await for (final chunk in stream) { - responseText += chunk.text ?? ''; - emit(initialState.copyWith( - status: GeminiStatus.success, - messages: [ - lessonScriptMessage, - Message( - source: MessageSource.agent, - text: responseText, - type: MessageType.text) - ], - model: model)); - } - } catch (e) { - emit(GeminiState( - status: GeminiStatus.error, - messages: state.messages, - error: e.toString(), - )); - } + // String responseText = ''; + + // await for (final chunk in stream) { + // responseText += chunk.text ?? ''; + // emit(initialState.copyWith( + // status: GeminiStatus.success, + // messages: [ + // lessonScriptMessage, + // Message( + // source: MessageSource.agent, + // text: responseText, + // type: MessageType.text) + // ], + // model: model)); + // } + // } catch (e) { + // emit(GeminiState( + // status: GeminiStatus.error, + // messages: state.messages, + // error: e.toString(), + // )); + // } } void sendMessage(String prompt) async { List messagesWithoutPrompt = state.messages; - var messagesWithPrompt = state.messages + - [ - Message( - text: prompt, type: MessageType.text, source: MessageSource.user) - ]; + List messagesWithPrompt; + if (prompt == "") { + messagesWithPrompt = state.messages; + } else { + messagesWithPrompt = state.messages + + [ + Message( + text: prompt, + type: MessageType.text, + source: MessageSource.user) + ]; + } emit(state.copyWith( status: GeminiStatus.loading, @@ -233,17 +247,30 @@ class GeminiCubit extends Cubit { String responseText = ''; + bool isAnalysisDone = false; + await for (final chunk in stream) { responseText += chunk.text ?? ''; - emit(state.copyWith( - status: GeminiStatus.success, - messages: messagesWithPrompt + - [ - Message( - source: MessageSource.agent, - text: responseText, - type: MessageType.text) - ])); + if (responseText.contains(LESSON_START_TOKEN)) { + isAnalysisDone = true; + var startIndex = responseText.indexOf(LESSON_START_TOKEN) + + LESSON_START_TOKEN.length; + var analysisData = responseText.substring(0, startIndex); + print("ANALYSIS DATA: $analysisData"); + responseText = + responseText.substring(startIndex, responseText.length); + } + if (isAnalysisDone) { + emit(state.copyWith( + status: GeminiStatus.success, + messages: messagesWithPrompt + + [ + Message( + source: MessageSource.agent, + text: responseText, + type: MessageType.text) + ])); + } } if (responseText.contains("")) { diff --git a/lib/config.dart b/lib/config.dart index d208279..4d4e6af 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -1,2 +1,2 @@ -const String geminiApiKey = ''; +const String geminiApiKey = 'AIzaSyBU-vIDA4IUfRReXcu7Vdw53gnrnroJjzI'; const bool isDebug = true; diff --git a/lib/eeg/eeg_service.dart b/lib/eeg/eeg_service.dart index e3b6227..d5a22d4 100644 --- a/lib/eeg/eeg_service.dart +++ b/lib/eeg/eeg_service.dart @@ -2,26 +2,24 @@ import 'dart:async'; import 'package:gemini_app/config.dart'; import 'package:http/http.dart' as http; -import 'package:flutter_bloc/flutter_bloc.dart'; - class EegState { - final double mind_wandering; + final double mindWandering; final double focus; EegState({ - required this.mind_wandering, + required this.mindWandering, required this.focus, }); String getJsonString() { - return '{"mind_wandering": $mind_wandering, "focus": $focus}'; + return '{"mind_wandering": $mindWandering, "focus": $focus}'; } } class EegService { EegState state; - EegService() : state = EegState(mind_wandering: 0.9, focus: 0.1) { + EegService() : state = EegState(mindWandering: 0.9, focus: 0.1) { // Start the timer when the cubit is created if (!isDebug) { startPolling(); @@ -32,7 +30,7 @@ class EegService { void startPolling() { // Poll every 1 second (adjust the duration as needed) - _timer = Timer.periodic(Duration(seconds: 1), (timer) { + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { // Simulate getting new EEG data // In a real application, you would fetch this data from your EEG device or API // double newMindWandering = (DateTime.now().millisecondsSinceEpoch % 100) / 100; @@ -84,16 +82,16 @@ class EegService { } void updateEegData(double mindWandering, double focus) { - state = EegState(mind_wandering: mindWandering, focus: focus); + state = EegState(mindWandering: mindWandering, focus: focus); print('Mind Wandering: $mindWandering, Focus: $focus'); } void toggleState() { // Toggle the state between mind_wandering and focus - if (state.mind_wandering > state.focus) { - updateEegData(state.focus, state.mind_wandering); + if (state.mindWandering > state.focus) { + updateEegData(state.focus, state.mindWandering); } else { - updateEegData(state.mind_wandering, state.focus); + updateEegData(state.mindWandering, state.focus); } } } diff --git a/lib/screens/gemini_chat_screen.dart b/lib/screens/gemini_chat_screen.dart index e48b16e..bba2fe9 100644 --- a/lib/screens/gemini_chat_screen.dart +++ b/lib/screens/gemini_chat_screen.dart @@ -58,14 +58,14 @@ class GeminiChatState extends State { } void _sendMessage() async { - context - .read() - .sendMessage(_textController.text); + context.read().sendMessage(_textController.text); _textController.clear(); } void _toggleEegState() { - _eegService.toggleState(); + setState(() { + _eegService.toggleState(); + }); } void _resetConversation() { @@ -91,20 +91,23 @@ class GeminiChatState extends State { body: Padding( padding: const EdgeInsets.all(16.0), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Mind Wandering: ${_eegService.state.mind_wandering.toStringAsFixed(2)}'), - Text('Focus: ${_eegService.state.focus.toStringAsFixed(2)}'), - ], - ), - ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Mind Wandering: ${_eegService.state.mindWandering.toStringAsFixed(2)}'), + Text( + 'Focus: ${_eegService.state.focus.toStringAsFixed(2)}'), + ], ), + ), + ), + const SizedBox(height: 16), Expanded( child: BlocBuilder( builder: (context, state) { @@ -118,54 +121,87 @@ class GeminiChatState extends State { }, ), ), + const SizedBox(height: 16), BlocBuilder( builder: (context, state) { return state.isQuizMode - ? Container() // Hide text input in quiz mode + ? Container() : TextField( controller: _textController, - decoration: const InputDecoration( + decoration: InputDecoration( hintText: 'Enter your message', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + ), + filled: true, + fillColor: Colors.grey[200], + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 12.0), ), onSubmitted: (_) => _sendMessage(), ); }, ), + const SizedBox(height: 16), Row( children: [ - BlocBuilder( - builder: (context, state) { - return state.isQuizMode - ? Container() - : Expanded( - child: ElevatedButton( - onPressed: _sendMessage, - child: const Text('Send'), - ), - ); - }, + // BlocBuilder( + // builder: (context, state) { + // return state.isQuizMode + // ? Container() + // : Expanded( + // child: ElevatedButton( + // onPressed: _sendMessage, + // child: const Text('Send'), + // ), + // ); + // }, + // ), + ElevatedButton( + onPressed: _sendMessage, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.send), + SizedBox(width: 3), + Text('Send'), + ], + ), ), - const SizedBox(width: 8), + ElevatedButton( onPressed: _resetConversation, - child: const Text('Reset'), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.refresh), + SizedBox(width: 3), + Text('Reset'), + ], + ), ), - const SizedBox(width: 8), ElevatedButton( onPressed: _toggleEegState, - child: const Text('Toggle State'), - ), - const SizedBox(width: 8), - BlocBuilder( - builder: (context, state) { - return state.isQuizMode - ? Container() - : ElevatedButton( - onPressed: _enterQuizMode, - child: const Text('Start Quiz'), - ); - }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.toggle_on), + SizedBox(width: 3), + Text('Toggle State'), + ], + ), ), + // ElevatedButton( + // onPressed: _enterQuizMode, + // child: const Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Icon(Icons.quiz), + // SizedBox(width: 8), + // Text('Start Quiz'), + // ], + // ), + // ), ], ), ], @@ -302,12 +338,13 @@ class BouncingDotsState extends State @override Widget build(BuildContext context) { return Row( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: List.generate(3, (index) { return AnimatedBuilder( animation: _controllers[index], builder: (context, child) { return Container( + margin: const EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.all(2.5), child: Transform.translate( offset: Offset(0, _animations[index].value), diff --git a/pubspec.lock b/pubspec.lock index de052a6..3da9757 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -18,7 +18,7 @@ packages: source: hosted version: "2.11.0" bloc: - dependency: transitive + dependency: "direct main" description: name: bloc sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" @@ -124,13 +124,13 @@ packages: source: hosted version: "0.4.3" http: - dependency: transitive + dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 304ccaf..d08c6c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,8 @@ dependencies: flutter_markdown: ^0.7.3 flutter_bloc: ^8.0.1 get_it: ^7.7.0 + http: ^1.2.2 + bloc: ^8.1.4 dev_dependencies: flutter_test: