feat(chatbot): fully implement chatbot, fix UI issues, add to permissions under user list.
This commit is contained in:
parent
0fe8c89dd9
commit
7114574ecf
|
|
@ -123,6 +123,7 @@ export default {
|
|||
personal_profile: "profile",
|
||||
timesheets: "timesheets",
|
||||
timesheets_approval: "timesheet approval",
|
||||
chatbot: "chatbot",
|
||||
user_access: "module access",
|
||||
by_role: "by role",
|
||||
by_module: "by module",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ export default {
|
|||
chatbot: {
|
||||
chat_header: "Agent IA",
|
||||
chat_initial_message: "Bienvenue à votre assistant technique.\nVeuillez fournir le ID du Client pour avoir un diagnostique",
|
||||
chat_placeholder: "Entré un Message",
|
||||
chat_placeholder: "Entrez un Message",
|
||||
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",
|
||||
|
|
@ -123,6 +123,7 @@ export default {
|
|||
personal_profile: "profil personnel",
|
||||
timesheets: "carte de temps",
|
||||
timesheets_approval: "validation cartes de temps",
|
||||
chatbot: "chatbot",
|
||||
user_access: "module access",
|
||||
by_role: "par rôle",
|
||||
by_module: "par module",
|
||||
|
|
|
|||
|
|
@ -3,9 +3,14 @@
|
|||
setup
|
||||
>
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import { useChatbotStore } from 'src/stores/chatbot-store';
|
||||
|
||||
// import HeaderBarNotification from './main-layout-header-bar-notification.vue';
|
||||
|
||||
const uiStore = useUiStore();
|
||||
const chatbot_store = useChatbotStore();
|
||||
const auth_store = useAuthStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -19,7 +24,11 @@
|
|||
@click="uiStore.toggleRightDrawer"
|
||||
class="q-px-none"
|
||||
>
|
||||
<q-icon name="menu" size="lg" class="q-mr-lg"/>
|
||||
<q-icon
|
||||
name="menu"
|
||||
size="lg"
|
||||
class="q-mr-lg"
|
||||
/>
|
||||
<q-img
|
||||
src="src/assets/logo-targo-white.svg"
|
||||
fit="contain"
|
||||
|
|
@ -28,8 +37,18 @@
|
|||
/>
|
||||
</q-btn>
|
||||
</q-toolbar-title>
|
||||
|
||||
<q-item class="q-pa-none">
|
||||
<!-- <HeaderBarNotification /> -->
|
||||
<q-btn
|
||||
v-if="auth_store.user?.user_module_access.includes('chatbot')"
|
||||
flat
|
||||
color="transparant"
|
||||
icon="las la-robot"
|
||||
size="lg"
|
||||
@click="chatbot_store.is_showing_chatbot = !chatbot_store.is_showing_chatbot"
|
||||
style="--q-icon-size: 28px; min-width: auto;"
|
||||
/>
|
||||
</q-item>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import HeaderBar from 'src/layouts/components/main-layout-header-bar.vue';
|
||||
import FooterBar from 'src/layouts/components/main-layout-footer-bar.vue';
|
||||
import LeftDrawer from 'src/layouts/components/main-layout-left-drawer.vue';
|
||||
import ChatbotDrawer from 'src/modules/chatbot/components/chatbot-drawer.vue';
|
||||
|
||||
import { onMounted, watch, ref } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
|
|
@ -35,6 +36,8 @@
|
|||
|
||||
<LeftDrawer />
|
||||
|
||||
<ChatbotDrawer />
|
||||
|
||||
<q-page-container>
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { useChatbotStore } from 'src/stores/chatbot-store';
|
||||
|
||||
const chatbot_store = useChatbotStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
style="z-index: 3; margin-right: 20px"
|
||||
class="display: flex"
|
||||
>
|
||||
<q-btn
|
||||
flat
|
||||
color="transparant"
|
||||
size="20px"
|
||||
icon="smart_toy"
|
||||
@click="chatbot_store.is_showing_chatbot = !chatbot_store.is_showing_chatbot"
|
||||
style="--q-icon-size: 28px; min-width: auto;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
75
src/modules/chatbot/components/chatbot-drawer.vue
Normal file
75
src/modules/chatbot/components/chatbot-drawer.vue
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import DialogueContent from "./dialogue-content.vue";
|
||||
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useChatbotStore } from "src/stores/chatbot-store";
|
||||
import { useChatbotApi } from "src/modules/chatbot/composables/chatbot-api";
|
||||
|
||||
const chatbot_api = useChatbotApi();
|
||||
const chatbot_store = useChatbotStore();
|
||||
|
||||
const text = ref('');
|
||||
|
||||
const handleSend = async () => {
|
||||
await chatbot_api.sendMessage(text.value.trim());
|
||||
text.value = '';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
chatbot_store.showInstructionsOnce();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-drawer
|
||||
v-model="chatbot_store.is_showing_chatbot"
|
||||
overlay
|
||||
side="right"
|
||||
:width="500"
|
||||
class="column justify-end no-wrap"
|
||||
>
|
||||
<div class="col q-px-md relative-position">
|
||||
<DialogueContent class="absolute-full" />
|
||||
</div>
|
||||
|
||||
<q-form
|
||||
submit="handleSend"
|
||||
class="col-auto row"
|
||||
>
|
||||
<q-input
|
||||
v-model="text"
|
||||
borderless
|
||||
:label="$t('chatbot.chat_placeholder')"
|
||||
:autogrow="false"
|
||||
dark
|
||||
label-color="white"
|
||||
class="col q-px-md"
|
||||
style="background: rgba(0, 0, 0, 0.3);"
|
||||
@keyup.enter="handleSend"
|
||||
/>
|
||||
|
||||
<!-- <q-input
|
||||
v-model="custId"
|
||||
:label="'Customer Id'"
|
||||
:autogrow="false"
|
||||
class="col"
|
||||
style="margin-left: 8px;"
|
||||
@keyup.enter="handleCustomerId"
|
||||
/> -->
|
||||
</q-form>
|
||||
|
||||
<!-- <div
|
||||
class="drag-handle"
|
||||
@mousedown.prevent="startDrag"
|
||||
></div> -->
|
||||
</q-drawer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.q-drawer) {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import DialogueContent from "./dialogue-content.vue";
|
||||
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useChatbotStore } from "src/stores/chatbot-store";
|
||||
import { useChatbotApi } from "src/modules/chatbot/composables/chatbot-api";
|
||||
|
||||
// Block to enable editing the width of the drawer
|
||||
// const drawerWidth = ref(370);
|
||||
// let dragging = false;
|
||||
// const startDrag = (e: MouseEvent) => {
|
||||
// dragging = true
|
||||
// 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 chatbot_api = useChatbotApi();
|
||||
const chatbot_store = useChatbotStore();
|
||||
|
||||
const text = ref('');
|
||||
|
||||
const handleSend = async () => {
|
||||
await chatbot_api.sendMessage(text.value.trim());
|
||||
text.value = '';
|
||||
};
|
||||
|
||||
// Capture and send page context to n8n
|
||||
// const currentContext = computed(() =>
|
||||
// pageContexts.find(ctx => ctx.path === route.fullPath.replace('/', ''))
|
||||
// )
|
||||
|
||||
// // Function that sends the page context to n8n
|
||||
// watch([currentContext, userEmail, userRole], async ([ctx, email, role]) => {
|
||||
// if (!ctx || !email || !role) return;
|
||||
|
||||
// const contextPayload: contextObject = {
|
||||
// name: ctx.name,
|
||||
// description: ctx.description,
|
||||
// features: ctx.features,
|
||||
// path: ctx.path
|
||||
// }
|
||||
|
||||
// try {
|
||||
// await Promise.all([chatbotService.sendUserCredentials(email, role),
|
||||
// sendPageContext(contextPayload),
|
||||
// ]);
|
||||
// is_session_ready.value = true;
|
||||
// } catch (err) {
|
||||
// console.error("Error", err)
|
||||
// }
|
||||
// },
|
||||
// { immediate: true }
|
||||
// );
|
||||
|
||||
onMounted(() => {
|
||||
chatbot_store.showInstructionsOnce();
|
||||
})
|
||||
|
||||
// const custId = ref('')
|
||||
// const handleCustomerId = async () => {
|
||||
// const cId = custId.value;
|
||||
// custId.value = '';
|
||||
// await chatbotService.retrieveCustomerDiagnostics(cId);
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-drawer
|
||||
side="right"
|
||||
v-model="chatbot_store.is_showing_chatbot"
|
||||
class="column justify-end"
|
||||
>
|
||||
<div class="bg-primary text-uppercase">{{ $t('chatbot.chat_header') }}</div>
|
||||
<div class="line-separator"></div>
|
||||
|
||||
<div class="chat-body">
|
||||
<DialogueContent />
|
||||
</div>
|
||||
|
||||
<div class="line-separator"></div>
|
||||
<q-form
|
||||
submit="handleSend"
|
||||
class="chat-footer row"
|
||||
>
|
||||
<q-input
|
||||
v-model="text"
|
||||
:label="$t('chatbot.chat_placeholder')"
|
||||
:autogrow="false"
|
||||
class="col"
|
||||
style="margin-left: 8px;"
|
||||
@keyup.enter="handleSend"
|
||||
/>
|
||||
|
||||
<!-- <q-input
|
||||
v-model="custId"
|
||||
:label="'Customer Id'"
|
||||
:autogrow="false"
|
||||
class="col"
|
||||
style="margin-left: 8px;"
|
||||
@keyup.enter="handleCustomerId"
|
||||
/> -->
|
||||
</q-form>
|
||||
|
||||
<!-- <div
|
||||
class="drag-handle"
|
||||
@mousedown.prevent="startDrag"
|
||||
></div> -->
|
||||
</q-drawer>
|
||||
</template>
|
||||
|
||||
<!-- <style scoped>
|
||||
.drag-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 8px;
|
||||
height: 100%;
|
||||
cursor: ew-resize;
|
||||
z-index: 1000;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
</style> -->
|
||||
|
|
@ -3,18 +3,28 @@
|
|||
lang="ts"
|
||||
>
|
||||
import DialoguePhrase from './dialogue-phrase.vue';
|
||||
|
||||
import { ref, watch } from 'vue';
|
||||
import { useChatbotStore } from 'src/stores/chatbot-store';
|
||||
import type { QScrollArea } from 'quasar';
|
||||
|
||||
|
||||
const chatbot_store = useChatbotStore();
|
||||
const scroll_area = ref<QScrollArea | null>(null);
|
||||
|
||||
watch(chatbot_store.messages, () => {
|
||||
if (scroll_area.value) {
|
||||
scroll_area.value.setScrollPercentage('vertical', 1.0, 500);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-infinite-scroll>
|
||||
<template
|
||||
<q-scroll-area ref="scroll_area">
|
||||
<div
|
||||
v-for="(msg, index) in chatbot_store.messages"
|
||||
:key="index"
|
||||
class="q-px-md"
|
||||
>
|
||||
<DialoguePhrase
|
||||
v-if="!msg.isThinking"
|
||||
|
|
@ -32,6 +42,6 @@
|
|||
/>
|
||||
<span class="q-ml-sm text-grey-7">{{ $t('chatbot.chat_thinking') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</q-infinite-scroll>
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
</template>
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import type { Message } from 'src/modules/chatbot/models/dialogue-message.model';
|
||||
|
||||
const {t}
|
||||
const auth_store = useAuthStore();
|
||||
|
||||
const { message } = defineProps<{
|
||||
|
|
@ -33,15 +34,17 @@
|
|||
// Compute parsed content
|
||||
const parsedText = computed((): string => {
|
||||
const cleaned = cleanMarkdown(message.text || '')
|
||||
return md.render(cleaned)
|
||||
return md.render(cleaned);
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-chat-message
|
||||
:sent="message.sent"
|
||||
:text="[parsedText]"
|
||||
:name="message.sent ? auth_store.user?.first_name : 'TargoBot'"
|
||||
:bg-color="message.sent ? 'accent' : 'secondary'"
|
||||
/>
|
||||
:bg-color="message.sent ? 'accent' : 'white'"
|
||||
:text-color="message.sent ? 'white' : ''"
|
||||
>
|
||||
<div v-html="parsedText"></div>
|
||||
</q-chat-message>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// composables/chat-api.ts
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useChatbotStore } from "src/stores/chatbot-store";
|
||||
import { RouteNames } from "src/router/router-constants";
|
||||
import { PageContexts } from "src/modules/chatbot/models/page-context.model";
|
||||
|
||||
export const useChatbotApi = () => {
|
||||
const chatbot_store = useChatbotStore();
|
||||
|
|
|
|||
|
|
@ -5,20 +5,19 @@ 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 });
|
||||
const response = await api.post("/chatbot", { 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);
|
||||
const response = await api.post("/chatbot/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 });
|
||||
const response = await api.post("/chatbot/user", { email, role });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ export const employee_access_options: QSelectOption<UserModuleAccess>[] = [
|
|||
{ label: 'timesheets', value: 'timesheets' },
|
||||
{ label: 'employee_management', value: 'employee_management' },
|
||||
{ label: 'timesheets_approval', value: 'timesheets_approval' },
|
||||
{ label: 'chatbot', value: 'chatbot' },
|
||||
]
|
||||
|
||||
export const employee_access_presets: Record<ModuleAccessPreset, UserModuleAccess[]> = {
|
||||
|
|
@ -145,5 +146,6 @@ export const getEmployeeAccessOptionIcon = (module: UserModuleAccess): string =>
|
|||
case 'personal_profile': return 'las la-id-card';
|
||||
case 'timesheets': return 'punch_clock';
|
||||
case 'timesheets_approval': return 'event_available';
|
||||
case 'chatbot': return 'las la-robot';
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
employee_list: default_employee_list,
|
||||
employee_management: default_employee_management,
|
||||
timesheets_approval: default_validation_page,
|
||||
chatbot: '',
|
||||
};
|
||||
|
||||
import type { HelpModuleOptions } from 'src/modules/help/models/help-module.model';
|
||||
|
|
|
|||
|
|
@ -134,9 +134,6 @@ export const timesheets_approval_options: HelpModuleOptions[] = [
|
|||
{ label: 'help.tutorial.shared.calendar', path: calendar, description: calendar_nav_desc, icon: 'calendar_month' },
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
export const help_module_details: Options = {
|
||||
dashboard: [],
|
||||
personal_profile: profile_options,
|
||||
|
|
@ -144,6 +141,7 @@ export const help_module_details: Options = {
|
|||
employee_list: employee_list_options,
|
||||
employee_management: employee_management_options,
|
||||
timesheets_approval: timesheets_approval_options,
|
||||
chatbot: [],
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,11 +34,6 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
|||
const auth_store = useAuthStore();
|
||||
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)) {
|
||||
console.error('no user account found');
|
||||
return { name: 'login' };
|
||||
|
|
@ -50,5 +45,14 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
|||
}
|
||||
})
|
||||
|
||||
Router.afterEach( async (destination_page) => {
|
||||
const auth_store = useAuthStore();
|
||||
|
||||
if (auth_store.user?.user_module_access.includes('chatbot')) {
|
||||
const chatbot_store = useChatbotStore();
|
||||
await chatbot_store.updatePageContext(destination_page.name as RouteNames);
|
||||
}
|
||||
})
|
||||
|
||||
return Router;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import { ref } from "vue";
|
||||
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 type { RouteNames } from "src/router/router-constants";
|
||||
import { PageContexts } from "src/modules/chatbot/models/page-context.model";
|
||||
|
||||
export const useChatbotStore = defineStore("chatbot", () => {
|
||||
const { t } = useI18n();
|
||||
const messages = ref<Message[]>([]);
|
||||
const has_shown_instructions = ref(false);
|
||||
const is_showing_chatbot = ref(false);
|
||||
|
|
@ -21,14 +19,14 @@ export const useChatbotStore = defineStore("chatbot", () => {
|
|||
last_chatbot_message.text = chatbot_response.text;
|
||||
last_chatbot_message.isThinking = false;
|
||||
} else {
|
||||
last_chatbot_message.text = t('chatbot.error.NO_REPLY_RECEIVED');
|
||||
last_chatbot_message.text = 'chatbot.error.NO_REPLY_RECEIVED';
|
||||
last_chatbot_message.isThinking = false;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
last_chatbot_message.text = t('chatbot.error.SEND_MESSAGE_FAILED');
|
||||
last_chatbot_message.isThinking = false
|
||||
|
||||
last_chatbot_message.text = 'chatbot.error.SEND_MESSAGE_FAILED';
|
||||
last_chatbot_message.isThinking = false;
|
||||
console.error('error sending message: ', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -42,7 +40,7 @@ export const useChatbotStore = defineStore("chatbot", () => {
|
|||
const showInstructionsOnce = () => {
|
||||
if (!has_shown_instructions.value) {
|
||||
messages.value.push({
|
||||
text: t("chatbot.chat_initial_message"),
|
||||
text: "chatbot.chat_initial_message",
|
||||
sent: false,
|
||||
isThinking: false,
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user