Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions team_a/teamA/lib/Api/llm/llm_api_modules_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ abstract class LLM {

LLM(this.apiKey);

// Abstract method that subclasses must implement
Future<String> generate(String prompt);


}
36 changes: 14 additions & 22 deletions team_a/teamA/lib/Api/llm/perplexity_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,23 @@ import 'dart:convert';
import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart';
import 'package:learninglens_app/services/api_service.dart';

class PerplexityLLM implements LLM
{
class PerplexityLLM implements LLM {
@override
final String apiKey;

@override
final String url = 'https://api.perplexity.ai/chat/completions';
@override
final String model = 'llama-3.1-sonar-large-128k-online';

PerplexityLLM(this.apiKey);

Map<String, dynamic> convertHttpRespToJson(String httpResponseString)
{
Map<String, dynamic> convertHttpRespToJson(String httpResponseString) {
return (json.decode(httpResponseString) as Map<String, dynamic>);
}

//
String getPostBody(String queryMessage)
{
String getPostBody(String queryMessage) {
return jsonEncode({
// 'model': 'llama-3-sonar-large-32k-online',
//'model': 'llama-3.1-sonar-large-128k-chat',
Expand All @@ -34,8 +32,7 @@ class PerplexityLLM implements LLM
}

//
Map<String, String> getPostHeaders()
{
Map<String, String> getPostHeaders() {
return ({
'accept': 'application/json',
'content-type': 'application/json',
Expand All @@ -44,12 +41,11 @@ class PerplexityLLM implements LLM
}

//
Uri getPostUrl() => Uri.https(this.url);
Uri getPostUrl() => Uri.https('api.perplexity.ai', '/chat/completions');

//
Future<String> postMessage(
Uri url, Map<String, String> postHeaders, Object postBody) async
{
Uri url, Map<String, String> postHeaders, Object postBody) async {
final httpPackageResponse =
await ApiService().httpPost(url, headers: postHeaders, body: postBody);

Expand All @@ -64,8 +60,7 @@ class PerplexityLLM implements LLM
return httpPackageResponse.body;
}

List<String> parseQueryResponse(String resp)
{
List<String> parseQueryResponse(String resp) {
// ignore: prefer_adjacent_string_concatenation
String quizRegExp =
// r'(<\?xml.*?\?>\s*<quiz>(\s*.*?<question>\s*.*?<text>\s*(.*?)</text>\s*.*?<options>(\s*.*?<option>\s*(.*?)</option>)+\s*</options>\s*.*?<answer>\s*(.*?)</answer>\s*.*?</question>)+\s*</quiz>)';
Expand All @@ -92,8 +87,7 @@ class PerplexityLLM implements LLM
}

//
Future<String> postToLlm(String queryPrompt) async
{
Future<String> postToLlm(String queryPrompt) async {
var resp = "";

// use the following test query so Perplexity doesn't charge
Expand All @@ -105,8 +99,7 @@ class PerplexityLLM implements LLM
}

//
Future<String> queryAI(String query) async
{
Future<String> queryAI(String query) async {
final postHeaders = getPostHeaders();
final postBody = getPostBody(query);
final httpPackageUrl = getPostUrl();
Expand All @@ -126,14 +119,14 @@ class PerplexityLLM implements LLM
}

Future<String> getChatResponse(String prompt) async {

final postHeaders = getPostHeaders();
final postBody = getPostBody(prompt);
final httpPackageUrl = getPostUrl();

try {
// Make the POST request to the chat completions endpoint
var response = await ApiService().httpPost(httpPackageUrl, headers: postHeaders, body: postBody);
var response = await ApiService()
.httpPost(httpPackageUrl, headers: postHeaders, body: postBody);

// Check for successful response
if (response.statusCode == 200) {
Expand All @@ -152,17 +145,16 @@ class PerplexityLLM implements LLM
return 'An error occurred. Please check your internet connection and try again.';
}
}

@override
Future<String> generate(String prompt) async {
print('Generating response for prompt Perplexity: $prompt');

final postHeaders = getPostHeaders();
final postHeaders = getPostHeaders();
final postBody = getPostBody(prompt);
final url = getPostUrl();
final responseString = await postMessage(url, postHeaders, postBody);
final responseJson = jsonDecode(responseString);
return responseJson['choices'][0]['message']['content'].trim();
}

}
9 changes: 7 additions & 2 deletions team_a/teamA/lib/Api/lms/moodle/moodle_lms_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,14 @@ class MoodleLmsService implements LmsInterface {
Future<void> login(String username, String password, String baseURL) async {
print('Logging in to Moodle...');

final body = {
'username': username,
'password': password,
'service': 'moodle_mobile_app'
};

// 1) Obtain the token by calling Moodle's login/token.php
final response = await ApiService().httpGet(Uri.parse(
'$baseURL/login/token.php?username=$username&password=$password&service=moodle_mobile_app'));
final response = await ApiService().httpPost(Uri.parse('$baseURL/login/token.php'), body: body);

if (response.statusCode != 200) {
throw HttpException(response.body);
Expand Down
19 changes: 18 additions & 1 deletion team_a/teamA/lib/Controller/custom_appbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
class _CustomAppBarState extends State<CustomAppBar> {
@override
Widget build(BuildContext context) {
final bool canAccessApp = canUserAccessApp(context);

return AppBar(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
title: Text(
Expand Down Expand Up @@ -81,7 +83,9 @@ class _CustomAppBarState extends State<CustomAppBar> {
Flexible(
child: IconButton(
icon: Icon(Icons.science), // Science Icon
onPressed: () {
onPressed: !canAccessApp
? null
: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TextBasedFunctionCallerView()),
Expand Down Expand Up @@ -192,4 +196,17 @@ class _CustomAppBarState extends State<CustomAppBar> {
),
);
}


bool canUserAccessApp(BuildContext context) {
return LocalStorageService.canUserAccessApp();
}

String getClassroom() {
return LocalStorageService.getClassroom();
}

bool isMoodle() {
return LocalStorageService.isMoodle();
}
}
2 changes: 1 addition & 1 deletion team_a/teamA/lib/Views/about_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class _TemplateState extends State{
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0), // Adds left and right padding
child: Text(
'Learning Lens is an application developed by students at the University of Maryland Global Campus in the SWEN 670 Software Engineering Capstone course. It originated in the Fall 2024 student cohort and has been further developed by the students of Spring 2025. Some features and ideas were also developed from an application developed by a Fall 2024 cohort team named EvaluAI.\n\nLearning Lens is intended to be used by educators who teach students who utilize Learning Management Systems (LMS) like Moodle and Google Classroom. The application allows teachers to automatically generate quizzes, essay assignments, and lesson plans using various Artificial Intelligence platforms. There are also added features for Individual Education Plans and advanced analytics.\n\nSpring 2025 Contributors Under Team Name "EduLense": Nathaniel Boyd, Daniel Diep, Dinesh Ghimire, Andrew Hammes, Dusty McKinnon, Derek Sappington, and Kevin Watts',
'Learning Lens is an application developed by students at the University of Maryland Global Campus in the SWEN 670 Software Engineering Capstone course. It originated in the Fall 2024 student cohort and has been further developed by the students of Spring 2025. Some features and ideas were also developed from an application developed by a Fall 2024 cohort team named EvaluAI.\n\nLearning Lens is intended to be used by educators who teach students who utilize Learning Management Systems (LMS) like Moodle and Google Classroom. The application allows teachers to automatically generate quizzes, essay assignments, and lesson plans using various Artificial Intelligence platforms. There are also added features for Individual Education Plans and advanced analytics.\n\nSpring 2025 Contributors Under Team Name "EduLense": Nathaniel Boyd, Daniel Diep, Dinesh Ghimire, Andrew Hammes, Dusty McKinnon, Derek Sappington, and Kevin Watts\n\nFall 2024 Contributors: Getinet Aga, Alexander Daugherty, Camille De Jesus, Desmond Herring, Jason Martin, Teja Tammali, Adam Williams, Scott McGlynn, Safia Azhar, Joneice Butler, Anthony Ohiosikha, Daanish Siddiqui, Conor Moore, and David Worthington\n\nSummer 2024 Contributors: Eric Bennett, George Gaynor, Nicholas Jungmarker, Syrone Robinson, Marsha Sapp, Henok Sibhatu, Tianming Zhu, Edward Shin, Jordan Gilberg, Mohammed Ghauri, Najwan Ismail, Stephen Buley, Whitney Meulink, and William Crowdus\n\nSpring 2024 Contributors: Tim Deering, Hemantha Adiga Madiyara, Colisian McLeod, Iriafen Ohiosikha, Nick Patton, Kathryn Scearce, Malaika Shell, Rene Wong',
style: TextStyle(fontSize: 15),
),
),
Expand Down
3 changes: 2 additions & 1 deletion team_a/teamA/lib/Views/chat_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class _ChatScreenState extends State<ChatScreen> {
// Function to handle user message sending and API response
Future<void> _sendMessage() async {
final input = _controller.text;
final aiPrompt = "$input IMPORTANT: Do not use any Markdown syntax (e.g., #, *, **, etc.). Use plain text only.";

if (input.isEmpty) {
return;
Expand Down Expand Up @@ -83,7 +84,7 @@ class _ChatScreenState extends State<ChatScreen> {
// Get ChatGPT response
// final chatGPTService = OpenAiLLM();
final prompt =
_role == 'teacher' ? "You are assisting a teacher. $input" : input;
_role == 'teacher' ? "You are assisting a teacher. $aiPrompt" : aiPrompt;
final response = await aiModel.getChatResponse(prompt);

setState(() {
Expand Down
9 changes: 3 additions & 6 deletions team_a/teamA/lib/Views/dashboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,15 @@ class TeacherDashboard extends StatelessWidget {
}

bool canUserAccessApp(BuildContext context) {
bool isLoggedIntoGoogleClassroom = LocalStorageService.isLoggedIntoGoogle() && LocalStorageService.hasLLMKey();
bool isLoggedIntoMoodle = LocalStorageService.isLoggedIntoMoodle() && LocalStorageService.hasLLMKey();
return isMoodle() ? isLoggedIntoMoodle : isLoggedIntoGoogleClassroom;
return LocalStorageService.canUserAccessApp();
}

String getClassroom() {
return LocalStorageService.getSelectedClassroom() == LmsType.MOODLE ? 'Moodle' : 'Google';
return LocalStorageService.getClassroom();
}

bool isMoodle() {
print(LocalStorageService.getSelectedClassroom());
return LocalStorageService.getSelectedClassroom() == LmsType.MOODLE;
return LocalStorageService.isMoodle();
}

Widget _buildDesktopLayout(BuildContext context, BoxConstraints constraints) {
Expand Down
2 changes: 1 addition & 1 deletion team_a/teamA/lib/Views/essay_generation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class _EssayGenerationState extends State<EssayGeneration>
String queryPrompt = '''
I am building a program that creates rubrics when provided with assignment information. I will provide you with the following information about the assignment that needs a rubric:
Difficulty level, point scale, assignment objective, assignment description. You may also receive additional customization rules.
Using this information, you will reply with a rubric that includes 3-5 criteria. Your reply must only contain the JSON information, and begin with a {.
Using this information, you will reply with a rubric that includes 4 criteria. Your reply must only contain the JSON information, and begin with a {.
Remove any ``` from your output.

You must reply with a representation of the rubric in JSON format that exactly matches this format:
Expand Down
25 changes: 22 additions & 3 deletions team_a/teamA/lib/Views/g_lesson_plan.dart
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,16 @@ class _LessonPlanState extends State<GoogleLessonPlans> {
}

String prompt =
"Create a concise, all-text lesson plan for ${lessonPlanNameController.text} for grade ${selectedGradeLevel == 'K' ? 'Kindergarten' : selectedGradeLevel} covering ${manualEntryController.text}. ${aiPromptDetailsController.text}. Write it as student-facing content for studying, essays, and quizzes. Use plain text, no Markdown, in 500 words.";

"""Generate an all text (no diagrams) lesson of less than 500 words for ${lessonPlanNameController.text} for grade $selectedGradeLevel covering key topics like ${manualEntryController.text}. ${aiPromptDetailsController.text}. This lesson is WHAT THE STUDENT WILL SEE! This lesson will be viewed by students and students will use it to study from (which will help them write essays and take quizzes). IMPORTANT: Do not use any Markdown syntax (e.g., #, *, **, etc.). Use plain text only.""";
var result = await aiModel.generate(prompt);
print('Generated lesson plan before cleaning: $result');

// Strip any Markdown from the result
String cleanedResult = stripMarkdown(result);
print('Generated lesson plan after cleaning: $cleanedResult');

setState(() {
manualEntryController.text = result;
manualEntryController.text = cleanedResult;
});
} catch (e) {
log.severe("Error generating lesson plan: $e");
Expand All @@ -197,6 +202,20 @@ class _LessonPlanState extends State<GoogleLessonPlans> {
}
}

String stripMarkdown(String text) {
// Remove common Markdown syntax
String cleanedText = text
.replaceAll(RegExp(r'#+\s'), '') // Remove headers (e.g., #, ##)
.replaceAll(RegExp(r'\*\*'), '') // Remove bold (**)
.replaceAll(RegExp(r'\*'), '') // Remove italics or list markers (*)
.replaceAll(RegExp(r'_'), '') // Remove italics or emphasis (_)
.replaceAll(RegExp(r'```[\s\S]*?```'), '') // Remove code blocks
.replaceAll(RegExp(r'`'), '') // Remove inline code
.replaceAll(RegExp(r'-\s'), '') // Remove list markers (-)
.replaceAll(RegExp(r'>\s'), ''); // Remove blockquotes (>)
return cleanedText.trim();
}

void _showLessonPlanDialog(dynamic lessonPlan) {
showDialog(
context: context,
Expand Down
2 changes: 1 addition & 1 deletion team_a/teamA/lib/Views/g_quiz_question_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ class _QuizQuestionPageState extends State<QuizQuestionPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
title: 'Quiz Questions Here ....',
title: 'Quiz Questions',
userprofileurl: LmsFactory.getLmsService().profileImage ?? '',
),
body: FutureBuilder<FormData>(
Expand Down
7 changes: 6 additions & 1 deletion team_a/teamA/lib/Views/send_essay_to_moodle.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:learninglens_app/Api/lms/factory/lms_factory.dart';
import 'package:learninglens_app/Controller/custom_appbar.dart';
import 'package:learninglens_app/beans/course.dart';
Expand Down Expand Up @@ -267,7 +268,11 @@ class EssayAssignmentSettingsState extends State<EssayAssignmentSettings> {
labelText: 'Section Number',
border: OutlineInputBorder(),
),
// Adding validator to ensure assignment name is not empty
keyboardType: TextInputType.number, // Set keyboard type to number
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly // Allow only digits
],
// Adding validator to ensure section number is not empty
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a section number';
Expand Down
7 changes: 4 additions & 3 deletions team_a/teamA/lib/Views/view_submissions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class SubmissionListState extends State<SubmissionList> {

LlmType? selectedLLM;

String filterOption = 'All';
String filterOption = 'All Students';
String fullNameFilter = '';

String getApiKey(LlmType selectedLLM) {
Expand Down Expand Up @@ -139,10 +139,11 @@ class SubmissionListState extends State<SubmissionList> {
child: Row(
children: [
Expanded(
child: DropdownButton<String>(
child: DropdownButtonFormField<String>(
value: filterOption,
decoration: InputDecoration(labelText: 'Submission Status'),
onChanged: _handleFilterChanged,
items: <String>['All', 'With Submission', 'Without Submission']
items: <String>['All Students', 'With Submission', 'Without Submission']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
Expand Down
56 changes: 49 additions & 7 deletions team_a/teamA/lib/beans/quiz.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,57 @@ class Quiz {
return quiz;
}

// JSON factory constructor using JSON map

static Quiz fromGoogleJson(Map<String, dynamic> json) {
Quiz tmpQuiz = Quiz();
tmpQuiz.name = json['title'];
tmpQuiz.description = json['description'];
tmpQuiz.questionList = <Question>[];
tmpQuiz.promptUsed = '';
tmpQuiz.id = int.parse(json['id']);
tmpQuiz.coursedId = int.parse(json['courseId']);


print('Debug: Parsing JSON: $json');

// Basic fields
tmpQuiz.name = json['title'] as String? ?? '';
print('Debug: Name set to: ${tmpQuiz.name}');

tmpQuiz.description = json['description'] as String? ?? '';
print('Debug: Description set to: ${tmpQuiz.description}');

tmpQuiz.questionList = <Question>[];
tmpQuiz.promptUsed = '';
print('Debug: Initialized questionList and promptUsed');

// Parse IDs
final idStr = json['id']?.toString() ?? '0';
tmpQuiz.id = int.tryParse(idStr) ?? 0;
print('Debug: ID parsed from "$idStr" to: ${tmpQuiz.id}');

final courseIdStr = json['courseId']?.toString() ?? '0';
tmpQuiz.coursedId = int.tryParse(courseIdStr) ?? 0;
print(
'Debug: CourseID parsed from "$courseIdStr" to: ${tmpQuiz.coursedId}');

// Parse creation time as open time
final creationTimeStr = json['creationTime']?.toString() ?? '';
tmpQuiz.timeOpen = DateTime.tryParse(creationTimeStr) ?? DateTime.now();
print(
'Debug: CreationTime parsed from "$creationTimeStr" to: ${tmpQuiz.timeOpen}');

// Parse due date (which is an object with year, month, day)
DateTime dueDateTime = DateTime.now();
try {
final dueDate = json['dueDate'] as Map<String, dynamic>?;
if (dueDate != null) {
final year = dueDate['year'] as int? ?? DateTime.now().year;
final month = dueDate['month'] as int? ?? DateTime.now().month;
final day = dueDate['day'] as int? ?? DateTime.now().day;
dueDateTime = DateTime(year, month, day);
}
} catch (e) {
print('Debug: Error parsing dueDate, using default: $e');
}
tmpQuiz.timeClose = dueDateTime;
print('Debug: DueDate parsed to: ${tmpQuiz.timeClose}');

print('Debug: Quiz object created successfully');
return tmpQuiz;
}

Expand Down
Loading
Loading