gemini-app/lib/bloc/gemini_state.dart

349 lines
10 KiB
Dart
Raw Normal View History

2024-08-03 22:51:03 +02:00
import 'dart:convert';
2024-07-16 19:02:02 +02:00
import 'package:bloc/bloc.dart';
import 'package:flutter/services.dart';
2024-07-29 22:33:48 +02:00
import 'package:gemini_app/config.dart';
import 'package:gemini_app/bloc/eeg_state.dart';
2024-07-16 19:02:02 +02:00
import 'package:google_generative_ai/google_generative_ai.dart';
2024-08-03 22:51:03 +02:00
const String systemPrmpt =
"""You are an AI tutor helping students understand topics with help of biometric data. You will be supplied with a json containing data extracted from an EEG device, use that data to modify your approach and help the student learn more effectively.
At the start you will be provided a script with a lesson to cover.
Keep the analysis and responses short.
Use language: POLISH
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
Write the response in markdown and split it into two parts (include the tokens):
optional: <QUIZ_START_TOKEN>
<ANALYSIS_START_TOKEN>
here describe what is the state of the student and how to best approach them
<LESSON_START_TOKEN>
here continue with the lesson, respond to answers, etc
""";
2024-07-16 19:02:02 +02:00
enum GeminiStatus { initial, loading, success, error }
2024-08-03 22:51:03 +02:00
// enum MessageType { text, image, audio, video }
2024-07-16 19:02:02 +02:00
enum MessageSource { user, agent }
2024-08-03 22:51:03 +02:00
class QuizMessage {
final String content;
final List<String> options;
final int correctAnswer;
int? userAnswer;
QuizMessage({
required this.content,
required this.options,
required this.correctAnswer,
this.userAnswer,
});
}
// class Message {
// final String text;
// final MessageType type;
// final MessageSource source;
// Message({
// required this.text,
// required this.type,
// required this.source,
// });
// }
enum MessageType { text, lessonScript, quizQuestion, quizAnswer }
2024-07-16 19:02:02 +02:00
class Message {
final String text;
final MessageType type;
final MessageSource source;
2024-08-03 22:51:03 +02:00
final List<String>? quizOptions; // Add this for ABCD options
final int? correctAnswer; // Add this for the correct answer index
2024-07-16 19:02:02 +02:00
Message({
required this.text,
required this.type,
required this.source,
2024-08-03 22:51:03 +02:00
this.quizOptions,
this.correctAnswer,
});
static Message fromGeminiContent(Content content) {
if (content.parts.isNotEmpty) {
final part = content.parts.first;
if (part is TextPart) {
return Message(
text: part.text,
type: MessageType.text,
source: content.role == 'model'
? MessageSource.agent
: MessageSource.user,
);
}
}
throw UnsupportedError('Unsupported content type');
}
Content toGeminiContent() {
if (source == MessageSource.user || type == MessageType.lessonScript) {
return Content.text(text);
} else {
return Content.model([TextPart(text)]);
}
}
}
class QuizQuestion {
String question;
List<String> options;
int correctAnswer;
QuizQuestion({
required this.question,
required this.options,
required this.correctAnswer,
2024-07-16 19:02:02 +02:00
});
}
class GeminiState {
GeminiStatus status;
String error;
2024-08-03 22:51:03 +02:00
List<Message> messages;
List<QuizQuestion>? quizQuestions;
bool isQuizMode;
int currentQuizIndex;
GenerativeModel? model;
2024-07-16 19:02:02 +02:00
2024-08-03 22:51:03 +02:00
GeminiState(
{required this.status,
this.error = '',
this.messages = const [],
this.quizQuestions,
this.isQuizMode = false,
this.currentQuizIndex = -1,
this.model});
GeminiState copyWith({
GeminiStatus? status,
String? error,
List<Message>? messages,
List<QuizQuestion>? quizQuestions,
bool? isQuizMode,
int? currentQuizIndex,
GenerativeModel? model,
}) {
return GeminiState(
status: status ?? this.status,
error: error ?? this.error,
messages: messages ?? this.messages,
quizQuestions: quizQuestions ?? this.quizQuestions,
isQuizMode: isQuizMode ?? this.isQuizMode,
currentQuizIndex: currentQuizIndex ?? this.currentQuizIndex,
model: model ?? this.model,
);
}
2024-07-16 19:02:02 +02:00
static GeminiState get initialState => GeminiState(
2024-08-03 22:51:03 +02:00
status: GeminiStatus.initial,
// messages: [Message(text: "Hello, I'm Gemini Pro. How can I help you?", type: MessageType.text, source: MessageSource.agent)],
messages: [
// Message.fromGeminiContent(Content.model(
// [TextPart("Hello, I'm Gemini Pro. How can I help you?")]))
],
error: '',
);
2024-07-16 19:02:02 +02:00
}
class GeminiCubit extends Cubit<GeminiState> {
GeminiCubit() : super(GeminiState.initialState);
2024-08-03 22:51:03 +02:00
void startLesson(EegState eegState) async {
final quizQuestions = await loadQuizQuestions();
final String rjp = await rootBundle.loadString('assets/lessons/rjp.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 safetySettings = [
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.none),
SafetySetting(HarmCategory.hateSpeech, HarmBlockThreshold.none),
SafetySetting(HarmCategory.sexuallyExplicit, HarmBlockThreshold.none),
SafetySetting(HarmCategory.dangerousContent, HarmBlockThreshold.none),
];
final model = GenerativeModel(
model: 'gemini-1.5-pro-latest',
apiKey: geminiApiKey,
safetySettings: safetySettings,
systemInstruction: Content.system(systemPrmpt));
Message lessonScriptMessage = Message(
text: prompt,
type: MessageType.lessonScript,
source: MessageSource.agent,
2024-07-29 22:33:48 +02:00
);
2024-07-16 19:02:02 +02:00
2024-08-03 22:51:03 +02:00
GeminiState initialState = GeminiState(
status: GeminiStatus.loading,
error: '',
messages: [lessonScriptMessage],
quizQuestions: quizQuestions,
isQuizMode: false,
model: model);
emit(initialState);
try {
final chat = state.model!.startChat(history: [Content.text(prompt)]);
final stream = chat.sendMessageStream(Content.text(
"EEG DATA:\n${eegState.getJsonString()}\nPytanie:\n$prompt"));
2024-07-16 19:02:02 +02:00
2024-08-03 22:51:03 +02:00
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) {
2024-07-16 19:02:02 +02:00
emit(GeminiState(
2024-08-03 22:51:03 +02:00
status: GeminiStatus.error,
messages: state.messages,
error: e.toString(),
2024-07-16 19:02:02 +02:00
));
}
2024-08-03 22:51:03 +02:00
// enterQuizMode();
// sendMessage(prompt, eegState);
}
void sendMessage(String prompt, EegState eegState) async {
List<Message> messagesWithoutPrompt = state.messages;
var messagesWithPrompt = state.messages +
[
Message(
text: prompt, type: MessageType.text, source: MessageSource.user)
];
emit(state.copyWith(
status: GeminiStatus.loading,
2024-07-16 19:02:02 +02:00
messages: messagesWithPrompt,
));
2024-08-03 22:51:03 +02:00
try {
final chat = state.model!.startChat(
history: messagesWithoutPrompt
.map((mess) => mess.toGeminiContent())
.toList());
final stream = chat.sendMessageStream(Content.text(
"EEG DATA:\n${eegState.getJsonString()}\nWiadomość od ucznia:\n$prompt"));
String responseText = '';
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("<QUIZ_START_TOKEN>")) {
enterQuizMode();
}
} catch (e) {
emit(GeminiState(
status: GeminiStatus.error,
messages: messagesWithPrompt,
error: e.toString(),
));
}
}
void passAnswerToGemini(int answer) async {
final quizQuestion = state.quizQuestions![state.currentQuizIndex];
final answerMessage = Message(
text: quizQuestion.options[answer],
type: MessageType.quizAnswer,
source: MessageSource.user,
quizOptions: quizQuestion.options,
correctAnswer: quizQuestion.correctAnswer,
);
final List<Message> updatedMessages = [
...state.messages,
answerMessage,
];
emit(state.copyWith(messages: updatedMessages));
askNextQuizQuestion();
}
Future<List<QuizQuestion>> loadQuizQuestions() async {
final String quizJson =
await rootBundle.loadString('assets/lessons/rjp.json');
final List<dynamic> quizData = json.decode(quizJson);
return quizData
.map((question) => QuizQuestion(
question: question['question'],
options: List<String>.from(question['options']),
correctAnswer: question['correctAnswer'],
))
.toList();
}
void enterQuizMode() async {
if (state.isQuizMode) return; // Prevent re-entering quiz mode
askNextQuizQuestion();
}
void askNextQuizQuestion() {
var currentQuizIndex = state.currentQuizIndex + 1;
final quizQuestion = state.quizQuestions![currentQuizIndex];
final quizQuestionMessage = Message(
text: quizQuestion.question,
type: MessageType.quizQuestion,
source: MessageSource.agent,
quizOptions: quizQuestion.options,
correctAnswer: quizQuestion.correctAnswer,
);
final List<Message> updatedMessages = [
...state.messages,
quizQuestionMessage,
];
emit(state.copyWith(
messages: updatedMessages,
isQuizMode: true,
currentQuizIndex: currentQuizIndex));
}
void checkAnswer(int answerIndex) {
passAnswerToGemini(answerIndex);
2024-07-16 19:02:02 +02:00
}
2024-07-29 22:33:48 +02:00
void resetConversation() {
emit(GeminiState.initialState);
}
2024-08-03 22:51:03 +02:00
}