Code fixes, style, analysis separation

This commit is contained in:
Dawid Pietrykowski 2024-08-08 18:18:01 +02:00
parent ff9cdc02d8
commit ebededacc1
8 changed files with 169 additions and 103 deletions

1
lib/api_key.dart Normal file
View File

@ -0,0 +1 @@
const String geminiApiKey = '';

View File

@ -0,0 +1 @@

View File

@ -2,8 +2,8 @@ import 'dart:convert';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:gemini_app/api_key.dart';
import 'package:gemini_app/config.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:gemini_app/eeg/eeg_service.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:google_generative_ai/google_generative_ai.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 here continue with the lesson, respond to answers, etc
"""; """;
const String LESSON_START_TOKEN = "<LESSON_START_TOKEN>";
const String ANALYSIS_START_TOKEN = "<ANALYSIS_START_TOKEN>";
const String QUIZ_START_TOKEN = "<QUIZ_START_TOKEN>";
enum GeminiStatus { initial, loading, success, error } enum GeminiStatus { initial, loading, success, error }
// enum MessageType { text, image, audio, video } // enum MessageType { text, image, audio, video }
@ -147,9 +151,10 @@ class GeminiCubit extends Cubit<GeminiState> {
void startLesson() async { void startLesson() async {
final quizQuestions = await loadQuizQuestions(); 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 = // 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 lesson for one student based on the script below:\n$lessonScript";
@ -181,42 +186,51 @@ class GeminiCubit extends Cubit<GeminiState> {
model: model); model: model);
emit(initialState); emit(initialState);
try { sendMessage("");
final chat = state.model!.startChat(history: [Content.text(prompt)]);
final stream = chat.sendMessageStream(Content.text(
"EEG DATA:\n${GetIt.instance<EegService>().state.getJsonString()}\nMessage:\n$prompt"));
String responseText = ''; // try {
// final chat = state.model!.startChat(history: [Content.text(prompt)]);
// final stream = chat.sendMessageStream(Content.text(
// "EEG DATA:\n${GetIt.instance<EegService>().state.getJsonString()}\nMessage:\n$prompt"));
await for (final chunk in stream) { // String responseText = '';
responseText += chunk.text ?? '';
emit(initialState.copyWith( // await for (final chunk in stream) {
status: GeminiStatus.success, // responseText += chunk.text ?? '';
messages: [ // emit(initialState.copyWith(
lessonScriptMessage, // status: GeminiStatus.success,
Message( // messages: [
source: MessageSource.agent, // lessonScriptMessage,
text: responseText, // Message(
type: MessageType.text) // source: MessageSource.agent,
], // text: responseText,
model: model)); // type: MessageType.text)
} // ],
} catch (e) { // model: model));
emit(GeminiState( // }
status: GeminiStatus.error, // } catch (e) {
messages: state.messages, // emit(GeminiState(
error: e.toString(), // status: GeminiStatus.error,
)); // messages: state.messages,
} // error: e.toString(),
// ));
// }
} }
void sendMessage(String prompt) async { void sendMessage(String prompt) async {
List<Message> messagesWithoutPrompt = state.messages; List<Message> messagesWithoutPrompt = state.messages;
var messagesWithPrompt = state.messages + List<Message> messagesWithPrompt;
[ if (prompt == "") {
Message( messagesWithPrompt = state.messages;
text: prompt, type: MessageType.text, source: MessageSource.user) } else {
]; messagesWithPrompt = state.messages +
[
Message(
text: prompt,
type: MessageType.text,
source: MessageSource.user)
];
}
emit(state.copyWith( emit(state.copyWith(
status: GeminiStatus.loading, status: GeminiStatus.loading,
@ -233,17 +247,30 @@ class GeminiCubit extends Cubit<GeminiState> {
String responseText = ''; String responseText = '';
bool isAnalysisDone = false;
await for (final chunk in stream) { await for (final chunk in stream) {
responseText += chunk.text ?? ''; responseText += chunk.text ?? '';
emit(state.copyWith( if (responseText.contains(LESSON_START_TOKEN)) {
status: GeminiStatus.success, isAnalysisDone = true;
messages: messagesWithPrompt + var startIndex = responseText.indexOf(LESSON_START_TOKEN) +
[ LESSON_START_TOKEN.length;
Message( var analysisData = responseText.substring(0, startIndex);
source: MessageSource.agent, print("ANALYSIS DATA: $analysisData");
text: responseText, responseText =
type: MessageType.text) 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("<QUIZ_START_TOKEN>")) { if (responseText.contains("<QUIZ_START_TOKEN>")) {

View File

@ -1,2 +1,2 @@
const String geminiApiKey = ''; const String geminiApiKey = 'AIzaSyBU-vIDA4IUfRReXcu7Vdw53gnrnroJjzI';
const bool isDebug = true; const bool isDebug = true;

View File

@ -2,26 +2,24 @@ import 'dart:async';
import 'package:gemini_app/config.dart'; import 'package:gemini_app/config.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:flutter_bloc/flutter_bloc.dart';
class EegState { class EegState {
final double mind_wandering; final double mindWandering;
final double focus; final double focus;
EegState({ EegState({
required this.mind_wandering, required this.mindWandering,
required this.focus, required this.focus,
}); });
String getJsonString() { String getJsonString() {
return '{"mind_wandering": $mind_wandering, "focus": $focus}'; return '{"mind_wandering": $mindWandering, "focus": $focus}';
} }
} }
class EegService { class EegService {
EegState state; 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 // Start the timer when the cubit is created
if (!isDebug) { if (!isDebug) {
startPolling(); startPolling();
@ -32,7 +30,7 @@ class EegService {
void startPolling() { void startPolling() {
// Poll every 1 second (adjust the duration as needed) // 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 // Simulate getting new EEG data
// In a real application, you would fetch this data from your EEG device or API // In a real application, you would fetch this data from your EEG device or API
// double newMindWandering = (DateTime.now().millisecondsSinceEpoch % 100) / 100; // double newMindWandering = (DateTime.now().millisecondsSinceEpoch % 100) / 100;
@ -84,16 +82,16 @@ class EegService {
} }
void updateEegData(double mindWandering, double focus) { 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'); print('Mind Wandering: $mindWandering, Focus: $focus');
} }
void toggleState() { void toggleState() {
// Toggle the state between mind_wandering and focus // Toggle the state between mind_wandering and focus
if (state.mind_wandering > state.focus) { if (state.mindWandering > state.focus) {
updateEegData(state.focus, state.mind_wandering); updateEegData(state.focus, state.mindWandering);
} else { } else {
updateEegData(state.mind_wandering, state.focus); updateEegData(state.mindWandering, state.focus);
} }
} }
} }

View File

@ -58,14 +58,14 @@ class GeminiChatState extends State<GeminiChat> {
} }
void _sendMessage() async { void _sendMessage() async {
context context.read<GeminiCubit>().sendMessage(_textController.text);
.read<GeminiCubit>()
.sendMessage(_textController.text);
_textController.clear(); _textController.clear();
} }
void _toggleEegState() { void _toggleEegState() {
_eegService.toggleState(); setState(() {
_eegService.toggleState();
});
} }
void _resetConversation() { void _resetConversation() {
@ -91,20 +91,23 @@ class GeminiChatState extends State<GeminiChat> {
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Card( Card(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Mind Wandering: ${_eegService.state.mind_wandering.toStringAsFixed(2)}'), 'Mind Wandering: ${_eegService.state.mindWandering.toStringAsFixed(2)}'),
Text('Focus: ${_eegService.state.focus.toStringAsFixed(2)}'), Text(
], 'Focus: ${_eegService.state.focus.toStringAsFixed(2)}'),
), ],
),
), ),
),
),
const SizedBox(height: 16),
Expanded( Expanded(
child: BlocBuilder<GeminiCubit, GeminiState>( child: BlocBuilder<GeminiCubit, GeminiState>(
builder: (context, state) { builder: (context, state) {
@ -118,54 +121,87 @@ class GeminiChatState extends State<GeminiChat> {
}, },
), ),
), ),
const SizedBox(height: 16),
BlocBuilder<GeminiCubit, GeminiState>( BlocBuilder<GeminiCubit, GeminiState>(
builder: (context, state) { builder: (context, state) {
return state.isQuizMode return state.isQuizMode
? Container() // Hide text input in quiz mode ? Container()
: TextField( : TextField(
controller: _textController, controller: _textController,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: 'Enter your message', 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(), onSubmitted: (_) => _sendMessage(),
); );
}, },
), ),
const SizedBox(height: 16),
Row( Row(
children: [ children: [
BlocBuilder<GeminiCubit, GeminiState>( // BlocBuilder<GeminiCubit, GeminiState>(
builder: (context, state) { // builder: (context, state) {
return state.isQuizMode // return state.isQuizMode
? Container() // ? Container()
: Expanded( // : Expanded(
child: ElevatedButton( // child: ElevatedButton(
onPressed: _sendMessage, // onPressed: _sendMessage,
child: const Text('Send'), // 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( ElevatedButton(
onPressed: _resetConversation, 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( ElevatedButton(
onPressed: _toggleEegState, onPressed: _toggleEegState,
child: const Text('Toggle State'), child: const Row(
), mainAxisAlignment: MainAxisAlignment.center,
const SizedBox(width: 8), children: [
BlocBuilder<GeminiCubit, GeminiState>( Icon(Icons.toggle_on),
builder: (context, state) { SizedBox(width: 3),
return state.isQuizMode Text('Toggle State'),
? Container() ],
: ElevatedButton( ),
onPressed: _enterQuizMode,
child: const Text('Start Quiz'),
);
},
), ),
// 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<BouncingDots>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(3, (index) { children: List.generate(3, (index) {
return AnimatedBuilder( return AnimatedBuilder(
animation: _controllers[index], animation: _controllers[index],
builder: (context, child) { builder: (context, child) {
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 4.0),
padding: const EdgeInsets.all(2.5), padding: const EdgeInsets.all(2.5),
child: Transform.translate( child: Transform.translate(
offset: Offset(0, _animations[index].value), offset: Offset(0, _animations[index].value),

View File

@ -18,7 +18,7 @@ packages:
source: hosted source: hosted
version: "2.11.0" version: "2.11.0"
bloc: bloc:
dependency: transitive dependency: "direct main"
description: description:
name: bloc name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
@ -124,13 +124,13 @@ packages:
source: hosted source: hosted
version: "0.4.3" version: "0.4.3"
http: http:
dependency: transitive dependency: "direct main"
description: description:
name: http name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.2"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:

View File

@ -42,6 +42,8 @@ dependencies:
flutter_markdown: ^0.7.3 flutter_markdown: ^0.7.3
flutter_bloc: ^8.0.1 flutter_bloc: ^8.0.1
get_it: ^7.7.0 get_it: ^7.7.0
http: ^1.2.2
bloc: ^8.1.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: