diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 24364992..648b3f12 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -28,8 +28,7 @@ jobs: distribution: 'temurin' cache: maven - name: Build with Maven - run: mvn -B package --file pom.xml + run: mvn -B clean compile --file pom.xml + - name: Test with Maven + run: mvn -B test --file pom.xml - # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - - name: Update dependency graph - uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 diff --git a/.gitignore b/.gitignore index bbfef583..547f7b99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -backend/target +target backend/.idea +.idea + diff --git a/backend/HELP.md b/HELP.md similarity index 100% rename from backend/HELP.md rename to HELP.md diff --git a/LICENSE b/LICENSE index 8e18f380..35893834 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Gerard Chaba, Vladislavs Kirilovics, Zixuan Zhu, Mohammed Elzobair Eltayeb, Weifan Liu +Copyright (c) 2024 Gerard Chaba, Vladislavs Kirilovics, Zixuan Zhu, Mohammed Elzobair Eltayeb, Weifan Liu, Siyuan Zhang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e24518ec..9c96590d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ - [Project Structure](#project-structure) - [Tech Stack](#tech-stack) - [User Instructions](#user-instructions) +- [Chatbot Interaction Flow](#chatbot-interaction-flow) - [Developer Instructions](#developer-instructions) - [Team Members](#team-members) @@ -83,17 +84,29 @@ As a university student, I want the AI chatbot to explain course concepts and fo ## Project Structure: Below is an overview of the key components of the system: -- docs: Contains all project-related documentation. Notable files include: - - ETHICS.md -- LICENSE: Includes the project's MIT license file. +- [workflows](/.github/workflows): Contains Maven Continuous Integration. +- [docs](/docs): Contains all project-related documentation. Notable files include: + - [ETHICS.md](/docs/ETHICS.md): Includes the date of the ethics pre-approval request. + - All diagrams/flowcharts. +- [frontend](/frontend): Contains all of the front-end code (in Vue 3 and Yarn) and documents (not used in the MVP stage): + - [api](/frontend/api): Includes the cookies API. + - [public](/frontend/public): Includes some front-end documents. + - [src](/frontend/src): Includes the front-end code. +- [src](/src): Contains all of the back-end code (in Java) and documents: + - [main](/src/main): Includes the back-end code. + - [static](/src/main/resources/static): Includes all static documents, including index.html of the front-end. + - [test/java/com/UoB/AILearningTool](/src/test/java/com/UoB/AILearningTool): Includes all of the unit tests. +- [LICENSE](/LICENSE): Includes the project's MIT license file. +- [mvnw](/mvnw) and [pom.xml](/pom.xml): Documents for Maven. ## Tech Stack: ### Frontend -The frontend is a JavaScript Vue 3-based web application. It makes requests to the backend using HTTP requests. +The front end is a JavaScript Vue 3-based web application. It makes requests to the backend using HTTP requests. ### Backend The backend is based on Spring Boot (open-source Java framework). Data will be stored in a MariaDB database. -User prompts for the chatbot will be sent using API requests from the Spring Boot backend to the IBM Watsonx language model.\ +User prompts for the chatbot will be sent using API requests from the Spring Boot backend to the IBM Watsonx language model. + ![Architecture diagram, showing the technologies used in the project.](/docs/architecture_diagram.png) ## User Instructions: @@ -134,6 +147,12 @@ User prompts for the chatbot will be sent using API requests from the Spring Boo If you accepted the optional cookies, your conversation history will be saved for 30 days. You can return to the web app at any time within that period to continue where you left off or ask follow-up questions based on previous conversations. +## Chatbot Interaction Flow: + +This flowchart outlines the interaction pathways within the chatbot, guiding users through key topics such as SkillsBuild courses, university life questions, and IBM SkillsBuild platform information. Each pathway details the chatbot's prompts, and user responses, providing an overview of the chatbot’s functionality. + +![watson_flow](/docs/watson_flow.png) + ## Developer Instructions: To get started with developing or contributing to this project, follow the steps below: @@ -151,12 +170,25 @@ To get started with developing or contributing to this project, follow the steps 4. **Install Maven**: This project uses Maven as the build automation tool. If you don't have Maven installed, download the latest stable release [here](https://maven.apache.org/download.cgi). +5. **Install Vue and Yarn**: + The front end of this project is built using Vue 3 and Yarn, so make sure you have them installed: + - Vue 3 installation guide [here](https://v3.ru.vuejs.org/guide/installation.html) + - Yarn installation guide [here](https://classic.yarnpkg.com/lang/en/docs/install/#windows-stable) + 6. **Open the Project in Your IDE**: Open the cloned repository in your preferred Integrated Development Environment (IDE) (we recommend IntelliJ) for further development. +7. **Test and Run the Server**: + - To run the unit tests, use the command ```mvn test``` + - To start the server, use the command ```mvn spring-boot:run``` + ## Team Members: -Vlad Kirlovics (fi23561) \ -Gerard Chaba (tl23383) \ -Mohammed Elzobair (yi23484) \ -Weifan Liu (au22116) \ -Zixuan Zhu (kh23199) + +| Name | GitHub | Email | +|-------------------|-----------------------------------------------------------------------------------------------------------|-----------------------| +| Vlad Kirilovics | [vladislav-k1](https://github.com/vladislav-k1) and [kirilovich-vlad](https://github.com/kirilovich-vlad) | fi23561@bristol.ac.uk | +| Gerard Chaba | [GerardChabaBristol](https://github.com/GerardChabaBristol) | tl23383@bristol.ac.uk | +| Mohammed Elzobair | [yi23484](https://github.com/yi23484) | yi23484@bristol.ac.uk | +| Weifan Liu | [Liuwf4319](https://github.com/Liuwf4319) | au22116@bristol.ac.uk | +| Zixuan Zhu | [RainBOY-ZZX](https://github.com/RainBOY-ZZX) | kh23199@bristol.ac.uk | +| Siyuan Zhang | [Siyuan106](https://github.com/Siyuan106) | gr23994@bristol.ac.uk | diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties deleted file mode 100644 index 8d0ccbb8..00000000 --- a/backend/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=AILearningTool diff --git a/backend/src/test/java/com/UoB/AILearningTool/AiLearningToolApplicationTests.java b/backend/src/test/java/com/UoB/AILearningTool/AiLearningToolApplicationTests.java deleted file mode 100644 index d6dcb73d..00000000 --- a/backend/src/test/java/com/UoB/AILearningTool/AiLearningToolApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.UoB.AILearningTool; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class AiLearningToolApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/docs/backendHTTPMethods.drawio b/docs/backendHTTPMethods.drawio new file mode 100644 index 00000000..3a169580 --- /dev/null +++ b/docs/backendHTTPMethods.drawio @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/backendHTTPMethods.png b/docs/backendHTTPMethods.png new file mode 100644 index 00000000..3993d5c4 Binary files /dev/null and b/docs/backendHTTPMethods.png differ diff --git a/docs/backendHTTPMethods.svg b/docs/backendHTTPMethods.svg new file mode 100644 index 00000000..76343940 --- /dev/null +++ b/docs/backendHTTPMethods.svg @@ -0,0 +1,4 @@ + + + +
GET /signup
GET /signup
Receives:
optionalConsent cookie
Receives:...
Returns:
userID cookie
Returns:...
GET /revokeConsent
GET /revokeConsent
Receives:
optionalConsent cookie
Receives:...
Returns:
userID cookie
Returns:...
GET /createChat
GET /createChat
Receives:
userID cookie
initialMessage string
Receives:...
Returns:
chatID text/plain
Returns:...
GET /sendMessage
GET /sendMessage
Receives:
userID cookie
newMessage string
chatID string
Receives:...
Returns:
responseText text/plain
Returns:...
GET /sendIncognitoMessage
GET /sendIncognit...
Receives:
userID cookie
inputString string
Receives:...
Returns:
responseText text/plain
Returns:...
GET /getChatHistory
GET /getChatHisto...
Receives:
userID cookie
chatID string
Receives:...
Returns:
responseText text/plain
Returns:...
Backend HTTP methods
Backend HTTP methods
\ No newline at end of file diff --git a/docs/pull_request_template.md b/docs/pull_request_template.md new file mode 100644 index 00000000..1479a932 --- /dev/null +++ b/docs/pull_request_template.md @@ -0,0 +1,18 @@ +### Issue(s): +#issueNumber + +### Type of change: (choose required ones) +- Bug fix +- New feature +- Breaking change +- Documentation update +- Refactor/Optimization + +### Description: +One or more sentences of description. + +### Additional context: +Some additional, important things about the code (eg. the code contains a temporary solution that will soon be refactored). + +### Testing instructions: +Commands and other guidance on how to test your new code. diff --git a/docs/userRegistrationFlowchart.drawio b/docs/userRegistrationFlowchart.drawio new file mode 100644 index 00000000..a69aeb8f --- /dev/null +++ b/docs/userRegistrationFlowchart.drawio @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/userRegistrationFlowchart.png b/docs/userRegistrationFlowchart.png new file mode 100644 index 00000000..d6ada594 Binary files /dev/null and b/docs/userRegistrationFlowchart.png differ diff --git a/docs/userRegistrationFlowchart.svg b/docs/userRegistrationFlowchart.svg new file mode 100644 index 00000000..64e759e7 --- /dev/null +++ b/docs/userRegistrationFlowchart.svg @@ -0,0 +1,4 @@ + + + +
Yes
Yes
No
No
Set optionalConsent cookie to true
Set optionalConsent cookie...
Set optionalConsent cookie to false
Set optionalConsent cookie...
Send GET /signup request to backend
Send GET /signup request to...
Save userID cookie received from response
Save userID cookie received...
User opens the webapp for
the first time
User opens the webapp for...
Does the user consent to optional cookies?
Does the user consent to optional cookies?
Backend generates userID and returns it with a cookie in the response
Backend generates userID an...
User registration process
User registration process
\ No newline at end of file diff --git a/docs/watson_flow.drawio b/docs/watson_flow.drawio new file mode 100644 index 00000000..6ca82d30 --- /dev/null +++ b/docs/watson_flow.drawio @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/watson_flow.png b/docs/watson_flow.png new file mode 100644 index 00000000..edb3a76f Binary files /dev/null and b/docs/watson_flow.png differ diff --git a/docs/watson_flow.svg b/docs/watson_flow.svg new file mode 100644 index 00000000..8a7b2263 --- /dev/null +++ b/docs/watson_flow.svg @@ -0,0 +1,4 @@ + + + +
Cookies consent form
Cookies consent form
Greeting
Greeting
University Life
University Life
SkillsBuild Courses
SkillsBuild Courses
Beginner (no prior knowledge)
Beginner (no prior k...
Intermediate (some prior knowledge)
Intermediate (some p...
Asks for duration preferences
Asks for duration pr...
Asks for areas of interest
Asks for areas of in...
IBM SkillsBuild Plaform
IBM SkillsBuild Plaf...
Advanced (lots of prior knowledge)
Advanced (lots of pr...
Short (1-6 hours)
Short (1-6 hours)
Medium (7-15 hours)
Medium (7-15 hou...
Long (16-20+ hours)
Long (16-20+ hours)
Provides a brief description of each course, including prerequisites, duration, and course link
Provides a brief des...
Academic Challenges (time management, studying advice...)
Academic Challenges...
Personal Well-being (social life, mental well-being...)
Personal Well-being...
Offers tips, suggestions, or university resources
Offers tips, suggest...
Provides guidance, links to support articles, SkillsBuild help pages, or a contact form for users needing direct assistance with platform navigation or account issues
Provides guidance, l...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/enable_https.sh b/enable_https.sh new file mode 100644 index 00000000..765f5b93 --- /dev/null +++ b/enable_https.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# The script will change Spring Boot application parameters to deploy with HTTPS encryption. + +# Define the application properties file and keystore file +PROPERTIES_FILE="src/main/resources/application.properties" +KEYSTORE_FILE="keystore.p12" + +# Verify if keystore.p12 exists in the current directory +if [[ ! -f "$KEYSTORE_FILE" ]]; then + echo "Error: $KEYSTORE_FILE not found in the current directory." + exit 1 +else + sudo cp "$KEYSTORE_FILE" "src/main/resources/$KEYSTORE_FILE" + sudo chmod 777 "src/main/resources/$KEYSTORE_FILE" + echo "keystore file has been successfully copied." +fi + +# Overwrite the application.properties file with the new content +cat > "$PROPERTIES_FILE" <org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-logging + + + org.json + json + 20240303 + org.mariadb.jdbc diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 00000000..36d3ace3 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/Display interface/Cookie.vue b/src/Display interface/Cookie.vue new file mode 100644 index 00000000..f7408f81 --- /dev/null +++ b/src/Display interface/Cookie.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/Display interface/MainView.vue b/src/Display interface/MainView.vue new file mode 100644 index 00000000..325a6603 --- /dev/null +++ b/src/Display interface/MainView.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 00000000..f3d2503f Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components/HistorySidebar.vue b/src/components/HistorySidebar.vue new file mode 100644 index 00000000..857c9ece --- /dev/null +++ b/src/components/HistorySidebar.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/src/components/ImportantSidebar.vue b/src/components/ImportantSidebar.vue new file mode 100644 index 00000000..f88cd709 --- /dev/null +++ b/src/components/ImportantSidebar.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/components/MainContent.vue b/src/components/MainContent.vue new file mode 100644 index 00000000..ed89c5dd --- /dev/null +++ b/src/components/MainContent.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/components/SettingSidebar.vue b/src/components/SettingSidebar.vue new file mode 100644 index 00000000..e7abba00 --- /dev/null +++ b/src/components/SettingSidebar.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/src/main.js b/src/main.js new file mode 100644 index 00000000..e967f253 --- /dev/null +++ b/src/main.js @@ -0,0 +1,25 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import { createRouter, createWebHistory } from 'vue-router'; + +const Cookie = () => import('./Display interface/Cookie.vue'); +const MainView = () => import('./Display interface/MainView.vue'); + +const routes = [ + { path: '/', component: Cookie, meta: { title: 'Cookie' } }, + { path: '/main', component: MainView, meta: { title: 'Chatbot' } }, + +]; + +const router = createRouter({ + history: createWebHistory(), + routes, +}); + +router.afterEach((to) => { + document.title = to.meta.title || 'Default title'; +}); + +const app = createApp(App); +app.use(router); +app.mount('#app'); diff --git a/backend/src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java b/src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java similarity index 52% rename from backend/src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java rename to src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java index 98d4a429..92b9777b 100644 --- a/backend/src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java +++ b/src/main/java/com/UoB/AILearningTool/AiLearningToolApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; -@SpringBootApplication +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, SecurityAutoConfiguration.class }) public class AiLearningToolApplication { public static void main(String[] args) { diff --git a/src/main/java/com/UoB/AILearningTool/Chat.java b/src/main/java/com/UoB/AILearningTool/Chat.java new file mode 100644 index 00000000..7711e1a6 --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/Chat.java @@ -0,0 +1,42 @@ +package com.UoB.AILearningTool; + +import java.util.ArrayList; + +public class Chat { + final private String owner; + private String messageHistory; + + public Chat(User user, String initialMessage) { + this.owner = user.getID(); + this.messageHistory = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\n" + initialMessage; + + } + + public Boolean checkOwner(User user) { + return this.owner.equals(user.getID()); + } + + public boolean addUserMessage(String userID, String message) { + if (! this.owner.equals(userID)) { + return false; + } else { + this.messageHistory += "\n<|user|>\n" + message; + return true; + } + } + + public boolean addAIMessage(String userID, String message) { + if (this.owner.equals(userID)) { + this.messageHistory += "\n<|assistant|>\n" + message; + return true; + } else { + return false; + } + } + + public String getMessageHistory(User user) { + if (checkOwner(user)) { + return this.messageHistory; + } else {return null;} + } +} diff --git a/src/main/java/com/UoB/AILearningTool/DatabaseController.java b/src/main/java/com/UoB/AILearningTool/DatabaseController.java new file mode 100644 index 00000000..3a7f0baa --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/DatabaseController.java @@ -0,0 +1,69 @@ +package com.UoB.AILearningTool; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +// Communication with SQL database. +public class DatabaseController { + private Map users = new HashMap<>(); + private Map chats = new HashMap<>(); + + public User getUser(String userID) { + return users.get(userID); + } + + public DatabaseController() { + // TODO: Connect to a MariaDB database. + } + + // Create a new user and return their ID for cookie assignment + public String addUser(boolean optionalConsent) { + User user = new User(optionalConsent); + String id = user.getID(); + // TODO: Add a user profile record to the MariaDB database. + users.put(id, user); + return id; + } + + // Remove all data stored about the user (profile, chat, etc.) + public boolean removeUser(String id) { + // TODO: Remove a user profile record from the MariaDB database. + if (users.containsKey(id)) { + users.remove(id); + return true; + } else {return false;} + } + + // Creates a new chat + public String createChat(User user, String initialMessage) { + String id = StringTools.RandomString(20); + chats.put(id, new Chat(user, initialMessage)); + return id; + } + + // Deletes an existing chat + public Boolean deleteChat(User user, String chatID) { + Boolean success; + Chat chat = chats.get(chatID); + if (chat != null) { + if (chat.checkOwner(user)) { + chats.remove(chatID); + return true; + } + } + return false; + } + + public Chat getChat(User user, String chatID) { + Chat chat = chats.get(chatID); + if (chat != null) { + if (chat.checkOwner(user)) { + return chat; + } else {return null;} + } else { + return null; + } + } + +} diff --git a/src/main/java/com/UoB/AILearningTool/IBMAuthenticator.java b/src/main/java/com/UoB/AILearningTool/IBMAuthenticator.java new file mode 100644 index 00000000..e674bda1 --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/IBMAuthenticator.java @@ -0,0 +1,80 @@ +package com.UoB.AILearningTool; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import org.json.JSONObject; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class IBMAuthenticator extends Thread { + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private final Logger log = LoggerFactory.getLogger(SpringController.class); + + private String bearerToken; + private Integer statusCode; + + // Returns a bearer token + public String getBearerToken() { + if (this.statusCode == null) { + System.out.println("Token is not available yet. Requesting a new token."); + requestNewToken(); + } + return this.bearerToken; + } + + // Return status code of the latest request + public int getStatusCode() { + if (this.statusCode == null) { + requestNewToken(); + } + return this.statusCode; + } + + // Uses IBM IAM API key to receive a temporary Bearer token. + public void requestNewToken() { + final String API_KEY = "wXU_-wyEW1tG1S8n4T3-6eiZ70Pfc2WxqXwqExsorjDH"; + + // Prepare a request with an API key to receive a Bearer token. + HttpRequest request = HttpRequest.newBuilder(URI.create("https://iam.cloud.ibm.com/identity/token")) + .headers("Content-Type", "application/x-www-form-urlencoded") + .POST( + HttpRequest.BodyPublishers.ofString( + "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=" + API_KEY + ) + ).build(); + + try { + HttpResponse response = HttpClient.newBuilder() + .build() + .send(request, HttpResponse.BodyHandlers.ofString()); + this.statusCode = response.statusCode(); + if (this.statusCode == 200) { + this.bearerToken = new JSONObject(response.body()).getString("access_token"); + log.info("New Bearer token obtained."); + } + } catch (Exception e) { + this.bearerToken = null; + this.statusCode = 500; + log.error("Exception {}\nHTTP status code: {}", e, this.statusCode.toString()); + } + } + + // Finishes the current request and stops the scheduler. + public void stopTimer() { + this.scheduler.shutdown(); + log.info("Scheduler stopped."); + } + + // Runs a scheduler that executes requestNewToken() in specified frequency. + public void run() { + this.scheduler.scheduleAtFixedRate(this::requestNewToken, 0, 58, TimeUnit.MINUTES); + } +} diff --git a/src/main/java/com/UoB/AILearningTool/OpenAIAPIController.java b/src/main/java/com/UoB/AILearningTool/OpenAIAPIController.java new file mode 100644 index 00000000..549073dd --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/OpenAIAPIController.java @@ -0,0 +1,64 @@ +package com.UoB.AILearningTool; + +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class OpenAIAPIController { + private final Logger log = LoggerFactory.getLogger(OpenAIAPIController.class); + private final OpenAIAuthenticator authenticator; + + public OpenAIAPIController() { + this.authenticator = new OpenAIAuthenticator(); + } + + // Sends a message to OpenAI's ChatGPT API. + public WatsonxResponse sendUserMessage(String messageHistory) { + String dataPayload = StringTools.watsonxToOpenAI(messageHistory); + + WatsonxResponse AIResponse; + // Create HTTP request for OpenAI with API key in header and message history in request body. + HttpRequest request = HttpRequest + .newBuilder(URI.create("https://api.openai.com/v1/chat/completions")) + .headers("Content-Type", "application/json", + "Authorization", "Bearer " + authenticator.getBearerToken()) + .POST(HttpRequest.BodyPublishers.ofString(dataPayload)) + .build(); + + // Try sending an HTTPS request to OpenAI API. + try { + HttpResponse response = HttpClient.newBuilder() + .build() + .send(request, HttpResponse.BodyHandlers.ofString()); + Integer statusCode = response.statusCode(); + // If OpenAI API returned status 200, return the status code and the message. + if (statusCode == 200) { + String message = new JSONObject(response.body()) + .getJSONArray("choices") + .getJSONObject(0) + .getJSONObject("message") + .getString("content"); + log.info("200 OpenAI response received."); + + AIResponse = new WatsonxResponse(statusCode, message); + } else { + // If OpenAI API request returned a status code other than 200, return error 500 and error message. + log.warn("Non-200 OpenAI response received."); + String message = new JSONObject(response.body()) + .getJSONArray("error") + .getJSONObject(0) + .getString("message"); + AIResponse = new WatsonxResponse(500, message); + } + } catch (Exception e) { + AIResponse = new WatsonxResponse(500, null); + log.error("Exception {}\nHTTP status code: {}", e, 500); + } + return AIResponse; + } +} diff --git a/src/main/java/com/UoB/AILearningTool/OpenAIAuthenticator.java b/src/main/java/com/UoB/AILearningTool/OpenAIAuthenticator.java new file mode 100644 index 00000000..90284d9e --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/OpenAIAuthenticator.java @@ -0,0 +1,30 @@ +package com.UoB.AILearningTool; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OpenAIAuthenticator { + private final Logger log = LoggerFactory.getLogger(OpenAIAuthenticator.class); + + private String apiKey; + + // Constructor that initializes the API key + public OpenAIAuthenticator() { + this.apiKey = "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"; + log.info("OpenAI API key set."); + } + + // Returns the API key as a "Bearer token" (used in authorization headers) + public String getBearerToken() { + if (this.apiKey == null || this.apiKey.isEmpty()) { + log.error("API Key not set or is empty."); + throw new IllegalStateException("API Key for OpenAI is not set."); + } + return this.apiKey; + } +} diff --git a/src/main/java/com/UoB/AILearningTool/SpringController.java b/src/main/java/com/UoB/AILearningTool/SpringController.java new file mode 100644 index 00000000..51bb9737 --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/SpringController.java @@ -0,0 +1,175 @@ +package com.UoB.AILearningTool; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +@RestController +public class SpringController { + private final Logger log = LoggerFactory.getLogger(SpringController.class); + private final DatabaseController DBC = new DatabaseController(); + // TODO: Replace with OpenAIAPIController with WatsonxAPIController when API quota issue will be resolved. + // private final WatsonxAPIController WXC = new WatsonxAPIController(); + private final OpenAIAPIController WXC = new OpenAIAPIController(); + + // Assign a unique user ID for the user. + @GetMapping("/signup") + public void signup(@CookieValue(value = "optionalConsent", defaultValue = "false") boolean optionalConsent, + HttpServletResponse response) { + Cookie userIDCookie = new Cookie("userID", DBC.addUser(optionalConsent)); + userIDCookie.setMaxAge(30 * 24 * 60 * 60); // Cookie will expire in 30 days + userIDCookie.setSecure(false); +// userIDCookie.setAttribute("SameSite", "None"); + userIDCookie.setPath("/"); + response.addCookie(userIDCookie); + log.info("Assigned a new userID."); + } + + // If user revokes their consent for data storage / optional cookies, + // remove all data stored about them. + @GetMapping("/revokeConsent") + public void revokeConsent(@CookieValue(value = "userID", defaultValue = "") String userID, + HttpServletResponse response) { + if (DBC.removeUser(userID)) { + response.setStatus(200); + Cookie cookie = new Cookie("userID", ""); + cookie.setMaxAge(0); + response.addCookie(cookie); + log.info("Revoked consent for user, ID: {}", userID); + } else { + response.setStatus(400); + } + + } + + // Create a new chat session with Watsonx + @GetMapping("/createChat") + public void createChat(@CookieValue(value = "userID", defaultValue = "") String userID, + @RequestParam(name = "initialMessage") String initialMessage, + HttpServletResponse response) { + response.setContentType("text/plain"); // Set the content type to text + + // Create a chat + User user = DBC.getUser(userID); + if (user != null) { + WatsonxResponse wresponse; + String chatID = DBC.createChat(user, initialMessage); + + // Send the message history (system prompt and initial message) to Watsonx API, + // add the AI response to the message history of the chat. + try { + wresponse = WXC.sendUserMessage(DBC.getChat(DBC.getUser(userID), chatID).getMessageHistory(user)); + response.getWriter().write(chatID); + response.setStatus(wresponse.statusCode); + if (wresponse.statusCode == 200) { + DBC.getChat(DBC.getUser(userID), chatID).addAIMessage(userID, wresponse.responseText); + } + } catch (IOException e) { + log.warn(String.valueOf(e)); + } + } else { + try { + response.getWriter().write("null"); + response.setStatus(401); + } catch (IOException e) { + log.warn(String.valueOf(e)); + } + } + } + + // Send a message to the chat and receive a response + @GetMapping("/sendMessage") + public void sendMessage(@CookieValue(value = "userID", defaultValue = "") String userID, + @RequestParam(name = "newMessage") String newMessage, + @RequestParam(name = "chatID") String chatID, + HttpServletResponse response) { + Chat chat = DBC.getChat(DBC.getUser(userID), chatID); + String inputString = chat.getMessageHistory(DBC.getUser(userID)); + + // Default response - Unauthorised + WatsonxResponse wresponse = new WatsonxResponse(401, ""); + response.setContentType("text/plain"); + response.setStatus(401); + + if (chat != null && inputString != null) { + // If a message can be added to the message history of a chat, then send the message history + // to Watsonx API. + boolean success = chat.addUserMessage(userID, newMessage); + if (success) { + inputString = chat.getMessageHistory(DBC.getUser(userID)); +// TODO: Revert wresponse when issue with Watsonx API quota will be resolved. +// wresponse = WXC.sendUserMessage(StringTools.messageHistoryPrepare(inputString)); + wresponse = WXC.sendUserMessage(inputString); + response.setStatus(wresponse.statusCode); + } + try { + if (wresponse.statusCode == 200) { + chat.addAIMessage(userID, wresponse.responseText); + } + response.getWriter().write(wresponse.responseText); + } catch (IOException e) { + log.warn(String.valueOf(e)); + response.setStatus(500); + } + } + } + + // Send a message history and receive a response from Watsonx. + // Used for users who declined optional cookies. + @GetMapping("/sendIncognitoMessage") + public void sendIncognitoMessage(@CookieValue(value = "userID") String userID, + @RequestParam(name = "inputString") String inputString, + HttpServletResponse response) { + log.warn("ID {}, newMessage: {}", userID, inputString); + User user = DBC.getUser(userID); + WatsonxResponse wresponse; + response.setContentType("text/plain"); + + if (user != null) { +// TODO: Revert wresponse when issue with Watsonx API quota will be resolved. + wresponse = WXC.sendUserMessage(inputString); +// wresponse = WXC.sendUserMessage(StringTools.messageHistoryPrepare(inputString)); + response.setStatus(wresponse.statusCode); + try { + response.getWriter().write(wresponse.responseText); + log.warn(wresponse.responseText); + } catch (IOException e) { + log.warn(String.valueOf(e)); + response.setStatus(500); + } + } else { + log.warn("else {}", DBC.getUser(userID) == null); + response.setStatus(401); + } + } + + // Get message history from a chat + @GetMapping("/getChatHistory") + public void getChatHistory(@CookieValue(value = "userID", defaultValue = "") String userID, + @RequestParam(name = "chatID") String chatID, + HttpServletResponse response) { + Chat chat = DBC.getChat(DBC.getUser(userID), chatID); + String messageHistory = chat.getMessageHistory(DBC.getUser(userID)); + if (chat != null && messageHistory != null) { + response.setContentType("text/plain"); + response.setStatus(200); + try { + response.getWriter().write(messageHistory); + } catch (IOException e) { + log.warn(String.valueOf(e)); + } + } else { + response.setContentType("text/plain"); + response.setStatus(401); + try { + response.getWriter().write(""); + } catch (IOException e) { + log.warn(String.valueOf(e)); + } + } + } +} diff --git a/src/main/java/com/UoB/AILearningTool/StringTools.java b/src/main/java/com/UoB/AILearningTool/StringTools.java new file mode 100644 index 00000000..3441a011 --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/StringTools.java @@ -0,0 +1,82 @@ +package com.UoB.AILearningTool; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; + +// StringTools create / transform strings to the required formats. +public class StringTools { + + // Generates a random string of specific size (e.g. for userID / chatID) + public static String RandomString(int n) { + String newString = ""; + String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + for (int i = 0; i < n; i++) { + int index = (int)(characters.length() * Math.random()); + newString = newString + characters.charAt(index); + } + return newString; + } + + // Prepares "input" JSON key value string for Watsonx API request + public static String messageHistoryPrepare(String input) { + input = input + "\n<|assistant|>\n"; + String x = "{\"input\": \"" + input.replace("\n", "\\n") + .replace("\'", "'\''") + .replace("\"", "\\\"") + "\","; + + System.out.println(x); + return x; + } + + // Convert Watsonx message history to OpenAI request payload + public static String watsonxToOpenAI(String messageHistory) { + String currentRole, nextRole, openAIRole; + String currentMessage; + JSONObject currentMessageJSON; + JSONObject dataPayload = new JSONObject(); + dataPayload.put("model", "gpt-4o-mini"); + + JSONArray messageArray = new JSONArray(); + + // Parse the message history string. + for (int i = 0; ;i++) { + if (i == 0) { + // Parameters for parsing first message in chat history (system prompt) + currentRole = "<|system|>"; + openAIRole = "system"; + nextRole = "<|user|>"; + } else if (i % 2 != 0) { + // Parameters for parsing a user message + currentRole = "<|user|>"; + openAIRole = "user"; + nextRole = "<|assistant|>"; + } else { + // Parameters for parsing an AI message + currentRole = "<|assistant|>"; + openAIRole = "assistant"; + nextRole = "<|user|>"; + } + if (messageHistory.contains(currentRole)) { + // Removing previous messages + messageHistory = messageHistory.substring(messageHistory.indexOf(currentRole) + currentRole.length()); + // Parsing a message + int nextRoleIndex = messageHistory.indexOf(nextRole); + if (nextRoleIndex != -1) { + currentMessage = messageHistory.substring(0, messageHistory.indexOf(nextRole)); + } else { + currentMessage = messageHistory; + } + currentMessageJSON = new JSONObject(); + currentMessageJSON.put("role", openAIRole); + currentMessageJSON.put("content", currentMessage); + messageArray.put(currentMessageJSON); + } else { + break; + } + } + + dataPayload.put("messages", messageArray); + return dataPayload.toString(); + } +} diff --git a/src/main/java/com/UoB/AILearningTool/User.java b/src/main/java/com/UoB/AILearningTool/User.java new file mode 100644 index 00000000..bda2cbbf --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/User.java @@ -0,0 +1,33 @@ +package com.UoB.AILearningTool; + +import java.time.LocalDateTime; +import java.util.ArrayList; + +// Class representing a user profile (someone who consented to optional cookies). +public class User { + private final String id; + private boolean optionalConsent = false; + private LocalDateTime lastActivityTime; + + public String getID() { + updateLastActivityTime(); + return this.id; + } + + public boolean getOptionalConsent() { + updateLastActivityTime(); + return this.optionalConsent; + } + + public void updateLastActivityTime() { + this.lastActivityTime = LocalDateTime.now(); + } + + + // Create a user + public User(boolean optionalConsent) { + updateLastActivityTime(); + this.id = StringTools.RandomString(25); + this.optionalConsent = optionalConsent; + } +} diff --git a/src/main/java/com/UoB/AILearningTool/WatsonxAPIController.java b/src/main/java/com/UoB/AILearningTool/WatsonxAPIController.java new file mode 100644 index 00000000..ddd7df5c --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/WatsonxAPIController.java @@ -0,0 +1,81 @@ +package com.UoB.AILearningTool; + +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.web.header.Header; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; + +public class WatsonxAPIController { + private final Logger log = LoggerFactory.getLogger(SpringController.class); + private final IBMAuthenticator authenticator; + + final static String technicalDataPayload = """ + "parameters": { + "decoding_method": "greedy", + "max_new_tokens": 900, + "min_new_tokens": 0, + "stop_sequences": [], + "repetition_penalty": 1.05 + }, + "model_id": "ibm/granite-13b-chat-v2", + "project_id": "30726e85-a528-491c-83a3-eee53797f0da" + } + """; + + public WatsonxAPIController() { + this.authenticator = new IBMAuthenticator(); + this.authenticator.start(); + this.authenticator.requestNewToken(); + } + + public void stopAuthenticator(){ + this.authenticator.stopTimer(); + } + + // Sends a message to Watsonx API. + public WatsonxResponse sendUserMessage(String preparedMessageHistory) { + String dataPayload = preparedMessageHistory + technicalDataPayload; + + WatsonxResponse WXResponse; + HttpRequest request = HttpRequest + .newBuilder(URI.create("https://eu-gb.ml.cloud.ibm.com/ml/v1/text/generation?version=2023-05-29")) + .headers("Content-Type", "application/json", + "Accept", "application/json", + "Authorization", ("Bearer " + authenticator.getBearerToken()) ) + .POST(HttpRequest.BodyPublishers.ofString(dataPayload)).build(); + + try { + HttpResponse response = HttpClient.newBuilder() + .build() + .send(request, HttpResponse.BodyHandlers.ofString()); + Integer statusCode = response.statusCode(); + if (statusCode == 200) { + String message = new JSONObject(response.body()) + .getJSONArray("results") + .getJSONObject(0) + .getString("generated_text"); + log.info("200 Watsonx response received."); + + WXResponse = new WatsonxResponse(statusCode, message); + } else { + log.warn("Non-200 Watsonx response received."); + String message = new JSONObject(response.body()) + .getJSONArray("errors") + .getJSONObject(0) + .getString("message"); + WXResponse = new WatsonxResponse(500, message); + } + } catch (Exception e) { + WXResponse = new WatsonxResponse(500, null); + log.error("Exception {}\nHTTP status code: {}", e, 500); + } + return WXResponse; + } +} diff --git a/src/main/java/com/UoB/AILearningTool/WatsonxResponse.java b/src/main/java/com/UoB/AILearningTool/WatsonxResponse.java new file mode 100644 index 00000000..01abc9ac --- /dev/null +++ b/src/main/java/com/UoB/AILearningTool/WatsonxResponse.java @@ -0,0 +1,10 @@ +package com.UoB.AILearningTool; + +public class WatsonxResponse { + public String responseText; + public Integer statusCode; + public WatsonxResponse(Integer statusCode, String responseText) { + this.statusCode = statusCode; + this.responseText = responseText; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 00000000..4ebf8f3d --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.application.name=AILearningTool +spring.servlet.multipart.max-file-size=50MB +spring.servlet.multipart.max-request-size=50MB +spring.web.resources.static-locations=classpath:/static/ +server.port=8080 diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css new file mode 100644 index 00000000..6a4a0afe --- /dev/null +++ b/src/main/resources/static/css/main.css @@ -0,0 +1,16 @@ +#cookiePopUp { + border: 5px solid grey; + display: block; +} + +.userMessage { + background-color: #8dff6e; +} + +.assistantMessage { + background-color: #a496ff; +} + +#chatContainer { + margin: 3rem 1rem 3rem 1rem; +} \ No newline at end of file diff --git a/src/main/resources/static/error.html b/src/main/resources/static/error.html new file mode 100644 index 00000000..62361444 --- /dev/null +++ b/src/main/resources/static/error.html @@ -0,0 +1,12 @@ + + + + + Not found + + +

AI Learning Tool

+

Error - not found.

+

Redirecting you to the homepage...

+ + \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 00000000..f41bb3ee --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,34 @@ + + + + AI-Learning-Tool + + + +

Watsonx assistant

+
+
Would you like to accept optional cookies?
+ + +
+ +
+
+ + + +
+
+
+ +
+ + +
+
+ + + + + diff --git a/src/main/resources/static/js/script.js b/src/main/resources/static/js/script.js new file mode 100644 index 00000000..1e475656 --- /dev/null +++ b/src/main/resources/static/js/script.js @@ -0,0 +1,164 @@ +let optionalConsent = false; +let currentTurn = "user" +const messageContainer = document.getElementById("messageContainer") + +// Creates "optionalConsent" cookie +function setConsentCookie() { + const d = new Date(1000*60*60*24*30); + d.setTime(d.getTime() + (24*30)); + let expires = "expires="+ d.toUTCString(); + document.cookie = "optionalConsent=" + optionalConsent + ";" + expires + ";path=/"; +} + +// Sends signup GET request +function signUp() { + fetch("http://localhost:8080/signup", + { + method: "GET", + credentials: "include", + } + ).then(response => { + if (!response.ok) { + throw new Error("Non-200 backend API response"); + } else { + document.getElementById("cookiePopUp").style.display = "none" + } + }) +} + +// Adds a message to the UI. +function addMessageToUI(message) { + messageContainer.innerHTML += ("
\n" + + "
\n" + + "
" + currentTurn + "
\n" + + "
" + message + "
\n" + + "
") + if (currentTurn === "user") { + currentTurn = "assistant" + } else { + currentTurn = "user" + } +} + +// Receives cookie preference from user and signs up in the system. +function acceptOpt(x) { + optionalConsent = x + setConsentCookie() + signUp() +} + +// Parses chat history from HTTP response to good UI. +function processChatHistory(messageHistory) { + messageHistory = String(messageHistory) + let currentRole; + let openAIRole; + let nextRole; + let nextRoleIndex; + let currentMessage; + for (let i = 0; ; i++) { + if (i === 0) { + // Parameters for parsing first message in chat history (system prompt) + currentRole = "<|system|>"; + openAIRole = "system"; + nextRole = "<|user|>"; + } else if (i % 2 !== 0) { + // Parameters for parsing a user message + currentRole = "<|user|>"; + openAIRole = "user"; + nextRole = "<|assistant|>"; + } else { + // Parameters for parsing an AI message + currentRole = "<|assistant|>"; + openAIRole = "assistant"; + nextRole = "<|user|>"; + } + if (messageHistory.includes(currentRole)) { + // Removing previous messages + messageHistory = messageHistory.substring(messageHistory.indexOf(currentRole) + currentRole.length); + // Parsing a message + nextRoleIndex = messageHistory.indexOf(nextRole); + if (nextRoleIndex !== -1) { + currentMessage = messageHistory.substring(0, messageHistory.indexOf(nextRole)); + } else { + currentMessage = messageHistory; + } + if (currentRole === "<|system|>") {continue} + addMessageToUI(currentMessage) + } else { + break; + } + } +} + +// Create a new chat and requests its history +function createChat(firstChoice) { + fetch("http://localhost:8080/createChat?" + new URLSearchParams({ + "initialMessage": firstChoice + }), + { + method: "GET", + credentials: "include", + } + ).then(async response => { + if (!response.ok) { + throw new Error("Non-200 backend API response"); + } + localStorage.setItem("chatID", await response.text()) + getChatHistory() + }) + document.getElementById("chatInitialiser").setAttribute("display", "none") +} + +// Makes a request for chat history +function getChatHistory() { + fetch("http://localhost:8080/getChatHistory?" + new URLSearchParams({ + "chatID": localStorage.getItem("chatID") + }), + { + method: "GET", + credentials: "include", + } + ).then(async response => { + if (!response.ok) { + throw new Error("Non-200 backend API response"); + } else { + processChatHistory(await response.text()) + } + + }) + document.getElementById("chatInitialiser").style.display = "none" +} + +// Sends a message to existing chat +function sendMessage() { + addMessageToUI(document.getElementById("promptInput").value); + fetch("http://localhost:8080/sendMessage?" + new URLSearchParams({ + "chatID": localStorage.getItem("chatID"), + "newMessage": document.getElementById("promptInput").value + }), + { + method: "GET", + credentials: "include", + } + ).then(async response => { + if (!response.ok) { + throw new Error("Non-200 backend API response"); + } else { + addMessageToUI(await response.text()) + } + }) +} + +function revokeConsent() { + fetch("http://localhost:8080/revokeConsent", + { + method: "GET", + credentials: "include", + } + ).then(response => { + if (!response.ok) { + throw new Error("Non-200 backend API response"); + } + }) +} + diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 00000000..161fcc56 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,15 @@ +import { createRouter, createWebHistory } from 'vue-router'; +import MainView from '../components/MainView.vue'; +import Cookie from '../Display interface/Cookie.vue'; + +const routes = [ + { path: '/', component: Cookie }, + { path: '/main', component: MainView }, +]; + +const router = createRouter({ + history: createWebHistory(), + routes, +}); + +export default router; diff --git a/src/test/java/com/UoB/AILearningTool/AiLearningToolApplicationTests.java b/src/test/java/com/UoB/AILearningTool/AiLearningToolApplicationTests.java new file mode 100644 index 00000000..cb04fb58 --- /dev/null +++ b/src/test/java/com/UoB/AILearningTool/AiLearningToolApplicationTests.java @@ -0,0 +1,44 @@ +package com.UoB.AILearningTool; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.ResponseEntity; + + +@SpringBootTest +class AiLearningToolApplicationTests { + + @LocalServerPort + private int port; + + /*@Autowired + private AiLearningToolApplication controller; + + private final RestTemplate restTemplate = new RestTemplate(); + + @Test + @DisplayName("Verify that the application context loads successfully.") + void contextLoads() { + Assertions.assertNotNull(controller); + } + + @Test + @DisplayName("Verify that the '/signup' endpoint creates a user ID cookie.") + void signupEndpointTest() { + // Act + ResponseEntity response = restTemplate.getForEntity("http://localhost:" + port + "/signup", String.class); + + // Assert + Assertions.assertNotNull(response.getHeaders().get("Set-Cookie"), "Response should contain a 'Set-Cookie' header."); + Assertions.assertTrue(response.getHeaders().get("Set-Cookie").toString().contains("userID"), "Cookie should contain 'userID'."); + } + + */ +} + + diff --git a/src/test/java/com/UoB/AILearningTool/ChatTest.java b/src/test/java/com/UoB/AILearningTool/ChatTest.java new file mode 100644 index 00000000..a5baadd1 --- /dev/null +++ b/src/test/java/com/UoB/AILearningTool/ChatTest.java @@ -0,0 +1,98 @@ +package com.UoB.AILearningTool; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +public class ChatTest { + @Test + @DisplayName("Check if checkOwner method compares owners correctly.") + public void checkOwnerTest() { + User user = new User(true); + Chat chat = new Chat(user, "This is a first message."); + + Assertions.assertTrue(chat.checkOwner(user)); + } + + @Test + @DisplayName("Check if initial message history is saved correctly.") + public void initialMessageHistoryTest() { + String initialMessage = "This is a first message."; + String expectedMessageHistory = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\nThis is a first message."; + User user = new User(true); + Chat chat = new Chat(user, initialMessage); + + String actualMessageHistory = chat.getMessageHistory(user); + Assertions.assertEquals(expectedMessageHistory, actualMessageHistory); + } + + @Test + @DisplayName("Check if user messages are added to message history correctly.") + public void addUserMessageTest() { + String initialMessage = "This is a first message."; + String expectedMessageHistory = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\nThis is a first message.\n<|user|>\nTell me a joke."; + String extraUserMessage = "Tell me a joke."; + + // Create a chat + User user = new User(true); + Chat chat = new Chat(user, initialMessage); + + // Add a new user message to the chat + chat.addUserMessage(user.getID(), extraUserMessage); + + String actualMessageHistory = chat.getMessageHistory(user); + Assertions.assertEquals(expectedMessageHistory, actualMessageHistory); + } + + @Test + @DisplayName("Check if AI messages are added to message history correctly.") + public void addAIMessageTest() { + String initialMessage = "This is a first message."; + String expectedMessageHistory = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\nThis is a first message.\n<|assistant|>\nTell me a joke."; + String extraUserMessage = "Tell me a joke."; + + // Create a chat + User user = new User(true); + Chat chat = new Chat(user, initialMessage); + + // Add a new user message to the chat + chat.addAIMessage(user.getID(), extraUserMessage); + + String actualMessageHistory = chat.getMessageHistory(user); + Assertions.assertEquals(expectedMessageHistory, actualMessageHistory); + } + + @Test + @DisplayName("Check if message history in a chat can only be accessed by its owner.") + public void chatMessageHistoryPermissionTest() { + final String initialMessage = "This is a first message."; + String actualMessageHistory; + User currentUser; + Chat currentChat; + ArrayList users = new ArrayList<>(); + ArrayList chats = new ArrayList<>(); + + // Creating chats and checking whether their owners can access their message histories. + for (int i = 0; i < 20; i++) { + currentUser = new User(true); + users.add(currentUser); + currentChat = new Chat(currentUser, initialMessage); + chats.add(currentChat); + // Message history has to be returned to its owner + Assertions.assertNotNull(currentChat.getMessageHistory(currentUser)); + } + + for (int i = 0; i < 20; i++) { + currentChat = chats.get(i); + // Users that aren't owners of the chat will receive null instead of message history. + for (int j = 0; j < 20; j++) { + if (i == j) {continue;} + currentUser = users.get(j); + actualMessageHistory = currentChat.getMessageHistory(currentUser); + Assertions.assertNull(actualMessageHistory); + } + } + } +} diff --git a/src/test/java/com/UoB/AILearningTool/DatabaseControllerTest.java b/src/test/java/com/UoB/AILearningTool/DatabaseControllerTest.java new file mode 100644 index 00000000..6ee3db56 --- /dev/null +++ b/src/test/java/com/UoB/AILearningTool/DatabaseControllerTest.java @@ -0,0 +1,103 @@ +package com.UoB.AILearningTool; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +public class DatabaseControllerTest { + @Test + @DisplayName("Check whether users can be created.") + public void createUsers() { + DatabaseController DBC = new DatabaseController(); + ArrayList usernames = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + usernames.add(DBC.addUser(true)); + } + + for (String username : usernames) { + User user = DBC.getUser(username); + Assertions.assertEquals(username, user.getID()); + } + } + + @Test + @DisplayName("Check whether users can be deleted.") + public void deleteUsers() { + DatabaseController DBC = new DatabaseController(); + ArrayList usernames = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + usernames.add(DBC.addUser(true)); + } + + // Deleting all users + for (String username : usernames) { + DBC.removeUser(username); + } + + // Search for non-existing user must return null + for (String username : usernames) { + Assertions.assertNull(DBC.getUser(username)); + } + } + + @Test + @DisplayName("Check whether chats can be created and accessed.") + public void createChats() { + DatabaseController DBC = new DatabaseController(); + ArrayList users = new ArrayList<>(); + ArrayList chatIDs = new ArrayList<>(); + + // Create users. + for (int i = 0; i < 20; i++) { + users.add(DBC.getUser(DBC.addUser(true))); + } + + // Create chats. + for (User user : users) { + chatIDs.add(DBC.createChat(user, "This is a first message.")); + } + + // DBC.getChat() must return a non-null element. + for (int i = 0; i < 20; i++) { + Chat actualChat = DBC.getChat(users.get(i), chatIDs.get(i)); + Assertions.assertNotNull(actualChat); + } + } + + @Test + @DisplayName("Check whether chats can only be accessed by their owners.") + public void accessChatPermissionTest() { + Chat currentChat; + User currentUser; + DatabaseController DBC = new DatabaseController(); + ArrayList users = new ArrayList<>(); + ArrayList chatIDs = new ArrayList<>(); + + // Create users. + for (int i = 0; i < 20; i++) { + users.add(DBC.getUser(DBC.addUser(true))); + } + + // Create chats. + for (User user : users) { + chatIDs.add(DBC.createChat(user, "This is a first message.")); + } + + for (int i = 0; i < 20; i++) { + // If currentUser is the chat owner, Chat object is returned. + currentUser = users.get(i); + currentChat = DBC.getChat(currentUser, chatIDs.get(i)); + Assertions.assertNotNull(currentChat); + for (int j = 0; j < 20; j++) { + if (i == j) {continue;} + // If currentUser isn't the chat owner, null object is returned. + currentUser = users.get(j); + currentChat = DBC.getChat(currentUser, chatIDs.get(i)); + Assertions.assertNull(currentChat); + } + } + } + +} diff --git a/src/test/java/com/UoB/AILearningTool/IBMAuthenticatorTest.java b/src/test/java/com/UoB/AILearningTool/IBMAuthenticatorTest.java new file mode 100644 index 00000000..09417224 --- /dev/null +++ b/src/test/java/com/UoB/AILearningTool/IBMAuthenticatorTest.java @@ -0,0 +1,38 @@ +package com.UoB.AILearningTool; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("Receiving IBM Bearer tokens.") +public class IBMAuthenticatorTest { + @Test + @DisplayName("Checking whether a single request works") + public void singleRequestTest() { + IBMAuthenticator testAuthenticator = new IBMAuthenticator(); + testAuthenticator.start(); + + String token1 = testAuthenticator.getBearerToken(); + + Assertions.assertEquals(200, testAuthenticator.getStatusCode()); + Assertions.assertNotEquals(null, token1); + + testAuthenticator.stopTimer(); + } + + @Test + // Checking whether multiple requests return unique tokens. + public void multiRequestTest() { + IBMAuthenticator testAuthenticator = new IBMAuthenticator(); + testAuthenticator.start(); + + String token1 = testAuthenticator.getBearerToken(); + testAuthenticator.requestNewToken(); + String token2 = testAuthenticator.getBearerToken(); + + Assertions.assertEquals(200, testAuthenticator.getStatusCode()); + Assertions.assertNotEquals(token1, token2); + + testAuthenticator.stopTimer(); + } +} diff --git a/src/test/java/com/UoB/AILearningTool/OpenAIAPIControllerTest.java b/src/test/java/com/UoB/AILearningTool/OpenAIAPIControllerTest.java new file mode 100644 index 00000000..96ed92f3 --- /dev/null +++ b/src/test/java/com/UoB/AILearningTool/OpenAIAPIControllerTest.java @@ -0,0 +1,35 @@ +package com.UoB.AILearningTool; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +@DisplayName("Testing OpenAI API Controller for response handling") +public class OpenAIAPIControllerTest { + + @Test + @DisplayName("OpenAI API should be able to generate some response for initial chat.") + public void initialMessageHistoryRequestTest() { + OpenAIAPIController openAIAPIController = new OpenAIAPIController(); + + String messageHistory = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\nThis is a first message.\n<|assistant|>\nTell me a joke."; + WatsonxResponse response = openAIAPIController.sendUserMessage((messageHistory)); + + Assertions.assertNotNull(response.responseText, "The message content should not be null."); + Assertions.assertEquals(200, response.statusCode, "The status code should be 200 for a successful response."); + } + + @Test + @DisplayName("OpenAI API should be able to generate response for chat history with multiple messages.") + public void developedMessageHistoryRequestTest() { + OpenAIAPIController openAIAPIController = new OpenAIAPIController(); + + String messageHistory = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\nThis is a first message.\n<|assistant|>\nHow are you doing?\n<|user|>\nI'm fine, thanks. What about you?\n<|assistant|>\nI feel the same, human."; + WatsonxResponse response = openAIAPIController.sendUserMessage(messageHistory); + + Assertions.assertNotNull(response.responseText, "The message content should not be null."); + Assertions.assertEquals(200, response.statusCode, "The status code should be 200 for a successful response."); + } +} diff --git a/src/test/java/com/UoB/AILearningTool/StringToolsTest.java b/src/test/java/com/UoB/AILearningTool/StringToolsTest.java new file mode 100644 index 00000000..f4a6886a --- /dev/null +++ b/src/test/java/com/UoB/AILearningTool/StringToolsTest.java @@ -0,0 +1,60 @@ +package com.UoB.AILearningTool; + +import org.json.JSONObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +public class StringToolsTest { + @Test + @DisplayName("Check if watsonxToOpenAI method converts initial message history correctly.") + public void watsonxToOpenAIInitialHistoryTest() { + String watsonxFormat = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\nThis is a first message."; + ArrayList messages = new ArrayList<>(); + messages.add("\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease."); + messages.add("\nThis is a first message."); + + String openAIFormat = StringTools.watsonxToOpenAI(watsonxFormat); + org.json.JSONArray messageArray = new JSONObject(openAIFormat).getJSONArray("messages"); + + // Check message contents + for (int i = 0; i < messages.size(); i++) { + Assertions.assertEquals(messages.get(i), + messageArray.getJSONObject(i).getString("content")); + } + + // Check message roles + Assertions.assertEquals("system", messageArray.getJSONObject(0).getString("role")); + Assertions.assertEquals("user", messageArray.getJSONObject(1).getString("role")); + } + + @Test + @DisplayName("Check if watsonxToOpenAI method converts chat history with more than 1 message correctly.") + public void watsonxToOpenAIDevelopedHistoryTest() { + String watsonxFormat = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\nThis is a first message.\n<|assistant|>\nHow are you doing?\n<|user|>\nI'm fine, thanks. What about you?\n<|assistant|>\nI feel the same, human."; + ArrayList messages = new ArrayList<>(); + messages.add("\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease."); + messages.add("\nThis is a first message.\n"); + messages.add("\nHow are you doing?\n"); + messages.add("\nI'm fine, thanks. What about you?\n"); + messages.add("\nI feel the same, human."); + + String openAIFormat = StringTools.watsonxToOpenAI(watsonxFormat); + org.json.JSONArray messageArray = new JSONObject(openAIFormat).getJSONArray("messages"); + + // Check message contents + for (int i = 0; i < messages.size(); i++) { + Assertions.assertEquals(messages.get(i), + messageArray.getJSONObject(i).getString("content")); + } + + // Check message roles + Assertions.assertEquals("system", messageArray.getJSONObject(0).getString("role")); + Assertions.assertEquals("user", messageArray.getJSONObject(1).getString("role")); + Assertions.assertEquals("assistant", messageArray.getJSONObject(2).getString("role")); + Assertions.assertEquals("user", messageArray.getJSONObject(3).getString("role")); + Assertions.assertEquals("assistant", messageArray.getJSONObject(4).getString("role")); + } +} diff --git a/src/test/java/com/UoB/AILearningTool/UserTest.java b/src/test/java/com/UoB/AILearningTool/UserTest.java new file mode 100644 index 00000000..c3cff525 --- /dev/null +++ b/src/test/java/com/UoB/AILearningTool/UserTest.java @@ -0,0 +1,22 @@ +package com.UoB.AILearningTool; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class UserTest { + @Test + @DisplayName("Checking whether 'User' constructor creates an object with optional consent correctly") + public void correctFullConsentUserCreationTest() { + User user = new User(true); + Assertions.assertEquals(25, user.getID().length()); + Assertions.assertTrue(user.getOptionalConsent()); + } + @Test + @DisplayName("Checking whether 'User' constructor creates an object without optional consent correctly") + public void correctRequiredConsentUserCreationTest() { + User user = new User(false); + Assertions.assertEquals(25, user.getID().length()); + Assertions.assertFalse(user.getOptionalConsent()); + } +} diff --git a/src/test/java/com/UoB/AILearningTool/WatsonxAPIControllerTest.java b/src/test/java/com/UoB/AILearningTool/WatsonxAPIControllerTest.java new file mode 100644 index 00000000..8cc8a12c --- /dev/null +++ b/src/test/java/com/UoB/AILearningTool/WatsonxAPIControllerTest.java @@ -0,0 +1,58 @@ +package com.UoB.AILearningTool; + +import org.json.JSONObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +@DisplayName("Checking the functionality of WatsonxAPIController.") +public class WatsonxAPIControllerTest { + +// @Test +// @DisplayName("Response from Watsonx API with status code 200 and size > 0 can be received") +// public void watsonxSingleValidResponseTest() { +// final Logger log = LoggerFactory.getLogger(SpringController.class); +// +// final String unformattedTestInput = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\nThis is a first message."; +// final String formattedTestInput = StringTools.messageHistoryPrepare(unformattedTestInput); +// +// WatsonxAPIController testWXController = new WatsonxAPIController(); +// +// WatsonxResponse testMessage = testWXController.sendUserMessage(formattedTestInput); +// +// testWXController.stopAuthenticator(); +// log.info(testMessage.responseText); +// +// Assertions.assertEquals(200, testMessage.statusCode); +// Assertions.assertTrue(testMessage.responseText.length() > 1); +// } +// +// @Test +// @DisplayName("Multiple valid responses from Watsonx API with status code 200 and size > 0 can be received") +// public void watsonxMultiValidResponsesTest() { +// final Logger log = LoggerFactory.getLogger(SpringController.class); +// +// final String unformattedTestInput = "<|system|>\nYour name is AI Learning Tool, and you are an assistant for IBM SkillsBuild, a platform dedicated to providing skills and training in technology and professional development. Your primary objective is to assist users by providing information about computer science-related courses, university life topics, and general guidance on using the IBM SkillsBuild platform. You help users find computer science courses that suit their knowledge level and time availability by tailoring recommendations based on their input, such as current experience level (beginner, intermediate, or advanced) and preferred course duration (short, medium, or long). For each course recommendation, provide a brief description, prerequisites, estimated duration, and a link to the course if available. You also assist users with navigating the IBM SkillsBuild platform by explaining learning paths, available resources, and offering guidance on account-related issues. If users need help with platform navigation or account matters, direct them to appropriate resources or help articles. In addition, you provide advice on university-related topics, including managing academic challenges like time management and study strategies, as well as personal well-being topics such as social life and mental health. Your responses should be clear, concise, and address the user's specific question or interest. Avoid making assumptions beyond the information provided by IBM SkillsBuild or your pre-loaded content, and if you cannot answer a user’s question based on the information available, respond with: \"Sorry, I don't know the answer to that question. Can you provide more information to help me understand it better?\" Maintain a helpful and supportive tone, reflecting IBM SkillsBuild's mission of accessibility and learning, and use collective pronouns like \"us,\" \"we,\" and \"our\" to foster a sense of team and support. Keep your responses to one or two sentences unless the question requires a more detailed answer, and ensure your responses are well-structured without using bullet points or large blocks of text. Do not provide any links, contact information, or resources that have not been explicitly included in your setup. Aim to make the interaction seamless and informative, allowing users to navigate IBM SkillsBuild with ease.<|user|>\nThis is a first message."; +// final String formattedTestInput = StringTools.messageHistoryPrepare(unformattedTestInput); +// +// WatsonxAPIController testWXController = new WatsonxAPIController(); +// +// for (int i = 1; i < 4; i++) { +// WatsonxResponse testMessage = testWXController.sendUserMessage(formattedTestInput); +// Assertions.assertEquals(200, testMessage.statusCode); +// Assertions.assertTrue(testMessage.responseText.length() > 1); +// log.info("Valid response N{}/10 received", i); +// log.info("response: {}", testMessage.responseText); +// } +// +// testWXController.stopAuthenticator(); +// } + +} diff --git a/startServer.sh b/startServer.sh new file mode 100644 index 00000000..8e04a09a --- /dev/null +++ b/startServer.sh @@ -0,0 +1,4 @@ +# Recompiles target classes and starts the server. + +mvn clean +mvn spring-boot:run \ No newline at end of file