Skip to content

Commit

Permalink
Merge pull request #51 from nemozak1/develop
Browse files Browse the repository at this point in the history
Add Opey Chatbot
  • Loading branch information
simonredfern authored Jun 17, 2024
2 parents 4214d2a + 4811611 commit 61e7112
Show file tree
Hide file tree
Showing 14 changed files with 1,704 additions and 959 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ VITE_OPB_SERVER_SESSION_PASSWORD=very secret
# Be sure to secure your Redis instance
VITE_OBP_REDIS_URL = redis://127.0.0.1:6379

# Enable the chatbot interface "Opey"
VITE_CHATBOT_ENABLED=false

# Product styling setting
#VITE_OBP_LINKS_COLOR="#52b165"
#VITE_OBP_HEADER_LINKS_COLOR="#39455f"
Expand Down
2 changes: 2 additions & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {}

declare module '@vue/runtime-core' {
export interface GlobalComponents {
ChatWidget: typeof import('./src/components/ChatWidget.vue')['default']
Collections: typeof import('./src/components/Collections.vue')['default']
Content: typeof import('./src/components/Content.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
Expand All @@ -35,6 +36,7 @@ declare module '@vue/runtime-core' {
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElRow: typeof import('element-plus/es')['ElRow']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
GlossarySearchNav: typeof import('./src/components/GlossarySearchNav.vue')['default']
HeaderNav: typeof import('./src/components/HeaderNav.vue')['default']
Menu: typeof import('./src/components/Menu.vue')['default']
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@element-plus/icons-vue": "^2.1.0",
"@fontsource/roboto": "^4.5.8",
"@highlightjs/vue-plugin": "^2.1.0",
"axios": "^1.7.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"connect-redis": "^7.1.1",
Expand All @@ -25,20 +26,25 @@
"express": "^4.18.2",
"express-session": "^1.17.3",
"highlight.js": "^11.7.0",
"markdown-it": "^14.1.0",
"oauth": "^0.10.0",
"obp-typescript": "^1.0.36",
"pinia": "^2.0.32",
"prismjs": "^1.29.0",
"redis": "^4.6.13",
"reflect-metadata": "^0.1.13",
"routing-controllers": "^0.10.3",
"typedi": "^0.10.0",
"uuid": "^9.0.1",
"vue": "^3.2.47",
"vue-i18n": "^9.2.2",
"vue-markdown-renderer": "^0.2.7",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@types/jsdom": "^21.1.0",
"@types/markdown-it": "^14.1.1",
"@types/node": "^18.14.2",
"@vitejs/plugin-vue": "^4.0.0",
"@vitejs/plugin-vue-jsx": "^3.0.0",
Expand Down
110 changes: 110 additions & 0 deletions public/js/inactivity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import * as countdownTimer from '../../src/assets/inactivity-timer.js'

// holds the idle duration in ms (current value = 301 seconds)
var timeoutIntervalInMillis = 5 * 60 * 1000 + 1000;
// holds the timeout variables for easy destruction and reconstruction of the setTimeout hooks
var timeHook = null;

function initializeTimeHook() {
// this method has the purpose of creating our timehooks and scheduling the call to our logout function when the idle time has been reached
if (timeHook == null) {
timeHook = setTimeout( function () { destroyTimeHook(); logout(); }.bind(this), timeoutIntervalInMillis);
}
}

function destroyTimeHook() {
// this method has the sole purpose of destroying any time hooks we might have created
clearTimeout(timeHook);
timeHook = null;
}

function resetTimeHook(event) {
// this method replaces the current time hook with a new time hook
destroyTimeHook();
initializeTimeHook();
countdownTimer.resetCountdownTimer(timeoutIntervalInMillis / 1000);
// show event type, element and coordinates of the click
// console.log(event.type + " at " + event.currentTarget);
// console.log("Coordinates: " + event.clientX + ":" + event.clientY);
console.log("Reset inactivity of a user");
}

function setupListeners() {
// here we setup the event listener for the mouse click operation
document.addEventListener("click", resetTimeHook);
document.addEventListener("mousemove", resetTimeHook);
document.addEventListener("mousedown", resetTimeHook);
document.addEventListener("keypress", resetTimeHook);
document.addEventListener("touchmove", resetTimeHook);
console.log("Listeners for user inactivity activated");
}

function destroyListeners() {
// here we destroy event listeners for the mouse click operation
document.removeEventListener("click", resetTimeHook);
document.removeEventListener("mousemove", resetTimeHook);
document.removeEventListener("mousedown", resetTimeHook);
document.removeEventListener("keypress", resetTimeHook);
document.removeEventListener("touchmove", resetTimeHook);
console.log("Listeners for user inactivity deactivated");
}

function logout() {
destroyListeners();
countdownTimer.destroyCountdownTimer();
console.log("Logging you out due to inactivity..");
const logoffButton = document.getElementById("logout");
logoffButton.click();
}

async function makeObpApiCall() {
//debug
console.log("calling API");
let timeoutInSeconds;
try {
let obpApiHost = document.getElementById("nav");
console.log(obpApiHost);
if(obpApiHost) {
obpApiHost = obpApiHost.href.split("?")[0];
}

const response = await fetch(`${obpApiHost}/obp/v5.1.0/ui/suggested-session-timeout`);
const json = await response.json();
if(json.timeout_in_seconds) {
timeoutInSeconds = json.timeout_in_seconds;
console.log(`Suggested value ${timeoutInSeconds} is used`);
} else {
timeoutInSeconds = 5 * 60 + 1; // Set default value to 301 seconds
console.log(`Default value ${timeoutInSeconds} is used`);
}
} catch (e) {
console.error(e);
timeoutInSeconds = 5 * 60 + 1; // Set default value to 301 seconds, even if the session timeout endpoint is not reachable for whatever reason
console.log(`Default value ${timeoutInSeconds} is used`);
}
return timeoutInSeconds;
}

async function getSuggestedSessionTimeout() {
if(!sessionStorage.getItem("suggested-session-timeout-in-seconds")) {
let timeoutInSeconds = await makeObpApiCall();
sessionStorage.setItem("suggested-session-timeout-in-seconds", timeoutInSeconds);
}
return sessionStorage.getItem("suggested-session-timeout-in-seconds") * 1000 + 1000; // We need timeout in millis
}

// self executing function to trigger the operation on page load
(async function () {
timeoutIntervalInMillis = await getSuggestedSessionTimeout(); // Try to get suggested value
const logoffButton = document.getElementById("countdown-timer-span");
if(logoffButton) {
// to prevent any lingering timeout handlers preventing memory leaks
destroyTimeHook();
// setup a fresh time hook
initializeTimeHook();
// setup initial event listeners
setupListeners();
// Reset countdown timer
countdownTimer.resetCountdownTimer(timeoutIntervalInMillis / 1000);
}
})();
4 changes: 4 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

<script setup lang="ts">
import HeaderNav from './components/HeaderNav.vue'
import ChatWidget from './components/ChatWidget.vue'
const isChatbotEnabled = import.meta.env.VITE_CHATBOT_ENABLED === 'true'
</script>

<template>
Expand All @@ -37,6 +40,7 @@ import HeaderNav from './components/HeaderNav.vue'
<HeaderNav />
</el-header>
<RouterView />
<ChatWidget v-if="isChatbotEnabled"/>
</el-container>
</div>
</template>
Expand Down
Binary file added src/assets/chatbot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions src/assets/inactivity-timer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
function addSeconds(date, seconds) {
date.setSeconds(date.getSeconds() + seconds);
return date;
}

export function showCountdownTimer() {

// Get current date and time
var now = new Date().getTime();
let distance = countDownDate - now;

// Output the result in an element with id="countdown-timer-span"
let elementId = ("countdown-timer-span");
document.getElementById(elementId).innerHTML = "in " + Math.floor(distance / 1000) + "s";

// If the count down is over release resources
if (distance < 0) {
destroyCountdownTimer();
}
}


// Set the date we're counting down to
let countDownDate = addSeconds(new Date(), 5);

let showTimerInterval = null;

export function destroyCountdownTimer() {
clearInterval(showTimerInterval);
}

export function resetCountdownTimer(seconds) {
destroyCountdownTimer(); // Destroy previous timer if any
countDownDate = addSeconds(new Date(), seconds); // Set the date we're counting down to
showTimerInterval = setInterval(showCountdownTimer, 1000); // Update the count down every 1 second
}
1 change: 1 addition & 0 deletions src/assets/powered-by-openai-badge-filled-on-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 61e7112

Please sign in to comment.