fix(chatbot): continue integration and trim of chatbot-related code.
This commit is contained in:
parent
ea5e2ef36e
commit
5ff20452a7
|
|
@ -4,6 +4,10 @@ export default {
|
||||||
chat_initial_message: "Welcome to your technical assistant.\nPlease provide the Customer ID to get a diagnostic report",
|
chat_initial_message: "Welcome to your technical assistant.\nPlease provide the Customer ID to get a diagnostic report",
|
||||||
chat_placeholder: "Enter a Message",
|
chat_placeholder: "Enter a Message",
|
||||||
chat_thinking: "Thinking...",
|
chat_thinking: "Thinking...",
|
||||||
|
error: {
|
||||||
|
NO_REPLY_RECEIVED: "encountered an error while waiting for chatbot to reply",
|
||||||
|
SEND_MESSAGE_FAILED: "unable to send message to chatbot",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
dashboard: {
|
dashboard: {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@ export default {
|
||||||
chat_initial_message: "Bienvenue à votre assistant technique.\nVeuillez fournir le ID du Client pour avoir un diagnostique",
|
chat_initial_message: "Bienvenue à votre assistant technique.\nVeuillez fournir le ID du Client pour avoir un diagnostique",
|
||||||
chat_placeholder: "Entré un Message",
|
chat_placeholder: "Entré un Message",
|
||||||
chat_thinking: "Réflexion en cours...",
|
chat_thinking: "Réflexion en cours...",
|
||||||
|
error: {
|
||||||
|
NO_REPLY_RECEIVED: "Une erreur est survenu lors de la réception de la réponse du chatbot",
|
||||||
|
SEND_MESSAGE_FAILED: "Une erreur est survenu lors de l'envoi de votre message",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
dashboard: {
|
dashboard: {
|
||||||
|
|
|
||||||
|
|
@ -4,131 +4,71 @@
|
||||||
>
|
>
|
||||||
import DialogueContent from "./dialogue-content.vue";
|
import DialogueContent from "./dialogue-content.vue";
|
||||||
|
|
||||||
import { computed, onMounted, ref, watch } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
|
import { useChatbotStore } from "src/stores/chatbot-store";
|
||||||
import { useChatbotApi } from "src/modules/chatbot/composables/chatbot-api";
|
import { useChatbotApi } from "src/modules/chatbot/composables/chatbot-api";
|
||||||
import { chatbotService } from "src/modules/chatbot/services/messages.service";
|
|
||||||
import { pageContexts } from "src/page-contexts";
|
|
||||||
import type { contextObject } from "src/page-contexts/pages/types/context-object";
|
|
||||||
import { useChatbotStore } from "src/stores/chatbot-store";
|
|
||||||
|
|
||||||
// Block to enable editing the width of the drawer
|
// Block to enable editing the width of the drawer
|
||||||
const drawerWidth = ref(370);
|
// const drawerWidth = ref(370);
|
||||||
let dragging = false
|
// let dragging = false;
|
||||||
const startDrag = (e: MouseEvent) => {
|
// const startDrag = (e: MouseEvent) => {
|
||||||
dragging = true
|
// dragging = true
|
||||||
e.preventDefault()
|
// e.preventDefault()
|
||||||
}
|
// }
|
||||||
const onDrag = (e: MouseEvent) => {
|
|
||||||
if (!dragging) return
|
|
||||||
// calculate new width
|
|
||||||
const newWidth = window.innerWidth - e.clientX
|
|
||||||
drawerWidth.value = Math.max(350, Math.min(1000, newWidth)) // min/max width
|
|
||||||
}
|
|
||||||
const stopDrag = () => {
|
|
||||||
dragging = false
|
|
||||||
}
|
|
||||||
window.addEventListener('mousemove', onDrag)
|
|
||||||
window.addEventListener('mouseup', stopDrag)
|
|
||||||
|
|
||||||
|
// const onDrag = (e: MouseEvent) => {
|
||||||
|
// if (!dragging) return
|
||||||
|
// // calculate new width
|
||||||
|
// const newWidth = window.innerWidth - e.clientX
|
||||||
|
// drawerWidth.value = Math.max(350, Math.min(1000, newWidth)) // min/max width
|
||||||
|
// }
|
||||||
|
// const stopDrag = () => {
|
||||||
|
// dragging = false
|
||||||
|
// }
|
||||||
|
// window.addEventListener('mousemove', onDrag)
|
||||||
|
// window.addEventListener('mouseup', stopDrag)
|
||||||
|
|
||||||
// Block to handle the incomming and sending of the messages from the user and the ai
|
const chatbot_api = useChatbotApi();
|
||||||
const text = ref('');
|
|
||||||
const chatbot_store = useChatbotStore();
|
const chatbot_store = useChatbotStore();
|
||||||
const chatbot_api = useChatApi();
|
|
||||||
const chatStore = useChatStore();
|
|
||||||
onMounted(() => {
|
|
||||||
chatStore.showInstructionsOnce();
|
|
||||||
})
|
|
||||||
|
|
||||||
const isSessionReady = ref(false);
|
const text = ref('');
|
||||||
|
|
||||||
const handleSend = async () => {
|
const handleSend = async () => {
|
||||||
const userMessage = text.value.trim();
|
await chatbot_api.sendMessage(text.value.trim());
|
||||||
|
|
||||||
|
|
||||||
text.value = '';
|
text.value = '';
|
||||||
messages.push({
|
|
||||||
text: userMessage,
|
|
||||||
sent: true,
|
|
||||||
isThinking: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const thinkingMessage = {
|
|
||||||
text: "Thinking...",
|
|
||||||
sent: false,
|
|
||||||
isThinking: true
|
|
||||||
}
|
|
||||||
messages.push(thinkingMessage)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const aiResponse = await sendMessage(userMessage);
|
|
||||||
|
|
||||||
const index = messages.indexOf(thinkingMessage);
|
|
||||||
if (index !== -1) {
|
|
||||||
messages.splice(index, 1, {
|
|
||||||
text: aiResponse.text,
|
|
||||||
sent: false,
|
|
||||||
isThinking: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
const index = messages.indexOf(thinkingMessage);
|
|
||||||
if (index !== -1) {
|
|
||||||
messages.splice(index, 1, {
|
|
||||||
text: "Sorry, Message wasn't able to sent.",
|
|
||||||
sent: false,
|
|
||||||
isThinking: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Block to receive user from child
|
|
||||||
const userEmail = ref('')
|
|
||||||
const userRole = ref('')
|
|
||||||
|
|
||||||
const HandleEmail = (email: string) => {
|
|
||||||
userEmail.value = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
const HandleRole = (role: string) => {
|
|
||||||
userRole.value = role;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block to handle sending the url to n8n
|
|
||||||
const route = useRoute()
|
|
||||||
const sendPageContext = chatbotService.sendPageContext;
|
|
||||||
// Capture and send page context to n8n
|
// Capture and send page context to n8n
|
||||||
const currentContext = computed(() =>
|
// const currentContext = computed(() =>
|
||||||
pageContexts.find(ctx => ctx.path === route.fullPath.replace('/', ''))
|
// pageContexts.find(ctx => ctx.path === route.fullPath.replace('/', ''))
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Function that sends the page context to n8n
|
// // Function that sends the page context to n8n
|
||||||
watch([currentContext, userEmail, userRole], async ([ctx, email, role]) => {
|
// watch([currentContext, userEmail, userRole], async ([ctx, email, role]) => {
|
||||||
if (!ctx || !email || !role) return;
|
// if (!ctx || !email || !role) return;
|
||||||
|
|
||||||
const contextPayload: contextObject = {
|
// const contextPayload: contextObject = {
|
||||||
name: ctx.name,
|
// name: ctx.name,
|
||||||
description: ctx.description,
|
// description: ctx.description,
|
||||||
features: ctx.features,
|
// features: ctx.features,
|
||||||
path: ctx.path
|
// path: ctx.path
|
||||||
}
|
// }
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
await Promise.all([chatbotService.sendUserCredentials(email, role),
|
// await Promise.all([chatbotService.sendUserCredentials(email, role),
|
||||||
sendPageContext(contextPayload),
|
// sendPageContext(contextPayload),
|
||||||
]);
|
// ]);
|
||||||
isSessionReady.value = true;
|
// is_session_ready.value = true;
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
console.error("Error", err)
|
// console.error("Error", err)
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
{ immediate: true }
|
// { immediate: true }
|
||||||
);
|
// );
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
chatbot_store.showInstructionsOnce();
|
||||||
|
})
|
||||||
|
|
||||||
// const custId = ref('')
|
// const custId = ref('')
|
||||||
// const handleCustomerId = async () => {
|
// const handleCustomerId = async () => {
|
||||||
|
|
@ -136,31 +76,19 @@ import { useChatbotStore } from "src/stores/chatbot-store";
|
||||||
// custId.value = '';
|
// custId.value = '';
|
||||||
// await chatbotService.retrieveCustomerDiagnostics(cId);
|
// await chatbotService.retrieveCustomerDiagnostics(cId);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-drawer
|
<q-drawer
|
||||||
side="right"
|
side="right"
|
||||||
v-model="isChatVisible"
|
v-model="chatbot_store.is_showing_chatbot"
|
||||||
class="chat-drawer"
|
class="column justify-end"
|
||||||
style="box-shadow: -4px 0 12px rgba(0,0,0,0.15);"
|
|
||||||
:width="drawerWidth"
|
|
||||||
>
|
>
|
||||||
<div
|
<div class="bg-primary text-uppercase">{{ $t('chatbot.chat_header') }}</div>
|
||||||
class="chat-header"
|
|
||||||
style="text-align:start; padding: 10px 10px 0px;"
|
|
||||||
>{{ $t('chatbot.chat_header') }}</div>
|
|
||||||
<div class="line-separator"></div>
|
<div class="line-separator"></div>
|
||||||
|
|
||||||
<div class="chat-body">
|
<div class="chat-body">
|
||||||
<DialogueContent
|
<DialogueContent />
|
||||||
@sendRole="HandleRole"
|
|
||||||
@sendEmail="HandleEmail"
|
|
||||||
:messages="messages"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="line-separator"></div>
|
<div class="line-separator"></div>
|
||||||
|
|
@ -176,23 +104,25 @@ import { useChatbotStore } from "src/stores/chatbot-store";
|
||||||
style="margin-left: 8px;"
|
style="margin-left: 8px;"
|
||||||
@keyup.enter="handleSend"
|
@keyup.enter="handleSend"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- <q-input
|
<!-- <q-input
|
||||||
v-model="custId"
|
v-model="custId"
|
||||||
:label="'Customer Id'"
|
:label="'Customer Id'"
|
||||||
:autogrow="false"
|
:autogrow="false"
|
||||||
class="col"
|
class="col"
|
||||||
style="margin-left: 8px;"
|
style="margin-left: 8px;"
|
||||||
@keyup.enter="handleCustomerId"
|
@keyup.enter="handleCustomerId"
|
||||||
/> -->
|
/> -->
|
||||||
</q-form>
|
</q-form>
|
||||||
<div
|
|
||||||
|
<!-- <div
|
||||||
class="drag-handle"
|
class="drag-handle"
|
||||||
@mousedown.prevent="startDrag"
|
@mousedown.prevent="startDrag"
|
||||||
></div>
|
></div> -->
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<!-- <style scoped>
|
||||||
.drag-handle {
|
.drag-handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -203,32 +133,4 @@ import { useChatbotStore } from "src/stores/chatbot-store";
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
background-color: rgba(0, 0, 0, 0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
</style> -->
|
||||||
.line-separator {
|
|
||||||
height: 1px;
|
|
||||||
background-color: #ccc;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-drawer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header at the top */
|
|
||||||
.chat-header {
|
|
||||||
color: #019547;
|
|
||||||
padding: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollable middle */
|
|
||||||
.chat-body {
|
|
||||||
flex: 1;
|
|
||||||
padding: 10px;
|
|
||||||
overflow-y: auto;
|
|
||||||
height: 81%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,30 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
import DialoguePhrase from './dialogue-phrase.vue';
|
setup
|
||||||
import type { Message } from '../types/dialogue-message';
|
lang="ts"
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
>
|
||||||
import { watch, nextTick, onMounted } from 'vue';
|
import DialoguePhrase from './dialogue-phrase.vue';
|
||||||
|
import { useChatbotStore } from 'src/stores/chatbot-store';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const currentUser = authStore.user;
|
|
||||||
const emitUser = defineEmits(['sendRole', 'sendEmail']);
|
|
||||||
|
|
||||||
const sendUser = () => {
|
const chatbot_store = useChatbotStore();
|
||||||
emitUser('sendEmail', currentUser.email)
|
|
||||||
emitUser('sendRole', currentUser.role)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
sendUser();
|
|
||||||
})
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
messages: Message[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
watch(props.messages, async () => {
|
|
||||||
await nextTick(() => {
|
|
||||||
const chatBody = document.querySelector('.chat-body');
|
|
||||||
if (chatBody) chatBody.scrollTop = chatBody.scrollHeight;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-infinite-scroll>
|
<q-infinite-scroll>
|
||||||
<template
|
<template
|
||||||
v-for="(msg, index) in messages"
|
v-for="(msg, index) in chatbot_store.messages"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<DialoguePhrase
|
<DialoguePhrase
|
||||||
v-if="!msg.isThinking"
|
v-if="!msg.isThinking"
|
||||||
:text="msg.text"
|
:message="msg"
|
||||||
:sent="msg.sent"
|
|
||||||
:name="msg.sent ? currentUser.firstName :
|
|
||||||
$t('chatbot.chat_header')"
|
|
||||||
:class="['chat-message', msg.sent ? 'user' : 'ai']"
|
|
||||||
/>
|
/>
|
||||||
<!-- 💭 Thinking bubble -->
|
|
||||||
|
<!-- thinking bubble while awaiting chatbot reply -->
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="chat-message ai flex items-center q-px-md q-py-sm"
|
class="row flex-center q-px-md q-py-sm"
|
||||||
>
|
>
|
||||||
<q-spinner-dots
|
<q-spinner-dots
|
||||||
size="20px"
|
size="20px"
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,47 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
import { computed } from 'vue';
|
setup
|
||||||
import MarkdownIt from 'markdown-it'
|
lang="ts"
|
||||||
|
>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
|
import type { Message } from 'src/modules/chatbot/models/dialogue-message.model';
|
||||||
|
|
||||||
const props = defineProps<{
|
const auth_store = useAuthStore();
|
||||||
text: string;
|
|
||||||
sent: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
// Initialize Markdown parser
|
const { message } = defineProps<{
|
||||||
const md = new MarkdownIt({
|
message: Message;
|
||||||
breaks: false, // Support line breaks
|
}>();
|
||||||
linkify: true, // Make URLs clickable
|
|
||||||
html: false // Prevent raw HTML injection
|
|
||||||
})
|
|
||||||
|
|
||||||
// Removes all the h1,h2,h3 to make the Md more user friendly
|
// Initialize Markdown parser
|
||||||
const cleanMarkdown = (markdown: string): string => {
|
const md = new MarkdownIt({
|
||||||
if (!markdown) return ''
|
breaks: false, // Support line breaks
|
||||||
return markdown
|
linkify: true, // Make URLs clickable
|
||||||
.replace(/\r\n/g, '\n') // normalize Windows line endings
|
html: false // Prevent raw HTML injection
|
||||||
.replace(/([.!?])(\s+)(?=[A-Z])/g, '$1\n') // insert line break after sentence-ending punctuation
|
})
|
||||||
.replace(/\n{3,}/g, '\n\n') // squash triple+ line breaks
|
|
||||||
.replace(/^#{1,3}\s(.*)$/gm, '#### $1') // downgrade headings
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute parsed content
|
// Removes all the h1,h2,h3 to make the Md more user friendly
|
||||||
const parsedText = computed((): string => {
|
const cleanMarkdown = (markdown: string): string => {
|
||||||
const cleaned = cleanMarkdown(props.text || '')
|
if (!markdown) return ''
|
||||||
return md.render(cleaned)
|
return markdown
|
||||||
})
|
.replace(/\r\n/g, '\n') // normalize Windows line endings
|
||||||
|
.replace(/([.!?])(\s+)(?=[A-Z])/g, '$1\n') // insert line break after sentence-ending punctuation
|
||||||
|
.replace(/\n{3,}/g, '\n\n') // squash triple+ line breaks
|
||||||
|
.replace(/^#{1,3}\s(.*)$/gm, '#### $1') // downgrade headings
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute parsed content
|
||||||
|
const parsedText = computed((): string => {
|
||||||
|
const cleaned = cleanMarkdown(message.text || '')
|
||||||
|
return md.render(cleaned)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-chat-message
|
<q-chat-message
|
||||||
:sent="props.sent"
|
:sent="message.sent"
|
||||||
:text="[text]"
|
:text="[parsedText]"
|
||||||
:bg-color="!sent ? 'secondary' : 'accent'"
|
:name="message.sent ? auth_store.user?.first_name : 'TargoBot'"
|
||||||
class="bubble"
|
:bg-color="message.sent ? 'accent' : 'secondary'"
|
||||||
:class="sent ? 'user' : 'ai'"
|
/>
|
||||||
>
|
|
||||||
<div v-html="parsedText"></div>
|
|
||||||
</q-chat-message>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.bubble {
|
|
||||||
margin: 8px;
|
|
||||||
border-radius: 12px;
|
|
||||||
line-height: 1.5;
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user {
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ai {
|
|
||||||
align-self: flex-start;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,32 @@
|
||||||
// composables/chat-api.ts
|
// composables/chat-api.ts
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { useChatbotStore } from "src/stores/chatbot-store";
|
import { useChatbotStore } from "src/stores/chatbot-store";
|
||||||
import type { Message } from "src/modules/chatbot/models/dialogue-message.model";
|
import { RouteNames } from "src/router/router-constants";
|
||||||
|
import { PageContexts } from "src/modules/chatbot/models/page-context.model";
|
||||||
|
|
||||||
export const useChatbotApi = () => {
|
export const useChatbotApi = () => {
|
||||||
const chatStore = useChatbotStore();
|
const chatbot_store = useChatbotStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const sendMessage = async (text: string): Promise<Message> => {
|
const sendMessage = async (user_message: string) => {
|
||||||
return await chatStore.sendMessage(text);
|
// push user input
|
||||||
};
|
chatbot_store.messages.push({
|
||||||
|
text: user_message,
|
||||||
|
sent: true,
|
||||||
|
isThinking: false,
|
||||||
|
})
|
||||||
|
|
||||||
return { sendMessage };
|
// automatically push chatbot pending reply
|
||||||
|
chatbot_store.messages.push({
|
||||||
|
text: t('chatbot.chat_thinking'),
|
||||||
|
sent: false,
|
||||||
|
isThinking: true
|
||||||
|
})
|
||||||
|
|
||||||
|
await chatbot_store.sendChatMessage(user_message);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendMessage,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
111
src/modules/chatbot/models/page-context.model.ts
Normal file
111
src/modules/chatbot/models/page-context.model.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { RouteNames } from "src/router/router-constants";
|
||||||
|
|
||||||
|
export interface chatbotPageContext {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
features: string[];
|
||||||
|
path: RouteNames | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dashboardContext: chatbotPageContext = {
|
||||||
|
name: "Dashboard",
|
||||||
|
description: "Landing page containing useful links and a carousel showcasing recent news, as well as a local weather widget in the top right corner",
|
||||||
|
features: [
|
||||||
|
"Used as a landing platform and navigate the left drawer",
|
||||||
|
"Access the AI chatbot from the header",
|
||||||
|
"Access common external tools using the links below the news Carousel",
|
||||||
|
],
|
||||||
|
path: RouteNames.DASHBOARD,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const leftDrawerContext: chatbotPageContext = {
|
||||||
|
name: "Left Drawer",
|
||||||
|
description: "A drawer that acts as a navigation bar, routes to different parts of the website. Some icons will be hidden according to user access",
|
||||||
|
features: [
|
||||||
|
"The user can navigate to the home page.",
|
||||||
|
"Can review and approve timesheets. requires access to timesheet_approval module.",
|
||||||
|
"By default, the user can see the full list of employees. Requires user_management module access to edit, add, or view detailed profiles.",
|
||||||
|
"Can access the timesheet interface to input employee hours.",
|
||||||
|
"Can access your user profile to view information or change your UI preferences (light/dark mode and display language)",
|
||||||
|
"Can access the Help page to view common Q&A regarding different modules",
|
||||||
|
"Can logout",
|
||||||
|
],
|
||||||
|
path: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const profileContext: chatbotPageContext = {
|
||||||
|
name: "Profile",
|
||||||
|
description: "Display and edit user information",
|
||||||
|
features: [
|
||||||
|
"View personal information such as first and last name, phone, birthdate and address.",
|
||||||
|
"View Career information such as job title, company, supervisor, email and hiring date.",
|
||||||
|
"Edit available preferences such as Display options of light and dark mode, as well as display language",
|
||||||
|
],
|
||||||
|
path: RouteNames.PROFILE,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const employeeListContext: chatbotPageContext = {
|
||||||
|
name: "Employee List",
|
||||||
|
description: "View all the hired and currently active Staff",
|
||||||
|
features: [
|
||||||
|
"View the list of hired and active employees",
|
||||||
|
"Access an individual employee's detailed profile if user has user_management module access",
|
||||||
|
"Add, edit, or remove an employee if user has user_management module access",
|
||||||
|
"Add, edit, or remove module access for any given employee if user has user_managmenet module access",
|
||||||
|
"Add, edit, or remove schedule presets and assign those presets to employees. These presets can then be applied to the employee's timesheet with a single click. Requires user_management module access."
|
||||||
|
],
|
||||||
|
path: RouteNames.EMPLOYEE_LIST,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const timesheetApprovalContext: chatbotPageContext = {
|
||||||
|
name: "Timesheet Approval",
|
||||||
|
description: "Page where employee hours and shifts are approved for accounting purposes. Requires timesheet_approval module access.",
|
||||||
|
features: [
|
||||||
|
"See a grid or list view of total hours for all employees in the given 2-week pay period.",
|
||||||
|
"Access different periods thanks to the pay period navigator buttons (previous, next, date picker)",
|
||||||
|
"Approve the hours for the cards displayed.",
|
||||||
|
"Open a detailed dialog window for any employee",
|
||||||
|
"The detailed dialog window includes bar and circle charts to visually see employee hours worked daily, the type of shifts worked, and expenses accrued",
|
||||||
|
"The detailed dialog window allows the user to edit the any employee's timesheets or expenses",
|
||||||
|
],
|
||||||
|
path: RouteNames.TIMESHEET_APPROVALS,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const timesheetContext: chatbotPageContext = {
|
||||||
|
name: "Timesheet",
|
||||||
|
description:
|
||||||
|
"Page where an employee can enter their hours for their day and week. Display in 2-week pay periods.",
|
||||||
|
features: [
|
||||||
|
"Enter your in and out times per day",
|
||||||
|
"Add and edit what kind of hours your shift was example regular, overtime, vacation etc.",
|
||||||
|
"Edit your own shift hours",
|
||||||
|
"Delete your shift hours",
|
||||||
|
"List your expenses for the week",
|
||||||
|
"Add expenses for the week, along with attached files for said expenses",
|
||||||
|
],
|
||||||
|
path: RouteNames.TIMESHEET,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const helpContext: chatbotPageContext = {
|
||||||
|
name: "Help",
|
||||||
|
description: "page containing common Q&A segments regarding website functionalities",
|
||||||
|
features: [
|
||||||
|
"Browse by module for common questions and answers",
|
||||||
|
"The modules displayed will only be those the user has access to.",
|
||||||
|
"Each module consists of a series of Expanding Items. Clicking on a question item reveals the answer beneath.",
|
||||||
|
"Each Expanding Item also has its own support image that appears next to the items.",
|
||||||
|
],
|
||||||
|
path: RouteNames.HELP,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PageContexts: Record<RouteNames, chatbotPageContext | null> = {
|
||||||
|
"login": null,
|
||||||
|
"login-success": null,
|
||||||
|
"error": null,
|
||||||
|
"/": dashboardContext,
|
||||||
|
"employees": employeeListContext,
|
||||||
|
"profile": profileContext,
|
||||||
|
"timesheet": timesheetContext,
|
||||||
|
"timesheet-approvals": timesheetApprovalContext,
|
||||||
|
"help": helpContext,
|
||||||
|
}
|
||||||
29
src/modules/chatbot/services/chatbot.service.ts
Normal file
29
src/modules/chatbot/services/chatbot.service.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { chatbotPageContext } from "src/modules/chatbot/models/page-context.model";
|
||||||
|
import type { Message } from "src/modules/chatbot/models/dialogue-message.model";
|
||||||
|
import { api } from "src/boot/axios";
|
||||||
|
|
||||||
|
export const chatbotService = {
|
||||||
|
// Function to send the message to the backend
|
||||||
|
sendChatMessage: async (userInput: string): Promise<Message> => {
|
||||||
|
const response = await api.post("/chat", { userInput });
|
||||||
|
return response.data as Message;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Function to send context to backend
|
||||||
|
sendPageContext: async (context: chatbotPageContext): Promise<string> => {
|
||||||
|
const response = await api.post("/chat/context", context);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Function to send user credentials to the backend to communicate with n8n.
|
||||||
|
// Will have to modify later on to accomodate newer versions of User Auth/User Structure
|
||||||
|
sendUserCredentials: async (email: string, role: string): Promise<boolean> => {
|
||||||
|
const response = await api.post("/chat/user", { email, role });
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
retrieveCustomerDiagnostics: async (id: string): Promise<string> => {
|
||||||
|
const response = await api.get(`/chat/customer/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import type { contextObject } from "src/page-contexts/pages/types/context-object";
|
|
||||||
import type { Message } from "../types/dialogue-message";
|
|
||||||
import { api } from "src/boot/axios";
|
|
||||||
|
|
||||||
export const chatbotService = {
|
|
||||||
// Function to send the message to the backend
|
|
||||||
sendChatMessage: async (userInput: string): Promise<Message> => {
|
|
||||||
const response = await api.post("/chat/respond", { userInput });
|
|
||||||
return response.data as Message;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Function to send context to backend
|
|
||||||
sendPageContext: async (context: contextObject): Promise<string> => {
|
|
||||||
console.log(context.path, " ", context.features);
|
|
||||||
const response = await api.post("/chat/context", context);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Function to send user credentials to the backend to communicate with n8n.
|
|
||||||
// Will have to modify later on to accomodate newer versions of User Auth/User Structure
|
|
||||||
sendUserCredentials: async (
|
|
||||||
email: string,
|
|
||||||
role: string
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const response = await api.post("/chat/user", { email, role });
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
retrieveCustomerDiagnostics: async (id: string): Promise<string> => {
|
|
||||||
const response = await api.get(`/chat/customer/${id}`);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -15,6 +15,7 @@ export const ModuleNames = {
|
||||||
PERSONAL_PROFILE: 'personal_profile',
|
PERSONAL_PROFILE: 'personal_profile',
|
||||||
TIMESHEETS: 'timesheets',
|
TIMESHEETS: 'timesheets',
|
||||||
TIMESHEETS_APPROVAL: 'timesheets_approval',
|
TIMESHEETS_APPROVAL: 'timesheets_approval',
|
||||||
|
CHATBOT: 'chatbot',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type UserModuleAccess = typeof ModuleNames[keyof typeof ModuleNames];
|
export type UserModuleAccess = typeof ModuleNames[keyof typeof ModuleNames];
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { dashboardContext } from "./pages/dashboard-context";
|
|
||||||
import { leftDrawerContext } from "./pages/left-drawer-context";
|
|
||||||
import { profileContext } from "./pages/profile-context";
|
|
||||||
import { supervisorContext } from "./pages/supervisor-crew-context";
|
|
||||||
import { timesheetApprovalContext } from "./pages/timesheet-approval-context";
|
|
||||||
import { timesheetContext } from "./pages/timesheet-context";
|
|
||||||
import type { contextObject } from "./pages/types/context-object";
|
|
||||||
|
|
||||||
export const pageContexts: contextObject[] = [
|
|
||||||
dashboardContext,
|
|
||||||
leftDrawerContext,
|
|
||||||
profileContext,
|
|
||||||
supervisorContext,
|
|
||||||
timesheetApprovalContext,
|
|
||||||
timesheetContext,
|
|
||||||
];
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
export const dashboardContext = {
|
|
||||||
name: "Test-Page",
|
|
||||||
description:
|
|
||||||
"Temporary static landing page while the application is still in development",
|
|
||||||
features: [
|
|
||||||
"Used as a landing platform and navigate the left drawer",
|
|
||||||
"Access the ai chatbot from the header",
|
|
||||||
"See your user icon with a notification icon",
|
|
||||||
],
|
|
||||||
path: "",
|
|
||||||
};
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
export const leftDrawerContext = {
|
|
||||||
name: "Left Drawer",
|
|
||||||
description:
|
|
||||||
"A drawer that acts as a navigation bar, able to surf to differnent parts of the website.",
|
|
||||||
features: [
|
|
||||||
"The user can navigate to the home page.",
|
|
||||||
"Depending on their role and clearence can approve timesheets. ",
|
|
||||||
"Depending on their role, can see the full list of employees.",
|
|
||||||
"Can access the timesheet interface to input employee hours.",
|
|
||||||
"Can acess your user profile to add or change information",
|
|
||||||
"Can access the Help page to ask for assistance.",
|
|
||||||
"Can logout",
|
|
||||||
],
|
|
||||||
path: "none",
|
|
||||||
};
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
export const profileContext = {
|
|
||||||
name: "Profile",
|
|
||||||
description: "Display and edit user information",
|
|
||||||
features: [
|
|
||||||
"Add and edit Personal information such as first and last name, phone, birthdate and address.",
|
|
||||||
"Add and edit Career information such job title, company, supervisor, email and hiring date.",
|
|
||||||
"Edit available preferences such as Display options of light and dark mode, and language options",
|
|
||||||
],
|
|
||||||
path: "user/profile",
|
|
||||||
};
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
export const supervisorContext = {
|
|
||||||
name: "Supervisor Crew Page",
|
|
||||||
description: "View all the hired Staff",
|
|
||||||
features: [
|
|
||||||
"View the list of hired employees",
|
|
||||||
"Access an individual employee",
|
|
||||||
],
|
|
||||||
path: "employees",
|
|
||||||
};
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
export const timesheetApprovalContext = {
|
|
||||||
name: "Timesheet-Approval",
|
|
||||||
description:
|
|
||||||
"Page where employee hours and shifts are approved by the HR deparment",
|
|
||||||
features: [
|
|
||||||
"See a list of cards with the total hours for the week for each employee.",
|
|
||||||
"Access different weeks thanks to the calender button",
|
|
||||||
"Approve the hours for the cards displayed.",
|
|
||||||
"Open a detailed modal for each card",
|
|
||||||
"Display a bar chart within the modal to display the type of hours, expenses, and mileage.",
|
|
||||||
"Edit the hours, and their type such as regular, holiday, vacation etc.",
|
|
||||||
"Add and edit expenses for the week, along with attached files for said expenses",
|
|
||||||
],
|
|
||||||
path: "timesheet-approvals",
|
|
||||||
};
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
export const timesheetContext = {
|
|
||||||
name: "Timesheet-page",
|
|
||||||
description:
|
|
||||||
"Page where an employee can enter their hours for their day and week",
|
|
||||||
features: [
|
|
||||||
"Enter your in and out times per day",
|
|
||||||
"Add and edit what kind of hours your shift was example regular, overtime, vacation etc.",
|
|
||||||
"Edit your own shift hours",
|
|
||||||
"Delete your shift hours",
|
|
||||||
"List your expenses for the week",
|
|
||||||
"Add expenses for the week, along with attached files for said expenses",
|
|
||||||
],
|
|
||||||
path: "timesheet-temp",
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export interface contextObject {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
features: string[];
|
|
||||||
path: string;
|
|
||||||
}
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { createMemoryHistory, createRouter, createWebHashHistory, createWebHisto
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
import { RouteNames } from 'src/router/router-constants';
|
import { RouteNames } from 'src/router/router-constants';
|
||||||
|
import { useChatbotStore } from 'src/stores/chatbot-store';
|
||||||
import type { UserModuleAccess } from 'src/modules/shared/models/user.models';
|
import type { UserModuleAccess } from 'src/modules/shared/models/user.models';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -33,6 +34,11 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
||||||
const auth_store = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
const result = await auth_store.getProfile() ?? { status: 400, message: 'unknown error occured' };
|
const result = await auth_store.getProfile() ?? { status: 400, message: 'unknown error occured' };
|
||||||
|
|
||||||
|
if (auth_store.user?.user_module_access.includes('chatbot')) {
|
||||||
|
const chatbot_store = useChatbotStore();
|
||||||
|
chatbot_store.updatePageContext(destination_page.name as RouteNames);
|
||||||
|
}
|
||||||
|
|
||||||
if (destination_page.meta.requires_auth && !auth_store.user || (result.status >= 400 && destination_page.name !== RouteNames.LOGIN)) {
|
if (destination_page.meta.requires_auth && !auth_store.user || (result.status >= 400 && destination_page.name !== RouteNames.LOGIN)) {
|
||||||
console.error('no user account found');
|
console.error('no user account found');
|
||||||
return { name: 'login' };
|
return { name: 'login' };
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,61 @@
|
||||||
import type { Message } from "src/modules/chatbot/models/dialogue-message.model";
|
|
||||||
import { defineStore } from "pinia";
|
|
||||||
import { chatbotService } from "src/modules/chatbot/services/messages.service";
|
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { chatbotService } from "src/modules/chatbot/services/chatbot.service";
|
||||||
|
import type { Message } from "src/modules/chatbot/models/dialogue-message.model";
|
||||||
|
import { RouteNames } from "src/router/router-constants";
|
||||||
|
import { PageContexts } from "src/modules/chatbot/models/page-context.model";
|
||||||
|
|
||||||
export const useChatbotStore = defineStore("chatbot", () => {
|
export const useChatbotStore = defineStore("chatbot", () => {
|
||||||
const messages = ref<Message[]>([]);
|
const { t } = useI18n();
|
||||||
const has_shown_instructions = ref(false);
|
const messages = ref<Message[]>([]);
|
||||||
const is_showing_chatbot = ref(false);
|
const has_shown_instructions = ref(false);
|
||||||
const { t } = useI18n();
|
const is_showing_chatbot = ref(false);
|
||||||
|
|
||||||
const sendMessage = async (userInput: string) => {
|
const sendChatMessage = async (user_message: string) => {
|
||||||
const reply = await chatbotService.sendChatMessage(userInput);
|
const last_chatbot_message = messages.value.at(messages.value.length - 1)!;
|
||||||
return reply;
|
|
||||||
};
|
|
||||||
|
|
||||||
const showInstructionsOnce = () => {
|
try {
|
||||||
if (!has_shown_instructions.value) {
|
const chatbot_response = await chatbotService.sendChatMessage(user_message);
|
||||||
messages.value.push({
|
if (chatbot_response) {
|
||||||
text: t("chatbot.chat_initial_message"),
|
last_chatbot_message.text = chatbot_response.text;
|
||||||
sent: false,
|
last_chatbot_message.isThinking = false;
|
||||||
isThinking: false,
|
} else {
|
||||||
});
|
last_chatbot_message.text = t('chatbot.error.NO_REPLY_RECEIVED');
|
||||||
has_shown_instructions.value = true;
|
last_chatbot_message.isThinking = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
catch (error) {
|
||||||
|
last_chatbot_message.text = t('chatbot.error.SEND_MESSAGE_FAILED');
|
||||||
|
last_chatbot_message.isThinking = false
|
||||||
|
|
||||||
return {
|
}
|
||||||
messages,
|
};
|
||||||
has_shown_instructions,
|
|
||||||
is_showing_chatbot,
|
const updatePageContext = async (page_name: RouteNames) => {
|
||||||
sendMessage,
|
const chatbot_page_context = PageContexts[page_name];
|
||||||
showInstructionsOnce,
|
|
||||||
};
|
if (chatbot_page_context)
|
||||||
|
await chatbotService.sendPageContext(chatbot_page_context);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showInstructionsOnce = () => {
|
||||||
|
if (!has_shown_instructions.value) {
|
||||||
|
messages.value.push({
|
||||||
|
text: t("chatbot.chat_initial_message"),
|
||||||
|
sent: false,
|
||||||
|
isThinking: false,
|
||||||
|
});
|
||||||
|
has_shown_instructions.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
has_shown_instructions,
|
||||||
|
is_showing_chatbot,
|
||||||
|
sendChatMessage,
|
||||||
|
updatePageContext,
|
||||||
|
showInstructionsOnce,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user