feat(chat): Integrate chatbot API for dynamic responses

This commit replaces the static, simulated chat functionality with a live integration to a chatbot backend API.

The chat page now sends user messages to a webhook endpoint using the `fetch` API. It implements session management by generating and storing a `sessionId` in `localStorage` to maintain conversation context between requests.

Key changes:
- Implement `async` `sendMessage` function to post to the chatbot API.
- Add session ID generation and persistence.
- Dynamically render text and image responses from the API.
- Remove hardcoded example messages from the HTML.
- Update the virtual assistant's name to "Maya".
- Fix navigation links from the dashboard to the chat page.
This commit is contained in:
2025-07-21 14:44:06 +07:00
parent 158b6d0886
commit 84c7cbd8ce
5 changed files with 158 additions and 80 deletions

View File

@@ -27,9 +27,9 @@
<!-- Header dengan avatar dan nama pengguna -->
<header class="chat-header">
<div class="user-info">
<div class="avatar">J</div>
<div class="avatar">M</div>
<div class="user-details">
<h2>John Doe</h2>
<h2>Maya</h2>
<span class="user-role">Virtual Assistant - Bake Shop</span>
</div>
</div>
@@ -37,21 +37,6 @@
<!-- Area chat utama -->
<div class="chat-area">
<!-- Contoh pesan masuk -->
<div class="message received">
<div class="message-content">
Hai, ada kabar terbaru?
</div>
<div class="message-time">10:00</div>
</div>
<!-- Contoh pesan keluar -->
<div class="message sent">
<div class="message-content">
Iya, proyek baru sudah selesai!
</div>
<div class="message-time">10:01</div>
</div>
</div>
<!-- Input area with WhatsApp-like design -->

View File

