From 60180f92aa1f5d43f4c544988e52a294136a8652 Mon Sep 17 00:00:00 2001 From: Nathan Boyd Date: Sun, 23 Mar 2025 18:32:34 -0500 Subject: [PATCH 01/12] #243 added label and change to 'All Students' --- team_a/teamA/lib/Views/view_submissions.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/team_a/teamA/lib/Views/view_submissions.dart b/team_a/teamA/lib/Views/view_submissions.dart index 0ed3eca7..4e09dd33 100644 --- a/team_a/teamA/lib/Views/view_submissions.dart +++ b/team_a/teamA/lib/Views/view_submissions.dart @@ -44,7 +44,7 @@ class SubmissionListState extends State { LlmType? selectedLLM; - String filterOption = 'All'; + String filterOption = 'All Students'; String fullNameFilter = ''; String getApiKey(LlmType selectedLLM) { @@ -139,10 +139,11 @@ class SubmissionListState extends State { child: Row( children: [ Expanded( - child: DropdownButton( + child: DropdownButtonFormField( value: filterOption, + decoration: InputDecoration(labelText: 'Submission Status'), onChanged: _handleFilterChanged, - items: ['All', 'With Submission', 'Without Submission'] + items: ['All Students', 'With Submission', 'Without Submission'] .map>((String value) { return DropdownMenuItem( value: value, From b521068bf55961f31d9cfa2a3cfa90067f581b78 Mon Sep 17 00:00:00 2001 From: Nathan Boyd Date: Sun, 23 Mar 2025 18:57:09 -0500 Subject: [PATCH 02/12] Stopped passing the Moodle password in clear text --- team_a/teamA/lib/Api/lms/moodle/moodle_lms_service.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/team_a/teamA/lib/Api/lms/moodle/moodle_lms_service.dart b/team_a/teamA/lib/Api/lms/moodle/moodle_lms_service.dart index 2f04ecbb..13c8c4ec 100644 --- a/team_a/teamA/lib/Api/lms/moodle/moodle_lms_service.dart +++ b/team_a/teamA/lib/Api/lms/moodle/moodle_lms_service.dart @@ -69,9 +69,14 @@ class MoodleLmsService implements LmsInterface { Future 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); From aeb302494fc80633a4e59e5c4086d533715ef75f Mon Sep 17 00:00:00 2001 From: DG Date: Mon, 24 Mar 2025 20:22:01 -0400 Subject: [PATCH 03/12] stripMarkdown from Google Lesson plan [Team A] Convert from Markdown in multiple locations?? #248 --- team_a/teamA/lib/Api/llm/perplexity_api.dart | 37 ++++++++------------ team_a/teamA/lib/Views/g_lesson_plan.dart | 25 +++++++++++-- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/team_a/teamA/lib/Api/llm/perplexity_api.dart b/team_a/teamA/lib/Api/llm/perplexity_api.dart index 49aae50a..d255462f 100644 --- a/team_a/teamA/lib/Api/llm/perplexity_api.dart +++ b/team_a/teamA/lib/Api/llm/perplexity_api.dart @@ -3,10 +3,9 @@ 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 -{ - @override +class PerplexityLLM implements LLM { final String apiKey; + @override final String url = 'https://api.perplexity.ai/chat/completions'; @override @@ -14,14 +13,12 @@ class PerplexityLLM implements LLM PerplexityLLM(this.apiKey); - Map convertHttpRespToJson(String httpResponseString) - { + Map convertHttpRespToJson(String httpResponseString) { return (json.decode(httpResponseString) as Map); } // - 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', @@ -34,8 +31,7 @@ class PerplexityLLM implements LLM } // - Map getPostHeaders() - { + Map getPostHeaders() { return ({ 'accept': 'application/json', 'content-type': 'application/json', @@ -44,12 +40,11 @@ class PerplexityLLM implements LLM } // - Uri getPostUrl() => Uri.https(this.url); + Uri getPostUrl() => Uri.https('api.perplexity.ai', '/chat/completions'); // Future postMessage( - Uri url, Map postHeaders, Object postBody) async - { + Uri url, Map postHeaders, Object postBody) async { final httpPackageResponse = await ApiService().httpPost(url, headers: postHeaders, body: postBody); @@ -64,8 +59,7 @@ class PerplexityLLM implements LLM return httpPackageResponse.body; } - List parseQueryResponse(String resp) - { + List parseQueryResponse(String resp) { // ignore: prefer_adjacent_string_concatenation String quizRegExp = // r'(<\?xml.*?\?>\s*(\s*.*?\s*.*?\s*(.*?)\s*.*?(\s*.*?)+\s*\s*.*?\s*(.*?)\s*.*?)+\s*)'; @@ -92,8 +86,7 @@ class PerplexityLLM implements LLM } // - Future postToLlm(String queryPrompt) async - { + Future postToLlm(String queryPrompt) async { var resp = ""; // use the following test query so Perplexity doesn't charge @@ -105,8 +98,7 @@ class PerplexityLLM implements LLM } // - Future queryAI(String query) async - { + Future queryAI(String query) async { final postHeaders = getPostHeaders(); final postBody = getPostBody(query); final httpPackageUrl = getPostUrl(); @@ -126,14 +118,14 @@ class PerplexityLLM implements LLM } Future 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) { @@ -152,17 +144,16 @@ class PerplexityLLM implements LLM return 'An error occurred. Please check your internet connection and try again.'; } } - + @override Future 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(); } - } diff --git a/team_a/teamA/lib/Views/g_lesson_plan.dart b/team_a/teamA/lib/Views/g_lesson_plan.dart index 1c96a21a..386ca5a7 100644 --- a/team_a/teamA/lib/Views/g_lesson_plan.dart +++ b/team_a/teamA/lib/Views/g_lesson_plan.dart @@ -179,11 +179,16 @@ class _LessonPlanState extends State { } 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"); @@ -197,6 +202,20 @@ class _LessonPlanState extends State { } } + 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, From cc159b08614578bd7d60da78a86c03fd4013a682 Mon Sep 17 00:00:00 2001 From: Kevin Watts Date: Tue, 25 Mar 2025 14:55:42 -0400 Subject: [PATCH 04/12] #250 changed essay generation point scale prompt to only make 4 criteria instead of 3-5 --- team_a/teamA/lib/Views/essay_generation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/team_a/teamA/lib/Views/essay_generation.dart b/team_a/teamA/lib/Views/essay_generation.dart index 5391c39a..0c1c3118 100644 --- a/team_a/teamA/lib/Views/essay_generation.dart +++ b/team_a/teamA/lib/Views/essay_generation.dart @@ -123,7 +123,7 @@ class _EssayGenerationState extends State 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: From 52cdb31744c2e0e13461cd429f5c50833aa3e690 Mon Sep 17 00:00:00 2001 From: Kevin Watts Date: Tue, 25 Mar 2025 15:05:36 -0400 Subject: [PATCH 05/12] #79 fixed section number input validation on create essay --- team_a/teamA/lib/Views/send_essay_to_moodle.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/team_a/teamA/lib/Views/send_essay_to_moodle.dart b/team_a/teamA/lib/Views/send_essay_to_moodle.dart index 65d567b3..4ff0b829 100644 --- a/team_a/teamA/lib/Views/send_essay_to_moodle.dart +++ b/team_a/teamA/lib/Views/send_essay_to_moodle.dart @@ -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'; @@ -267,7 +268,11 @@ class EssayAssignmentSettingsState extends State { labelText: 'Section Number', border: OutlineInputBorder(), ), - // Adding validator to ensure assignment name is not empty + keyboardType: TextInputType.number, // Set keyboard type to number + inputFormatters: [ + 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'; From ed7ab8032ab3460e3a17cb48a71d616d6a6b540a Mon Sep 17 00:00:00 2001 From: Derek Sappington Date: Tue, 25 Mar 2025 21:54:32 -0400 Subject: [PATCH 06/12] #247 Add locic to only allow the EduLense Assistant work if the user is logged in. --- .../teamA/lib/Controller/custom_appbar.dart | 19 ++++++++++++++++++- team_a/teamA/lib/Views/dashboard.dart | 9 +++------ .../lib/services/local_storage_service.dart | 15 +++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/team_a/teamA/lib/Controller/custom_appbar.dart b/team_a/teamA/lib/Controller/custom_appbar.dart index b212a208..2d1450fb 100644 --- a/team_a/teamA/lib/Controller/custom_appbar.dart +++ b/team_a/teamA/lib/Controller/custom_appbar.dart @@ -29,6 +29,8 @@ class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { class _CustomAppBarState extends State { @override Widget build(BuildContext context) { + final bool canAccessApp = canUserAccessApp(context); + return AppBar( backgroundColor: Theme.of(context).colorScheme.primaryContainer, title: Text( @@ -81,7 +83,9 @@ class _CustomAppBarState extends State { Flexible( child: IconButton( icon: Icon(Icons.science), // Science Icon - onPressed: () { + onPressed: !canAccessApp + ? null + : () { Navigator.push( context, MaterialPageRoute(builder: (context) => TextBasedFunctionCallerView()), @@ -192,4 +196,17 @@ class _CustomAppBarState extends State { ), ); } + + + bool canUserAccessApp(BuildContext context) { + return LocalStorageService.canUserAccessApp(); + } + + String getClassroom() { + return LocalStorageService.getClassroom(); + } + + bool isMoodle() { + return LocalStorageService.isMoodle(); + } } diff --git a/team_a/teamA/lib/Views/dashboard.dart b/team_a/teamA/lib/Views/dashboard.dart index d80cc950..39b9b2b1 100644 --- a/team_a/teamA/lib/Views/dashboard.dart +++ b/team_a/teamA/lib/Views/dashboard.dart @@ -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) { diff --git a/team_a/teamA/lib/services/local_storage_service.dart b/team_a/teamA/lib/services/local_storage_service.dart index cbbea01e..1abb5c1d 100644 --- a/team_a/teamA/lib/services/local_storage_service.dart +++ b/team_a/teamA/lib/services/local_storage_service.dart @@ -237,4 +237,19 @@ class LocalStorageService { return false; } + + static bool canUserAccessApp() { + bool isLoggedIntoGoogleClassroom = LocalStorageService.isLoggedIntoGoogle() && LocalStorageService.hasLLMKey(); + bool isLoggedIntoMoodle = LocalStorageService.isLoggedIntoMoodle() && LocalStorageService.hasLLMKey(); + return isMoodle() ? isLoggedIntoMoodle : isLoggedIntoGoogleClassroom; + } + + static String getClassroom() { + return LocalStorageService.getSelectedClassroom() == LmsType.MOODLE ? 'Moodle' : 'Google'; + } + + static bool isMoodle() { + print(LocalStorageService.getSelectedClassroom()); + return LocalStorageService.getSelectedClassroom() == LmsType.MOODLE; + } } From d614b01675b956fda0a0f943e154d604dee004d5 Mon Sep 17 00:00:00 2001 From: Kevin Watts Date: Wed, 26 Mar 2025 13:57:53 -0400 Subject: [PATCH 07/12] #248 fixed markdown in everything but chat bot --- team_a/teamA/lib/Api/llm/grok_api.dart | 6 +++--- team_a/teamA/lib/Api/llm/llm_api_modules_base.dart | 14 +++++++++++++- team_a/teamA/lib/Api/llm/openai_api.dart | 6 +++--- team_a/teamA/lib/Api/llm/perplexity_api.dart | 6 +++--- team_a/teamA/lib/Views/g_lesson_plan.dart | 4 ++-- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/team_a/teamA/lib/Api/llm/grok_api.dart b/team_a/teamA/lib/Api/llm/grok_api.dart index f197b933..92c1f62f 100644 --- a/team_a/teamA/lib/Api/llm/grok_api.dart +++ b/team_a/teamA/lib/Api/llm/grok_api.dart @@ -4,7 +4,7 @@ import 'package:http/http.dart' as http; import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart'; import 'package:learninglens_app/services/api_service.dart'; -class GrokLLM implements LLM { +class GrokLLM extends LLM { @override final String apiKey; @override @@ -12,7 +12,7 @@ class GrokLLM implements LLM { @override final String model = 'grok-2-latest'; - GrokLLM(this.apiKey); + GrokLLM(this.apiKey) : super(apiKey); Map convertHttpRespToJson(String httpResponseString) { return (json.decode(httpResponseString) as Map); @@ -165,7 +165,7 @@ class GrokLLM implements LLM { @override - Future generate(String prompt) async { + Future _generate(String prompt) async { final url = Uri.parse(this.url); // Hypothetical endpoint final headers = { 'Authorization': 'Bearer $apiKey', diff --git a/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart b/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart index bbb6bdf9..11ce8907 100644 --- a/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart +++ b/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart @@ -13,5 +13,17 @@ abstract class LLM { LLM(this.apiKey); - Future generate(String prompt); + // Define a constant instruction to append to all prompts + static const String _noMarkdownInstruction = + " Respond in plain text, without Markdown formatting (like *, **, __, #, ##, ###, and other markdown formatting). Do not use Markdown syntax in your response. Also, do not include apostrophes (') in your response. "; + + // Abstract method that subclasses must implement + Future _generate(String prompt); + + // Public method that wraps the prompt with the no-Markdown instruction + Future generate(String prompt) async { + // Append the instruction to the original prompt + final modifiedPrompt = "$prompt$_noMarkdownInstruction"; + return await _generate(modifiedPrompt); + } } diff --git a/team_a/teamA/lib/Api/llm/openai_api.dart b/team_a/teamA/lib/Api/llm/openai_api.dart index e5e96b1c..f3d1eff8 100644 --- a/team_a/teamA/lib/Api/llm/openai_api.dart +++ b/team_a/teamA/lib/Api/llm/openai_api.dart @@ -4,14 +4,14 @@ import 'package:http/http.dart' as http; import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart'; import 'package:learninglens_app/services/api_service.dart'; -class OpenAiLLM implements LLM { +class OpenAiLLM extends LLM { @override final String apiKey; @override final String url = 'https://api.openai.com/v1/chat/completions'; @override final String model = 'gpt-4o-mini'; - OpenAiLLM(this.apiKey); + OpenAiLLM(this.apiKey) : super(apiKey); Map convertHttpRespToJson(String httpResponseString) { return (json.decode(httpResponseString) as Map); @@ -161,7 +161,7 @@ class OpenAiLLM implements LLM { } @override - Future generate(String prompt) async { + Future _generate(String prompt) async { print("In generate - prompt : $prompt"); final url = Uri.parse(this.url); diff --git a/team_a/teamA/lib/Api/llm/perplexity_api.dart b/team_a/teamA/lib/Api/llm/perplexity_api.dart index d255462f..2e5a73ef 100644 --- a/team_a/teamA/lib/Api/llm/perplexity_api.dart +++ b/team_a/teamA/lib/Api/llm/perplexity_api.dart @@ -3,7 +3,7 @@ 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 extends LLM { final String apiKey; @override @@ -11,7 +11,7 @@ class PerplexityLLM implements LLM { @override final String model = 'llama-3.1-sonar-large-128k-online'; - PerplexityLLM(this.apiKey); + PerplexityLLM(this.apiKey) : super(apiKey); Map convertHttpRespToJson(String httpResponseString) { return (json.decode(httpResponseString) as Map); @@ -146,7 +146,7 @@ class PerplexityLLM implements LLM { } @override - Future generate(String prompt) async { + Future _generate(String prompt) async { print('Generating response for prompt Perplexity: $prompt'); final postHeaders = getPostHeaders(); diff --git a/team_a/teamA/lib/Views/g_lesson_plan.dart b/team_a/teamA/lib/Views/g_lesson_plan.dart index 386ca5a7..e0df04fa 100644 --- a/team_a/teamA/lib/Views/g_lesson_plan.dart +++ b/team_a/teamA/lib/Views/g_lesson_plan.dart @@ -163,7 +163,7 @@ class _LessonPlanState extends State { final perplexityApiKey = LocalStorageService.getPerplexityKey(); try { - late final LLM aiModel; + late final aiModel; switch (selectedLLM) { case 'ChatGPT': aiModel = OpenAiLLM(openApiKey); @@ -180,7 +180,7 @@ class _LessonPlanState extends State { String prompt = """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); + var result = await aiModel.postToLlm(prompt); print('Generated lesson plan before cleaning: $result'); // Strip any Markdown from the result From 781e4084548ac1ec48b8e3c8170d9214492af52f Mon Sep 17 00:00:00 2001 From: Kevin Watts Date: Wed, 26 Mar 2025 14:52:53 -0400 Subject: [PATCH 08/12] #248 reverted changes from previous commit (Commit d614b01) --- team_a/teamA/lib/Api/llm/grok_api.dart | 6 +++--- team_a/teamA/lib/Api/llm/llm_api_modules_base.dart | 13 ++----------- team_a/teamA/lib/Api/llm/openai_api.dart | 6 +++--- team_a/teamA/lib/Api/llm/perplexity_api.dart | 7 ++++--- team_a/teamA/lib/Views/g_lesson_plan.dart | 4 ++-- 5 files changed, 14 insertions(+), 22 deletions(-) diff --git a/team_a/teamA/lib/Api/llm/grok_api.dart b/team_a/teamA/lib/Api/llm/grok_api.dart index 92c1f62f..f197b933 100644 --- a/team_a/teamA/lib/Api/llm/grok_api.dart +++ b/team_a/teamA/lib/Api/llm/grok_api.dart @@ -4,7 +4,7 @@ import 'package:http/http.dart' as http; import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart'; import 'package:learninglens_app/services/api_service.dart'; -class GrokLLM extends LLM { +class GrokLLM implements LLM { @override final String apiKey; @override @@ -12,7 +12,7 @@ class GrokLLM extends LLM { @override final String model = 'grok-2-latest'; - GrokLLM(this.apiKey) : super(apiKey); + GrokLLM(this.apiKey); Map convertHttpRespToJson(String httpResponseString) { return (json.decode(httpResponseString) as Map); @@ -165,7 +165,7 @@ class GrokLLM extends LLM { @override - Future _generate(String prompt) async { + Future generate(String prompt) async { final url = Uri.parse(this.url); // Hypothetical endpoint final headers = { 'Authorization': 'Bearer $apiKey', diff --git a/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart b/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart index 11ce8907..1a420f89 100644 --- a/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart +++ b/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart @@ -13,17 +13,8 @@ abstract class LLM { LLM(this.apiKey); - // Define a constant instruction to append to all prompts - static const String _noMarkdownInstruction = - " Respond in plain text, without Markdown formatting (like *, **, __, #, ##, ###, and other markdown formatting). Do not use Markdown syntax in your response. Also, do not include apostrophes (') in your response. "; - // Abstract method that subclasses must implement - Future _generate(String prompt); + Future generate(String prompt); - // Public method that wraps the prompt with the no-Markdown instruction - Future generate(String prompt) async { - // Append the instruction to the original prompt - final modifiedPrompt = "$prompt$_noMarkdownInstruction"; - return await _generate(modifiedPrompt); - } + } diff --git a/team_a/teamA/lib/Api/llm/openai_api.dart b/team_a/teamA/lib/Api/llm/openai_api.dart index f3d1eff8..e5e96b1c 100644 --- a/team_a/teamA/lib/Api/llm/openai_api.dart +++ b/team_a/teamA/lib/Api/llm/openai_api.dart @@ -4,14 +4,14 @@ import 'package:http/http.dart' as http; import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart'; import 'package:learninglens_app/services/api_service.dart'; -class OpenAiLLM extends LLM { +class OpenAiLLM implements LLM { @override final String apiKey; @override final String url = 'https://api.openai.com/v1/chat/completions'; @override final String model = 'gpt-4o-mini'; - OpenAiLLM(this.apiKey) : super(apiKey); + OpenAiLLM(this.apiKey); Map convertHttpRespToJson(String httpResponseString) { return (json.decode(httpResponseString) as Map); @@ -161,7 +161,7 @@ class OpenAiLLM extends LLM { } @override - Future _generate(String prompt) async { + Future generate(String prompt) async { print("In generate - prompt : $prompt"); final url = Uri.parse(this.url); diff --git a/team_a/teamA/lib/Api/llm/perplexity_api.dart b/team_a/teamA/lib/Api/llm/perplexity_api.dart index 2e5a73ef..6e5860a8 100644 --- a/team_a/teamA/lib/Api/llm/perplexity_api.dart +++ b/team_a/teamA/lib/Api/llm/perplexity_api.dart @@ -3,7 +3,8 @@ import 'dart:convert'; import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart'; import 'package:learninglens_app/services/api_service.dart'; -class PerplexityLLM extends LLM { +class PerplexityLLM implements LLM { + @override final String apiKey; @override @@ -11,7 +12,7 @@ class PerplexityLLM extends LLM { @override final String model = 'llama-3.1-sonar-large-128k-online'; - PerplexityLLM(this.apiKey) : super(apiKey); + PerplexityLLM(this.apiKey); Map convertHttpRespToJson(String httpResponseString) { return (json.decode(httpResponseString) as Map); @@ -146,7 +147,7 @@ class PerplexityLLM extends LLM { } @override - Future _generate(String prompt) async { + Future generate(String prompt) async { print('Generating response for prompt Perplexity: $prompt'); final postHeaders = getPostHeaders(); diff --git a/team_a/teamA/lib/Views/g_lesson_plan.dart b/team_a/teamA/lib/Views/g_lesson_plan.dart index e0df04fa..386ca5a7 100644 --- a/team_a/teamA/lib/Views/g_lesson_plan.dart +++ b/team_a/teamA/lib/Views/g_lesson_plan.dart @@ -163,7 +163,7 @@ class _LessonPlanState extends State { final perplexityApiKey = LocalStorageService.getPerplexityKey(); try { - late final aiModel; + late final LLM aiModel; switch (selectedLLM) { case 'ChatGPT': aiModel = OpenAiLLM(openApiKey); @@ -180,7 +180,7 @@ class _LessonPlanState extends State { String prompt = """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.postToLlm(prompt); + var result = await aiModel.generate(prompt); print('Generated lesson plan before cleaning: $result'); // Strip any Markdown from the result From 8760182ecf9ac0fbd33710c1541a3fbdd51e960c Mon Sep 17 00:00:00 2001 From: Kevin Watts Date: Wed, 26 Mar 2025 15:58:34 -0400 Subject: [PATCH 09/12] #248 fixed markdown in chatbot --- team_a/teamA/lib/Views/chat_screen.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/team_a/teamA/lib/Views/chat_screen.dart b/team_a/teamA/lib/Views/chat_screen.dart index 6b2f63ff..0136ed6d 100644 --- a/team_a/teamA/lib/Views/chat_screen.dart +++ b/team_a/teamA/lib/Views/chat_screen.dart @@ -52,6 +52,7 @@ class _ChatScreenState extends State { // Function to handle user message sending and API response Future _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; @@ -83,7 +84,7 @@ class _ChatScreenState extends State { // 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(() { From 285c1a44068992d3043430e537652e42d8d274d9 Mon Sep 17 00:00:00 2001 From: Kevin Watts Date: Wed, 26 Mar 2025 16:15:15 -0400 Subject: [PATCH 10/12] #252 added previous students' names to the About Page --- team_a/teamA/lib/Views/about_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/team_a/teamA/lib/Views/about_page.dart b/team_a/teamA/lib/Views/about_page.dart index c7f9822b..6ffb7816 100644 --- a/team_a/teamA/lib/Views/about_page.dart +++ b/team_a/teamA/lib/Views/about_page.dart @@ -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), ), ), From e3ce7a7ec6939798f6574ecfdf3c02b3c5b8c5d4 Mon Sep 17 00:00:00 2001 From: DG Date: Thu, 27 Mar 2025 20:09:04 -0400 Subject: [PATCH 11/12] fix for due date on google assignment page --- .../teamA/lib/Views/g_quiz_question_page.dart | 2 +- team_a/teamA/lib/beans/quiz.dart | 56 ++++++++++++++++--- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/team_a/teamA/lib/Views/g_quiz_question_page.dart b/team_a/teamA/lib/Views/g_quiz_question_page.dart index 7e8f7af9..d3974606 100644 --- a/team_a/teamA/lib/Views/g_quiz_question_page.dart +++ b/team_a/teamA/lib/Views/g_quiz_question_page.dart @@ -260,7 +260,7 @@ class _QuizQuestionPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar( - title: 'Quiz Questions Here ....', + title: 'Quiz Questions', userprofileurl: LmsFactory.getLmsService().profileImage ?? '', ), body: FutureBuilder( diff --git a/team_a/teamA/lib/beans/quiz.dart b/team_a/teamA/lib/beans/quiz.dart index e05ef45e..95e69620 100644 --- a/team_a/teamA/lib/beans/quiz.dart +++ b/team_a/teamA/lib/beans/quiz.dart @@ -49,15 +49,57 @@ class Quiz { return quiz; } + // JSON factory constructor using JSON map + static Quiz fromGoogleJson(Map json) { Quiz tmpQuiz = Quiz(); - tmpQuiz.name = json['title']; - tmpQuiz.description = json['description']; - tmpQuiz.questionList = []; - 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 = []; + 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?; + 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; } From 61c59c1c1c07b56154a7db44a2329d4877703845 Mon Sep 17 00:00:00 2001 From: Nathan Boyd Date: Fri, 28 Mar 2025 13:47:13 -0500 Subject: [PATCH 12/12] Blocked students from being able to log in --- .../teamA/lib/notifiers/login_notifier.dart | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/team_a/teamA/lib/notifiers/login_notifier.dart b/team_a/teamA/lib/notifiers/login_notifier.dart index d10203ff..a4dc8204 100644 --- a/team_a/teamA/lib/notifiers/login_notifier.dart +++ b/team_a/teamA/lib/notifiers/login_notifier.dart @@ -97,17 +97,25 @@ class LoginNotifier with ChangeNotifier { await _api.login(username, password, moodleUrl); if (_api.isLoggedIn()) { - _moodleState.isLoggedIn = true; - _moodleState.errorMessage = null; // Clear any old error - _username = username; - _password = password; - _moodleUrl = moodleUrl; - - // Save to local storage - LocalStorageService.saveMoodleLoginState(_moodleState.isLoggedIn); - LocalStorageService.saveCredentials(username, password); - LocalStorageService.saveMoodleUrl(moodleUrl); - + // Make sure moodle user is a teacher. + if (await _api.isUserTeacher(_api.courses!)) { + // User is a teacher + _moodleState.isLoggedIn = true; + _moodleState.errorMessage = null; // Clear any old error + _username = username; + _password = password; + _moodleUrl = moodleUrl; + + // Save to local storage + LocalStorageService.saveMoodleLoginState(_moodleState.isLoggedIn); + LocalStorageService.saveCredentials(username, password); + LocalStorageService.saveMoodleUrl(moodleUrl); + } else { + // user is not a teacher + _api.logout(); + _moodleState.isLoggedIn = false; + _moodleState.errorMessage = "User is not a teacher"; + } } else { // Logged in is false; set a custom error _moodleState.isLoggedIn = false;