Quiz fixes
This commit is contained in:
parent
bc088720ee
commit
7158824ffa
33
assets/lessons/rjp.json
Normal file
33
assets/lessons/rjp.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"question": "Które z poniższych stwierdzeń opisuje ruch jednostajnie przyspieszony?",
|
||||||
|
"options": [
|
||||||
|
"Prędkość obiektu pozostaje stała.",
|
||||||
|
"Przyspieszenie obiektu jest stałe.",
|
||||||
|
"Pozycja obiektu zmienia się w regularnych odstępach czasu.",
|
||||||
|
"Prędkość obiektu zmienia się w sposób losowy."
|
||||||
|
],
|
||||||
|
"correctAnswer": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "Które z poniższych równań opisuje ruch jednostajnie przyspieszony?",
|
||||||
|
"options": [
|
||||||
|
"v = u",
|
||||||
|
"a = 0",
|
||||||
|
"s = ut + ½at²",
|
||||||
|
"v² = u² - 2as"
|
||||||
|
],
|
||||||
|
"correctAnswer": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "Samochód przyspiesza z 0 km/h do 100 km/h w ciągu 10 sekund. Jaka jest wartość przyspieszenia samochodu?",
|
||||||
|
"options": [
|
||||||
|
"5 m/s²",
|
||||||
|
"10 m/s²",
|
||||||
|
"15 m/s²",
|
||||||
|
"20 m/s²"
|
||||||
|
],
|
||||||
|
"correctAnswer": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
**Wstęp:**
|
**Wstęp:**
|
||||||
|
|
||||||
* Witam wszystkich. Dzisiaj będziemy rozmawiać o ruchu jednostajnie przyspieszonym.
|
|
||||||
* Jest to rodzaj ruchu, w którym obiekt porusza się z coraz większą prędkością w regularnych odstępach czasu.
|
|
||||||
|
|
||||||
**Definicja ruchu jednostajnie przyspieszanego:**
|
**Definicja ruchu jednostajnie przyspieszanego:**
|
||||||
|
|
||||||
* Ruch jednostajnie przyspieszony to ruch, w którym przyspieszenie obiektu jest stałe.
|
* Ruch jednostajnie przyspieszony to ruch, w którym przyspieszenie obiektu jest stałe.
|
||||||
|
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
@ -18,11 +18,10 @@ class EegState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class EegCubit extends Cubit<EegState> {
|
class EegCubit extends Cubit<EegState> {
|
||||||
EegCubit() : super(EegState(mind_wandering: 0.9, focus: 0.1)) {
|
EegCubit() : super(EegState(mind_wandering: 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,46 +37,44 @@ class EegCubit extends Cubit<EegState> {
|
|||||||
// double newFocus = 1 - newMindWandering;
|
// double newFocus = 1 - newMindWandering;
|
||||||
|
|
||||||
fetchEegData().then((data) {
|
fetchEegData().then((data) {
|
||||||
double newMindWandering = data[0];
|
double newMindWandering = data[0];
|
||||||
double newFocus = data[1];
|
double newFocus = data[1];
|
||||||
// Update the state with the new EEG data
|
// Update the state with the new EEG data
|
||||||
updateEegData(newMindWandering, newFocus);
|
updateEegData(newMindWandering, newFocus);
|
||||||
});
|
});
|
||||||
|
|
||||||
// updateEegData(newMindWandering, newFocus);
|
// updateEegData(newMindWandering, newFocus);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<double>> fetchEegData() async {
|
||||||
Future<List<double>> fetchEegData() async {
|
if (isDebug) {
|
||||||
if (isDebug) {
|
return [0.9, 0.1]; // Placeholder ret
|
||||||
return [0.9, 0.1]; // Placeholder ret
|
|
||||||
}
|
|
||||||
|
|
||||||
final url = Uri.parse('http://192.168.83.153:1234');
|
|
||||||
|
|
||||||
try {
|
|
||||||
final response = await http.get(url);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// Split the response body by newline and parse as floats
|
|
||||||
List<String> values = response.body.trim().split('\n');
|
|
||||||
if (values.length == 2) {
|
|
||||||
return [
|
|
||||||
double.parse(values[0]),
|
|
||||||
double.parse(values[1]),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
throw Exception('Unexpected response format');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to load EEG data: ${response.statusCode}');
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Error fetching EEG data: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
final url = Uri.parse('http://192.168.83.153:1234');
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.get(url);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
// Split the response body by newline and parse as floats
|
||||||
|
List<String> values = response.body.trim().split('\n');
|
||||||
|
if (values.length == 2) {
|
||||||
|
return [
|
||||||
|
double.parse(values[0]),
|
||||||
|
double.parse(values[1]),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw Exception('Unexpected response format');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load EEG data: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Error fetching EEG data: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void stopPolling() {
|
void stopPolling() {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
|
@ -1,102 +1,346 @@
|
|||||||
|
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/config.dart';
|
import 'package:gemini_app/config.dart';
|
||||||
import 'package:gemini_app/bloc/eeg_state.dart';
|
import 'package:gemini_app/bloc/eeg_state.dart';
|
||||||
import 'package:google_generative_ai/google_generative_ai.dart';
|
import 'package:google_generative_ai/google_generative_ai.dart';
|
||||||
|
|
||||||
|
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
|
||||||
|
""";
|
||||||
|
|
||||||
enum GeminiStatus { initial, loading, success, error }
|
enum GeminiStatus { initial, loading, success, error }
|
||||||
enum MessageType { text, image, audio, video }
|
|
||||||
|
// enum MessageType { text, image, audio, video }
|
||||||
|
|
||||||
enum MessageSource { user, agent }
|
enum MessageSource { user, agent }
|
||||||
|
|
||||||
|
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 }
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
final String text;
|
final String text;
|
||||||
final MessageType type;
|
final MessageType type;
|
||||||
final MessageSource source;
|
final MessageSource source;
|
||||||
|
final List<String>? quizOptions; // Add this for ABCD options
|
||||||
|
final int? correctAnswer; // Add this for the correct answer index
|
||||||
|
|
||||||
Message({
|
Message({
|
||||||
required this.text,
|
required this.text,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.source,
|
required this.source,
|
||||||
|
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class GeminiState {
|
class GeminiState {
|
||||||
GeminiStatus status;
|
GeminiStatus status;
|
||||||
String error;
|
String error;
|
||||||
List<Content> messages;
|
List<Message> messages;
|
||||||
|
List<QuizQuestion>? quizQuestions;
|
||||||
|
bool isQuizMode;
|
||||||
|
int currentQuizIndex;
|
||||||
|
GenerativeModel? model;
|
||||||
|
|
||||||
GeminiState({
|
GeminiState(
|
||||||
required this.status,
|
{required this.status,
|
||||||
required this.error,
|
this.error = '',
|
||||||
this.messages = const [],
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static GeminiState get initialState => GeminiState(
|
static GeminiState get initialState => GeminiState(
|
||||||
status: GeminiStatus.initial,
|
status: GeminiStatus.initial,
|
||||||
// messages: [Message(text: "Hello, I'm Gemini Pro. How can I help you?", type: MessageType.text, source: MessageSource.agent)],
|
// messages: [Message(text: "Hello, I'm Gemini Pro. How can I help you?", type: MessageType.text, source: MessageSource.agent)],
|
||||||
messages: [Content.model([TextPart("Hello, I'm Gemini Pro. How can I help you?")])],
|
messages: [
|
||||||
error: '',
|
// Message.fromGeminiContent(Content.model(
|
||||||
);
|
// [TextPart("Hello, I'm Gemini Pro. How can I help you?")]))
|
||||||
|
],
|
||||||
|
error: '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class GeminiCubit extends Cubit<GeminiState> {
|
class GeminiCubit extends Cubit<GeminiState> {
|
||||||
GeminiCubit() : super(GeminiState.initialState);
|
GeminiCubit() : super(GeminiState.initialState);
|
||||||
|
|
||||||
void sendMessage(String prompt, EegState eegState) async {
|
void startLesson(EegState eegState) async {
|
||||||
var messagesWithoutPrompt = state.messages;
|
final quizQuestions = await loadQuizQuestions();
|
||||||
var messagesWithPrompt = state.messages + [
|
final String rjp = await rootBundle.loadString('assets/lessons/rjp.md');
|
||||||
Content.text(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";
|
||||||
|
|
||||||
emit(GeminiState(status: GeminiStatus.loading, messages: messagesWithPrompt, error: ''));
|
final safetySettings = [
|
||||||
final safetySettings = [
|
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.none),
|
||||||
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.none),
|
SafetySetting(HarmCategory.hateSpeech, HarmBlockThreshold.none),
|
||||||
SafetySetting(HarmCategory.hateSpeech, HarmBlockThreshold.none),
|
SafetySetting(HarmCategory.sexuallyExplicit, HarmBlockThreshold.none),
|
||||||
SafetySetting(HarmCategory.sexuallyExplicit, HarmBlockThreshold.none),
|
SafetySetting(HarmCategory.dangerousContent, HarmBlockThreshold.none),
|
||||||
SafetySetting(HarmCategory.dangerousContent, HarmBlockThreshold.none),
|
];
|
||||||
// SafetySetting(HarmCategory.unspecified, HarmBlockThreshold.none),
|
|
||||||
];
|
|
||||||
|
|
||||||
final String rjp = await rootBundle.loadString('assets/lessons/rjp.md');
|
final model = GenerativeModel(
|
||||||
|
model: 'gemini-1.5-pro-latest',
|
||||||
|
apiKey: geminiApiKey,
|
||||||
|
safetySettings: safetySettings,
|
||||||
|
systemInstruction: Content.system(systemPrmpt));
|
||||||
|
|
||||||
|
Message lessonScriptMessage = Message(
|
||||||
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.
|
text: prompt,
|
||||||
Use language: POLISH
|
type: MessageType.lessonScript,
|
||||||
Write the response in markdown and split it into two parts:
|
source: MessageSource.agent,
|
||||||
State analysis: describe what is the state of the student and how to best approach them
|
|
||||||
Tutor response: continue with the lesson, respond to answers, etc""";
|
|
||||||
|
|
||||||
final model = GenerativeModel(
|
|
||||||
model: 'gemini-1.5-pro-latest',
|
|
||||||
apiKey: geminiApiKey,
|
|
||||||
safetySettings: safetySettings,
|
|
||||||
systemInstruction: Content.system(systemPrmpt)
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final chat = model.startChat(history: messagesWithoutPrompt);
|
|
||||||
final stream = chat.sendMessageStream(
|
|
||||||
Content.text("EEG DATA:\n${eegState.getJsonString()}\nPytanie:\n$prompt")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
String responseText = '';
|
GeminiState initialState = GeminiState(
|
||||||
|
status: GeminiStatus.loading,
|
||||||
await for (final chunk in stream) {
|
|
||||||
responseText += chunk.text ?? '';
|
|
||||||
emit(GeminiState(
|
|
||||||
status: GeminiStatus.success,
|
|
||||||
messages: messagesWithPrompt + [Content.model([TextPart(responseText)])],
|
|
||||||
error: '',
|
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"));
|
||||||
|
|
||||||
|
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(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
emit(GeminiState(
|
// enterQuizMode();
|
||||||
status: GeminiStatus.error,
|
|
||||||
messages: messagesWithPrompt,
|
// sendMessage(prompt, eegState);
|
||||||
error: e.toString(),
|
}
|
||||||
));
|
|
||||||
|
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,
|
||||||
|
messages: messagesWithPrompt,
|
||||||
|
));
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void resetConversation() {
|
void resetConversation() {
|
||||||
emit(GeminiState.initialState);
|
emit(GeminiState.initialState);
|
||||||
|
@ -30,29 +30,41 @@ 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
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_startConversation();
|
_startConversation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void _toggleQuizMode() {
|
||||||
void dispose() {
|
setState(() {
|
||||||
context.read<EegCubit>().stopPolling();
|
_quizMode = !_quizMode;
|
||||||
super.dispose();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _checkAnswer(int answer) {
|
||||||
|
context.read<GeminiCubit>().checkAnswer(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
context.read<EegCubit>().stopPolling();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
void _startConversation() async {
|
void _startConversation() async {
|
||||||
final String rjp = await rootBundle.loadString('assets/lessons/rjp.md');
|
context.read<GeminiCubit>().startLesson(context.read<EegCubit>().state);
|
||||||
print(rjp);
|
|
||||||
context.read<GeminiCubit>().sendMessage("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, context.read<EegCubit>().state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _sendMessage() async {
|
void _sendMessage() async {
|
||||||
context.read<GeminiCubit>().sendMessage(_textController.text, context.read<EegCubit>().state);
|
context
|
||||||
|
.read<GeminiCubit>()
|
||||||
|
.sendMessage(_textController.text, context.read<EegCubit>().state);
|
||||||
_textController.clear();
|
_textController.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleEegState() {
|
void _toggleEegState() {
|
||||||
context.read<EegCubit>().toggleState();
|
context.read<EegCubit>().toggleState();
|
||||||
}
|
}
|
||||||
@ -62,35 +74,42 @@ void dispose() {
|
|||||||
_startConversation();
|
_startConversation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _enterQuizMode() {
|
||||||
|
context.read<GeminiCubit>().enterQuizMode();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Gemini Pro Chat'),
|
title: BlocBuilder<GeminiCubit, GeminiState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Text(state.isQuizMode ? 'Quiz Mode' : 'Gemini Pro Chat');
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
BlocBuilder<EegCubit, EegState>(
|
||||||
BlocBuilder<EegCubit, EegState>(
|
builder: (context, eegState) {
|
||||||
builder: (context, eegState) {
|
return Card(
|
||||||
return Card(
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(8.0),
|
||||||
padding: const EdgeInsets.all(8.0),
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Text('Mind Wandering: ${eegState.mind_wandering.toStringAsFixed(2)}'),
|
'Mind Wandering: ${eegState.mind_wandering.toStringAsFixed(2)}'),
|
||||||
Text('Focus: ${eegState.focus.toStringAsFixed(2)}'),
|
Text('Focus: ${eegState.focus.toStringAsFixed(2)}'),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: BlocBuilder<GeminiCubit, GeminiState>(
|
child: BlocBuilder<GeminiCubit, GeminiState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@ -104,82 +123,143 @@ void dispose() {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextField(
|
BlocBuilder<GeminiCubit, GeminiState>(
|
||||||
controller: _textController,
|
builder: (context, state) {
|
||||||
decoration: const InputDecoration(
|
return state.isQuizMode
|
||||||
hintText: 'Enter your message',
|
? Container() // Hide text input in quiz mode
|
||||||
),
|
: TextField(
|
||||||
onSubmitted: (_) => _sendMessage(),
|
controller: _textController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Enter your message',
|
||||||
|
),
|
||||||
|
onSubmitted: (_) => _sendMessage(),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
BlocBuilder<GeminiCubit, GeminiState>(
|
||||||
child: ElevatedButton(
|
builder: (context, state) {
|
||||||
onPressed: _sendMessage,
|
return state.isQuizMode
|
||||||
child: const Text('Send'),
|
? Container()
|
||||||
|
: Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _sendMessage,
|
||||||
|
child: const Text('Send'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
ElevatedButton(
|
||||||
ElevatedButton(
|
onPressed: _resetConversation,
|
||||||
onPressed: _resetConversation,
|
child: const Text('Reset'),
|
||||||
child: const Text('Reset'),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
ElevatedButton(
|
||||||
ElevatedButton(
|
onPressed: _toggleEegState,
|
||||||
onPressed: _toggleEegState,
|
child: const Text('Toggle State'),
|
||||||
child: const Text('Toggle State'),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
],
|
BlocBuilder<GeminiCubit, GeminiState>(
|
||||||
),
|
builder: (context, state) {
|
||||||
|
return state.isQuizMode
|
||||||
|
? Container()
|
||||||
|
: ElevatedButton(
|
||||||
|
onPressed: _enterQuizMode,
|
||||||
|
child: const Text('Start Quiz'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index == state.messages.length && loading) {
|
if (index == state.messages.length && loading) {
|
||||||
return Card(
|
return const Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: BouncingDots(),
|
child: BouncingDots(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final message = state.messages[index];
|
|
||||||
|
|
||||||
String text = "";
|
|
||||||
for (var part in message.parts) {
|
|
||||||
if (part is TextPart) {
|
|
||||||
text += part.text;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Card(
|
if (state.messages[index].type == MessageType.lessonScript) {
|
||||||
child: Padding(
|
// skip
|
||||||
padding: const EdgeInsets.all(8.0),
|
return Container();
|
||||||
child: Column(
|
}
|
||||||
crossAxisAlignment: message.role != 'user'
|
|
||||||
? CrossAxisAlignment.start
|
|
||||||
: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
MarkdownBody(data: text),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
final message = state.messages[index];
|
||||||
|
// String text = message.parts.whereType<TextPart>().map((part) => part.text).join();
|
||||||
|
String text = message.text;
|
||||||
|
|
||||||
|
if (message.type == MessageType.quizQuestion) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MarkdownBody(data: text),
|
||||||
|
...message.quizOptions!.asMap().entries.map((entry) {
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: () => {_checkAnswer(entry.key)},
|
||||||
|
child: Text(entry.value),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (message.type == MessageType.quizAnswer) {
|
||||||
|
bool correct = message.text == message.correctAnswer;
|
||||||
|
var text = Text(
|
||||||
|
correct
|
||||||
|
? "Correct!"
|
||||||
|
: "Incorrect. The correct answer was: ${message.quizOptions![message.correctAnswer!]}",
|
||||||
|
style: TextStyle(
|
||||||
|
color: correct ? Colors.green : Colors.red,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [text],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: message.source == MessageSource.agent
|
||||||
|
? CrossAxisAlignment.start
|
||||||
|
: CrossAxisAlignment.end,
|
||||||
|
children: [MarkdownBody(data: text)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class BouncingDots extends StatefulWidget {
|
class BouncingDots extends StatefulWidget {
|
||||||
const BouncingDots({super.key});
|
const BouncingDots({super.key});
|
||||||
@ -188,7 +268,8 @@ class BouncingDots extends StatefulWidget {
|
|||||||
BouncingDotsState createState() => BouncingDotsState();
|
BouncingDotsState createState() => BouncingDotsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class BouncingDotsState extends State<BouncingDots> with TickerProviderStateMixin {
|
class BouncingDotsState extends State<BouncingDots>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
late List<AnimationController> _controllers;
|
late List<AnimationController> _controllers;
|
||||||
late List<Animation<double>> _animations;
|
late List<Animation<double>> _animations;
|
||||||
|
|
||||||
@ -197,16 +278,16 @@ class BouncingDotsState extends State<BouncingDots> with TickerProviderStateMixi
|
|||||||
super.initState();
|
super.initState();
|
||||||
_controllers = List.generate(
|
_controllers = List.generate(
|
||||||
3,
|
3,
|
||||||
(index) => AnimationController(
|
(index) => AnimationController(
|
||||||
duration: const Duration(milliseconds: 400),
|
duration: const Duration(milliseconds: 400),
|
||||||
vsync: this,
|
vsync: this,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_animations = _controllers.map((controller) =>
|
_animations = _controllers
|
||||||
Tween<double>(begin: 0, end: -10).animate(
|
.map((controller) => Tween<double>(begin: 0, end: -10).animate(
|
||||||
CurvedAnimation(parent: controller, curve: Curves.easeInOut),
|
CurvedAnimation(parent: controller, curve: Curves.easeInOut),
|
||||||
)
|
))
|
||||||
).toList();
|
.toList();
|
||||||
|
|
||||||
for (var i = 0; i < 3; i++) {
|
for (var i = 0; i < 3; i++) {
|
||||||
Future.delayed(Duration(milliseconds: i * 180), () {
|
Future.delayed(Duration(milliseconds: i * 180), () {
|
||||||
|
@ -70,6 +70,7 @@ flutter:
|
|||||||
|
|
||||||
assets:
|
assets:
|
||||||
- assets/lessons/rjp.md
|
- assets/lessons/rjp.md
|
||||||
|
- assets/lessons/rjp.json
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user