@@ -72,8 +72,13 @@ function hideTypingIndicator(indicator) {
}
}
// Generate unique session ID
function generateSessionId() {
return 'session-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
}
// Fungsi untuk mengirim pesan
function sendMessage() {
async function sendMessage() {
const input = document.querySelector('.message-input');
const message = input.value.trim();
@@ -82,16 +87,47 @@ function sendMessage() {
input.value = '';
toggleAttachmentIcons(true); // Tampilkan kembali ikon setelah mengirim
// Simulasikan balasan otomatis setelah 2 detik
setTimeout(() => {
const typingIndicator = showTypingIndicator();
// Setelah 3 detik, sembunyikan indikator dan tampilkan balasan
setTimeout(() => {
try {
// Get or create session ID
let sessionId = localStorage.getItem('chatSessionId');
if (!sessionId) {
sessionId = generateSessionId();
localStorage.setItem('chatSessionId', sessionId);
}
const response = await fetch('https://bot.kediritechnopark.com/webhook/e4559efb-e624-478b-8d40-c287c7c203e7', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: message,
sessionId: sessionId
})
});
hideTypingIndicator(typingIndicator);
addMessage('Pesan Anda sudah saya terima. Terima kasih!', false);
}, 3000);
}, 2000);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
// Cek apakah ada balasan dari bot
if (data && data.output) {
addMessage(data.output, false);
} else {
addMessage('Maaf, terjadi kesalahan saat memproses balasan.', false);
}
} catch (error) {
hideTypingIndicator(typingIndicator);
console.error('Error sending message:', error);
addMessage('Maaf, tidak dapat terhubung ke server. Silakan coba lagi nanti.', false);
}
}
}
@@ -134,17 +170,29 @@ document.addEventListener('DOMContentLoaded', () => {
// Inisialisasi chat setelah DOM selesai dimuat
document.addEventListener('DOMContentLoaded', () => {
// Tampilkan indikator mengetik saat halaman pertama dimuat
const initialTyping = showTypingIndicator();
// Simulate explanatory demo conversation
const simulateConversation = async () => {
await new Promise(resolve => setTimeout(resolve, 1500));
addMessage('Halo, saya tertarik dengan layanan Mayagen. Bisa jelaskan apa saja fiturnya?', true);
// Setelah 3 detik, tampilkan pesan sapaan
setTimeout(() => {
hideTypingIndicator(initialTyping);
addMessage('Hai, ada yang bisa saya bantu?', false);
}, 3000);
await new Promise(resolve => setTimeout(resolve, 2500));
addMessage('Tentu! Senang Anda tertarik. Mayagen adalah asisten AI yang dirancang untuk mengotomatisasi interaksi pelanggan dan meningkatkan efisiensi bisnis Anda 24/7.', false);
await new Promise(resolve => setTimeout(resolve, 2500));
addMessage('Sangat menarik. Apakah Mayagen hanya berfungsi di webchat seperti yang saya gunakan sekarang?', true);
await new Promise(resolve => setTimeout(resolve, 3500));
addMessage('Pertanyaan yang sangat bagus! Webchat ini hanyalah salah satu contoh platform yang bisa saya layani. Kekuatan utama Mayagen adalah kemampuan untuk menjawab pesan secara otomatis di berbagai channel populer secara terpisah.', false);
await new Promise(resolve => setTimeout(resolve, 3500));
addMessage('Saya bisa membalas DM di Instagram, merespons chat di WhatsApp, dan berinteraksi dengan pelanggan Anda di Telegram. Semua fitur ini berjalan independen di masing-masing platform, memberikan pengalaman terbaik bagi pelanggan Anda.', false);
await new Promise(resolve => setTimeout(resolve, 2500));
addMessage('Apakah ada fitur atau channel spesifik yang ingin Anda ketahui lebih lanjut?', false);
};
simulateConversation();
// Update avatar utama dengan inisial dari nama saat halaman dimuat
document.addEventListener('DOMContentLoaded', () => {
// Handle viewport height for mobile browsers
const setVh = () => {
const vh = window.innerHeight * 0.01;
@@ -158,7 +206,6 @@ document.addEventListener('DOMContentLoaded', () => {
const userInitial = userName.split(' ').map(n => n[0]).join('').substring(0, 1);
document.querySelector('.avatar').textContent = userInitial;
});
});
// Pastikan animasi mengetik muncul sebelum pesan sambutan
// (Sudah diimplementasikan di atas)

View File

@@ -40,7 +40,7 @@
</div>
</header>
<main>
<main class="container mx-auto px-4">
<div class="layout-content-container">
<h2 class="text-[#141414] tracking-light text-[28px] font-bold leading-tight px-4 text-left pb-3 pt-5">Chatbot Performance Overview</h2>
<div class="controls-container">
@@ -229,6 +229,7 @@
</div>
</main>
<script src="scripts/main.js"></script>
<!-- Floating circles background -->
<ul class="floating-circles">
<li></li><li></li><li></li><li></li><li></li>

View File

@@ -4,13 +4,13 @@ const dropdownMenu = document.querySelector('.dropdown-menu');
if (menuButton && dropdownMenu) {
menuButton.addEventListener('click', function() {
dropdownMenu.classList.toggle('visible');
dropdownMenu.classList.toggle('hidden');
});
}
// Close dropdown when clicking outside
document.addEventListener('click', function(event) {
if (!event.target.closest('.mobile-header-right') && dropdownMenu) {
dropdownMenu.classList.remove('visible');
dropdownMenu.classList.add('hidden');
}
});

View File

@@ -18,6 +18,14 @@ body {
padding: 0 20px; /* Tambahan padding horizontal */
}
/* Container for responsive design */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}
/* Universal Header (works for both desktop and mobile) */
header {
position: sticky;
@@ -27,18 +35,19 @@ header {
backdrop-filter: blur(35px);
border-radius: 0 0 20px 20px;
margin: 0;
padding: 20px 25px; /* Increased vertical padding */
padding: 20px 25px;
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.15);
min-height: 110px; /* Significantly taller header */
min-height: 110px;
width: 100%;
max-width: 100%;
display: flex;
flex-wrap: wrap; /* Allow elements to wrap on smaller screens */
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 15px; /* Consistent spacing between elements */
gap: 15px;
}
/* Universal Header Elements */
.mobile-header-user-info {
display: flex;
@@ -253,12 +262,37 @@ th:nth-child(3), td:nth-child(3) { width: 30%; }
/* Responsivitas */
@media (max-width: 768px) {
/* Show mobile elements, hide desktop nav */
.mobile-header-user-info,
.mobile-header-right {
display: flex;
}
.desktop-nav {
display: none;
}
/* Stack cards vertically on mobile */
#stat-cards, #secondary-stats {
grid-template-columns: 1fr;
}
/* Adjust header layout for mobile */
header {
flex-direction: column;
align-items: flex-start;
padding: 15px;
}
.mobile-header-right {
align-self: flex-end;
margin-top: -50px;
}
body {
padding: 0 10px;
}
/* Removed mobile-specific scaling as we now have universal styles */
/* Stat Cards */
#stat-cards, #secondary-stats {
flex-direction: column;
@@ -285,24 +319,35 @@ th:nth-child(3), td:nth-child(3) { width: 30%; }
}
}
@media (min-width: 769px) {
/* Adjust header layout for desktop */
header {
padding: 15px 25px;
}
.mobile-header-user-info {
display: flex;
align-items: center;
}
.mobile-header-right {
display: flex;
}
}
/* Definitive Vertical Layout */
.controls-container,
.card-container {
display: block;
}
/* Definitive Horizontal Layout for Stat Cards */
/* Responsive Layout for Cards */
#stat-cards, #secondary-stats {
display: flex;
flex-direction: row;
justify-content: space-between;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
#stat-cards > div, #secondary-stats > div {
flex: 1; /* Ensure cards share space equally */
}
/* Ensure cards within the container stack vertically */
.card-container > div {
margin-bottom: 20px; /* Add space between vertical cards */